bos@133: #!/usr/bin/env python bos@133: bos@133: import getopt bos@133: import itertools bos@133: import os bos@133: import re bos@133: import sys bos@133: bos@133: def usage(exitcode): bos@133: print >> sys.stderr, ('usage: %s [-H|--hidden] hg_repo' % bos@133: os.path.basename(sys.argv[0])) bos@133: sys.exit(exitcode) bos@133: bos@133: try: bos@133: opts, args = getopt.getopt(sys.argv[1:], 'AHh?', ['all', 'help', 'hidden']) bos@133: opt_all = False bos@133: opt_hidden = False bos@133: for o, a in opts: bos@133: if o in ('-h', '-?', '--help'): bos@133: usage(0) bos@133: if o in ('-A', '--all'): bos@133: opt_all = True bos@133: if o in ('-H', '--hidden'): bos@133: opt_hidden = True bos@133: except getopt.GetoptError, err: bos@133: print >> sys.stderr, 'error:', err bos@133: usage(1) bos@133: bos@133: try: bos@133: hg_repo, ltx_file = args bos@133: except ValueError: bos@133: usage(1) bos@133: bos@133: if not os.path.isfile(os.path.join(hg_repo, 'mercurial', 'commands.py')): bos@133: print >> sys.stderr, ('error: %r does not contain mercurial code' % bos@133: hg_repo) bos@133: sys.exit(1) bos@133: bos@133: sys.path.insert(0, hg_repo) bos@133: bos@133: from mercurial import commands bos@133: bos@133: def get_commands(): bos@133: seen = {} bos@133: for name, info in sorted(commands.table.iteritems()): bos@133: aliases = name.split('|', 1) bos@133: name = aliases.pop(0).lstrip('^') bos@133: function, options, synopsis = info bos@133: seen[name] = {} bos@133: for shortopt, longopt, arg, desc in options: bos@133: seen[name][longopt] = shortopt bos@133: return seen bos@133: bos@133: def cmd_filter((name, aliases, options)): bos@133: if opt_all: bos@133: return True bos@133: if opt_hidden: bos@133: return name.startswith('debug') bos@133: return not name.startswith('debug') bos@133: bos@133: def scan(ltx_file): bos@133: cmdref_re = re.compile(r'^\\cmdref{(?P\w+)}') bos@133: optref_re = re.compile(r'^\\l?optref{(?P\w+)}' bos@133: r'(?:{(?P[^}])})?' bos@133: r'{(?P[^}]+)}') bos@133: bos@133: seen = {} bos@133: locs = {} bos@133: for lnum, line in enumerate(open(ltx_file)): bos@133: m = cmdref_re.match(line) bos@133: if m: bos@133: d = m.groupdict() bos@133: cmd = d['cmd'] bos@133: seen[cmd] = {} bos@133: locs[cmd] = lnum + 1 bos@133: continue bos@133: m = optref_re.match(line) bos@133: if m: bos@133: d = m.groupdict() bos@133: seen[d['cmd']][d['long']] = d['short'] bos@133: continue bos@133: return seen, locs bos@133: bos@133: documented, locs = scan(ltx_file) bos@133: known = get_commands() bos@133: bos@133: doc_set = set(documented) bos@133: known_set = set(known) bos@133: bos@133: errors = 0 bos@133: bos@133: for nonexistent in sorted(doc_set.difference(known_set)): bos@133: print >> sys.stderr, ('%s:%d: %r command does not exist' % bos@133: (ltx_file, locs[nonexistent], nonexistent)) bos@133: errors += 1 bos@133: bos@133: def optcmp(a, b): bos@133: la, sa = a bos@133: lb, sb = b bos@133: sc = cmp(sa, sb) bos@133: if sc: bos@133: return sc bos@133: return cmp(la, lb) bos@133: bos@133: for cmd in doc_set.intersection(known_set): bos@133: doc_opts = documented[cmd] bos@133: known_opts = known[cmd] bos@133: bos@133: do_set = set(doc_opts) bos@133: ko_set = set(known_opts) bos@133: bos@133: for nonexistent in sorted(do_set.difference(ko_set)): bos@133: print >> sys.stderr, ('%s:%d: %r option to %r command does not exist' % bos@133: (ltx_file, locs[cmd], nonexistent, cmd)) bos@133: errors += 1 bos@133: bos@133: def mycmp(la, lb): bos@133: sa = known_opts[la] bos@133: sb = known_opts[lb] bos@133: return optcmp((la, sa), (lb, sb)) bos@133: bos@133: for undocumented in sorted(ko_set.difference(do_set), cmp=mycmp): bos@133: print >> sys.stderr, ('%s:%d: %r option to %r command not documented' % bos@133: (ltx_file, locs[cmd], undocumented, cmd)) bos@133: shortopt = known_opts[undocumented] bos@133: if shortopt: bos@133: print '\optref{%s}{%s}{%s}' % (cmd, shortopt, undocumented) bos@133: else: bos@133: print '\loptref{%s}{%s}' % (cmd, undocumented) bos@133: errors += 1 bos@133: sys.stdout.flush() bos@133: bos@133: if errors: bos@133: sys.exit(1) bos@133: bos@133: sorted_locs = sorted(locs.iteritems(), key=lambda x:x[1]) bos@133: bos@133: def next_loc(cmd): bos@133: for i, (name, loc) in enumerate(sorted_locs): bos@133: if name >= cmd: bos@133: return sorted_locs[i-1][1] + 1 bos@133: return loc bos@133: bos@133: for undocumented in sorted(known_set.difference(doc_set)): bos@133: print >> sys.stderr, ('%s:%d: %r command not documented' % bos@133: (ltx_file, next_loc(undocumented), undocumented)) bos@133: print '\cmdref{%s}' % undocumented bos@133: for longopt, shortopt in sorted(known[undocumented].items(), cmp=optcmp): bos@133: if shortopt: bos@133: print '\optref{%s}{%s}{%s}' % (undocumented, shortopt, longopt) bos@133: else: bos@133: print '\loptref{%s}{%s}' % (undocumented, longopt) bos@133: sys.stdout.flush() bos@133: errors += 1 bos@133: bos@133: sys.exit(errors and 1 or 0)