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