at master 3.3 kB view raw
1import collections 2import json 3import os 4import sys 5from typing import Any, Dict, List 6 7JSON = Dict[str, Any] 8 9class Key: 10 def __init__(self, path: List[str]): 11 self.path = path 12 def __hash__(self): 13 result = 0 14 for id in self.path: 15 result ^= hash(id) 16 return result 17 def __eq__(self, other): 18 return type(self) is type(other) and self.path == other.path 19 20Option = collections.namedtuple('Option', ['name', 'value']) 21 22# pivot a dict of options keyed by their display name to a dict keyed by their path 23def pivot(options: Dict[str, JSON]) -> Dict[Key, Option]: 24 result: Dict[Key, Option] = dict() 25 for (name, opt) in options.items(): 26 result[Key(opt['loc'])] = Option(name, opt) 27 return result 28 29# pivot back to indexed-by-full-name 30# like the docbook build we'll just fail if multiple options with differing locs 31# render to the same option name. 32def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]: 33 result: Dict[str, Dict] = dict() 34 for (key, opt) in options.items(): 35 if opt.name in result: 36 raise RuntimeError( 37 'multiple options with colliding ids found', 38 opt.name, 39 result[opt.name]['loc'], 40 opt.value['loc'], 41 ) 42 result[opt.name] = opt.value 43 return result 44 45warningsAreErrors = False 46optOffset = 0 47for arg in sys.argv[1:]: 48 if arg == "--warnings-are-errors": 49 optOffset += 1 50 warningsAreErrors = True 51 52options = pivot(json.load(open(sys.argv[1 + optOffset], 'r'))) 53overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r'))) 54 55# merge both descriptions 56for (k, v) in overrides.items(): 57 cur = options.setdefault(k, v).value 58 for (ok, ov) in v.value.items(): 59 if ok == 'declarations': 60 decls = cur[ok] 61 for d in ov: 62 if d not in decls: 63 decls += [d] 64 elif ok == "type": 65 # ignore types of placeholder options 66 if ov != "_unspecified" or cur[ok] == "_unspecified": 67 cur[ok] = ov 68 elif ov is not None or cur.get(ok, None) is None: 69 cur[ok] = ov 70 71severity = "error" if warningsAreErrors else "warning" 72 73# check that every option has a description 74hasWarnings = False 75hasErrors = False 76for (k, v) in options.items(): 77 if v.value.get('description', None) is None: 78 hasWarnings = True 79 print(f"\x1b[1;31m{severity}: option {v.name} has no description\x1b[0m", file=sys.stderr) 80 v.value['description'] = "This option has no description." 81 if v.value.get('type', "unspecified") == "unspecified": 82 hasWarnings = True 83 print( 84 f"\x1b[1;31m{severity}: option {v.name} has no type. Please specify a valid type, see " + 85 "https://nixos.org/manual/nixos/stable/index.html#sec-option-types\x1b[0m", file=sys.stderr) 86 87if hasErrors: 88 sys.exit(1) 89if hasWarnings and warningsAreErrors: 90 print( 91 "\x1b[1;31m" + 92 "Treating warnings as errors. Set documentation.nixos.options.warningsAreErrors " + 93 "to false to ignore these warnings." + 94 "\x1b[0m", 95 file=sys.stderr) 96 sys.exit(1) 97 98json.dump(unpivot(options), fp=sys.stdout)