changeset 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 95dc4b009f60
children 917be0574d7f
files contrib/check-config.py
diffstat 1 files changed, 95 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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 <mpm@selenic.com>
+#
+# 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 = '<variable>'
+                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:]))