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