##// END OF EJS Templates
check-config: look for ui.configwith...
Gregory Szorc -
r32849:e9fc5550 default
parent child Browse files
Show More
@@ -1,120 +1,130 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-config - a config flag documentation checker for Mercurial
3 # check-config - a config flag documentation checker for Mercurial
4 #
4 #
5 # Copyright 2015 Matt Mackall <mpm@selenic.com>
5 # Copyright 2015 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
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.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import, print_function
10 from __future__ import absolute_import, print_function
11 import re
11 import re
12 import sys
12 import sys
13
13
14 foundopts = {}
14 foundopts = {}
15 documented = {}
15 documented = {}
16
16
17 configre = re.compile(r'''
17 configre = re.compile(r'''
18 # Function call
18 # Function call
19 ui\.config(?P<ctype>|int|bool|list)\(
19 ui\.config(?P<ctype>|int|bool|list)\(
20 # First argument.
20 # First argument.
21 ['"](?P<section>\S+)['"],\s*
21 ['"](?P<section>\S+)['"],\s*
22 # Second argument
22 # Second argument
23 ['"](?P<option>\S+)['"](,\s+
23 ['"](?P<option>\S+)['"](,\s+
24 (?:default=)?(?P<default>\S+?))?
24 (?:default=)?(?P<default>\S+?))?
25 \)''', re.VERBOSE | re.MULTILINE)
25 \)''', re.VERBOSE | re.MULTILINE)
26
26
27 configwithre = re.compile('''
28 ui\.config(?P<ctype>with)\(
29 # First argument is callback function. This doesn't parse robustly
30 # if it is e.g. a function call.
31 [^,]+,\s*
32 ['"](?P<section>\S+)['"],\s*
33 ['"](?P<option>\S+)['"](,\s+
34 (?:default=)?(?P<default>\S+?))?
35 \)''', re.VERBOSE | re.MULTILINE)
36
27 configpartialre = (r"""ui\.config""")
37 configpartialre = (r"""ui\.config""")
28
38
29 def main(args):
39 def main(args):
30 for f in args:
40 for f in args:
31 sect = ''
41 sect = ''
32 prevname = ''
42 prevname = ''
33 confsect = ''
43 confsect = ''
34 carryover = ''
44 carryover = ''
35 for l in open(f):
45 for l in open(f):
36
46
37 # check topic-like bits
47 # check topic-like bits
38 m = re.match('\s*``(\S+)``', l)
48 m = re.match('\s*``(\S+)``', l)
39 if m:
49 if m:
40 prevname = m.group(1)
50 prevname = m.group(1)
41 if re.match('^\s*-+$', l):
51 if re.match('^\s*-+$', l):
42 sect = prevname
52 sect = prevname
43 prevname = ''
53 prevname = ''
44
54
45 if sect and prevname:
55 if sect and prevname:
46 name = sect + '.' + prevname
56 name = sect + '.' + prevname
47 documented[name] = 1
57 documented[name] = 1
48
58
49 # check docstring bits
59 # check docstring bits
50 m = re.match(r'^\s+\[(\S+)\]', l)
60 m = re.match(r'^\s+\[(\S+)\]', l)
51 if m:
61 if m:
52 confsect = m.group(1)
62 confsect = m.group(1)
53 continue
63 continue
54 m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l)
64 m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l)
55 if m:
65 if m:
56 name = confsect + '.' + m.group(1)
66 name = confsect + '.' + m.group(1)
57 documented[name] = 1
67 documented[name] = 1
58
68
59 # like the bugzilla extension
69 # like the bugzilla extension
60 m = re.match(r'^\s*(\S+\.\S+)$', l)
70 m = re.match(r'^\s*(\S+\.\S+)$', l)
61 if m:
71 if m:
62 documented[m.group(1)] = 1
72 documented[m.group(1)] = 1
63
73
64 # like convert
74 # like convert
65 m = re.match(r'^\s*:(\S+\.\S+):\s+', l)
75 m = re.match(r'^\s*:(\S+\.\S+):\s+', l)
66 if m:
76 if m:
67 documented[m.group(1)] = 1
77 documented[m.group(1)] = 1
68
78
69 # quoted in help or docstrings
79 # quoted in help or docstrings
70 m = re.match(r'.*?``(\S+\.\S+)``', l)
80 m = re.match(r'.*?``(\S+\.\S+)``', l)
71 if m:
81 if m:
72 documented[m.group(1)] = 1
82 documented[m.group(1)] = 1
73
83
74 # look for ignore markers
84 # look for ignore markers
75 m = re.search(r'# (?:internal|experimental|deprecated|developer)'
85 m = re.search(r'# (?:internal|experimental|deprecated|developer)'
76 ' config: (\S+\.\S+)$', l)
86 ' config: (\S+\.\S+)$', l)
77 if m:
87 if m:
78 documented[m.group(1)] = 1
88 documented[m.group(1)] = 1
79
89
80 # look for code-like bits
90 # look for code-like bits
81 line = carryover + l
91 line = carryover + l
82 m = configre.search(line)
92 m = configre.search(line) or configwithre.search(line)
83 if m:
93 if m:
84 ctype = m.group('ctype')
94 ctype = m.group('ctype')
85 if not ctype:
95 if not ctype:
86 ctype = 'str'
96 ctype = 'str'
87 name = m.group('section') + "." + m.group('option')
97 name = m.group('section') + "." + m.group('option')
88 default = m.group('default')
98 default = m.group('default')
89 if default in (None, 'False', 'None', '0', '[]', '""', "''"):
99 if default in (None, 'False', 'None', '0', '[]', '""', "''"):
90 default = ''
100 default = ''
91 if re.match('[a-z.]+$', default):
101 if re.match('[a-z.]+$', default):
92 default = '<variable>'
102 default = '<variable>'
93 if name in foundopts and (ctype, default) != foundopts[name]:
103 if name in foundopts and (ctype, default) != foundopts[name]:
94 print(l)
104 print(l)
95 print("conflict on %s: %r != %r" % (name, (ctype, default),
105 print("conflict on %s: %r != %r" % (name, (ctype, default),
96 foundopts[name]))
106 foundopts[name]))
97 foundopts[name] = (ctype, default)
107 foundopts[name] = (ctype, default)
98 carryover = ''
108 carryover = ''
99 else:
109 else:
100 m = re.search(configpartialre, line)
110 m = re.search(configpartialre, line)
101 if m:
111 if m:
102 carryover = line
112 carryover = line
103 else:
113 else:
104 carryover = ''
114 carryover = ''
105
115
106 for name in sorted(foundopts):
116 for name in sorted(foundopts):
107 if name not in documented:
117 if name not in documented:
108 if not (name.startswith("devel.") or
118 if not (name.startswith("devel.") or
109 name.startswith("experimental.") or
119 name.startswith("experimental.") or
110 name.startswith("debug.")):
120 name.startswith("debug.")):
111 ctype, default = foundopts[name]
121 ctype, default = foundopts[name]
112 if default:
122 if default:
113 default = ' [%s]' % default
123 default = ' [%s]' % default
114 print("undocumented: %s (%s)%s" % (name, ctype, default))
124 print("undocumented: %s (%s)%s" % (name, ctype, default))
115
125
116 if __name__ == "__main__":
126 if __name__ == "__main__":
117 if len(sys.argv) > 1:
127 if len(sys.argv) > 1:
118 sys.exit(main(sys.argv[1:]))
128 sys.exit(main(sys.argv[1:]))
119 else:
129 else:
120 sys.exit(main([l.rstrip() for l in sys.stdin]))
130 sys.exit(main([l.rstrip() for l in sys.stdin]))
@@ -1,35 +1,37 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4
4
5 Sanity check check-config.py
5 Sanity check check-config.py
6
6
7 $ cat > testfile.py << EOF
7 $ cat > testfile.py << EOF
8 > # Good
8 > # Good
9 > foo = ui.config('ui', 'username')
9 > foo = ui.config('ui', 'username')
10 > # Missing
10 > # Missing
11 > foo = ui.config('ui', 'doesnotexist')
11 > foo = ui.config('ui', 'doesnotexist')
12 > # Missing different type
12 > # Missing different type
13 > foo = ui.configint('ui', 'missingint')
13 > foo = ui.configint('ui', 'missingint')
14 > # Missing with default value
14 > # Missing with default value
15 > foo = ui.configbool('ui', 'missingbool1', default=True)
15 > foo = ui.configbool('ui', 'missingbool1', default=True)
16 > foo = ui.configbool('ui', 'missingbool2', False)
16 > foo = ui.configbool('ui', 'missingbool2', False)
17 > EOF
17 > EOF
18
18
19 $ cat > files << EOF
19 $ cat > files << EOF
20 > mercurial/help/config.txt
20 > mercurial/help/config.txt
21 > $TESTTMP/testfile.py
21 > $TESTTMP/testfile.py
22 > EOF
22 > EOF
23
23
24 $ cd "$TESTDIR"/..
24 $ cd "$TESTDIR"/..
25
25
26 $ python contrib/check-config.py < $TESTTMP/files
26 $ python contrib/check-config.py < $TESTTMP/files
27 undocumented: ui.doesnotexist (str)
27 undocumented: ui.doesnotexist (str)
28 undocumented: ui.missingbool1 (bool) [True]
28 undocumented: ui.missingbool1 (bool) [True]
29 undocumented: ui.missingbool2 (bool)
29 undocumented: ui.missingbool2 (bool)
30 undocumented: ui.missingint (int)
30 undocumented: ui.missingint (int)
31
31
32 New errors are not allowed. Warnings are strongly discouraged.
32 New errors are not allowed. Warnings are strongly discouraged.
33
33
34 $ hg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
34 $ hg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
35 > python contrib/check-config.py
35 > python contrib/check-config.py
36 undocumented: profiling.showmax (with) [0.999]
37 undocumented: profiling.showmin (with) [0.005]
General Comments 0
You need to be logged in to leave comments. Login now