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