diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -35,10 +35,17 @@ dist packages doc/common.txt +doc/commandlist.txt +doc/extensionlist.txt +doc/topiclist.txt +doc/*.mk doc/*.[0-9] doc/*.[0-9].txt -doc/*.[0-9].gendoc.txt -doc/*.[0-9].{x,ht}ml +doc/*.{x,ht}ml +doc/*.gendoc.txt +doc/hg-*.html +doc/topic-*.html +doc/ext-*.html MANIFEST MANIFEST.in patches diff --git a/doc/Makefile b/doc/Makefile --- a/doc/Makefile +++ b/doc/Makefile @@ -1,8 +1,11 @@ SOURCES=$(notdir $(wildcard ../mercurial/helptext/*.[0-9].txt)) MAN=$(SOURCES:%.txt=%) -HTML=$(SOURCES:%.txt=%.html) +HTML=$(SOURCES:%.txt=%.html) index.html GENDOC=gendoc.py ../mercurial/commands.py ../mercurial/help.py \ ../mercurial/helptext/*.txt ../hgext/*.py ../hgext/*/__init__.py +HGCMDTPL=templates/cmdheader.txt +TOPICTPL=templates/topicheader.txt +EXTTPL=templates/extheader.txt PREFIX=/usr/local MANDIR=$(PREFIX)/share/man INSTALL=install -m 644 @@ -11,6 +14,91 @@ export HGENCODING=UTF-8 +.PHONY: all man html install clean + +# Generate a list of hg commands and extensions. +commandlist.txt: $(GENDOC) + ${PYTHON} gendoc.py commandlist > $@.tmp + mv $@.tmp $@ + +topiclist.txt: $(GENDOC) + ${PYTHON} gendoc.py topiclist > $@.tmp + mv $@.tmp $@ + +extensionlist.txt: $(GENDOC) + ${PYTHON} gendoc.py extensionlist > $@.tmp + mv $@.tmp $@ + +# Generate a sub-Makefile that can build the RST/man/html doc for +# each hg command. +define RuleAllCommandsTemplate +HG_COMMANDS=$(1) +all-commands: $$(HG_COMMANDS:%=hg-%.gendoc.txt) +endef + +define RuleAllTopicsTemplate +HG_TOPICS=$(1) +all-topics: $$(HG_TOPICS:%=%.gendoc.txt) +endef + +define RuleAllExtensionsTemplate +HG_EXTENSIONS=$(1) +all-extensions: $$(HG_EXTENSIONS:%=%.gendoc.txt) +endef + +define RuleCommandTemplate +hg-$C.gendoc.txt: $$(GENDOC) $$(HGCMDTPL) + $$(PYTHON) gendoc.py cmd-$C > $$@.tmp + mv $$@.tmp $$@ +endef + +define RuleTopicTemplate +topic-$T.gendoc.txt: $$(GENDOC) $$(TOPICTPL) + $$(PYTHON) gendoc.py topic-$T > $$@.tmp + mv $$@.tmp $$@ +endef + +define RuleExtensionTemplate +ext-$E.gendoc.txt: $$(GENDOC) $$(EXTTPL) + $$(PYTHON) gendoc.py ext-$E > $$@.tmp + mv $$@.tmp $$@ +endef + +# Note that this type of stuff is only supported by GNU Make 4 and above. +# Check the version you have installed if this rule fails. +CommandsTopicsExtensions.mk: commandlist.txt topiclist.txt extensionlist.txt Makefile + $(file > $@.tmp,# Generated by Makefile) + $(file >> $@.tmp,$(call RuleAllCommandsTemplate,$(file < commandlist.txt))) + $(file >> $@.tmp,$(call RuleAllTopicsTemplate,$(file < topiclist.txt))) + $(file >> $@.tmp,$(call RuleAllExtensionsTemplate,$(file < extensionlist.txt))) + $(foreach C,$(file < commandlist.txt),$(file >> $@.tmp,$(RuleCommandTemplate))) + $(foreach T,$(file < topiclist.txt),$(file >> $@.tmp,$(RuleTopicTemplate))) + $(foreach E,$(file < extensionlist.txt),$(file >> $@.tmp,$(RuleExtensionTemplate))) + mv $@.tmp $@ + +# Include the sub-Makefile. +# NOTE: since we define (above) a rule for this sub-Makefile, make will +# automatically try to generate it if it's not up to date, and then +# will restart itself using the newly generated file. +ifeq (,$(filter clean,$(MAKECMDGOALS))) +-include CommandsTopicsExtensions.mk +endif + +# If the sub-Makefile is available, add all the hg commands/topics/extensions +# to the list of things to generate html pages for. +ifdef HG_COMMANDS +HTML+=$(HG_COMMANDS:%=hg-%.html) +endif + +ifdef HG_TOPICS +HTML+=$(HG_TOPICS:%=topic-%.html) +endif + +ifdef HG_EXTENSIONS +HTML+=$(HG_EXTENSIONS:%=ext-%.html) +endif + + all: man html man: $(MAN) @@ -22,14 +110,26 @@ ${PYTHON} gendoc.py "$(basename $@)" > $@.tmp mv $@.tmp $@ +index.gendoc.txt: $(GENDOC) + $(PYTHON) gendoc.py index > $@.tmp + mv $@.tmp $@ + %: %.txt %.gendoc.txt common.txt $(PYTHON) runrst hgmanpage $(RSTARGS) --halt warning \ --strip-elements-with-class htmlonly $*.txt $* +%: %.gendoc.txt common.txt + $(PYTHON) runrst hgmanpage $(RSTARGS) --halt warning \ + --strip-elements-with-class htmlonly $*.gendoc.txt $* + %.html: %.txt %.gendoc.txt common.txt $(PYTHON) runrst html $(RSTARGS) --halt warning \ --link-stylesheet --stylesheet-path style.css $*.txt $*.html +%.html: %.gendoc.txt common.txt + $(PYTHON) runrst html $(RSTARGS) --halt warning \ + --link-stylesheet --stylesheet-path style.css $*.gendoc.txt $*.html + MANIFEST: man html # tracked files are already in the main MANIFEST $(RM) $@ @@ -45,4 +145,4 @@ done clean: - $(RM) $(MAN) $(HTML) common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt) MANIFEST + $(RM) *[0-9] *.html common.txt commandlist.txt topiclist.txt extensionlist.txt $(SOURCES) MANIFEST CommandsTopicsExtensions.mk *.gendoc.txt diff --git a/doc/gendoc.py b/doc/gendoc.py --- a/doc/gendoc.py +++ b/doc/gendoc.py @@ -131,7 +131,8 @@ # print help topics # The config help topic is included in the hgrc.5 man page. - helpprinter(ui, helptable, minirst.section, exclude=[b'config']) + topics = findtopics(helptable, exclude=[b'config']) + helpprinter(ui, topics, minirst.section) ui.write(minirst.section(_(b"Extensions"))) ui.write( @@ -166,7 +167,162 @@ ) -def showtopic(ui, topic): +def showcommandlist(ui): + # ui.verbose = True + cmdnames = allcommandnames(table, debugcmds=False) + for mainname, allnames in cmdnames.items(): + ui.write(mainname) + ui.write(b" ") + + +def showtopiclist(ui): + for topic in helptable: + ui.write(topic[0][0]) + ui.write(b" ") + + +def showextensionlist(ui): + for extensionname in allextensionnames(): + ui.write(extensionname) + ui.write(b" ") + + +def showhelpindex(ui): + ui.write(minirst.section(_(b"Mercurial Distributed SCM"))) + + missingdoc = _(b"(no help text available)") + + cats, h, syns = help._getcategorizedhelpcmds(ui, table, None) + ui.write(minirst.subsection(_(b"Commands"))) + + for cat in help.CATEGORY_ORDER: + catfns = sorted(cats.get(cat, [])) + if not catfns: + continue + + catname = gettext(help.CATEGORY_NAMES[cat]) + ui.write(minirst.subsubsection(catname)) + for c in catfns: + url = b'hg-%s.html' % c + ui.write(b" :`%s <%s>`__: %s" % (c, url, h[c])) + syns[c].remove(c) + if syns[c]: + ui.write(b" (aliases: *%s*)" % (b', '.join(syns[c]))) + ui.write(b"\n") + ui.write(b"\n\n") + + ui.write(b"\n\n") + + ui.write(minirst.subsection(_(b"Additional Help Topics"))) + topiccats, topicsyns = help._getcategorizedhelptopics(ui, helptable) + for cat in help.TOPIC_CATEGORY_ORDER: + topics = topiccats.get(cat, []) + if not topics: + continue + + catname = gettext(help.TOPIC_CATEGORY_NAMES[cat]) + ui.write(minirst.subsubsection(catname)) + for t, desc in topics: + url = b'topic-%s.html' % t + ui.write(b" :`%s <%s>`__: %s" % (t, url, desc)) + topicsyns[t].remove(t) + if topicsyns[t]: + ui.write(b" (aliases: *%s*)" % (b', '.join(topicsyns[t]))) + ui.write(b"\n") + ui.write(b"\n\n") + + ui.write(b"\n\n") + + # Add an alphabetical list of extensions, categorized by group. + sectionkeywords = [ + (b"(ADVANCED)", _(b"(ADVANCED)")), + (b"(EXPERIMENTAL)", _(b"(EXPERIMENTAL)")), + (b"(DEPRECATED)", _(b"(DEPRECATED)"))] + extensionsections = [ + (b"Extensions", []), + (b"Advanced Extensions", []), + (b"Experimental Extensions", []), + (b"Deprecated Extensions", [])] + for extensionname in allextensionnames(): + mod = extensions.load(ui, extensionname, None) + shortdoc, longdoc = _splitdoc(mod) + for i, kwds in enumerate(sectionkeywords): + if any([kwd in shortdoc for kwd in kwds]): + extensionsections[i+1][1].append( + (extensionname, mod, shortdoc)) + break + else: + extensionsections[0][1].append( + (extensionname, mod, shortdoc)) + for sectiontitle, extinfos in extensionsections: + ui.write(minirst.subsection(_(sectiontitle))) + for extinfo in sorted(extinfos, key=lambda ei: ei[0]): + extensionname, mod, shortdoc = extinfo + url = b'ext-%s.html' % extensionname + ui.write(minirst.subsubsection( + b'`%s <%s>`__' % (extensionname, url))) + ui.write(shortdoc) + ui.write(b'\n\n') + cmdtable = getattr(mod, 'cmdtable', None) + if cmdtable: + cmdnames = allcommandnames(cmdtable) + for f in sorted(cmdnames.keys()): + d = get_cmd(cmdnames[f], cmdtable) + ui.write(b':%s: ' % d[b'cmd']) + ui.write(d[b'desc'][0] or (missingdoc + b"\n")) + ui.write(b'\n') + ui.write(b'\n') + + +def showcommand(ui, mainname): + ui.verbose = True + cmdnames = allcommandnames(table, debugcmds=False) + allnames = cmdnames[mainname] + d = get_cmd(allnames, table) + + header = _rendertpl( + 'cmdheader.txt', + {'cmdname': mainname, + 'cmdtitle': minirst.section(b'hg ' + mainname), + 'cmdshortdesc': minirst.subsection(d[b'desc'][0]), + 'cmdlongdesc': d[b'desc'][1], + 'cmdsynopsis': d[b'synopsis']}) + ui.write(header.encode()) + + _optionsprinter(ui, d, minirst.subsubsection) + if d[b'aliases']: + ui.write(minirst.subsubsection(_(b"Aliases"))) + ui.write(b"::\n\n ") + ui.write(b", ".join(d[b'aliases'])) + ui.write(b"\n") + + +def _splitdoc(obj): + objdoc = pycompat.getdoc(obj) + firstnl = objdoc.find(b'\n') + if firstnl > 0: + shortdoc = objdoc[:firstnl] + longdoc = objdoc[firstnl+1:] + else: + shortdoc = objdoc + longdoc = '' + return shortdoc, longdoc + + +def _rendertpl(tplname, data): + tplpath = os.path.join(os.path.dirname(__file__), 'templates', tplname) + with open(tplpath, 'r') as f: + tpl = f.read() + + if isinstance(tpl, bytes): + tpl = tpl.decode() + for k in data: + data[k] = data[k].decode() + + return tpl % data + + +def gettopicstable(): extrahelptable = [ ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC), ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG), @@ -179,6 +335,12 @@ ), ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG), ( + [b"hg-ssh.8.gendoc"], + b'', + b'', + help.TOPIC_CATEGORY_CONFIG + ), + ( [b"hgignore.5.gendoc"], b'', loaddoc(b'hgignore'), @@ -191,16 +353,40 @@ help.TOPIC_CATEGORY_CONFIG, ), ] - helpprinter(ui, helptable + extrahelptable, None, include=[topic]) + return helptable + extrahelptable -def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]): +def findtopics(helptable, include=[], exclude=[]): + found = [] for h in helptable: names, sec, doc = h[0:3] if exclude and names[0] in exclude: continue if include and names[0] not in include: continue + found.append((names, sec, doc)) + return found + + +def showtopic(ui, topic, wraptpl=False): + found = findtopics(gettopicstable(), include=[topic]) + if not found: + raise Exception("ERROR: no such topic: %s\n" % topic) + + ui.verbose = True + if wraptpl: + header = _rendertpl('topicheader.txt', { + 'topicname': topic, + 'topictitle': minirst.section(found[0][1]) + }) + ui.write(header.encode()) + helpprinter(ui, found, None) + return True + + +def helpprinter(ui, topics, sectionfunc): + for h in topics: + names, sec, doc = h[0:3] for name in names: ui.write(b".. _%s:\n" % name) ui.write(b"\n") @@ -212,6 +398,25 @@ ui.write(b"\n") +def showextension(ui, extensionname): + mod = extensions.load(ui, extensionname, None) + + ui.verbose = True + header = _rendertpl('extheader.txt', { + 'extname': extensionname, + 'exttitle': minirst.section(extensionname) + }) + ui.write(header.encode()) + + shortdoc, longdoc = _splitdoc(mod) + ui.write(minirst.subsection(_(b"Description"))) + ui.write(b"%s\n\n" % gettext(longdoc)) + cmdtable = getattr(mod, 'cmdtable', None) + if cmdtable: + ui.write(minirst.subsection(_(b'Commands'))) + commandprinter(ui, cmdtable, minirst.subsubsection, minirst.subsubsubsection) + + def commandprinter(ui, cmdtable, sectionfunc, subsectionfunc): """Render restructuredtext describing a list of commands and their documentations, grouped by command category. @@ -233,88 +438,63 @@ sectionfunc: minirst function to format command category headers subsectionfunc: minirst function to format command headers """ - h = {} - for c, attr in cmdtable.items(): - f = c.split(b"|")[0] - f = f.lstrip(b"^") - h[f] = c - cmds = h.keys() + cmdnames = allcommandnames(cmdtable) + for f in sorted(cmdnames.keys()): + d = get_cmd(cmdnames[f], cmdtable) + _onecommandprinter(ui, d, sectionfunc) + - def helpcategory(cmd): - """Given a canonical command name from `cmds` (above), retrieve its - help category. If helpcategory is None, default to CATEGORY_NONE. - """ - fullname = h[cmd] - details = cmdtable[fullname] - helpcategory = details[0].helpcategory - return helpcategory or help.registrar.command.CATEGORY_NONE +def _optionsprinter(ui, cmd, sectionfunc): + opt_output = list(cmd[b'opts']) + if opt_output: + opts_len = max([len(line[0]) for line in opt_output]) + ui.write(sectionfunc(_(b"Options"))) + multioccur = False + for optstr, desc in opt_output: + if desc: + s = b"%-*s %s" % (opts_len, optstr, desc) + else: + s = optstr + ui.write(b"%s\n" % s) + if optstr.endswith(b"[+]>"): + multioccur = True + if multioccur: + ui.write(_(b"\n[+] marked option can be specified" + b" multiple times\n")) + ui.write(b"\n") + - cmdsbycategory = {category: [] for category in help.CATEGORY_ORDER} - for cmd in cmds: - # If a command category wasn't registered, the command won't get - # rendered below, so we raise an AssertionError. - if helpcategory(cmd) not in cmdsbycategory: - raise AssertionError( - "The following command did not register its (category) in " - "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd)) - ) - cmdsbycategory[helpcategory(cmd)].append(cmd) +def _onecommandprinter(ui, cmd, sectionfunc): + ui.write(sectionfunc(cmd[b'cmd'])) + # short description + ui.write(cmd[b'desc'][0]) + # synopsis + ui.write(b"::\n\n") + synopsislines = cmd[b'synopsis'].splitlines() + for line in synopsislines: + # some commands (such as rebase) have a multi-line + # synopsis + ui.write(b" %s\n" % line) + ui.write(b'\n') + # description + ui.write(b"%s\n\n" % cmd[b'desc'][1]) + # options + def _optsection(s): + return b"%s:\n\n" % s + _optionsprinter(ui, cmd, _optsection) + # aliases + if cmd[b'aliases']: + ui.write(_(b" aliases: %s\n\n") % b" ".join(cmd[b'aliases'])) - # Print the help for each command. We present the commands grouped by - # category, and we use help.CATEGORY_ORDER as a guide for a helpful order - # in which to present the categories. - for category in help.CATEGORY_ORDER: - categorycmds = cmdsbycategory[category] - if not categorycmds: - # Skip empty categories + +def allcommandnames(cmdtable, debugcmds=False): + allcmdnames = {} + for cmdnames, attr in cmdtable.items(): + mainname = cmdnames.split(b"|")[0].lstrip(b"^") + if not debugcmds and mainname.startswith(b"debug"): continue - # Print a section header for the category. - # For now, the category header is at the same level as the headers for - # the commands in the category; this is fixed in the next commit. - ui.write(sectionfunc(help.CATEGORY_NAMES[category])) - # Print each command in the category - for f in sorted(categorycmds): - if f.startswith(b"debug"): - continue - d = get_cmd(h[f], cmdtable) - ui.write(subsectionfunc(d[b'cmd'])) - # short description - ui.write(d[b'desc'][0]) - # synopsis - ui.write(b"::\n\n") - synopsislines = d[b'synopsis'].splitlines() - for line in synopsislines: - # some commands (such as rebase) have a multi-line - # synopsis - ui.write(b" %s\n" % line) - ui.write(b'\n') - # description - ui.write(b"%s\n\n" % d[b'desc'][1]) - # options - opt_output = list(d[b'opts']) - if opt_output: - opts_len = max([len(line[0]) for line in opt_output]) - ui.write(_(b"Options:\n\n")) - multioccur = False - for optstr, desc in opt_output: - if desc: - s = b"%-*s %s" % (opts_len, optstr, desc) - else: - s = optstr - ui.write(b"%s\n" % s) - if optstr.endswith(b"[+]>"): - multioccur = True - if multioccur: - ui.write( - _( - b"\n[+] marked option can be specified" - b" multiple times\n" - ) - ) - ui.write(b"\n") - # aliases - if d[b'aliases']: - ui.write(_(b" aliases: %s\n\n") % b" ".join(d[b'aliases'])) + allcmdnames[mainname] = cmdnames + return allcmdnames def allextensionnames(): @@ -327,7 +507,21 @@ doc = encoding.strtolocal(sys.argv[1]) ui = uimod.ui.load() - if doc == b'hg.1.gendoc': + if doc == b'commandlist': + showcommandlist(ui) + elif doc == b'topiclist': + showtopiclist(ui) + elif doc == b'extensionlist': + showextensionlist(ui) + elif doc == b'index': + showhelpindex(ui) + elif doc == b'hg.1.gendoc': showdoc(ui) + elif doc.startswith(b'cmd-'): + showcommand(ui, doc[4:]) + elif doc.startswith(b'topic-'): + showtopic(ui, doc[6:], wraptpl=True) + elif doc.startswith(b'ext-'): + showextension(ui, doc[4:]) else: - showtopic(ui, encoding.strtolocal(sys.argv[1])) + showtopic(ui, doc) diff --git a/doc/templates/cmdheader.txt b/doc/templates/cmdheader.txt new file mode 100644 --- /dev/null +++ b/doc/templates/cmdheader.txt @@ -0,0 +1,22 @@ +.. _hg-%(cmdname)s.1: + +%(cmdtitle)s + +%(cmdshortdesc)s + +.. contents:: + :backlinks: top + :class: htmlonly + :depth: 1 + +Synopsis +-------- + +:: + + %(cmdsynopsis)s + +Description +----------- +%(cmdlongdesc)s + diff --git a/doc/templates/extheader.txt b/doc/templates/extheader.txt new file mode 100644 --- /dev/null +++ b/doc/templates/extheader.txt @@ -0,0 +1,4 @@ +.. _ext-%(extname)s: + +%(exttitle)s + diff --git a/doc/templates/topicheader.txt b/doc/templates/topicheader.txt new file mode 100644 --- /dev/null +++ b/doc/templates/topicheader.txt @@ -0,0 +1,4 @@ +.. _topic-%(topicname)s: + +%(topictitle)s +