##// END OF EJS Templates
check-config: mention the file and line of the error...
Ryan McElroy -
r33571:e470f12d default
parent child Browse files
Show More
@@ -1,139 +1,142 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 allowinconsistent = set()
16 allowinconsistent = set()
17
17
18 configre = re.compile(r'''
18 configre = re.compile(r'''
19 # Function call
19 # Function call
20 ui\.config(?P<ctype>|int|bool|list)\(
20 ui\.config(?P<ctype>|int|bool|list)\(
21 # First argument.
21 # First argument.
22 ['"](?P<section>\S+)['"],\s*
22 ['"](?P<section>\S+)['"],\s*
23 # Second argument
23 # Second argument
24 ['"](?P<option>\S+)['"](,\s+
24 ['"](?P<option>\S+)['"](,\s+
25 (?:default=)?(?P<default>\S+?))?
25 (?:default=)?(?P<default>\S+?))?
26 \)''', re.VERBOSE | re.MULTILINE)
26 \)''', re.VERBOSE | re.MULTILINE)
27
27
28 configwithre = re.compile('''
28 configwithre = re.compile('''
29 ui\.config(?P<ctype>with)\(
29 ui\.config(?P<ctype>with)\(
30 # First argument is callback function. This doesn't parse robustly
30 # First argument is callback function. This doesn't parse robustly
31 # if it is e.g. a function call.
31 # if it is e.g. a function call.
32 [^,]+,\s*
32 [^,]+,\s*
33 ['"](?P<section>\S+)['"],\s*
33 ['"](?P<section>\S+)['"],\s*
34 ['"](?P<option>\S+)['"](,\s+
34 ['"](?P<option>\S+)['"](,\s+
35 (?:default=)?(?P<default>\S+?))?
35 (?:default=)?(?P<default>\S+?))?
36 \)''', re.VERBOSE | re.MULTILINE)
36 \)''', re.VERBOSE | re.MULTILINE)
37
37
38 configpartialre = (r"""ui\.config""")
38 configpartialre = (r"""ui\.config""")
39
39
40 ignorere = re.compile(r'''
40 ignorere = re.compile(r'''
41 \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s
41 \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s
42 config:\s(?P<config>\S+\.\S+)$
42 config:\s(?P<config>\S+\.\S+)$
43 ''', re.VERBOSE | re.MULTILINE)
43 ''', re.VERBOSE | re.MULTILINE)
44
44
45 def main(args):
45 def main(args):
46 for f in args:
46 for f in args:
47 sect = ''
47 sect = ''
48 prevname = ''
48 prevname = ''
49 confsect = ''
49 confsect = ''
50 carryover = ''
50 carryover = ''
51 linenum = 0
51 for l in open(f):
52 for l in open(f):
53 linenum += 1
52
54
53 # check topic-like bits
55 # check topic-like bits
54 m = re.match('\s*``(\S+)``', l)
56 m = re.match('\s*``(\S+)``', l)
55 if m:
57 if m:
56 prevname = m.group(1)
58 prevname = m.group(1)
57 if re.match('^\s*-+$', l):
59 if re.match('^\s*-+$', l):
58 sect = prevname
60 sect = prevname
59 prevname = ''
61 prevname = ''
60
62
61 if sect and prevname:
63 if sect and prevname:
62 name = sect + '.' + prevname
64 name = sect + '.' + prevname
63 documented[name] = 1
65 documented[name] = 1
64
66
65 # check docstring bits
67 # check docstring bits
66 m = re.match(r'^\s+\[(\S+)\]', l)
68 m = re.match(r'^\s+\[(\S+)\]', l)
67 if m:
69 if m:
68 confsect = m.group(1)
70 confsect = m.group(1)
69 continue
71 continue
70 m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l)
72 m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l)
71 if m:
73 if m:
72 name = confsect + '.' + m.group(1)
74 name = confsect + '.' + m.group(1)
73 documented[name] = 1
75 documented[name] = 1
74
76
75 # like the bugzilla extension
77 # like the bugzilla extension
76 m = re.match(r'^\s*(\S+\.\S+)$', l)
78 m = re.match(r'^\s*(\S+\.\S+)$', l)
77 if m:
79 if m:
78 documented[m.group(1)] = 1
80 documented[m.group(1)] = 1
79
81
80 # like convert
82 # like convert
81 m = re.match(r'^\s*:(\S+\.\S+):\s+', l)
83 m = re.match(r'^\s*:(\S+\.\S+):\s+', l)
82 if m:
84 if m:
83 documented[m.group(1)] = 1
85 documented[m.group(1)] = 1
84
86
85 # quoted in help or docstrings
87 # quoted in help or docstrings
86 m = re.match(r'.*?``(\S+\.\S+)``', l)
88 m = re.match(r'.*?``(\S+\.\S+)``', l)
87 if m:
89 if m:
88 documented[m.group(1)] = 1
90 documented[m.group(1)] = 1
89
91
90 # look for ignore markers
92 # look for ignore markers
91 m = ignorere.search(l)
93 m = ignorere.search(l)
92 if m:
94 if m:
93 if m.group('reason') == 'inconsistent':
95 if m.group('reason') == 'inconsistent':
94 allowinconsistent.add(m.group('config'))
96 allowinconsistent.add(m.group('config'))
95 else:
97 else:
96 documented[m.group('config')] = 1
98 documented[m.group('config')] = 1
97
99
98 # look for code-like bits
100 # look for code-like bits
99 line = carryover + l
101 line = carryover + l
100 m = configre.search(line) or configwithre.search(line)
102 m = configre.search(line) or configwithre.search(line)
101 if m:
103 if m:
102 ctype = m.group('ctype')
104 ctype = m.group('ctype')
103 if not ctype:
105 if not ctype:
104 ctype = 'str'
106 ctype = 'str'
105 name = m.group('section') + "." + m.group('option')
107 name = m.group('section') + "." + m.group('option')
106 default = m.group('default')
108 default = m.group('default')
107 if default in (None, 'False', 'None', '0', '[]', '""', "''"):
109 if default in (None, 'False', 'None', '0', '[]', '""', "''"):
108 default = ''
110 default = ''
109 if re.match('[a-z.]+$', default):
111 if re.match('[a-z.]+$', default):
110 default = '<variable>'
112 default = '<variable>'
111 if (name in foundopts and (ctype, default) != foundopts[name]
113 if (name in foundopts and (ctype, default) != foundopts[name]
112 and name not in allowinconsistent):
114 and name not in allowinconsistent):
113 print(l)
115 print(l.rstrip())
114 print("conflict on %s: %r != %r" % (name, (ctype, default),
116 print("conflict on %s: %r != %r" % (name, (ctype, default),
115 foundopts[name]))
117 foundopts[name]))
118 print("at %s:%d:" % (f, linenum))
116 foundopts[name] = (ctype, default)
119 foundopts[name] = (ctype, default)
117 carryover = ''
120 carryover = ''
118 else:
121 else:
119 m = re.search(configpartialre, line)
122 m = re.search(configpartialre, line)
120 if m:
123 if m:
121 carryover = line
124 carryover = line
122 else:
125 else:
123 carryover = ''
126 carryover = ''
124
127
125 for name in sorted(foundopts):
128 for name in sorted(foundopts):
126 if name not in documented:
129 if name not in documented:
127 if not (name.startswith("devel.") or
130 if not (name.startswith("devel.") or
128 name.startswith("experimental.") or
131 name.startswith("experimental.") or
129 name.startswith("debug.")):
132 name.startswith("debug.")):
130 ctype, default = foundopts[name]
133 ctype, default = foundopts[name]
131 if default:
134 if default:
132 default = ' [%s]' % default
135 default = ' [%s]' % default
133 print("undocumented: %s (%s)%s" % (name, ctype, default))
136 print("undocumented: %s (%s)%s" % (name, ctype, default))
134
137
135 if __name__ == "__main__":
138 if __name__ == "__main__":
136 if len(sys.argv) > 1:
139 if len(sys.argv) > 1:
137 sys.exit(main(sys.argv[1:]))
140 sys.exit(main(sys.argv[1:]))
138 else:
141 else:
139 sys.exit(main([l.rstrip() for l in sys.stdin]))
142 sys.exit(main([l.rstrip() for l in sys.stdin]))
@@ -1,47 +1,47 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 > # Inconsistent values for defaults.
17 > # Inconsistent values for defaults.
18 > foo = ui.configint('ui', 'intdefault', default=1)
18 > foo = ui.configint('ui', 'intdefault', default=1)
19 > foo = ui.configint('ui', 'intdefault', default=42)
19 > foo = ui.configint('ui', 'intdefault', default=42)
20 > # Can suppress inconsistent value error
20 > # Can suppress inconsistent value error
21 > foo = ui.configint('ui', 'intdefault2', default=1)
21 > foo = ui.configint('ui', 'intdefault2', default=1)
22 > # inconsistent config: ui.intdefault2
22 > # inconsistent config: ui.intdefault2
23 > foo = ui.configint('ui', 'intdefault2', default=42)
23 > foo = ui.configint('ui', 'intdefault2', default=42)
24 > EOF
24 > EOF
25
25
26 $ cat > files << EOF
26 $ cat > files << EOF
27 > mercurial/help/config.txt
27 > mercurial/help/config.txt
28 > $TESTTMP/testfile.py
28 > $TESTTMP/testfile.py
29 > EOF
29 > EOF
30
30
31 $ cd "$TESTDIR"/..
31 $ cd "$TESTDIR"/..
32
32
33 $ $PYTHON contrib/check-config.py < $TESTTMP/files
33 $ $PYTHON contrib/check-config.py < $TESTTMP/files
34 foo = ui.configint('ui', 'intdefault', default=42)
34 foo = ui.configint('ui', 'intdefault', default=42)
35
36 conflict on ui.intdefault: ('int', '42') != ('int', '1')
35 conflict on ui.intdefault: ('int', '42') != ('int', '1')
36 at $TESTTMP/testfile.py:12: (glob)
37 undocumented: ui.doesnotexist (str)
37 undocumented: ui.doesnotexist (str)
38 undocumented: ui.intdefault (int) [42]
38 undocumented: ui.intdefault (int) [42]
39 undocumented: ui.intdefault2 (int) [42]
39 undocumented: ui.intdefault2 (int) [42]
40 undocumented: ui.missingbool1 (bool) [True]
40 undocumented: ui.missingbool1 (bool) [True]
41 undocumented: ui.missingbool2 (bool)
41 undocumented: ui.missingbool2 (bool)
42 undocumented: ui.missingint (int)
42 undocumented: ui.missingint (int)
43
43
44 New errors are not allowed. Warnings are strongly discouraged.
44 New errors are not allowed. Warnings are strongly discouraged.
45
45
46 $ testrepohg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
46 $ testrepohg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
47 > $PYTHON contrib/check-config.py
47 > $PYTHON contrib/check-config.py
General Comments 0
You need to be logged in to leave comments. Login now