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