# HG changeset patch # User Matt Mackall # Date 1435271693 18000 # Node ID db5b6a1c064d704e4eed32a17e4d7b52d706854a # Parent 95dc4b009f6061909cf965236ac3221928ed2a5d 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. diff -r 95dc4b009f60 -r db5b6a1c064d contrib/check-config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/check-config.py Thu Jun 25 17:34:53 2015 -0500 @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# +# check-config - a config flag documentation checker for Mercurial +# +# Copyright 2015 Matt Mackall +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import re +import sys + +foundopts = {} +documented = {} + +configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"], ?""" + r"""['"](\S+)['"](,\s(?:default=)?(\S+?))?\)""") + +def main(args): + for f in args: + sect = '' + prevname = '' + confsect = '' + for l in open(f): + + # check topic-like bits + m = re.match('\s*``(\S+)``', l) + if m: + prevname = m.group(1) + continue + if re.match('^\s*-+$', l): + sect = prevname + prevname = '' + continue + + if sect and prevname: + name = sect + '.' + prevname + documented[name] = 1 + + # check docstring bits + m = re.match(r'^\s+\[(\S+)\]', l) + if m: + confsect = m.group(1) + continue + m = re.match(r'^\s+(?:#\s*)?([a-z._]+) = ', l) + if m: + name = confsect + '.' + m.group(1) + documented[name] = 1 + + # like the bugzilla extension + m = re.match(r'^\s*([a-z]+\.[a-z]+)$', l) + if m: + documented[m.group(1)] = 1 + + # quoted in help or docstrings + m = re.match(r'.*?``([-a-z_]+\.[-a-z_]+)``', l) + if m: + documented[m.group(1)] = 1 + + # look for ignore markers + m = re.search(r'# (?:internal|experimental|deprecated|developer)' + ' config: (\S+.\S+)$', l) + if m: + documented[m.group(1)] = 1 + + # look for code-like bits + m = re.search(configre, l) + if m: + ctype = m.group(1) + if not ctype: + ctype = 'str' + name = m.group(2) + "." + m.group(3) + default = m.group(5) + if default in (None, 'False', 'None', '0', '[]', '""', "''"): + default = '' + if re.match('[a-z.]+$', default): + default = '' + if name in foundopts and (ctype, default) != foundopts[name]: + print l + print "conflict on %s: %r != %r" % (name, (ctype, default), + foundopts[name]) + foundopts[name] = (ctype, default) + + for name in sorted(foundopts): + if name not in documented: + if not (name.startswith("devel.") or + name.startswith("experimental.") or + name.startswith("debug.")): + ctype, default = foundopts[name] + if default: + default = ' [%s]' % default + print "undocumented: %s (%s)%s" % (name, ctype, default) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))