Mercurial > public > mercurial-scm > hg
comparison contrib/check-config.py @ 25790:db5b6a1c064d
check-config: add config option checker
This script scans files for lines that look like either ui.config
usage or config variable documentation. It then ensures:
- ui.config calls for each option agree on types and defaults
- every option appears to be mentioned in documentation
It doesn't complain about devel/experimental options and allows
marking options that are not intended to be public.
Since we haven't been able to come up with a good scheme for
documenting config options at point of use, this will help close the
loop of making sure all options that should be documented are.
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Thu, 25 Jun 2015 17:34:53 -0500 |
parents | |
children | d1cb185b9ee2 |
comparison
equal
deleted
inserted
replaced
25789:95dc4b009f60 | 25790:db5b6a1c064d |
---|---|
1 #!/usr/bin/env python | |
2 # | |
3 # check-config - a config flag documentation checker for Mercurial | |
4 # | |
5 # Copyright 2015 Matt Mackall <mpm@selenic.com> | |
6 # | |
7 # This software may be used and distributed according to the terms of the | |
8 # GNU General Public License version 2 or any later version. | |
9 | |
10 import re | |
11 import sys | |
12 | |
13 foundopts = {} | |
14 documented = {} | |
15 | |
16 configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"], ?""" | |
17 r"""['"](\S+)['"](,\s(?:default=)?(\S+?))?\)""") | |
18 | |
19 def main(args): | |
20 for f in args: | |
21 sect = '' | |
22 prevname = '' | |
23 confsect = '' | |
24 for l in open(f): | |
25 | |
26 # check topic-like bits | |
27 m = re.match('\s*``(\S+)``', l) | |
28 if m: | |
29 prevname = m.group(1) | |
30 continue | |
31 if re.match('^\s*-+$', l): | |
32 sect = prevname | |
33 prevname = '' | |
34 continue | |
35 | |
36 if sect and prevname: | |
37 name = sect + '.' + prevname | |
38 documented[name] = 1 | |
39 | |
40 # check docstring bits | |
41 m = re.match(r'^\s+\[(\S+)\]', l) | |
42 if m: | |
43 confsect = m.group(1) | |
44 continue | |
45 m = re.match(r'^\s+(?:#\s*)?([a-z._]+) = ', l) | |
46 if m: | |
47 name = confsect + '.' + m.group(1) | |
48 documented[name] = 1 | |
49 | |
50 # like the bugzilla extension | |
51 m = re.match(r'^\s*([a-z]+\.[a-z]+)$', l) | |
52 if m: | |
53 documented[m.group(1)] = 1 | |
54 | |
55 # quoted in help or docstrings | |
56 m = re.match(r'.*?``([-a-z_]+\.[-a-z_]+)``', l) | |
57 if m: | |
58 documented[m.group(1)] = 1 | |
59 | |
60 # look for ignore markers | |
61 m = re.search(r'# (?:internal|experimental|deprecated|developer)' | |
62 ' config: (\S+.\S+)$', l) | |
63 if m: | |
64 documented[m.group(1)] = 1 | |
65 | |
66 # look for code-like bits | |
67 m = re.search(configre, l) | |
68 if m: | |
69 ctype = m.group(1) | |
70 if not ctype: | |
71 ctype = 'str' | |
72 name = m.group(2) + "." + m.group(3) | |
73 default = m.group(5) | |
74 if default in (None, 'False', 'None', '0', '[]', '""', "''"): | |
75 default = '' | |
76 if re.match('[a-z.]+$', default): | |
77 default = '<variable>' | |
78 if name in foundopts and (ctype, default) != foundopts[name]: | |
79 print l | |
80 print "conflict on %s: %r != %r" % (name, (ctype, default), | |
81 foundopts[name]) | |
82 foundopts[name] = (ctype, default) | |
83 | |
84 for name in sorted(foundopts): | |
85 if name not in documented: | |
86 if not (name.startswith("devel.") or | |
87 name.startswith("experimental.") or | |
88 name.startswith("debug.")): | |
89 ctype, default = foundopts[name] | |
90 if default: | |
91 default = ' [%s]' % default | |
92 print "undocumented: %s (%s)%s" % (name, ctype, default) | |
93 | |
94 if __name__ == "__main__": | |
95 sys.exit(main(sys.argv[1:])) |