##// END OF EJS Templates
help: escape ':' (as '\:') when generating command names...
marmoute -
r47115:d481f30e default
parent child Browse files
Show More
@@ -1,1160 +1,1161 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 __future__ import absolute_import
9 9
10 10 import itertools
11 11 import re
12 12 import textwrap
13 13
14 14 from .i18n import (
15 15 _,
16 16 gettext,
17 17 )
18 18 from .pycompat import getattr
19 19 from . import (
20 20 cmdutil,
21 21 encoding,
22 22 error,
23 23 extensions,
24 24 fancyopts,
25 25 filemerge,
26 26 fileset,
27 27 minirst,
28 28 pycompat,
29 29 registrar,
30 30 revset,
31 31 templatefilters,
32 32 templatefuncs,
33 33 templatekw,
34 34 ui as uimod,
35 35 util,
36 36 )
37 37 from .hgweb import webcommands
38 38 from .utils import (
39 39 compression,
40 40 resourceutil,
41 41 )
42 42
43 43 _exclkeywords = {
44 44 b"(ADVANCED)",
45 45 b"(DEPRECATED)",
46 46 b"(EXPERIMENTAL)",
47 47 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
48 48 _(b"(ADVANCED)"),
49 49 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
50 50 _(b"(DEPRECATED)"),
51 51 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
52 52 _(b"(EXPERIMENTAL)"),
53 53 }
54 54
55 55 # The order in which command categories will be displayed.
56 56 # Extensions with custom categories should insert them into this list
57 57 # after/before the appropriate item, rather than replacing the list or
58 58 # assuming absolute positions.
59 59 CATEGORY_ORDER = [
60 60 registrar.command.CATEGORY_REPO_CREATION,
61 61 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
62 62 registrar.command.CATEGORY_COMMITTING,
63 63 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
64 64 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
65 65 registrar.command.CATEGORY_FILE_CONTENTS,
66 66 registrar.command.CATEGORY_CHANGE_NAVIGATION,
67 67 registrar.command.CATEGORY_WORKING_DIRECTORY,
68 68 registrar.command.CATEGORY_IMPORT_EXPORT,
69 69 registrar.command.CATEGORY_MAINTENANCE,
70 70 registrar.command.CATEGORY_HELP,
71 71 registrar.command.CATEGORY_MISC,
72 72 registrar.command.CATEGORY_NONE,
73 73 ]
74 74
75 75 # Human-readable category names. These are translated.
76 76 # Extensions with custom categories should add their names here.
77 77 CATEGORY_NAMES = {
78 78 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation',
79 79 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management',
80 80 registrar.command.CATEGORY_COMMITTING: b'Change creation',
81 81 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation',
82 82 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation',
83 83 registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization',
84 84 registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management',
85 85 registrar.command.CATEGORY_FILE_CONTENTS: b'File content management',
86 86 registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export',
87 87 registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance',
88 88 registrar.command.CATEGORY_HELP: b'Help',
89 89 registrar.command.CATEGORY_MISC: b'Miscellaneous commands',
90 90 registrar.command.CATEGORY_NONE: b'Uncategorized commands',
91 91 }
92 92
93 93 # Topic categories.
94 94 TOPIC_CATEGORY_IDS = b'ids'
95 95 TOPIC_CATEGORY_OUTPUT = b'output'
96 96 TOPIC_CATEGORY_CONFIG = b'config'
97 97 TOPIC_CATEGORY_CONCEPTS = b'concepts'
98 98 TOPIC_CATEGORY_MISC = b'misc'
99 99 TOPIC_CATEGORY_NONE = b'none'
100 100
101 101 # The order in which topic categories will be displayed.
102 102 # Extensions with custom categories should insert them into this list
103 103 # after/before the appropriate item, rather than replacing the list or
104 104 # assuming absolute positions.
105 105 TOPIC_CATEGORY_ORDER = [
106 106 TOPIC_CATEGORY_IDS,
107 107 TOPIC_CATEGORY_OUTPUT,
108 108 TOPIC_CATEGORY_CONFIG,
109 109 TOPIC_CATEGORY_CONCEPTS,
110 110 TOPIC_CATEGORY_MISC,
111 111 TOPIC_CATEGORY_NONE,
112 112 ]
113 113
114 114 # Human-readable topic category names. These are translated.
115 115 TOPIC_CATEGORY_NAMES = {
116 116 TOPIC_CATEGORY_IDS: b'Mercurial identifiers',
117 117 TOPIC_CATEGORY_OUTPUT: b'Mercurial output',
118 118 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration',
119 119 TOPIC_CATEGORY_CONCEPTS: b'Concepts',
120 120 TOPIC_CATEGORY_MISC: b'Miscellaneous',
121 121 TOPIC_CATEGORY_NONE: b'Uncategorized topics',
122 122 }
123 123
124 124
125 125 def listexts(header, exts, indent=1, showdeprecated=False):
126 126 '''return a text listing of the given extensions'''
127 127 rst = []
128 128 if exts:
129 129 for name, desc in sorted(pycompat.iteritems(exts)):
130 130 if not showdeprecated and any(w in desc for w in _exclkeywords):
131 131 continue
132 132 rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc))
133 133 if rst:
134 134 rst.insert(0, b'\n%s\n\n' % header)
135 135 return rst
136 136
137 137
138 138 def extshelp(ui):
139 139 rst = loaddoc(b'extensions')(ui).splitlines(True)
140 140 rst.extend(
141 141 listexts(
142 142 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True
143 143 )
144 144 )
145 145 rst.extend(
146 146 listexts(
147 147 _(b'disabled extensions:'),
148 148 extensions.disabled(),
149 149 showdeprecated=ui.verbose,
150 150 )
151 151 )
152 152 doc = b''.join(rst)
153 153 return doc
154 154
155 155
156 156 def parsedefaultmarker(text):
157 157 """given a text 'abc (DEFAULT: def.ghi)',
158 158 returns (b'abc', (b'def', b'ghi')). Otherwise return None"""
159 159 if text[-1:] == b')':
160 160 marker = b' (DEFAULT: '
161 161 pos = text.find(marker)
162 162 if pos >= 0:
163 163 item = text[pos + len(marker) : -1]
164 164 return text[:pos], item.split(b'.', 2)
165 165
166 166
167 167 def optrst(header, options, verbose, ui):
168 168 data = []
169 169 multioccur = False
170 170 for option in options:
171 171 if len(option) == 5:
172 172 shortopt, longopt, default, desc, optlabel = option
173 173 else:
174 174 shortopt, longopt, default, desc = option
175 175 optlabel = _(b"VALUE") # default label
176 176
177 177 if not verbose and any(w in desc for w in _exclkeywords):
178 178 continue
179 179 defaultstrsuffix = b''
180 180 if default is None:
181 181 parseresult = parsedefaultmarker(desc)
182 182 if parseresult is not None:
183 183 (desc, (section, name)) = parseresult
184 184 if ui.configbool(section, name):
185 185 default = True
186 186 defaultstrsuffix = _(b' from config')
187 187 so = b''
188 188 if shortopt:
189 189 so = b'-' + shortopt
190 190 lo = b'--' + longopt
191 191 if default is True:
192 192 lo = b'--[no-]' + longopt
193 193
194 194 if isinstance(default, fancyopts.customopt):
195 195 default = default.getdefaultvalue()
196 196 if default and not callable(default):
197 197 # default is of unknown type, and in Python 2 we abused
198 198 # the %s-shows-repr property to handle integers etc. To
199 199 # match that behavior on Python 3, we do str(default) and
200 200 # then convert it to bytes.
201 201 defaultstr = pycompat.bytestr(default)
202 202 if default is True:
203 203 defaultstr = _(b"on")
204 204 desc += _(b" (default: %s)") % (defaultstr + defaultstrsuffix)
205 205
206 206 if isinstance(default, list):
207 207 lo += b" %s [+]" % optlabel
208 208 multioccur = True
209 209 elif (default is not None) and not isinstance(default, bool):
210 210 lo += b" %s" % optlabel
211 211
212 212 data.append((so, lo, desc))
213 213
214 214 if multioccur:
215 215 header += _(b" ([+] can be repeated)")
216 216
217 217 rst = [b'\n%s:\n\n' % header]
218 218 rst.extend(minirst.maketable(data, 1))
219 219
220 220 return b''.join(rst)
221 221
222 222
223 223 def indicateomitted(rst, omitted, notomitted=None):
224 224 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted)
225 225 if notomitted:
226 226 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
227 227
228 228
229 229 def filtercmd(ui, cmd, func, kw, doc):
230 230 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug":
231 231 # Debug command, and user is not looking for those.
232 232 return True
233 233 if not ui.verbose:
234 234 if not kw and not doc:
235 235 # Command had no documentation, no point in showing it by default.
236 236 return True
237 237 if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False):
238 238 # Alias didn't have its own documentation.
239 239 return True
240 240 if doc and any(w in doc for w in _exclkeywords):
241 241 # Documentation has excluded keywords.
242 242 return True
243 243 if kw == b"shortlist" and not getattr(func, 'helpbasic', False):
244 244 # We're presenting the short list but the command is not basic.
245 245 return True
246 246 if ui.configbool(b'help', b'hidden-command.%s' % cmd):
247 247 # Configuration explicitly hides the command.
248 248 return True
249 249 return False
250 250
251 251
252 252 def filtertopic(ui, topic):
253 253 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False)
254 254
255 255
256 256 def topicmatch(ui, commands, kw):
257 257 """Return help topics matching kw.
258 258
259 259 Returns {'section': [(name, summary), ...], ...} where section is
260 260 one of topics, commands, extensions, or extensioncommands.
261 261 """
262 262 kw = encoding.lower(kw)
263 263
264 264 def lowercontains(container):
265 265 return kw in encoding.lower(container) # translated in helptable
266 266
267 267 results = {
268 268 b'topics': [],
269 269 b'commands': [],
270 270 b'extensions': [],
271 271 b'extensioncommands': [],
272 272 }
273 273 for topic in helptable:
274 274 names, header, doc = topic[0:3]
275 275 # Old extensions may use a str as doc.
276 276 if (
277 277 sum(map(lowercontains, names))
278 278 or lowercontains(header)
279 279 or (callable(doc) and lowercontains(doc(ui)))
280 280 ):
281 281 name = names[0]
282 282 if not filtertopic(ui, name):
283 283 results[b'topics'].append((names[0], header))
284 284 for cmd, entry in pycompat.iteritems(commands.table):
285 285 if len(entry) == 3:
286 286 summary = entry[2]
287 287 else:
288 288 summary = b''
289 289 # translate docs *before* searching there
290 290 func = entry[0]
291 291 docs = _(pycompat.getdoc(func)) or b''
292 292 if kw in cmd or lowercontains(summary) or lowercontains(docs):
293 293 doclines = docs.splitlines()
294 294 if doclines:
295 295 summary = doclines[0]
296 296 cmdname = cmdutil.parsealiases(cmd)[0]
297 297 if filtercmd(ui, cmdname, func, kw, docs):
298 298 continue
299 299 results[b'commands'].append((cmdname, summary))
300 300 for name, docs in itertools.chain(
301 301 pycompat.iteritems(extensions.enabled(False)),
302 302 pycompat.iteritems(extensions.disabled()),
303 303 ):
304 304 if not docs:
305 305 continue
306 306 name = name.rpartition(b'.')[-1]
307 307 if lowercontains(name) or lowercontains(docs):
308 308 # extension docs are already translated
309 309 results[b'extensions'].append((name, docs.splitlines()[0]))
310 310 try:
311 311 mod = extensions.load(ui, name, b'')
312 312 except ImportError:
313 313 # debug message would be printed in extensions.load()
314 314 continue
315 315 for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})):
316 316 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
317 317 cmdname = cmdutil.parsealiases(cmd)[0]
318 318 func = entry[0]
319 319 cmddoc = pycompat.getdoc(func)
320 320 if cmddoc:
321 321 cmddoc = gettext(cmddoc).splitlines()[0]
322 322 else:
323 323 cmddoc = _(b'(no help text available)')
324 324 if filtercmd(ui, cmdname, func, kw, cmddoc):
325 325 continue
326 326 results[b'extensioncommands'].append((cmdname, cmddoc))
327 327 return results
328 328
329 329
330 330 def loaddoc(topic, subdir=None):
331 331 """Return a delayed loader for help/topic.txt."""
332 332
333 333 def loader(ui):
334 334 package = b'mercurial.helptext'
335 335 if subdir:
336 336 package += b'.' + subdir
337 337 with resourceutil.open_resource(package, topic + b'.txt') as fp:
338 338 doc = gettext(fp.read())
339 339 for rewriter in helphooks.get(topic, []):
340 340 doc = rewriter(ui, topic, doc)
341 341 return doc
342 342
343 343 return loader
344 344
345 345
346 346 internalstable = sorted(
347 347 [
348 348 (
349 349 [b'bid-merge'],
350 350 _(b'Bid Merge Algorithm'),
351 351 loaddoc(b'bid-merge', subdir=b'internals'),
352 352 ),
353 353 ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')),
354 354 ([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')),
355 355 ([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')),
356 356 ([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')),
357 357 (
358 358 [b'changegroups'],
359 359 _(b'Changegroups'),
360 360 loaddoc(b'changegroups', subdir=b'internals'),
361 361 ),
362 362 (
363 363 [b'config'],
364 364 _(b'Config Registrar'),
365 365 loaddoc(b'config', subdir=b'internals'),
366 366 ),
367 367 (
368 368 [b'extensions', b'extension'],
369 369 _(b'Extension API'),
370 370 loaddoc(b'extensions', subdir=b'internals'),
371 371 ),
372 372 (
373 373 [b'mergestate'],
374 374 _(b'Mergestate'),
375 375 loaddoc(b'mergestate', subdir=b'internals'),
376 376 ),
377 377 (
378 378 [b'requirements'],
379 379 _(b'Repository Requirements'),
380 380 loaddoc(b'requirements', subdir=b'internals'),
381 381 ),
382 382 (
383 383 [b'revlogs'],
384 384 _(b'Revision Logs'),
385 385 loaddoc(b'revlogs', subdir=b'internals'),
386 386 ),
387 387 (
388 388 [b'wireprotocol'],
389 389 _(b'Wire Protocol'),
390 390 loaddoc(b'wireprotocol', subdir=b'internals'),
391 391 ),
392 392 (
393 393 [b'wireprotocolrpc'],
394 394 _(b'Wire Protocol RPC'),
395 395 loaddoc(b'wireprotocolrpc', subdir=b'internals'),
396 396 ),
397 397 (
398 398 [b'wireprotocolv2'],
399 399 _(b'Wire Protocol Version 2'),
400 400 loaddoc(b'wireprotocolv2', subdir=b'internals'),
401 401 ),
402 402 ]
403 403 )
404 404
405 405
406 406 def internalshelp(ui):
407 407 """Generate the index for the "internals" topic."""
408 408 lines = [
409 409 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n',
410 410 b'\n',
411 411 ]
412 412 for names, header, doc in internalstable:
413 413 lines.append(b' :%s: %s\n' % (names[0], header))
414 414
415 415 return b''.join(lines)
416 416
417 417
418 418 helptable = sorted(
419 419 [
420 420 (
421 421 [b'bundlespec'],
422 422 _(b"Bundle File Formats"),
423 423 loaddoc(b'bundlespec'),
424 424 TOPIC_CATEGORY_CONCEPTS,
425 425 ),
426 426 (
427 427 [b'color'],
428 428 _(b"Colorizing Outputs"),
429 429 loaddoc(b'color'),
430 430 TOPIC_CATEGORY_OUTPUT,
431 431 ),
432 432 (
433 433 [b"config", b"hgrc"],
434 434 _(b"Configuration Files"),
435 435 loaddoc(b'config'),
436 436 TOPIC_CATEGORY_CONFIG,
437 437 ),
438 438 (
439 439 [b'deprecated'],
440 440 _(b"Deprecated Features"),
441 441 loaddoc(b'deprecated'),
442 442 TOPIC_CATEGORY_MISC,
443 443 ),
444 444 (
445 445 [b"dates"],
446 446 _(b"Date Formats"),
447 447 loaddoc(b'dates'),
448 448 TOPIC_CATEGORY_OUTPUT,
449 449 ),
450 450 (
451 451 [b"flags"],
452 452 _(b"Command-line flags"),
453 453 loaddoc(b'flags'),
454 454 TOPIC_CATEGORY_CONFIG,
455 455 ),
456 456 (
457 457 [b"patterns"],
458 458 _(b"File Name Patterns"),
459 459 loaddoc(b'patterns'),
460 460 TOPIC_CATEGORY_IDS,
461 461 ),
462 462 (
463 463 [b'environment', b'env'],
464 464 _(b'Environment Variables'),
465 465 loaddoc(b'environment'),
466 466 TOPIC_CATEGORY_CONFIG,
467 467 ),
468 468 (
469 469 [
470 470 b'revisions',
471 471 b'revs',
472 472 b'revsets',
473 473 b'revset',
474 474 b'multirevs',
475 475 b'mrevs',
476 476 ],
477 477 _(b'Specifying Revisions'),
478 478 loaddoc(b'revisions'),
479 479 TOPIC_CATEGORY_IDS,
480 480 ),
481 481 (
482 482 [b'filesets', b'fileset'],
483 483 _(b"Specifying File Sets"),
484 484 loaddoc(b'filesets'),
485 485 TOPIC_CATEGORY_IDS,
486 486 ),
487 487 (
488 488 [b'diffs'],
489 489 _(b'Diff Formats'),
490 490 loaddoc(b'diffs'),
491 491 TOPIC_CATEGORY_OUTPUT,
492 492 ),
493 493 (
494 494 [b'merge-tools', b'mergetools', b'mergetool'],
495 495 _(b'Merge Tools'),
496 496 loaddoc(b'merge-tools'),
497 497 TOPIC_CATEGORY_CONFIG,
498 498 ),
499 499 (
500 500 [b'templating', b'templates', b'template', b'style'],
501 501 _(b'Template Usage'),
502 502 loaddoc(b'templates'),
503 503 TOPIC_CATEGORY_OUTPUT,
504 504 ),
505 505 ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS),
506 506 (
507 507 [b"extensions"],
508 508 _(b"Using Additional Features"),
509 509 extshelp,
510 510 TOPIC_CATEGORY_CONFIG,
511 511 ),
512 512 (
513 513 [b"subrepos", b"subrepo"],
514 514 _(b"Subrepositories"),
515 515 loaddoc(b'subrepos'),
516 516 TOPIC_CATEGORY_CONCEPTS,
517 517 ),
518 518 (
519 519 [b"hgweb"],
520 520 _(b"Configuring hgweb"),
521 521 loaddoc(b'hgweb'),
522 522 TOPIC_CATEGORY_CONFIG,
523 523 ),
524 524 (
525 525 [b"glossary"],
526 526 _(b"Glossary"),
527 527 loaddoc(b'glossary'),
528 528 TOPIC_CATEGORY_CONCEPTS,
529 529 ),
530 530 (
531 531 [b"hgignore", b"ignore"],
532 532 _(b"Syntax for Mercurial Ignore Files"),
533 533 loaddoc(b'hgignore'),
534 534 TOPIC_CATEGORY_IDS,
535 535 ),
536 536 (
537 537 [b"phases"],
538 538 _(b"Working with Phases"),
539 539 loaddoc(b'phases'),
540 540 TOPIC_CATEGORY_CONCEPTS,
541 541 ),
542 542 (
543 543 [b'scripting'],
544 544 _(b'Using Mercurial from scripts and automation'),
545 545 loaddoc(b'scripting'),
546 546 TOPIC_CATEGORY_MISC,
547 547 ),
548 548 (
549 549 [b'internals'],
550 550 _(b"Technical implementation topics"),
551 551 internalshelp,
552 552 TOPIC_CATEGORY_MISC,
553 553 ),
554 554 (
555 555 [b'pager'],
556 556 _(b"Pager Support"),
557 557 loaddoc(b'pager'),
558 558 TOPIC_CATEGORY_CONFIG,
559 559 ),
560 560 ]
561 561 )
562 562
563 563 # Maps topics with sub-topics to a list of their sub-topics.
564 564 subtopics = {
565 565 b'internals': internalstable,
566 566 }
567 567
568 568 # Map topics to lists of callable taking the current topic help and
569 569 # returning the updated version
570 570 helphooks = {}
571 571
572 572
573 573 def addtopichook(topic, rewriter):
574 574 helphooks.setdefault(topic, []).append(rewriter)
575 575
576 576
577 577 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
578 578 """Extract docstring from the items key to function mapping, build a
579 579 single documentation block and use it to overwrite the marker in doc.
580 580 """
581 581 entries = []
582 582 for name in sorted(items):
583 583 text = (pycompat.getdoc(items[name]) or b'').rstrip()
584 584 if not text or not ui.verbose and any(w in text for w in _exclkeywords):
585 585 continue
586 586 text = gettext(text)
587 587 if dedent:
588 588 # Abuse latin1 to use textwrap.dedent() on bytes.
589 589 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
590 590 lines = text.splitlines()
591 591 doclines = [(lines[0])]
592 592 for l in lines[1:]:
593 593 # Stop once we find some Python doctest
594 594 if l.strip().startswith(b'>>>'):
595 595 break
596 596 if dedent:
597 597 doclines.append(l.rstrip())
598 598 else:
599 599 doclines.append(b' ' + l.strip())
600 600 entries.append(b'\n'.join(doclines))
601 601 entries = b'\n\n'.join(entries)
602 602 return doc.replace(marker, entries)
603 603
604 604
605 605 def addtopicsymbols(topic, marker, symbols, dedent=False):
606 606 def add(ui, topic, doc):
607 607 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
608 608
609 609 addtopichook(topic, add)
610 610
611 611
612 612 addtopicsymbols(
613 613 b'bundlespec',
614 614 b'.. bundlecompressionmarker',
615 615 compression.bundlecompressiontopics(),
616 616 )
617 617 addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols)
618 618 addtopicsymbols(
619 619 b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc
620 620 )
621 621 addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols)
622 622 addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords)
623 623 addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters)
624 624 addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs)
625 625 addtopicsymbols(
626 626 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True
627 627 )
628 628
629 629
630 630 def inserttweakrc(ui, topic, doc):
631 631 marker = b'.. tweakdefaultsmarker'
632 632 repl = uimod.tweakrc
633 633
634 634 def sub(m):
635 635 lines = [m.group(1) + s for s in repl.splitlines()]
636 636 return b'\n'.join(lines)
637 637
638 638 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
639 639
640 640
641 641 def _getcategorizedhelpcmds(ui, cmdtable, name, select=None):
642 642 # Category -> list of commands
643 643 cats = {}
644 644 # Command -> short description
645 645 h = {}
646 646 # Command -> string showing synonyms
647 647 syns = {}
648 648 for c, e in pycompat.iteritems(cmdtable):
649 649 fs = cmdutil.parsealiases(c)
650 650 f = fs[0]
651 651 syns[f] = fs
652 652 func = e[0]
653 653 if select and not select(f):
654 654 continue
655 655 doc = pycompat.getdoc(func)
656 656 if filtercmd(ui, f, func, name, doc):
657 657 continue
658 658 doc = gettext(doc)
659 659 if not doc:
660 660 doc = _(b"(no help text available)")
661 661 h[f] = doc.splitlines()[0].rstrip()
662 662
663 663 cat = getattr(func, 'helpcategory', None) or (
664 664 registrar.command.CATEGORY_NONE
665 665 )
666 666 cats.setdefault(cat, []).append(f)
667 667 return cats, h, syns
668 668
669 669
670 670 def _getcategorizedhelptopics(ui, topictable):
671 671 # Group commands by category.
672 672 topiccats = {}
673 673 syns = {}
674 674 for topic in topictable:
675 675 names, header, doc = topic[0:3]
676 676 if len(topic) > 3 and topic[3]:
677 677 category = topic[3]
678 678 else:
679 679 category = TOPIC_CATEGORY_NONE
680 680
681 681 topicname = names[0]
682 682 syns[topicname] = list(names)
683 683 if not filtertopic(ui, topicname):
684 684 topiccats.setdefault(category, []).append((topicname, header))
685 685 return topiccats, syns
686 686
687 687
688 688 addtopichook(b'config', inserttweakrc)
689 689
690 690
691 691 def help_(
692 692 ui,
693 693 commands,
694 694 name,
695 695 unknowncmd=False,
696 696 full=True,
697 697 subtopic=None,
698 698 fullname=None,
699 699 **opts
700 700 ):
701 701 """
702 702 Generate the help for 'name' as unformatted restructured text. If
703 703 'name' is None, describe the commands available.
704 704 """
705 705
706 706 opts = pycompat.byteskwargs(opts)
707 707
708 708 def helpcmd(name, subtopic=None):
709 709 try:
710 710 aliases, entry = cmdutil.findcmd(
711 711 name, commands.table, strict=unknowncmd
712 712 )
713 713 except error.AmbiguousCommand as inst:
714 714 # py3 fix: except vars can't be used outside the scope of the
715 715 # except block, nor can be used inside a lambda. python issue4617
716 716 prefix = inst.prefix
717 717 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
718 718 rst = helplist(select)
719 719 return rst
720 720
721 721 rst = []
722 722
723 723 # check if it's an invalid alias and display its error if it is
724 724 if getattr(entry[0], 'badalias', None):
725 725 rst.append(entry[0].badalias + b'\n')
726 726 if entry[0].unknowncmd:
727 727 try:
728 728 rst.extend(helpextcmd(entry[0].cmdname))
729 729 except error.UnknownCommand:
730 730 pass
731 731 return rst
732 732
733 733 # synopsis
734 734 if len(entry) > 2:
735 735 if entry[2].startswith(b'hg'):
736 736 rst.append(b"%s\n" % entry[2])
737 737 else:
738 738 rst.append(b'hg %s %s\n' % (aliases[0], entry[2]))
739 739 else:
740 740 rst.append(b'hg %s\n' % aliases[0])
741 741 # aliases
742 742 if full and not ui.quiet and len(aliases) > 1:
743 743 rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:]))
744 744 rst.append(b'\n')
745 745
746 746 # description
747 747 doc = gettext(pycompat.getdoc(entry[0]))
748 748 if not doc:
749 749 doc = _(b"(no help text available)")
750 750 if util.safehasattr(entry[0], b'definition'): # aliased command
751 751 source = entry[0].source
752 752 if entry[0].definition.startswith(b'!'): # shell alias
753 753 doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % (
754 754 entry[0].definition[1:],
755 755 doc,
756 756 source,
757 757 )
758 758 else:
759 759 doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % (
760 760 entry[0].definition,
761 761 doc,
762 762 source,
763 763 )
764 764 doc = doc.splitlines(True)
765 765 if ui.quiet or not full:
766 766 rst.append(doc[0])
767 767 else:
768 768 rst.extend(doc)
769 769 rst.append(b'\n')
770 770
771 771 # check if this command shadows a non-trivial (multi-line)
772 772 # extension help text
773 773 try:
774 774 mod = extensions.find(name)
775 775 doc = gettext(pycompat.getdoc(mod)) or b''
776 776 if b'\n' in doc.strip():
777 777 msg = _(
778 778 b"(use 'hg help -e %s' to show help for "
779 779 b"the %s extension)"
780 780 ) % (name, name)
781 781 rst.append(b'\n%s\n' % msg)
782 782 except KeyError:
783 783 pass
784 784
785 785 # options
786 786 if not ui.quiet and entry[1]:
787 787 rst.append(optrst(_(b"options"), entry[1], ui.verbose, ui))
788 788
789 789 if ui.verbose:
790 790 rst.append(
791 791 optrst(
792 792 _(b"global options"), commands.globalopts, ui.verbose, ui
793 793 )
794 794 )
795 795
796 796 if not ui.verbose:
797 797 if not full:
798 798 rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name)
799 799 elif not ui.quiet:
800 800 rst.append(
801 801 _(
802 802 b'\n(some details hidden, use --verbose '
803 803 b'to show complete help)'
804 804 )
805 805 )
806 806
807 807 return rst
808 808
809 809 def helplist(select=None, **opts):
810 810 cats, h, syns = _getcategorizedhelpcmds(
811 811 ui, commands.table, name, select
812 812 )
813 813
814 814 rst = []
815 815 if not h:
816 816 if not ui.quiet:
817 817 rst.append(_(b'no commands defined\n'))
818 818 return rst
819 819
820 820 # Output top header.
821 821 if not ui.quiet:
822 822 if name == b"shortlist":
823 823 rst.append(_(b'basic commands:\n\n'))
824 824 elif name == b"debug":
825 825 rst.append(_(b'debug commands (internal and unsupported):\n\n'))
826 826 else:
827 827 rst.append(_(b'list of commands:\n'))
828 828
829 829 def appendcmds(cmds):
830 830 cmds = sorted(cmds)
831 831 for c in cmds:
832 display_cmd = c
832 833 if ui.verbose:
833 rst.append(b" :%s: %s\n" % (b', '.join(syns[c]), h[c]))
834 else:
835 rst.append(b' :%s: %s\n' % (c, h[c]))
834 display_cmd = b', '.join(syns[c])
835 display_cmd = display_cmd.replace(b':', br'\:')
836 rst.append(b' :%s: %s\n' % (display_cmd, h[c]))
836 837
837 838 if name in (b'shortlist', b'debug'):
838 839 # List without categories.
839 840 appendcmds(h)
840 841 else:
841 842 # Check that all categories have an order.
842 843 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
843 844 if missing_order:
844 845 ui.develwarn(
845 846 b'help categories missing from CATEGORY_ORDER: %s'
846 847 % missing_order
847 848 )
848 849
849 850 # List per category.
850 851 for cat in CATEGORY_ORDER:
851 852 catfns = cats.get(cat, [])
852 853 if catfns:
853 854 if len(cats) > 1:
854 855 catname = gettext(CATEGORY_NAMES[cat])
855 856 rst.append(b"\n%s:\n" % catname)
856 857 rst.append(b"\n")
857 858 appendcmds(catfns)
858 859
859 860 ex = opts.get
860 861 anyopts = ex('keyword') or not (ex('command') or ex('extension'))
861 862 if not name and anyopts:
862 863 exts = listexts(
863 864 _(b'enabled extensions:'),
864 865 extensions.enabled(),
865 866 showdeprecated=ui.verbose,
866 867 )
867 868 if exts:
868 869 rst.append(b'\n')
869 870 rst.extend(exts)
870 871
871 872 rst.append(_(b"\nadditional help topics:\n"))
872 873 topiccats, topicsyns = _getcategorizedhelptopics(ui, helptable)
873 874
874 875 # Check that all categories have an order.
875 876 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER)
876 877 if missing_order:
877 878 ui.develwarn(
878 879 b'help categories missing from TOPIC_CATEGORY_ORDER: %s'
879 880 % missing_order
880 881 )
881 882
882 883 # Output topics per category.
883 884 for cat in TOPIC_CATEGORY_ORDER:
884 885 topics = topiccats.get(cat, [])
885 886 if topics:
886 887 if len(topiccats) > 1:
887 888 catname = gettext(TOPIC_CATEGORY_NAMES[cat])
888 889 rst.append(b"\n%s:\n" % catname)
889 890 rst.append(b"\n")
890 891 for t, desc in topics:
891 892 rst.append(b" :%s: %s\n" % (t, desc))
892 893
893 894 if ui.quiet:
894 895 pass
895 896 elif ui.verbose:
896 897 rst.append(
897 898 b'\n%s\n'
898 899 % optrst(
899 900 _(b"global options"), commands.globalopts, ui.verbose, ui
900 901 )
901 902 )
902 903 if name == b'shortlist':
903 904 rst.append(
904 905 _(b"\n(use 'hg help' for the full list of commands)\n")
905 906 )
906 907 else:
907 908 if name == b'shortlist':
908 909 rst.append(
909 910 _(
910 911 b"\n(use 'hg help' for the full list of commands "
911 912 b"or 'hg -v' for details)\n"
912 913 )
913 914 )
914 915 elif name and not full:
915 916 rst.append(
916 917 _(b"\n(use 'hg help %s' to show the full help text)\n")
917 918 % name
918 919 )
919 920 elif name and syns and name in syns.keys():
920 921 rst.append(
921 922 _(
922 923 b"\n(use 'hg help -v -e %s' to show built-in "
923 924 b"aliases and global options)\n"
924 925 )
925 926 % name
926 927 )
927 928 else:
928 929 rst.append(
929 930 _(
930 931 b"\n(use 'hg help -v%s' to show built-in aliases "
931 932 b"and global options)\n"
932 933 )
933 934 % (name and b" " + name or b"")
934 935 )
935 936 return rst
936 937
937 938 def helptopic(name, subtopic=None):
938 939 # Look for sub-topic entry first.
939 940 header, doc = None, None
940 941 if subtopic and name in subtopics:
941 942 for names, header, doc in subtopics[name]:
942 943 if subtopic in names:
943 944 break
944 945 if not any(subtopic in s[0] for s in subtopics[name]):
945 946 raise error.UnknownCommand(name)
946 947
947 948 if not header:
948 949 for topic in helptable:
949 950 names, header, doc = topic[0:3]
950 951 if name in names:
951 952 break
952 953 else:
953 954 raise error.UnknownCommand(name)
954 955
955 956 rst = [minirst.section(header)]
956 957
957 958 # description
958 959 if not doc:
959 960 rst.append(b" %s\n" % _(b"(no help text available)"))
960 961 if callable(doc):
961 962 rst += [b" %s\n" % l for l in doc(ui).splitlines()]
962 963
963 964 if not ui.verbose:
964 965 omitted = _(
965 966 b'(some details hidden, use --verbose'
966 967 b' to show complete help)'
967 968 )
968 969 indicateomitted(rst, omitted)
969 970
970 971 try:
971 972 cmdutil.findcmd(name, commands.table)
972 973 rst.append(
973 974 _(b"\nuse 'hg help -c %s' to see help for the %s command\n")
974 975 % (name, name)
975 976 )
976 977 except error.UnknownCommand:
977 978 pass
978 979 return rst
979 980
980 981 def helpext(name, subtopic=None):
981 982 try:
982 983 mod = extensions.find(name)
983 984 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available')
984 985 except KeyError:
985 986 mod = None
986 987 doc = extensions.disabled_help(name)
987 988 if not doc:
988 989 raise error.UnknownCommand(name)
989 990
990 991 if b'\n' not in doc:
991 992 head, tail = doc, b""
992 993 else:
993 994 head, tail = doc.split(b'\n', 1)
994 995 rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)]
995 996 if tail:
996 997 rst.extend(tail.splitlines(True))
997 998 rst.append(b'\n')
998 999
999 1000 if not ui.verbose:
1000 1001 omitted = _(
1001 1002 b'(some details hidden, use --verbose'
1002 1003 b' to show complete help)'
1003 1004 )
1004 1005 indicateomitted(rst, omitted)
1005 1006
1006 1007 if mod:
1007 1008 try:
1008 1009 ct = mod.cmdtable
1009 1010 except AttributeError:
1010 1011 ct = {}
1011 1012 modcmds = {c.partition(b'|')[0] for c in ct}
1012 1013 rst.extend(helplist(modcmds.__contains__))
1013 1014 else:
1014 1015 rst.append(
1015 1016 _(
1016 1017 b"(use 'hg help extensions' for information on enabling"
1017 1018 b" extensions)\n"
1018 1019 )
1019 1020 )
1020 1021 return rst
1021 1022
1022 1023 def helpextcmd(name, subtopic=None):
1023 1024 cmd, ext, doc = extensions.disabledcmd(
1024 1025 ui, name, ui.configbool(b'ui', b'strict')
1025 1026 )
1026 1027 doc = doc.splitlines()[0]
1027 1028
1028 1029 rst = listexts(
1029 1030 _(b"'%s' is provided by the following extension:") % cmd,
1030 1031 {ext: doc},
1031 1032 indent=4,
1032 1033 showdeprecated=True,
1033 1034 )
1034 1035 rst.append(b'\n')
1035 1036 rst.append(
1036 1037 _(
1037 1038 b"(use 'hg help extensions' for information on enabling "
1038 1039 b"extensions)\n"
1039 1040 )
1040 1041 )
1041 1042 return rst
1042 1043
1043 1044 rst = []
1044 1045 kw = opts.get(b'keyword')
1045 1046 if kw or name is None and any(opts[o] for o in opts):
1046 1047 matches = topicmatch(ui, commands, name or b'')
1047 1048 helpareas = []
1048 1049 if opts.get(b'extension'):
1049 1050 helpareas += [(b'extensions', _(b'Extensions'))]
1050 1051 if opts.get(b'command'):
1051 1052 helpareas += [(b'commands', _(b'Commands'))]
1052 1053 if not helpareas:
1053 1054 helpareas = [
1054 1055 (b'topics', _(b'Topics')),
1055 1056 (b'commands', _(b'Commands')),
1056 1057 (b'extensions', _(b'Extensions')),
1057 1058 (b'extensioncommands', _(b'Extension Commands')),
1058 1059 ]
1059 1060 for t, title in helpareas:
1060 1061 if matches[t]:
1061 1062 rst.append(b'%s:\n\n' % title)
1062 1063 rst.extend(minirst.maketable(sorted(matches[t]), 1))
1063 1064 rst.append(b'\n')
1064 1065 if not rst:
1065 1066 msg = _(b'no matches')
1066 1067 hint = _(b"try 'hg help' for a list of topics")
1067 1068 raise error.InputError(msg, hint=hint)
1068 1069 elif name and name != b'shortlist':
1069 1070 queries = []
1070 1071 if unknowncmd:
1071 1072 queries += [helpextcmd]
1072 1073 if opts.get(b'extension'):
1073 1074 queries += [helpext]
1074 1075 if opts.get(b'command'):
1075 1076 queries += [helpcmd]
1076 1077 if not queries:
1077 1078 queries = (helptopic, helpcmd, helpext, helpextcmd)
1078 1079 for f in queries:
1079 1080 try:
1080 1081 rst = f(name, subtopic)
1081 1082 break
1082 1083 except error.UnknownCommand:
1083 1084 pass
1084 1085 else:
1085 1086 if unknowncmd:
1086 1087 raise error.UnknownCommand(name)
1087 1088 else:
1088 1089 if fullname:
1089 1090 formatname = fullname
1090 1091 else:
1091 1092 formatname = name
1092 1093 if subtopic:
1093 1094 hintname = subtopic
1094 1095 else:
1095 1096 hintname = name
1096 1097 msg = _(b'no such help topic: %s') % formatname
1097 1098 hint = _(b"try 'hg help --keyword %s'") % hintname
1098 1099 raise error.InputError(msg, hint=hint)
1099 1100 else:
1100 1101 # program name
1101 1102 if not ui.quiet:
1102 1103 rst = [_(b"Mercurial Distributed SCM\n"), b'\n']
1103 1104 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
1104 1105
1105 1106 return b''.join(rst)
1106 1107
1107 1108
1108 1109 def formattedhelp(
1109 1110 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts
1110 1111 ):
1111 1112 """get help for a given topic (as a dotted name) as rendered rst
1112 1113
1113 1114 Either returns the rendered help text or raises an exception.
1114 1115 """
1115 1116 if keep is None:
1116 1117 keep = []
1117 1118 else:
1118 1119 keep = list(keep) # make a copy so we can mutate this later
1119 1120
1120 1121 # <fullname> := <name>[.<subtopic][.<section>]
1121 1122 name = subtopic = section = None
1122 1123 if fullname is not None:
1123 1124 nameparts = fullname.split(b'.')
1124 1125 name = nameparts.pop(0)
1125 1126 if nameparts and name in subtopics:
1126 1127 subtopic = nameparts.pop(0)
1127 1128 if nameparts:
1128 1129 section = encoding.lower(b'.'.join(nameparts))
1129 1130
1130 1131 textwidth = ui.configint(b'ui', b'textwidth')
1131 1132 termwidth = ui.termwidth() - 2
1132 1133 if textwidth <= 0 or termwidth < textwidth:
1133 1134 textwidth = termwidth
1134 1135 text = help_(
1135 1136 ui,
1136 1137 commands,
1137 1138 name,
1138 1139 fullname=fullname,
1139 1140 subtopic=subtopic,
1140 1141 unknowncmd=unknowncmd,
1141 1142 full=full,
1142 1143 **opts
1143 1144 )
1144 1145
1145 1146 blocks, pruned = minirst.parse(text, keep=keep)
1146 1147 if b'verbose' in pruned:
1147 1148 keep.append(b'omitted')
1148 1149 else:
1149 1150 keep.append(b'notomitted')
1150 1151 blocks, pruned = minirst.parse(text, keep=keep)
1151 1152 if section:
1152 1153 blocks = minirst.filtersections(blocks, section)
1153 1154
1154 1155 # We could have been given a weird ".foo" section without a name
1155 1156 # to look for, or we could have simply failed to found "foo.bar"
1156 1157 # because bar isn't a section of foo
1157 1158 if section and not (blocks and name):
1158 1159 raise error.InputError(_(b"help section not found: %s") % fullname)
1159 1160
1160 1161 return minirst.formatplain(blocks, textwidth)
General Comments 0
You need to be logged in to leave comments. Login now