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<cmd>\w+)}')
bos@133:     optref_re = re.compile(r'^\\l?optref{(?P<cmd>\w+)}'
bos@133:                            r'(?:{(?P<short>[^}])})?'
bos@133:                            r'{(?P<long>[^}]+)}')
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)