view contrib/check-config.py @ 40171:dac438b7346e

httppeer: expose API descriptor on httpv2peer The API descriptor in wireprotov2 is much more expressive than space-delimited tokens and it will be difficult to define methods to query it in all of the ways we'll want to query it. So let's just declare defeat and expose the API descriptor on the peer instance. As part of this, we define a new interface for version 2 peers, fulfilling a TODO in the process. Differential Revision: https://phab.mercurial-scm.org/D4974
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 03 Oct 2018 13:07:28 -0700
parents fe28267d5223
children 5519697b71b3
line wrap: on
line source

#!/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.

from __future__ import absolute_import, print_function
import re
import sys

foundopts = {}
documented = {}
allowinconsistent = set()

configre = re.compile(br'''
    # Function call
    ui\.config(?P<ctype>|int|bool|list)\(
        # First argument.
        ['"](?P<section>\S+)['"],\s*
        # Second argument
        ['"](?P<option>\S+)['"](,\s+
        (?:default=)?(?P<default>\S+?))?
    \)''', re.VERBOSE | re.MULTILINE)

configwithre = re.compile(b'''
    ui\.config(?P<ctype>with)\(
        # First argument is callback function. This doesn't parse robustly
        # if it is e.g. a function call.
        [^,]+,\s*
        ['"](?P<section>\S+)['"],\s*
        ['"](?P<option>\S+)['"](,\s+
        (?:default=)?(?P<default>\S+?))?
    \)''', re.VERBOSE | re.MULTILINE)

configpartialre = (br"""ui\.config""")

ignorere = re.compile(br'''
    \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s
    config:\s(?P<config>\S+\.\S+)$
    ''', re.VERBOSE | re.MULTILINE)

def main(args):
    for f in args:
        sect = b''
        prevname = b''
        confsect = b''
        carryover = b''
        linenum = 0
        for l in open(f, 'rb'):
            linenum += 1

            # check topic-like bits
            m = re.match(b'\s*``(\S+)``', l)
            if m:
                prevname = m.group(1)
            if re.match(b'^\s*-+$', l):
                sect = prevname
                prevname = b''

            if sect and prevname:
                name = sect + b'.' + prevname
                documented[name] = 1

            # check docstring bits
            m = re.match(br'^\s+\[(\S+)\]', l)
            if m:
                confsect = m.group(1)
                continue
            m = re.match(br'^\s+(?:#\s*)?(\S+) = ', l)
            if m:
                name = confsect + b'.' + m.group(1)
                documented[name] = 1

            # like the bugzilla extension
            m = re.match(br'^\s*(\S+\.\S+)$', l)
            if m:
                documented[m.group(1)] = 1

            # like convert
            m = re.match(br'^\s*:(\S+\.\S+):\s+', l)
            if m:
                documented[m.group(1)] = 1

            # quoted in help or docstrings
            m = re.match(br'.*?``(\S+\.\S+)``', l)
            if m:
                documented[m.group(1)] = 1

            # look for ignore markers
            m = ignorere.search(l)
            if m:
                if m.group('reason') == 'inconsistent':
                    allowinconsistent.add(m.group('config'))
                else:
                    documented[m.group('config')] = 1

            # look for code-like bits
            line = carryover + l
            m = configre.search(line) or configwithre.search(line)
            if m:
                ctype = m.group('ctype')
                if not ctype:
                    ctype = 'str'
                name = m.group('section') + b"." + m.group('option')
                default = m.group('default')
                if default in (None, 'False', 'None', '0', '[]', '""', "''"):
                    default = b''
                if re.match(b'[a-z.]+$', default):
                    default = b'<variable>'
                if (name in foundopts and (ctype, default) != foundopts[name]
                    and name not in allowinconsistent):
                    print(l.rstrip())
                    print("conflict on %s: %r != %r" % (name, (ctype, default),
                                                        foundopts[name]))
                    print("at %s:%d:" % (f, linenum))
                foundopts[name] = (ctype, default)
                carryover = b''
            else:
                m = re.search(configpartialre, line)
                if m:
                    carryover = line
                else:
                    carryover = b''

    for name in sorted(foundopts):
        if name not in documented:
            if not (name.startswith(b"devel.") or
                    name.startswith(b"experimental.") or
                    name.startswith(b"debug.")):
                ctype, default = foundopts[name]
                if default:
                    default = ' [%s]' % default
                print("undocumented: %s (%s)%s" % (name, ctype, default))

if __name__ == "__main__":
    if len(sys.argv) > 1:
        sys.exit(main(sys.argv[1:]))
    else:
        sys.exit(main([l.rstrip() for l in sys.stdin]))