##// END OF EJS Templates
gendoc: nest command headers under category headers...
Sietse Brouwer -
r42438:a42cc325 default
parent child Browse files
Show More
@@ -1,271 +1,292
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """usage: %s DOC ...
2 """usage: %s DOC ...
3
3
4 where DOC is the name of a document
4 where DOC is the name of a document
5 """
5 """
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import textwrap
11 import textwrap
12
12
13 try:
13 try:
14 import msvcrt
14 import msvcrt
15 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
15 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
16 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
16 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
17 except ImportError:
17 except ImportError:
18 pass
18 pass
19
19
20 # This script is executed during installs and may not have C extensions
20 # This script is executed during installs and may not have C extensions
21 # available. Relax C module requirements.
21 # available. Relax C module requirements.
22 os.environ[r'HGMODULEPOLICY'] = r'allow'
22 os.environ[r'HGMODULEPOLICY'] = r'allow'
23 # import from the live mercurial repo
23 # import from the live mercurial repo
24 sys.path.insert(0, r"..")
24 sys.path.insert(0, r"..")
25 from mercurial import demandimport; demandimport.enable()
25 from mercurial import demandimport; demandimport.enable()
26 # Load util so that the locale path is set by i18n.setdatapath() before
26 # Load util so that the locale path is set by i18n.setdatapath() before
27 # calling _().
27 # calling _().
28 from mercurial import util
28 from mercurial import util
29 util.datapath
29 util.datapath
30 from mercurial import (
30 from mercurial import (
31 commands,
31 commands,
32 encoding,
32 encoding,
33 extensions,
33 extensions,
34 help,
34 help,
35 minirst,
35 minirst,
36 pycompat,
36 pycompat,
37 ui as uimod,
37 ui as uimod,
38 )
38 )
39 from mercurial.i18n import (
39 from mercurial.i18n import (
40 gettext,
40 gettext,
41 _,
41 _,
42 )
42 )
43
43
44 table = commands.table
44 table = commands.table
45 globalopts = commands.globalopts
45 globalopts = commands.globalopts
46 helptable = help.helptable
46 helptable = help.helptable
47 loaddoc = help.loaddoc
47 loaddoc = help.loaddoc
48
48
49 def get_desc(docstr):
49 def get_desc(docstr):
50 if not docstr:
50 if not docstr:
51 return b"", b""
51 return b"", b""
52 # sanitize
52 # sanitize
53 docstr = docstr.strip(b"\n")
53 docstr = docstr.strip(b"\n")
54 docstr = docstr.rstrip()
54 docstr = docstr.rstrip()
55 shortdesc = docstr.splitlines()[0].strip()
55 shortdesc = docstr.splitlines()[0].strip()
56
56
57 i = docstr.find(b"\n")
57 i = docstr.find(b"\n")
58 if i != -1:
58 if i != -1:
59 desc = docstr[i + 2:]
59 desc = docstr[i + 2:]
60 else:
60 else:
61 desc = shortdesc
61 desc = shortdesc
62
62
63 desc = textwrap.dedent(desc.decode('latin1')).encode('latin1')
63 desc = textwrap.dedent(desc.decode('latin1')).encode('latin1')
64
64
65 return (shortdesc, desc)
65 return (shortdesc, desc)
66
66
67 def get_opts(opts):
67 def get_opts(opts):
68 for opt in opts:
68 for opt in opts:
69 if len(opt) == 5:
69 if len(opt) == 5:
70 shortopt, longopt, default, desc, optlabel = opt
70 shortopt, longopt, default, desc, optlabel = opt
71 else:
71 else:
72 shortopt, longopt, default, desc = opt
72 shortopt, longopt, default, desc = opt
73 optlabel = _(b"VALUE")
73 optlabel = _(b"VALUE")
74 allopts = []
74 allopts = []
75 if shortopt:
75 if shortopt:
76 allopts.append(b"-%s" % shortopt)
76 allopts.append(b"-%s" % shortopt)
77 if longopt:
77 if longopt:
78 allopts.append(b"--%s" % longopt)
78 allopts.append(b"--%s" % longopt)
79 if isinstance(default, list):
79 if isinstance(default, list):
80 allopts[-1] += b" <%s[+]>" % optlabel
80 allopts[-1] += b" <%s[+]>" % optlabel
81 elif (default is not None) and not isinstance(default, bool):
81 elif (default is not None) and not isinstance(default, bool):
82 allopts[-1] += b" <%s>" % optlabel
82 allopts[-1] += b" <%s>" % optlabel
83 if b'\n' in desc:
83 if b'\n' in desc:
84 # only remove line breaks and indentation
84 # only remove line breaks and indentation
85 desc = b' '.join(l.lstrip() for l in desc.split(b'\n'))
85 desc = b' '.join(l.lstrip() for l in desc.split(b'\n'))
86 desc += default and _(b" (default: %s)") % bytes(default) or b""
86 desc += default and _(b" (default: %s)") % bytes(default) or b""
87 yield (b", ".join(allopts), desc)
87 yield (b", ".join(allopts), desc)
88
88
89 def get_cmd(cmd, cmdtable):
89 def get_cmd(cmd, cmdtable):
90 d = {}
90 d = {}
91 attr = cmdtable[cmd]
91 attr = cmdtable[cmd]
92 cmds = cmd.lstrip(b"^").split(b"|")
92 cmds = cmd.lstrip(b"^").split(b"|")
93
93
94 d[b'cmd'] = cmds[0]
94 d[b'cmd'] = cmds[0]
95 d[b'aliases'] = cmd.split(b"|")[1:]
95 d[b'aliases'] = cmd.split(b"|")[1:]
96 d[b'desc'] = get_desc(gettext(pycompat.getdoc(attr[0])))
96 d[b'desc'] = get_desc(gettext(pycompat.getdoc(attr[0])))
97 d[b'opts'] = list(get_opts(attr[1]))
97 d[b'opts'] = list(get_opts(attr[1]))
98
98
99 s = b'hg ' + cmds[0]
99 s = b'hg ' + cmds[0]
100 if len(attr) > 2:
100 if len(attr) > 2:
101 if not attr[2].startswith(b'hg'):
101 if not attr[2].startswith(b'hg'):
102 s += b' ' + attr[2]
102 s += b' ' + attr[2]
103 else:
103 else:
104 s = attr[2]
104 s = attr[2]
105 d[b'synopsis'] = s.strip()
105 d[b'synopsis'] = s.strip()
106
106
107 return d
107 return d
108
108
109 def showdoc(ui):
109 def showdoc(ui):
110 # print options
110 # print options
111 ui.write(minirst.section(_(b"Options")))
111 ui.write(minirst.section(_(b"Options")))
112 multioccur = False
112 multioccur = False
113 for optstr, desc in get_opts(globalopts):
113 for optstr, desc in get_opts(globalopts):
114 ui.write(b"%s\n %s\n\n" % (optstr, desc))
114 ui.write(b"%s\n %s\n\n" % (optstr, desc))
115 if optstr.endswith(b"[+]>"):
115 if optstr.endswith(b"[+]>"):
116 multioccur = True
116 multioccur = True
117 if multioccur:
117 if multioccur:
118 ui.write(_(b"\n[+] marked option can be specified multiple times\n"))
118 ui.write(_(b"\n[+] marked option can be specified multiple times\n"))
119 ui.write(b"\n")
119 ui.write(b"\n")
120
120
121 # print cmds
121 # print cmds
122 ui.write(minirst.section(_(b"Commands")))
122 ui.write(minirst.section(_(b"Commands")))
123 commandprinter(ui, table, minirst.subsection)
123 commandprinter(ui, table, minirst.subsection, minirst.subsubsection)
124
124
125 # print help topics
125 # print help topics
126 # The config help topic is included in the hgrc.5 man page.
126 # The config help topic is included in the hgrc.5 man page.
127 helpprinter(ui, helptable, minirst.section, exclude=[b'config'])
127 helpprinter(ui, helptable, minirst.section, exclude=[b'config'])
128
128
129 ui.write(minirst.section(_(b"Extensions")))
129 ui.write(minirst.section(_(b"Extensions")))
130 ui.write(_(b"This section contains help for extensions that are "
130 ui.write(_(b"This section contains help for extensions that are "
131 b"distributed together with Mercurial. Help for other "
131 b"distributed together with Mercurial. Help for other "
132 b"extensions is available in the help system."))
132 b"extensions is available in the help system."))
133 ui.write((b"\n\n"
133 ui.write((b"\n\n"
134 b".. contents::\n"
134 b".. contents::\n"
135 b" :class: htmlonly\n"
135 b" :class: htmlonly\n"
136 b" :local:\n"
136 b" :local:\n"
137 b" :depth: 1\n\n"))
137 b" :depth: 1\n\n"))
138
138
139 for extensionname in sorted(allextensionnames()):
139 for extensionname in sorted(allextensionnames()):
140 mod = extensions.load(ui, extensionname, None)
140 mod = extensions.load(ui, extensionname, None)
141 ui.write(minirst.subsection(extensionname))
141 ui.write(minirst.subsection(extensionname))
142 ui.write(b"%s\n\n" % gettext(pycompat.getdoc(mod)))
142 ui.write(b"%s\n\n" % gettext(pycompat.getdoc(mod)))
143 cmdtable = getattr(mod, 'cmdtable', None)
143 cmdtable = getattr(mod, 'cmdtable', None)
144 if cmdtable:
144 if cmdtable:
145 ui.write(minirst.subsubsection(_(b'Commands')))
145 ui.write(minirst.subsubsection(_(b'Commands')))
146 commandprinter(ui, cmdtable, minirst.subsubsubsection)
146 commandprinter(ui, cmdtable, minirst.subsubsubsection,
147 minirst.subsubsubsubsection)
147
148
148 def showtopic(ui, topic):
149 def showtopic(ui, topic):
149 extrahelptable = [
150 extrahelptable = [
150 ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC),
151 ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC),
151 ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG),
152 ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG),
152 ([b"hg-ssh.8"], b'', loaddoc(b'hg-ssh.8'), help.TOPIC_CATEGORY_CONFIG),
153 ([b"hg-ssh.8"], b'', loaddoc(b'hg-ssh.8'), help.TOPIC_CATEGORY_CONFIG),
153 ([b"hgignore.5"], b'', loaddoc(b'hgignore.5'),
154 ([b"hgignore.5"], b'', loaddoc(b'hgignore.5'),
154 help.TOPIC_CATEGORY_CONFIG),
155 help.TOPIC_CATEGORY_CONFIG),
155 ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG),
156 ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG),
156 ([b"hgignore.5.gendoc"], b'', loaddoc(b'hgignore'),
157 ([b"hgignore.5.gendoc"], b'', loaddoc(b'hgignore'),
157 help.TOPIC_CATEGORY_CONFIG),
158 help.TOPIC_CATEGORY_CONFIG),
158 ([b"hgrc.5.gendoc"], b'', loaddoc(b'config'),
159 ([b"hgrc.5.gendoc"], b'', loaddoc(b'config'),
159 help.TOPIC_CATEGORY_CONFIG),
160 help.TOPIC_CATEGORY_CONFIG),
160 ]
161 ]
161 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
162 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
162
163
163 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
164 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
164 for h in helptable:
165 for h in helptable:
165 names, sec, doc = h[0:3]
166 names, sec, doc = h[0:3]
166 if exclude and names[0] in exclude:
167 if exclude and names[0] in exclude:
167 continue
168 continue
168 if include and names[0] not in include:
169 if include and names[0] not in include:
169 continue
170 continue
170 for name in names:
171 for name in names:
171 ui.write(b".. _%s:\n" % name)
172 ui.write(b".. _%s:\n" % name)
172 ui.write(b"\n")
173 ui.write(b"\n")
173 if sectionfunc:
174 if sectionfunc:
174 ui.write(sectionfunc(sec))
175 ui.write(sectionfunc(sec))
175 if callable(doc):
176 if callable(doc):
176 doc = doc(ui)
177 doc = doc(ui)
177 ui.write(doc)
178 ui.write(doc)
178 ui.write(b"\n")
179 ui.write(b"\n")
179
180
180 def commandprinter(ui, cmdtable, sectionfunc):
181 def commandprinter(ui, cmdtable, sectionfunc, subsectionfunc):
182 """Render restructuredtext describing a list of commands and their
183 documentations, grouped by command category.
184
185 Args:
186 ui: UI object to write the output to
187 cmdtable: a dict that maps a string of the command name plus its aliases
188 (separated with pipes) to a 3-tuple of (the command's function, a list
189 of its option descriptions, and a string summarizing available
190 options). Example, with aliases added for demonstration purposes:
191
192 'phase|alias1|alias2': (
193 <function phase at 0x7f0816b05e60>,
194 [ ('p', 'public', False, 'set changeset phase to public'),
195 ...,
196 ('r', 'rev', [], 'target revision', 'REV')],
197 '[-p|-d|-s] [-f] [-r] [REV...]'
198 )
199 sectionfunc: minirst function to format command category headers
200 subsectionfunc: minirst function to format command headers
201 """
181 h = {}
202 h = {}
182 for c, attr in cmdtable.items():
203 for c, attr in cmdtable.items():
183 f = c.split(b"|")[0]
204 f = c.split(b"|")[0]
184 f = f.lstrip(b"^")
205 f = f.lstrip(b"^")
185 h[f] = c
206 h[f] = c
186 cmds = h.keys()
207 cmds = h.keys()
187
208
188 def helpcategory(cmd):
209 def helpcategory(cmd):
189 """Given a canonical command name from `cmds` (above), retrieve its
210 """Given a canonical command name from `cmds` (above), retrieve its
190 help category. If helpcategory is None, default to CATEGORY_NONE.
211 help category. If helpcategory is None, default to CATEGORY_NONE.
191 """
212 """
192 fullname = h[cmd]
213 fullname = h[cmd]
193 details = cmdtable[fullname]
214 details = cmdtable[fullname]
194 helpcategory = details[0].helpcategory
215 helpcategory = details[0].helpcategory
195 return helpcategory or help.registrar.command.CATEGORY_NONE
216 return helpcategory or help.registrar.command.CATEGORY_NONE
196
217
197 cmdsbycategory = {category: [] for category in help.CATEGORY_ORDER}
218 cmdsbycategory = {category: [] for category in help.CATEGORY_ORDER}
198 for cmd in cmds:
219 for cmd in cmds:
199 # If a command category wasn't registered, the command won't get
220 # If a command category wasn't registered, the command won't get
200 # rendered below, so we raise an AssertionError.
221 # rendered below, so we raise an AssertionError.
201 if helpcategory(cmd) not in cmdsbycategory:
222 if helpcategory(cmd) not in cmdsbycategory:
202 raise AssertionError(
223 raise AssertionError(
203 "The following command did not register its (category) in "
224 "The following command did not register its (category) in "
204 "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd)))
225 "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd)))
205 cmdsbycategory[helpcategory(cmd)].append(cmd)
226 cmdsbycategory[helpcategory(cmd)].append(cmd)
206
227
207 # Print the help for each command. We present the commands grouped by
228 # Print the help for each command. We present the commands grouped by
208 # category, and we use help.CATEGORY_ORDER as a guide for a helpful order
229 # category, and we use help.CATEGORY_ORDER as a guide for a helpful order
209 # in which to present the categories.
230 # in which to present the categories.
210 for category in help.CATEGORY_ORDER:
231 for category in help.CATEGORY_ORDER:
211 categorycmds = cmdsbycategory[category]
232 categorycmds = cmdsbycategory[category]
212 if not categorycmds:
233 if not categorycmds:
213 # Skip empty categories
234 # Skip empty categories
214 continue
235 continue
215 # Print a section header for the category.
236 # Print a section header for the category.
216 # For now, the category header is at the same level as the headers for
237 # For now, the category header is at the same level as the headers for
217 # the commands in the category; this is fixed in the next commit.
238 # the commands in the category; this is fixed in the next commit.
218 ui.write(sectionfunc(help.CATEGORY_NAMES[category]))
239 ui.write(sectionfunc(help.CATEGORY_NAMES[category]))
219 # Print each command in the category
240 # Print each command in the category
220 for f in sorted(categorycmds):
241 for f in sorted(categorycmds):
221 if f.startswith(b"debug"):
242 if f.startswith(b"debug"):
222 continue
243 continue
223 d = get_cmd(h[f], cmdtable)
244 d = get_cmd(h[f], cmdtable)
224 ui.write(sectionfunc(d[b'cmd']))
245 ui.write(subsectionfunc(d[b'cmd']))
225 # short description
246 # short description
226 ui.write(d[b'desc'][0])
247 ui.write(d[b'desc'][0])
227 # synopsis
248 # synopsis
228 ui.write(b"::\n\n")
249 ui.write(b"::\n\n")
229 synopsislines = d[b'synopsis'].splitlines()
250 synopsislines = d[b'synopsis'].splitlines()
230 for line in synopsislines:
251 for line in synopsislines:
231 # some commands (such as rebase) have a multi-line
252 # some commands (such as rebase) have a multi-line
232 # synopsis
253 # synopsis
233 ui.write(b" %s\n" % line)
254 ui.write(b" %s\n" % line)
234 ui.write(b'\n')
255 ui.write(b'\n')
235 # description
256 # description
236 ui.write(b"%s\n\n" % d[b'desc'][1])
257 ui.write(b"%s\n\n" % d[b'desc'][1])
237 # options
258 # options
238 opt_output = list(d[b'opts'])
259 opt_output = list(d[b'opts'])
239 if opt_output:
260 if opt_output:
240 opts_len = max([len(line[0]) for line in opt_output])
261 opts_len = max([len(line[0]) for line in opt_output])
241 ui.write(_(b"Options:\n\n"))
262 ui.write(_(b"Options:\n\n"))
242 multioccur = False
263 multioccur = False
243 for optstr, desc in opt_output:
264 for optstr, desc in opt_output:
244 if desc:
265 if desc:
245 s = b"%-*s %s" % (opts_len, optstr, desc)
266 s = b"%-*s %s" % (opts_len, optstr, desc)
246 else:
267 else:
247 s = optstr
268 s = optstr
248 ui.write(b"%s\n" % s)
269 ui.write(b"%s\n" % s)
249 if optstr.endswith(b"[+]>"):
270 if optstr.endswith(b"[+]>"):
250 multioccur = True
271 multioccur = True
251 if multioccur:
272 if multioccur:
252 ui.write(_(b"\n[+] marked option can be specified"
273 ui.write(_(b"\n[+] marked option can be specified"
253 b" multiple times\n"))
274 b" multiple times\n"))
254 ui.write(b"\n")
275 ui.write(b"\n")
255 # aliases
276 # aliases
256 if d[b'aliases']:
277 if d[b'aliases']:
257 ui.write(_(b" aliases: %s\n\n") % b" ".join(d[b'aliases']))
278 ui.write(_(b" aliases: %s\n\n") % b" ".join(d[b'aliases']))
258
279
259 def allextensionnames():
280 def allextensionnames():
260 return set(extensions.enabled().keys()) | set(extensions.disabled().keys())
281 return set(extensions.enabled().keys()) | set(extensions.disabled().keys())
261
282
262 if __name__ == "__main__":
283 if __name__ == "__main__":
263 doc = b'hg.1.gendoc'
284 doc = b'hg.1.gendoc'
264 if len(sys.argv) > 1:
285 if len(sys.argv) > 1:
265 doc = encoding.strtolocal(sys.argv[1])
286 doc = encoding.strtolocal(sys.argv[1])
266
287
267 ui = uimod.ui.load()
288 ui = uimod.ui.load()
268 if doc == b'hg.1.gendoc':
289 if doc == b'hg.1.gendoc':
269 showdoc(ui)
290 showdoc(ui)
270 else:
291 else:
271 showtopic(ui, encoding.strtolocal(sys.argv[1]))
292 showtopic(ui, encoding.strtolocal(sys.argv[1]))
General Comments 0
You need to be logged in to leave comments. Login now