##// END OF EJS Templates
help: add web commands to help documentation...
Gregory Szorc -
r24080:a3f2ea1d default
parent child Browse files
Show More
@@ -1,504 +1,506 b''
1 1 # help.py - help data for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import gettext, _
9 9 import itertools, os
10 10 import error
11 11 import extensions, revset, fileset, templatekw, templatefilters, filemerge
12 12 import encoding, util, minirst
13 13 import cmdutil
14 import hgweb.webcommands as webcommands
14 15
15 16 def listexts(header, exts, indent=1, showdeprecated=False):
16 17 '''return a text listing of the given extensions'''
17 18 rst = []
18 19 if exts:
19 20 rst.append('\n%s\n\n' % header)
20 21 for name, desc in sorted(exts.iteritems()):
21 22 if '(DEPRECATED)' in desc and not showdeprecated:
22 23 continue
23 24 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
24 25 return rst
25 26
26 27 def extshelp():
27 28 rst = loaddoc('extensions')().splitlines(True)
28 29 rst.extend(listexts(
29 30 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
30 31 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
31 32 doc = ''.join(rst)
32 33 return doc
33 34
34 35 def optrst(header, options, verbose):
35 36 data = []
36 37 multioccur = False
37 38 for option in options:
38 39 if len(option) == 5:
39 40 shortopt, longopt, default, desc, optlabel = option
40 41 else:
41 42 shortopt, longopt, default, desc = option
42 43 optlabel = _("VALUE") # default label
43 44
44 45 if not verbose and ("DEPRECATED" in desc or _("DEPRECATED") in desc):
45 46 continue
46 47
47 48 so = ''
48 49 if shortopt:
49 50 so = '-' + shortopt
50 51 lo = '--' + longopt
51 52 if default:
52 53 desc += _(" (default: %s)") % default
53 54
54 55 if isinstance(default, list):
55 56 lo += " %s [+]" % optlabel
56 57 multioccur = True
57 58 elif (default is not None) and not isinstance(default, bool):
58 59 lo += " %s" % optlabel
59 60
60 61 data.append((so, lo, desc))
61 62
62 63 if multioccur:
63 64 header += (_(" ([+] can be repeated)"))
64 65
65 66 rst = ['\n%s:\n\n' % header]
66 67 rst.extend(minirst.maketable(data, 1))
67 68
68 69 return ''.join(rst)
69 70
70 71 def indicateomitted(rst, omitted, notomitted=None):
71 72 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
72 73 if notomitted:
73 74 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
74 75
75 76 def topicmatch(kw):
76 77 """Return help topics matching kw.
77 78
78 79 Returns {'section': [(name, summary), ...], ...} where section is
79 80 one of topics, commands, extensions, or extensioncommands.
80 81 """
81 82 kw = encoding.lower(kw)
82 83 def lowercontains(container):
83 84 return kw in encoding.lower(container) # translated in helptable
84 85 results = {'topics': [],
85 86 'commands': [],
86 87 'extensions': [],
87 88 'extensioncommands': [],
88 89 }
89 90 for names, header, doc in helptable:
90 91 # Old extensions may use a str as doc.
91 92 if (sum(map(lowercontains, names))
92 93 or lowercontains(header)
93 94 or (callable(doc) and lowercontains(doc()))):
94 95 results['topics'].append((names[0], header))
95 96 import commands # avoid cycle
96 97 for cmd, entry in commands.table.iteritems():
97 98 if len(entry) == 3:
98 99 summary = entry[2]
99 100 else:
100 101 summary = ''
101 102 # translate docs *before* searching there
102 103 docs = _(getattr(entry[0], '__doc__', None)) or ''
103 104 if kw in cmd or lowercontains(summary) or lowercontains(docs):
104 105 doclines = docs.splitlines()
105 106 if doclines:
106 107 summary = doclines[0]
107 108 cmdname = cmd.split('|')[0].lstrip('^')
108 109 results['commands'].append((cmdname, summary))
109 110 for name, docs in itertools.chain(
110 111 extensions.enabled(False).iteritems(),
111 112 extensions.disabled().iteritems()):
112 113 # extensions.load ignores the UI argument
113 114 mod = extensions.load(None, name, '')
114 115 name = name.split('.')[-1]
115 116 if lowercontains(name) or lowercontains(docs):
116 117 # extension docs are already translated
117 118 results['extensions'].append((name, docs.splitlines()[0]))
118 119 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
119 120 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
120 121 cmdname = cmd.split('|')[0].lstrip('^')
121 122 if entry[0].__doc__:
122 123 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
123 124 else:
124 125 cmddoc = _('(no help text available)')
125 126 results['extensioncommands'].append((cmdname, cmddoc))
126 127 return results
127 128
128 129 def loaddoc(topic):
129 130 """Return a delayed loader for help/topic.txt."""
130 131
131 132 def loader():
132 133 docdir = os.path.join(util.datapath, 'help')
133 134 path = os.path.join(docdir, topic + ".txt")
134 135 doc = gettext(util.readfile(path))
135 136 for rewriter in helphooks.get(topic, []):
136 137 doc = rewriter(topic, doc)
137 138 return doc
138 139
139 140 return loader
140 141
141 142 helptable = sorted([
142 143 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
143 144 (["dates"], _("Date Formats"), loaddoc('dates')),
144 145 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
145 146 (['environment', 'env'], _('Environment Variables'),
146 147 loaddoc('environment')),
147 148 (['revisions', 'revs'], _('Specifying Single Revisions'),
148 149 loaddoc('revisions')),
149 150 (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'),
150 151 loaddoc('multirevs')),
151 152 (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')),
152 153 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
153 154 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
154 155 (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
155 156 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
156 157 loaddoc('templates')),
157 158 (['urls'], _('URL Paths'), loaddoc('urls')),
158 159 (["extensions"], _("Using Additional Features"), extshelp),
159 160 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
160 161 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
161 162 (["glossary"], _("Glossary"), loaddoc('glossary')),
162 163 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
163 164 loaddoc('hgignore')),
164 165 (["phases"], _("Working with Phases"), loaddoc('phases')),
165 166 ])
166 167
167 168 # Map topics to lists of callable taking the current topic help and
168 169 # returning the updated version
169 170 helphooks = {}
170 171
171 172 def addtopichook(topic, rewriter):
172 173 helphooks.setdefault(topic, []).append(rewriter)
173 174
174 175 def makeitemsdoc(topic, doc, marker, items):
175 176 """Extract docstring from the items key to function mapping, build a
176 177 .single documentation block and use it to overwrite the marker in doc
177 178 """
178 179 entries = []
179 180 for name in sorted(items):
180 181 text = (items[name].__doc__ or '').rstrip()
181 182 if not text:
182 183 continue
183 184 text = gettext(text)
184 185 lines = text.splitlines()
185 186 doclines = [(lines[0])]
186 187 for l in lines[1:]:
187 188 # Stop once we find some Python doctest
188 189 if l.strip().startswith('>>>'):
189 190 break
190 191 doclines.append(' ' + l.strip())
191 192 entries.append('\n'.join(doclines))
192 193 entries = '\n\n'.join(entries)
193 194 return doc.replace(marker, entries)
194 195
195 196 def addtopicsymbols(topic, marker, symbols):
196 197 def add(topic, doc):
197 198 return makeitemsdoc(topic, doc, marker, symbols)
198 199 addtopichook(topic, add)
199 200
200 201 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
201 202 addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals)
202 203 addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
203 204 addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
204 205 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
206 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands)
205 207
206 208 def help_(ui, name, unknowncmd=False, full=True, **opts):
207 209 '''
208 210 Generate the help for 'name' as unformatted restructured text. If
209 211 'name' is None, describe the commands available.
210 212 '''
211 213
212 214 import commands # avoid cycle
213 215
214 216 def helpcmd(name):
215 217 try:
216 218 aliases, entry = cmdutil.findcmd(name, commands.table,
217 219 strict=unknowncmd)
218 220 except error.AmbiguousCommand, inst:
219 221 # py3k fix: except vars can't be used outside the scope of the
220 222 # except block, nor can be used inside a lambda. python issue4617
221 223 prefix = inst.args[0]
222 224 select = lambda c: c.lstrip('^').startswith(prefix)
223 225 rst = helplist(select)
224 226 return rst
225 227
226 228 rst = []
227 229
228 230 # check if it's an invalid alias and display its error if it is
229 231 if getattr(entry[0], 'badalias', None):
230 232 rst.append(entry[0].badalias + '\n')
231 233 if entry[0].unknowncmd:
232 234 try:
233 235 rst.extend(helpextcmd(entry[0].cmdname))
234 236 except error.UnknownCommand:
235 237 pass
236 238 return rst
237 239
238 240 # synopsis
239 241 if len(entry) > 2:
240 242 if entry[2].startswith('hg'):
241 243 rst.append("%s\n" % entry[2])
242 244 else:
243 245 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
244 246 else:
245 247 rst.append('hg %s\n' % aliases[0])
246 248 # aliases
247 249 if full and not ui.quiet and len(aliases) > 1:
248 250 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
249 251 rst.append('\n')
250 252
251 253 # description
252 254 doc = gettext(entry[0].__doc__)
253 255 if not doc:
254 256 doc = _("(no help text available)")
255 257 if util.safehasattr(entry[0], 'definition'): # aliased command
256 258 if entry[0].definition.startswith('!'): # shell alias
257 259 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
258 260 else:
259 261 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
260 262 doc = doc.splitlines(True)
261 263 if ui.quiet or not full:
262 264 rst.append(doc[0])
263 265 else:
264 266 rst.extend(doc)
265 267 rst.append('\n')
266 268
267 269 # check if this command shadows a non-trivial (multi-line)
268 270 # extension help text
269 271 try:
270 272 mod = extensions.find(name)
271 273 doc = gettext(mod.__doc__) or ''
272 274 if '\n' in doc.strip():
273 275 msg = _('(use "hg help -e %s" to show help for '
274 276 'the %s extension)') % (name, name)
275 277 rst.append('\n%s\n' % msg)
276 278 except KeyError:
277 279 pass
278 280
279 281 # options
280 282 if not ui.quiet and entry[1]:
281 283 rst.append(optrst(_("options"), entry[1], ui.verbose))
282 284
283 285 if ui.verbose:
284 286 rst.append(optrst(_("global options"),
285 287 commands.globalopts, ui.verbose))
286 288
287 289 if not ui.verbose:
288 290 if not full:
289 291 rst.append(_('\n(use "hg %s -h" to show more help)\n')
290 292 % name)
291 293 elif not ui.quiet:
292 294 rst.append(_('\n(some details hidden, use --verbose '
293 295 'to show complete help)'))
294 296
295 297 return rst
296 298
297 299
298 300 def helplist(select=None):
299 301 # list of commands
300 302 if name == "shortlist":
301 303 header = _('basic commands:\n\n')
302 304 elif name == "debug":
303 305 header = _('debug commands (internal and unsupported):\n\n')
304 306 else:
305 307 header = _('list of commands:\n\n')
306 308
307 309 h = {}
308 310 cmds = {}
309 311 for c, e in commands.table.iteritems():
310 312 f = c.split("|", 1)[0]
311 313 if select and not select(f):
312 314 continue
313 315 if (not select and name != 'shortlist' and
314 316 e[0].__module__ != commands.__name__):
315 317 continue
316 318 if name == "shortlist" and not f.startswith("^"):
317 319 continue
318 320 f = f.lstrip("^")
319 321 if not ui.debugflag and f.startswith("debug") and name != "debug":
320 322 continue
321 323 doc = e[0].__doc__
322 324 if doc and 'DEPRECATED' in doc and not ui.verbose:
323 325 continue
324 326 doc = gettext(doc)
325 327 if not doc:
326 328 doc = _("(no help text available)")
327 329 h[f] = doc.splitlines()[0].rstrip()
328 330 cmds[f] = c.lstrip("^")
329 331
330 332 rst = []
331 333 if not h:
332 334 if not ui.quiet:
333 335 rst.append(_('no commands defined\n'))
334 336 return rst
335 337
336 338 if not ui.quiet:
337 339 rst.append(header)
338 340 fns = sorted(h)
339 341 for f in fns:
340 342 if ui.verbose:
341 343 commacmds = cmds[f].replace("|",", ")
342 344 rst.append(" :%s: %s\n" % (commacmds, h[f]))
343 345 else:
344 346 rst.append(' :%s: %s\n' % (f, h[f]))
345 347
346 348 if not name:
347 349 exts = listexts(_('enabled extensions:'), extensions.enabled())
348 350 if exts:
349 351 rst.append('\n')
350 352 rst.extend(exts)
351 353
352 354 rst.append(_("\nadditional help topics:\n\n"))
353 355 topics = []
354 356 for names, header, doc in helptable:
355 357 topics.append((names[0], header))
356 358 for t, desc in topics:
357 359 rst.append(" :%s: %s\n" % (t, desc))
358 360
359 361 if ui.quiet:
360 362 pass
361 363 elif ui.verbose:
362 364 rst.append('\n%s\n' % optrst(_("global options"),
363 365 commands.globalopts, ui.verbose))
364 366 if name == 'shortlist':
365 367 rst.append(_('\n(use "hg help" for the full list '
366 368 'of commands)\n'))
367 369 else:
368 370 if name == 'shortlist':
369 371 rst.append(_('\n(use "hg help" for the full list of commands '
370 372 'or "hg -v" for details)\n'))
371 373 elif name and not full:
372 374 rst.append(_('\n(use "hg help %s" to show the full help '
373 375 'text)\n') % name)
374 376 elif name and cmds and name in cmds.keys():
375 377 rst.append(_('\n(use "hg help -v -e %s" to show built-in '
376 378 'aliases and global options)\n') % name)
377 379 else:
378 380 rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
379 381 'and global options)\n')
380 382 % (name and " " + name or ""))
381 383 return rst
382 384
383 385 def helptopic(name):
384 386 for names, header, doc in helptable:
385 387 if name in names:
386 388 break
387 389 else:
388 390 raise error.UnknownCommand(name)
389 391
390 392 rst = [minirst.section(header)]
391 393
392 394 # description
393 395 if not doc:
394 396 rst.append(" %s\n" % _("(no help text available)"))
395 397 if callable(doc):
396 398 rst += [" %s\n" % l for l in doc().splitlines()]
397 399
398 400 if not ui.verbose:
399 401 omitted = _('(some details hidden, use --verbose'
400 402 ' to show complete help)')
401 403 indicateomitted(rst, omitted)
402 404
403 405 try:
404 406 cmdutil.findcmd(name, commands.table)
405 407 rst.append(_('\nuse "hg help -c %s" to see help for '
406 408 'the %s command\n') % (name, name))
407 409 except error.UnknownCommand:
408 410 pass
409 411 return rst
410 412
411 413 def helpext(name):
412 414 try:
413 415 mod = extensions.find(name)
414 416 doc = gettext(mod.__doc__) or _('no help text available')
415 417 except KeyError:
416 418 mod = None
417 419 doc = extensions.disabledext(name)
418 420 if not doc:
419 421 raise error.UnknownCommand(name)
420 422
421 423 if '\n' not in doc:
422 424 head, tail = doc, ""
423 425 else:
424 426 head, tail = doc.split('\n', 1)
425 427 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
426 428 if tail:
427 429 rst.extend(tail.splitlines(True))
428 430 rst.append('\n')
429 431
430 432 if not ui.verbose:
431 433 omitted = _('(some details hidden, use --verbose'
432 434 ' to show complete help)')
433 435 indicateomitted(rst, omitted)
434 436
435 437 if mod:
436 438 try:
437 439 ct = mod.cmdtable
438 440 except AttributeError:
439 441 ct = {}
440 442 modcmds = set([c.split('|', 1)[0] for c in ct])
441 443 rst.extend(helplist(modcmds.__contains__))
442 444 else:
443 445 rst.append(_('(use "hg help extensions" for information on enabling'
444 446 ' extensions)\n'))
445 447 return rst
446 448
447 449 def helpextcmd(name):
448 450 cmd, ext, mod = extensions.disabledcmd(ui, name,
449 451 ui.configbool('ui', 'strict'))
450 452 doc = gettext(mod.__doc__).splitlines()[0]
451 453
452 454 rst = listexts(_("'%s' is provided by the following "
453 455 "extension:") % cmd, {ext: doc}, indent=4)
454 456 rst.append('\n')
455 457 rst.append(_('(use "hg help extensions" for information on enabling '
456 458 'extensions)\n'))
457 459 return rst
458 460
459 461
460 462 rst = []
461 463 kw = opts.get('keyword')
462 464 if kw:
463 465 matches = topicmatch(kw)
464 466 for t, title in (('topics', _('Topics')),
465 467 ('commands', _('Commands')),
466 468 ('extensions', _('Extensions')),
467 469 ('extensioncommands', _('Extension Commands'))):
468 470 if matches[t]:
469 471 rst.append('%s:\n\n' % title)
470 472 rst.extend(minirst.maketable(sorted(matches[t]), 1))
471 473 rst.append('\n')
472 474 if not rst:
473 475 msg = _('no matches')
474 476 hint = _('try "hg help" for a list of topics')
475 477 raise util.Abort(msg, hint=hint)
476 478 elif name and name != 'shortlist':
477 479 if unknowncmd:
478 480 queries = (helpextcmd,)
479 481 elif opts.get('extension'):
480 482 queries = (helpext,)
481 483 elif opts.get('command'):
482 484 queries = (helpcmd,)
483 485 else:
484 486 queries = (helptopic, helpcmd, helpext, helpextcmd)
485 487 for f in queries:
486 488 try:
487 489 rst = f(name)
488 490 break
489 491 except error.UnknownCommand:
490 492 pass
491 493 else:
492 494 if unknowncmd:
493 495 raise error.UnknownCommand(name)
494 496 else:
495 497 msg = _('no such help topic: %s') % name
496 498 hint = _('try "hg help --keyword %s"') % name
497 499 raise util.Abort(msg, hint=hint)
498 500 else:
499 501 # program name
500 502 if not ui.quiet:
501 503 rst = [_("Mercurial Distributed SCM\n"), '\n']
502 504 rst.extend(helplist())
503 505
504 506 return ''.join(rst)
@@ -1,79 +1,86 b''
1 1 Mercurial's internal web server, hgweb, can serve either a single
2 2 repository, or a tree of repositories. In the second case, repository
3 3 paths and global options can be defined using a dedicated
4 4 configuration file common to :hg:`serve`, ``hgweb.wsgi``,
5 5 ``hgweb.cgi`` and ``hgweb.fcgi``.
6 6
7 7 This file uses the same syntax as other Mercurial configuration files
8 8 but recognizes only the following sections:
9 9
10 10 - web
11 11 - paths
12 12 - collections
13 13
14 14 The ``web`` options are thoroughly described in :hg:`help config`.
15 15
16 16 The ``paths`` section maps URL paths to paths of repositories in the
17 17 filesystem. hgweb will not expose the filesystem directly - only
18 18 Mercurial repositories can be published and only according to the
19 19 configuration.
20 20
21 21 The left hand side is the path in the URL. Note that hgweb reserves
22 22 subpaths like ``rev`` or ``file``, try using different names for
23 23 nested repositories to avoid confusing effects.
24 24
25 25 The right hand side is the path in the filesystem. If the specified
26 26 path ends with ``*`` or ``**`` the filesystem will be searched
27 27 recursively for repositories below that point.
28 28 With ``*`` it will not recurse into the repositories it finds (except for
29 29 ``.hg/patches``).
30 30 With ``**`` it will also search inside repository working directories
31 31 and possibly find subrepositories.
32 32
33 33 In this example::
34 34
35 35 [paths]
36 36 /projects/a = /srv/tmprepos/a
37 37 /projects/b = c:/repos/b
38 38 / = /srv/repos/*
39 39 /user/bob = /home/bob/repos/**
40 40
41 41 - The first two entries make two repositories in different directories
42 42 appear under the same directory in the web interface
43 43 - The third entry will publish every Mercurial repository found in
44 44 ``/srv/repos/``, for instance the repository ``/srv/repos/quux/``
45 45 will appear as ``http://server/quux/``
46 46 - The fourth entry will publish both ``http://server/user/bob/quux/``
47 47 and ``http://server/user/bob/quux/testsubrepo/``
48 48
49 49 The ``collections`` section is deprecated and has been superseded by
50 50 ``paths``.
51 51
52 52 URLs and Common Arguments
53 53 =========================
54 54
55 55 URLs under each repository have the form ``/{command}[/{arguments}]``
56 56 where ``{command}`` represents the name of a command or handler and
57 57 ``{arguments}`` represents any number of additional URL parameters
58 58 to that command.
59 59
60 60 The web server has a default style associated with it. Styles map to
61 61 a collection of named templates. Each template is used to render a
62 62 specific piece of data, such as a changeset or diff.
63 63
64 64 The style for the current request can be overwritten two ways. First,
65 65 if ``{command}`` contains a hyphen (``-``), the text before the hyphen
66 66 defines the style. For example, ``/atom-log`` will render the ``log``
67 67 command handler with the ``atom`` style. The second way to set the
68 68 style is with the ``style`` query string argument. For example,
69 69 ``/log?style=atom``. The hyphenated URL parameter is preferred.
70 70
71 71 Not all templates are available for all styles. Attempting to use
72 72 a style that doesn't have all templates defined may result in an error
73 73 rendering the page.
74 74
75 75 Many commands take a ``{revision}`` URL parameter. This defines the
76 76 changeset to operate on. This is commonly specified as the short,
77 77 12 digit hexidecimal abbreviation for the full 40 character unique
78 78 revision identifier. However, any value described by
79 79 :hg:`help revisions` typically works.
80
81 Commands and URLs
82 =================
83
84 The following web commands and their URLs are available:
85
86 .. webcommandsmarker
General Comments 0
You need to be logged in to leave comments. Login now