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