D9135: doc: Generate separate commands/topics/extension web pages.
ludovicchabant (Ludovic Chabant)
phabricator at mercurial-scm.org
Wed Sep 30 21:35:32 UTC 2020
ludovicchabant created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.
REPOSITORY
rHG Mercurial
REVISION DETAIL
https://phab.mercurial-scm.org/D9135
AFFECTED FILES
.hgignore
doc/Makefile
doc/gendoc.py
doc/templates/cmdheader.txt
doc/templates/extheader.txt
doc/templates/topicheader.txt
CHANGE DETAILS
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
+
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/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/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/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/.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
To: ludovicchabant, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
More information about the Mercurial-devel
mailing list