comparison mercurial/help.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 9d31581cc44e
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
32 templatefuncs, 32 templatefuncs,
33 templatekw, 33 templatekw,
34 ui as uimod, 34 ui as uimod,
35 util, 35 util,
36 ) 36 )
37 from .hgweb import ( 37 from .hgweb import webcommands
38 webcommands, 38 from .utils import compression
39 )
40 from .utils import (
41 compression,
42 )
43 39
44 _exclkeywords = { 40 _exclkeywords = {
45 "(ADVANCED)", 41 "(ADVANCED)",
46 "(DEPRECATED)", 42 "(DEPRECATED)",
47 "(EXPERIMENTAL)", 43 "(EXPERIMENTAL)",
62 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT, 58 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
63 registrar.command.CATEGORY_COMMITTING, 59 registrar.command.CATEGORY_COMMITTING,
64 registrar.command.CATEGORY_CHANGE_MANAGEMENT, 60 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
65 registrar.command.CATEGORY_CHANGE_ORGANIZATION, 61 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
66 registrar.command.CATEGORY_FILE_CONTENTS, 62 registrar.command.CATEGORY_FILE_CONTENTS,
67 registrar.command.CATEGORY_CHANGE_NAVIGATION , 63 registrar.command.CATEGORY_CHANGE_NAVIGATION,
68 registrar.command.CATEGORY_WORKING_DIRECTORY, 64 registrar.command.CATEGORY_WORKING_DIRECTORY,
69 registrar.command.CATEGORY_IMPORT_EXPORT, 65 registrar.command.CATEGORY_IMPORT_EXPORT,
70 registrar.command.CATEGORY_MAINTENANCE, 66 registrar.command.CATEGORY_MAINTENANCE,
71 registrar.command.CATEGORY_HELP, 67 registrar.command.CATEGORY_HELP,
72 registrar.command.CATEGORY_MISC, 68 registrar.command.CATEGORY_MISC,
75 71
76 # Human-readable category names. These are translated. 72 # Human-readable category names. These are translated.
77 # Extensions with custom categories should add their names here. 73 # Extensions with custom categories should add their names here.
78 CATEGORY_NAMES = { 74 CATEGORY_NAMES = {
79 registrar.command.CATEGORY_REPO_CREATION: 'Repository creation', 75 registrar.command.CATEGORY_REPO_CREATION: 'Repository creation',
80 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: 76 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: 'Remote repository management',
81 'Remote repository management',
82 registrar.command.CATEGORY_COMMITTING: 'Change creation', 77 registrar.command.CATEGORY_COMMITTING: 'Change creation',
83 registrar.command.CATEGORY_CHANGE_NAVIGATION: 'Change navigation', 78 registrar.command.CATEGORY_CHANGE_NAVIGATION: 'Change navigation',
84 registrar.command.CATEGORY_CHANGE_MANAGEMENT: 'Change manipulation', 79 registrar.command.CATEGORY_CHANGE_MANAGEMENT: 'Change manipulation',
85 registrar.command.CATEGORY_CHANGE_ORGANIZATION: 'Change organization', 80 registrar.command.CATEGORY_CHANGE_ORGANIZATION: 'Change organization',
86 registrar.command.CATEGORY_WORKING_DIRECTORY: 81 registrar.command.CATEGORY_WORKING_DIRECTORY: 'Working directory management',
87 'Working directory management',
88 registrar.command.CATEGORY_FILE_CONTENTS: 'File content management', 82 registrar.command.CATEGORY_FILE_CONTENTS: 'File content management',
89 registrar.command.CATEGORY_IMPORT_EXPORT: 'Change import/export', 83 registrar.command.CATEGORY_IMPORT_EXPORT: 'Change import/export',
90 registrar.command.CATEGORY_MAINTENANCE: 'Repository maintenance', 84 registrar.command.CATEGORY_MAINTENANCE: 'Repository maintenance',
91 registrar.command.CATEGORY_HELP: 'Help', 85 registrar.command.CATEGORY_HELP: 'Help',
92 registrar.command.CATEGORY_MISC: 'Miscellaneous commands', 86 registrar.command.CATEGORY_MISC: 'Miscellaneous commands',
122 TOPIC_CATEGORY_CONCEPTS: 'Concepts', 116 TOPIC_CATEGORY_CONCEPTS: 'Concepts',
123 TOPIC_CATEGORY_MISC: 'Miscellaneous', 117 TOPIC_CATEGORY_MISC: 'Miscellaneous',
124 TOPIC_CATEGORY_NONE: 'Uncategorized topics', 118 TOPIC_CATEGORY_NONE: 'Uncategorized topics',
125 } 119 }
126 120
121
127 def listexts(header, exts, indent=1, showdeprecated=False): 122 def listexts(header, exts, indent=1, showdeprecated=False):
128 '''return a text listing of the given extensions''' 123 '''return a text listing of the given extensions'''
129 rst = [] 124 rst = []
130 if exts: 125 if exts:
131 for name, desc in sorted(exts.iteritems()): 126 for name, desc in sorted(exts.iteritems()):
134 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc)) 129 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
135 if rst: 130 if rst:
136 rst.insert(0, '\n%s\n\n' % header) 131 rst.insert(0, '\n%s\n\n' % header)
137 return rst 132 return rst
138 133
134
139 def extshelp(ui): 135 def extshelp(ui):
140 rst = loaddoc('extensions')(ui).splitlines(True) 136 rst = loaddoc('extensions')(ui).splitlines(True)
141 rst.extend(listexts( 137 rst.extend(
142 _('enabled extensions:'), extensions.enabled(), showdeprecated=True)) 138 listexts(
143 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(), 139 _('enabled extensions:'), extensions.enabled(), showdeprecated=True
144 showdeprecated=ui.verbose)) 140 )
141 )
142 rst.extend(
143 listexts(
144 _('disabled extensions:'),
145 extensions.disabled(),
146 showdeprecated=ui.verbose,
147 )
148 )
145 doc = ''.join(rst) 149 doc = ''.join(rst)
146 return doc 150 return doc
151
147 152
148 def optrst(header, options, verbose): 153 def optrst(header, options, verbose):
149 data = [] 154 data = []
150 multioccur = False 155 multioccur = False
151 for option in options: 156 for option in options:
152 if len(option) == 5: 157 if len(option) == 5:
153 shortopt, longopt, default, desc, optlabel = option 158 shortopt, longopt, default, desc, optlabel = option
154 else: 159 else:
155 shortopt, longopt, default, desc = option 160 shortopt, longopt, default, desc = option
156 optlabel = _("VALUE") # default label 161 optlabel = _("VALUE") # default label
157 162
158 if not verbose and any(w in desc for w in _exclkeywords): 163 if not verbose and any(w in desc for w in _exclkeywords):
159 continue 164 continue
160 165
161 so = '' 166 so = ''
184 lo += " %s" % optlabel 189 lo += " %s" % optlabel
185 190
186 data.append((so, lo, desc)) 191 data.append((so, lo, desc))
187 192
188 if multioccur: 193 if multioccur:
189 header += (_(" ([+] can be repeated)")) 194 header += _(" ([+] can be repeated)")
190 195
191 rst = ['\n%s:\n\n' % header] 196 rst = ['\n%s:\n\n' % header]
192 rst.extend(minirst.maketable(data, 1)) 197 rst.extend(minirst.maketable(data, 1))
193 198
194 return ''.join(rst) 199 return ''.join(rst)
200
195 201
196 def indicateomitted(rst, omitted, notomitted=None): 202 def indicateomitted(rst, omitted, notomitted=None):
197 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted) 203 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
198 if notomitted: 204 if notomitted:
199 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) 205 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
206
200 207
201 def filtercmd(ui, cmd, func, kw, doc): 208 def filtercmd(ui, cmd, func, kw, doc):
202 if not ui.debugflag and cmd.startswith("debug") and kw != "debug": 209 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
203 # Debug command, and user is not looking for those. 210 # Debug command, and user is not looking for those.
204 return True 211 return True
218 if ui.configbool('help', 'hidden-command.%s' % cmd): 225 if ui.configbool('help', 'hidden-command.%s' % cmd):
219 # Configuration explicitly hides the command. 226 # Configuration explicitly hides the command.
220 return True 227 return True
221 return False 228 return False
222 229
230
223 def filtertopic(ui, topic): 231 def filtertopic(ui, topic):
224 return ui.configbool('help', 'hidden-topic.%s' % topic, False) 232 return ui.configbool('help', 'hidden-topic.%s' % topic, False)
233
225 234
226 def topicmatch(ui, commands, kw): 235 def topicmatch(ui, commands, kw):
227 """Return help topics matching kw. 236 """Return help topics matching kw.
228 237
229 Returns {'section': [(name, summary), ...], ...} where section is 238 Returns {'section': [(name, summary), ...], ...} where section is
230 one of topics, commands, extensions, or extensioncommands. 239 one of topics, commands, extensions, or extensioncommands.
231 """ 240 """
232 kw = encoding.lower(kw) 241 kw = encoding.lower(kw)
242
233 def lowercontains(container): 243 def lowercontains(container):
234 return kw in encoding.lower(container) # translated in helptable 244 return kw in encoding.lower(container) # translated in helptable
235 results = {'topics': [], 245
236 'commands': [], 246 results = {
237 'extensions': [], 247 'topics': [],
238 'extensioncommands': [], 248 'commands': [],
239 } 249 'extensions': [],
250 'extensioncommands': [],
251 }
240 for topic in helptable: 252 for topic in helptable:
241 names, header, doc = topic[0:3] 253 names, header, doc = topic[0:3]
242 # Old extensions may use a str as doc. 254 # Old extensions may use a str as doc.
243 if (sum(map(lowercontains, names)) 255 if (
256 sum(map(lowercontains, names))
244 or lowercontains(header) 257 or lowercontains(header)
245 or (callable(doc) and lowercontains(doc(ui)))): 258 or (callable(doc) and lowercontains(doc(ui)))
259 ):
246 name = names[0] 260 name = names[0]
247 if not filtertopic(ui, name): 261 if not filtertopic(ui, name):
248 results['topics'].append((names[0], header)) 262 results['topics'].append((names[0], header))
249 for cmd, entry in commands.table.iteritems(): 263 for cmd, entry in commands.table.iteritems():
250 if len(entry) == 3: 264 if len(entry) == 3:
261 cmdname = cmdutil.parsealiases(cmd)[0] 275 cmdname = cmdutil.parsealiases(cmd)[0]
262 if filtercmd(ui, cmdname, func, kw, docs): 276 if filtercmd(ui, cmdname, func, kw, docs):
263 continue 277 continue
264 results['commands'].append((cmdname, summary)) 278 results['commands'].append((cmdname, summary))
265 for name, docs in itertools.chain( 279 for name, docs in itertools.chain(
266 extensions.enabled(False).iteritems(), 280 extensions.enabled(False).iteritems(), extensions.disabled().iteritems()
267 extensions.disabled().iteritems()): 281 ):
268 if not docs: 282 if not docs:
269 continue 283 continue
270 name = name.rpartition('.')[-1] 284 name = name.rpartition('.')[-1]
271 if lowercontains(name) or lowercontains(docs): 285 if lowercontains(name) or lowercontains(docs):
272 # extension docs are already translated 286 # extension docs are already translated
288 if filtercmd(ui, cmdname, func, kw, cmddoc): 302 if filtercmd(ui, cmdname, func, kw, cmddoc):
289 continue 303 continue
290 results['extensioncommands'].append((cmdname, cmddoc)) 304 results['extensioncommands'].append((cmdname, cmddoc))
291 return results 305 return results
292 306
307
293 def loaddoc(topic, subdir=None): 308 def loaddoc(topic, subdir=None):
294 """Return a delayed loader for help/topic.txt.""" 309 """Return a delayed loader for help/topic.txt."""
295 310
296 def loader(ui): 311 def loader(ui):
297 docdir = os.path.join(util.datapath, 'help') 312 docdir = os.path.join(util.datapath, 'help')
303 doc = rewriter(ui, topic, doc) 318 doc = rewriter(ui, topic, doc)
304 return doc 319 return doc
305 320
306 return loader 321 return loader
307 322
308 internalstable = sorted([ 323
309 (['bundle2'], _('Bundle2'), 324 internalstable = sorted(
310 loaddoc('bundle2', subdir='internals')), 325 [
311 (['bundles'], _('Bundles'), 326 (['bundle2'], _('Bundle2'), loaddoc('bundle2', subdir='internals')),
312 loaddoc('bundles', subdir='internals')), 327 (['bundles'], _('Bundles'), loaddoc('bundles', subdir='internals')),
313 (['cbor'], _('CBOR'), 328 (['cbor'], _('CBOR'), loaddoc('cbor', subdir='internals')),
314 loaddoc('cbor', subdir='internals')), 329 (['censor'], _('Censor'), loaddoc('censor', subdir='internals')),
315 (['censor'], _('Censor'), 330 (
316 loaddoc('censor', subdir='internals')), 331 ['changegroups'],
317 (['changegroups'], _('Changegroups'), 332 _('Changegroups'),
318 loaddoc('changegroups', subdir='internals')), 333 loaddoc('changegroups', subdir='internals'),
319 (['config'], _('Config Registrar'), 334 ),
320 loaddoc('config', subdir='internals')), 335 (
321 (['extensions', 'extension'], _('Extension API'), 336 ['config'],
322 loaddoc('extensions', subdir='internals')), 337 _('Config Registrar'),
323 (['mergestate'], _('Mergestate'), 338 loaddoc('config', subdir='internals'),
324 loaddoc('mergestate', subdir='internals')), 339 ),
325 (['requirements'], _('Repository Requirements'), 340 (
326 loaddoc('requirements', subdir='internals')), 341 ['extensions', 'extension'],
327 (['revlogs'], _('Revision Logs'), 342 _('Extension API'),
328 loaddoc('revlogs', subdir='internals')), 343 loaddoc('extensions', subdir='internals'),
329 (['wireprotocol'], _('Wire Protocol'), 344 ),
330 loaddoc('wireprotocol', subdir='internals')), 345 (
331 (['wireprotocolrpc'], _('Wire Protocol RPC'), 346 ['mergestate'],
332 loaddoc('wireprotocolrpc', subdir='internals')), 347 _('Mergestate'),
333 (['wireprotocolv2'], _('Wire Protocol Version 2'), 348 loaddoc('mergestate', subdir='internals'),
334 loaddoc('wireprotocolv2', subdir='internals')), 349 ),
335 ]) 350 (
351 ['requirements'],
352 _('Repository Requirements'),
353 loaddoc('requirements', subdir='internals'),
354 ),
355 (
356 ['revlogs'],
357 _('Revision Logs'),
358 loaddoc('revlogs', subdir='internals'),
359 ),
360 (
361 ['wireprotocol'],
362 _('Wire Protocol'),
363 loaddoc('wireprotocol', subdir='internals'),
364 ),
365 (
366 ['wireprotocolrpc'],
367 _('Wire Protocol RPC'),
368 loaddoc('wireprotocolrpc', subdir='internals'),
369 ),
370 (
371 ['wireprotocolv2'],
372 _('Wire Protocol Version 2'),
373 loaddoc('wireprotocolv2', subdir='internals'),
374 ),
375 ]
376 )
377
336 378
337 def internalshelp(ui): 379 def internalshelp(ui):
338 """Generate the index for the "internals" topic.""" 380 """Generate the index for the "internals" topic."""
339 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n', 381 lines = [
340 '\n'] 382 'To access a subtopic, use "hg help internals.{subtopic-name}"\n',
383 '\n',
384 ]
341 for names, header, doc in internalstable: 385 for names, header, doc in internalstable:
342 lines.append(' :%s: %s\n' % (names[0], header)) 386 lines.append(' :%s: %s\n' % (names[0], header))
343 387
344 return ''.join(lines) 388 return ''.join(lines)
345 389
346 helptable = sorted([ 390
347 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec'), 391 helptable = sorted(
348 TOPIC_CATEGORY_CONCEPTS), 392 [
349 (['color'], _("Colorizing Outputs"), loaddoc('color'), 393 (
350 TOPIC_CATEGORY_OUTPUT), 394 ['bundlespec'],
351 (["config", "hgrc"], _("Configuration Files"), loaddoc('config'), 395 _("Bundle File Formats"),
352 TOPIC_CATEGORY_CONFIG), 396 loaddoc('bundlespec'),
353 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated'), 397 TOPIC_CATEGORY_CONCEPTS,
354 TOPIC_CATEGORY_MISC), 398 ),
355 (["dates"], _("Date Formats"), loaddoc('dates'), TOPIC_CATEGORY_OUTPUT), 399 (
356 (["flags"], _("Command-line flags"), loaddoc('flags'), 400 ['color'],
357 TOPIC_CATEGORY_CONFIG), 401 _("Colorizing Outputs"),
358 (["patterns"], _("File Name Patterns"), loaddoc('patterns'), 402 loaddoc('color'),
359 TOPIC_CATEGORY_IDS), 403 TOPIC_CATEGORY_OUTPUT,
360 (['environment', 'env'], _('Environment Variables'), 404 ),
361 loaddoc('environment'), TOPIC_CATEGORY_CONFIG), 405 (
362 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'], 406 ["config", "hgrc"],
363 _('Specifying Revisions'), loaddoc('revisions'), TOPIC_CATEGORY_IDS), 407 _("Configuration Files"),
364 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets'), 408 loaddoc('config'),
365 TOPIC_CATEGORY_IDS), 409 TOPIC_CATEGORY_CONFIG,
366 (['diffs'], _('Diff Formats'), loaddoc('diffs'), TOPIC_CATEGORY_OUTPUT), 410 ),
367 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'), 411 (
368 loaddoc('merge-tools'), TOPIC_CATEGORY_CONFIG), 412 ['deprecated'],
369 (['templating', 'templates', 'template', 'style'], _('Template Usage'), 413 _("Deprecated Features"),
370 loaddoc('templates'), TOPIC_CATEGORY_OUTPUT), 414 loaddoc('deprecated'),
371 (['urls'], _('URL Paths'), loaddoc('urls'), TOPIC_CATEGORY_IDS), 415 TOPIC_CATEGORY_MISC,
372 (["extensions"], _("Using Additional Features"), extshelp, 416 ),
373 TOPIC_CATEGORY_CONFIG), 417 (["dates"], _("Date Formats"), loaddoc('dates'), TOPIC_CATEGORY_OUTPUT),
374 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos'), 418 (
375 TOPIC_CATEGORY_CONCEPTS), 419 ["flags"],
376 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb'), 420 _("Command-line flags"),
377 TOPIC_CATEGORY_CONFIG), 421 loaddoc('flags'),
378 (["glossary"], _("Glossary"), loaddoc('glossary'), TOPIC_CATEGORY_CONCEPTS), 422 TOPIC_CATEGORY_CONFIG,
379 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"), 423 ),
380 loaddoc('hgignore'), TOPIC_CATEGORY_IDS), 424 (
381 (["phases"], _("Working with Phases"), loaddoc('phases'), 425 ["patterns"],
382 TOPIC_CATEGORY_CONCEPTS), 426 _("File Name Patterns"),
383 (['scripting'], _('Using Mercurial from scripts and automation'), 427 loaddoc('patterns'),
384 loaddoc('scripting'), TOPIC_CATEGORY_MISC), 428 TOPIC_CATEGORY_IDS,
385 (['internals'], _("Technical implementation topics"), internalshelp, 429 ),
386 TOPIC_CATEGORY_MISC), 430 (
387 (['pager'], _("Pager Support"), loaddoc('pager'), TOPIC_CATEGORY_CONFIG), 431 ['environment', 'env'],
388 ]) 432 _('Environment Variables'),
433 loaddoc('environment'),
434 TOPIC_CATEGORY_CONFIG,
435 ),
436 (
437 ['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
438 _('Specifying Revisions'),
439 loaddoc('revisions'),
440 TOPIC_CATEGORY_IDS,
441 ),
442 (
443 ['filesets', 'fileset'],
444 _("Specifying File Sets"),
445 loaddoc('filesets'),
446 TOPIC_CATEGORY_IDS,
447 ),
448 (['diffs'], _('Diff Formats'), loaddoc('diffs'), TOPIC_CATEGORY_OUTPUT),
449 (
450 ['merge-tools', 'mergetools', 'mergetool'],
451 _('Merge Tools'),
452 loaddoc('merge-tools'),
453 TOPIC_CATEGORY_CONFIG,
454 ),
455 (
456 ['templating', 'templates', 'template', 'style'],
457 _('Template Usage'),
458 loaddoc('templates'),
459 TOPIC_CATEGORY_OUTPUT,
460 ),
461 (['urls'], _('URL Paths'), loaddoc('urls'), TOPIC_CATEGORY_IDS),
462 (
463 ["extensions"],
464 _("Using Additional Features"),
465 extshelp,
466 TOPIC_CATEGORY_CONFIG,
467 ),
468 (
469 ["subrepos", "subrepo"],
470 _("Subrepositories"),
471 loaddoc('subrepos'),
472 TOPIC_CATEGORY_CONCEPTS,
473 ),
474 (
475 ["hgweb"],
476 _("Configuring hgweb"),
477 loaddoc('hgweb'),
478 TOPIC_CATEGORY_CONFIG,
479 ),
480 (
481 ["glossary"],
482 _("Glossary"),
483 loaddoc('glossary'),
484 TOPIC_CATEGORY_CONCEPTS,
485 ),
486 (
487 ["hgignore", "ignore"],
488 _("Syntax for Mercurial Ignore Files"),
489 loaddoc('hgignore'),
490 TOPIC_CATEGORY_IDS,
491 ),
492 (
493 ["phases"],
494 _("Working with Phases"),
495 loaddoc('phases'),
496 TOPIC_CATEGORY_CONCEPTS,
497 ),
498 (
499 ['scripting'],
500 _('Using Mercurial from scripts and automation'),
501 loaddoc('scripting'),
502 TOPIC_CATEGORY_MISC,
503 ),
504 (
505 ['internals'],
506 _("Technical implementation topics"),
507 internalshelp,
508 TOPIC_CATEGORY_MISC,
509 ),
510 (
511 ['pager'],
512 _("Pager Support"),
513 loaddoc('pager'),
514 TOPIC_CATEGORY_CONFIG,
515 ),
516 ]
517 )
389 518
390 # Maps topics with sub-topics to a list of their sub-topics. 519 # Maps topics with sub-topics to a list of their sub-topics.
391 subtopics = { 520 subtopics = {
392 'internals': internalstable, 521 'internals': internalstable,
393 } 522 }
394 523
395 # Map topics to lists of callable taking the current topic help and 524 # Map topics to lists of callable taking the current topic help and
396 # returning the updated version 525 # returning the updated version
397 helphooks = {} 526 helphooks = {}
398 527
528
399 def addtopichook(topic, rewriter): 529 def addtopichook(topic, rewriter):
400 helphooks.setdefault(topic, []).append(rewriter) 530 helphooks.setdefault(topic, []).append(rewriter)
531
401 532
402 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False): 533 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
403 """Extract docstring from the items key to function mapping, build a 534 """Extract docstring from the items key to function mapping, build a
404 single documentation block and use it to overwrite the marker in doc. 535 single documentation block and use it to overwrite the marker in doc.
405 """ 536 """
406 entries = [] 537 entries = []
407 for name in sorted(items): 538 for name in sorted(items):
408 text = (pycompat.getdoc(items[name]) or '').rstrip() 539 text = (pycompat.getdoc(items[name]) or '').rstrip()
409 if (not text 540 if not text or not ui.verbose and any(w in text for w in _exclkeywords):
410 or not ui.verbose and any(w in text for w in _exclkeywords)):
411 continue 541 continue
412 text = gettext(text) 542 text = gettext(text)
413 if dedent: 543 if dedent:
414 # Abuse latin1 to use textwrap.dedent() on bytes. 544 # Abuse latin1 to use textwrap.dedent() on bytes.
415 text = textwrap.dedent(text.decode('latin1')).encode('latin1') 545 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
425 doclines.append(' ' + l.strip()) 555 doclines.append(' ' + l.strip())
426 entries.append('\n'.join(doclines)) 556 entries.append('\n'.join(doclines))
427 entries = '\n\n'.join(entries) 557 entries = '\n\n'.join(entries)
428 return doc.replace(marker, entries) 558 return doc.replace(marker, entries)
429 559
560
430 def addtopicsymbols(topic, marker, symbols, dedent=False): 561 def addtopicsymbols(topic, marker, symbols, dedent=False):
431 def add(ui, topic, doc): 562 def add(ui, topic, doc):
432 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) 563 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
564
433 addtopichook(topic, add) 565 addtopichook(topic, add)
434 566
435 addtopicsymbols('bundlespec', '.. bundlecompressionmarker', 567
436 compression.bundlecompressiontopics()) 568 addtopicsymbols(
569 'bundlespec',
570 '.. bundlecompressionmarker',
571 compression.bundlecompressiontopics(),
572 )
437 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols) 573 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
438 addtopicsymbols('merge-tools', '.. internaltoolsmarker', 574 addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internalsdoc)
439 filemerge.internalsdoc)
440 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols) 575 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
441 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords) 576 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
442 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters) 577 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
443 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs) 578 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
444 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands, 579 addtopicsymbols(
445 dedent=True) 580 'hgweb', '.. webcommandsmarker', webcommands.commands, dedent=True
581 )
582
446 583
447 def inserttweakrc(ui, topic, doc): 584 def inserttweakrc(ui, topic, doc):
448 marker = '.. tweakdefaultsmarker' 585 marker = '.. tweakdefaultsmarker'
449 repl = uimod.tweakrc 586 repl = uimod.tweakrc
587
450 def sub(m): 588 def sub(m):
451 lines = [m.group(1) + s for s in repl.splitlines()] 589 lines = [m.group(1) + s for s in repl.splitlines()]
452 return '\n'.join(lines) 590 return '\n'.join(lines)
591
453 return re.sub(br'( *)%s' % re.escape(marker), sub, doc) 592 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
454 593
594
455 addtopichook('config', inserttweakrc) 595 addtopichook('config', inserttweakrc)
456 596
457 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None, 597
458 fullname=None, **opts): 598 def help_(
599 ui,
600 commands,
601 name,
602 unknowncmd=False,
603 full=True,
604 subtopic=None,
605 fullname=None,
606 **opts
607 ):
459 ''' 608 '''
460 Generate the help for 'name' as unformatted restructured text. If 609 Generate the help for 'name' as unformatted restructured text. If
461 'name' is None, describe the commands available. 610 'name' is None, describe the commands available.
462 ''' 611 '''
463 612
464 opts = pycompat.byteskwargs(opts) 613 opts = pycompat.byteskwargs(opts)
465 614
466 def helpcmd(name, subtopic=None): 615 def helpcmd(name, subtopic=None):
467 try: 616 try:
468 aliases, entry = cmdutil.findcmd(name, commands.table, 617 aliases, entry = cmdutil.findcmd(
469 strict=unknowncmd) 618 name, commands.table, strict=unknowncmd
619 )
470 except error.AmbiguousCommand as inst: 620 except error.AmbiguousCommand as inst:
471 # py3 fix: except vars can't be used outside the scope of the 621 # py3 fix: except vars can't be used outside the scope of the
472 # except block, nor can be used inside a lambda. python issue4617 622 # except block, nor can be used inside a lambda. python issue4617
473 prefix = inst.args[0] 623 prefix = inst.args[0]
474 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix) 624 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
505 if not doc: 655 if not doc:
506 doc = _("(no help text available)") 656 doc = _("(no help text available)")
507 if util.safehasattr(entry[0], 'definition'): # aliased command 657 if util.safehasattr(entry[0], 'definition'): # aliased command
508 source = entry[0].source 658 source = entry[0].source
509 if entry[0].definition.startswith('!'): # shell alias 659 if entry[0].definition.startswith('!'): # shell alias
510 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') % 660 doc = _('shell alias for: %s\n\n%s\n\ndefined by: %s\n') % (
511 (entry[0].definition[1:], doc, source)) 661 entry[0].definition[1:],
662 doc,
663 source,
664 )
512 else: 665 else:
513 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') % 666 doc = _('alias for: hg %s\n\n%s\n\ndefined by: %s\n') % (
514 (entry[0].definition, doc, source)) 667 entry[0].definition,
668 doc,
669 source,
670 )
515 doc = doc.splitlines(True) 671 doc = doc.splitlines(True)
516 if ui.quiet or not full: 672 if ui.quiet or not full:
517 rst.append(doc[0]) 673 rst.append(doc[0])
518 else: 674 else:
519 rst.extend(doc) 675 rst.extend(doc)
523 # extension help text 679 # extension help text
524 try: 680 try:
525 mod = extensions.find(name) 681 mod = extensions.find(name)
526 doc = gettext(pycompat.getdoc(mod)) or '' 682 doc = gettext(pycompat.getdoc(mod)) or ''
527 if '\n' in doc.strip(): 683 if '\n' in doc.strip():
528 msg = _("(use 'hg help -e %s' to show help for " 684 msg = _(
529 "the %s extension)") % (name, name) 685 "(use 'hg help -e %s' to show help for " "the %s extension)"
686 ) % (name, name)
530 rst.append('\n%s\n' % msg) 687 rst.append('\n%s\n' % msg)
531 except KeyError: 688 except KeyError:
532 pass 689 pass
533 690
534 # options 691 # options
535 if not ui.quiet and entry[1]: 692 if not ui.quiet and entry[1]:
536 rst.append(optrst(_("options"), entry[1], ui.verbose)) 693 rst.append(optrst(_("options"), entry[1], ui.verbose))
537 694
538 if ui.verbose: 695 if ui.verbose:
539 rst.append(optrst(_("global options"), 696 rst.append(
540 commands.globalopts, ui.verbose)) 697 optrst(_("global options"), commands.globalopts, ui.verbose)
698 )
541 699
542 if not ui.verbose: 700 if not ui.verbose:
543 if not full: 701 if not full:
544 rst.append(_("\n(use 'hg %s -h' to show more help)\n") 702 rst.append(_("\n(use 'hg %s -h' to show more help)\n") % name)
545 % name)
546 elif not ui.quiet: 703 elif not ui.quiet:
547 rst.append(_('\n(some details hidden, use --verbose ' 704 rst.append(
548 'to show complete help)')) 705 _(
706 '\n(some details hidden, use --verbose '
707 'to show complete help)'
708 )
709 )
549 710
550 return rst 711 return rst
551 712
552 def helplist(select=None, **opts): 713 def helplist(select=None, **opts):
553 # Category -> list of commands 714 # Category -> list of commands
570 if not doc: 731 if not doc:
571 doc = _("(no help text available)") 732 doc = _("(no help text available)")
572 h[f] = doc.splitlines()[0].rstrip() 733 h[f] = doc.splitlines()[0].rstrip()
573 734
574 cat = getattr(func, 'helpcategory', None) or ( 735 cat = getattr(func, 'helpcategory', None) or (
575 registrar.command.CATEGORY_NONE) 736 registrar.command.CATEGORY_NONE
737 )
576 cats.setdefault(cat, []).append(f) 738 cats.setdefault(cat, []).append(f)
577 739
578 rst = [] 740 rst = []
579 if not h: 741 if not h:
580 if not ui.quiet: 742 if not ui.quiet:
603 appendcmds(h) 765 appendcmds(h)
604 else: 766 else:
605 # Check that all categories have an order. 767 # Check that all categories have an order.
606 missing_order = set(cats.keys()) - set(CATEGORY_ORDER) 768 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
607 if missing_order: 769 if missing_order:
608 ui.develwarn('help categories missing from CATEGORY_ORDER: %s' % 770 ui.develwarn(
609 missing_order) 771 'help categories missing from CATEGORY_ORDER: %s'
772 % missing_order
773 )
610 774
611 # List per category. 775 # List per category.
612 for cat in CATEGORY_ORDER: 776 for cat in CATEGORY_ORDER:
613 catfns = cats.get(cat, []) 777 catfns = cats.get(cat, [])
614 if catfns: 778 if catfns:
617 rst.append("\n%s:\n" % catname) 781 rst.append("\n%s:\n" % catname)
618 rst.append("\n") 782 rst.append("\n")
619 appendcmds(catfns) 783 appendcmds(catfns)
620 784
621 ex = opts.get 785 ex = opts.get
622 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension'))) 786 anyopts = ex(r'keyword') or not (ex(r'command') or ex(r'extension'))
623 if not name and anyopts: 787 if not name and anyopts:
624 exts = listexts(_('enabled extensions:'), extensions.enabled(), 788 exts = listexts(
625 showdeprecated=ui.verbose) 789 _('enabled extensions:'),
790 extensions.enabled(),
791 showdeprecated=ui.verbose,
792 )
626 if exts: 793 if exts:
627 rst.append('\n') 794 rst.append('\n')
628 rst.extend(exts) 795 rst.extend(exts)
629 796
630 rst.append(_("\nadditional help topics:\n")) 797 rst.append(_("\nadditional help topics:\n"))
638 category = TOPIC_CATEGORY_NONE 805 category = TOPIC_CATEGORY_NONE
639 806
640 topicname = names[0] 807 topicname = names[0]
641 if not filtertopic(ui, topicname): 808 if not filtertopic(ui, topicname):
642 topiccats.setdefault(category, []).append( 809 topiccats.setdefault(category, []).append(
643 (topicname, header)) 810 (topicname, header)
811 )
644 812
645 # Check that all categories have an order. 813 # Check that all categories have an order.
646 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER) 814 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER)
647 if missing_order: 815 if missing_order:
648 ui.develwarn( 816 ui.develwarn(
649 'help categories missing from TOPIC_CATEGORY_ORDER: %s' % 817 'help categories missing from TOPIC_CATEGORY_ORDER: %s'
650 missing_order) 818 % missing_order
819 )
651 820
652 # Output topics per category. 821 # Output topics per category.
653 for cat in TOPIC_CATEGORY_ORDER: 822 for cat in TOPIC_CATEGORY_ORDER:
654 topics = topiccats.get(cat, []) 823 topics = topiccats.get(cat, [])
655 if topics: 824 if topics:
661 rst.append(" :%s: %s\n" % (t, desc)) 830 rst.append(" :%s: %s\n" % (t, desc))
662 831
663 if ui.quiet: 832 if ui.quiet:
664 pass 833 pass
665 elif ui.verbose: 834 elif ui.verbose:
666 rst.append('\n%s\n' % optrst(_("global options"), 835 rst.append(
667 commands.globalopts, ui.verbose)) 836 '\n%s\n'
837 % optrst(_("global options"), commands.globalopts, ui.verbose)
838 )
668 if name == 'shortlist': 839 if name == 'shortlist':
669 rst.append(_("\n(use 'hg help' for the full list " 840 rst.append(
670 "of commands)\n")) 841 _("\n(use 'hg help' for the full list " "of commands)\n")
842 )
671 else: 843 else:
672 if name == 'shortlist': 844 if name == 'shortlist':
673 rst.append(_("\n(use 'hg help' for the full list of commands " 845 rst.append(
674 "or 'hg -v' for details)\n")) 846 _(
847 "\n(use 'hg help' for the full list of commands "
848 "or 'hg -v' for details)\n"
849 )
850 )
675 elif name and not full: 851 elif name and not full:
676 rst.append(_("\n(use 'hg help %s' to show the full help " 852 rst.append(
677 "text)\n") % name) 853 _("\n(use 'hg help %s' to show the full help " "text)\n")
854 % name
855 )
678 elif name and syns and name in syns.keys(): 856 elif name and syns and name in syns.keys():
679 rst.append(_("\n(use 'hg help -v -e %s' to show built-in " 857 rst.append(
680 "aliases and global options)\n") % name) 858 _(
859 "\n(use 'hg help -v -e %s' to show built-in "
860 "aliases and global options)\n"
861 )
862 % name
863 )
681 else: 864 else:
682 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases " 865 rst.append(
683 "and global options)\n") 866 _(
684 % (name and " " + name or "")) 867 "\n(use 'hg help -v%s' to show built-in aliases "
868 "and global options)\n"
869 )
870 % (name and " " + name or "")
871 )
685 return rst 872 return rst
686 873
687 def helptopic(name, subtopic=None): 874 def helptopic(name, subtopic=None):
688 # Look for sub-topic entry first. 875 # Look for sub-topic entry first.
689 header, doc = None, None 876 header, doc = None, None
709 rst.append(" %s\n" % _("(no help text available)")) 896 rst.append(" %s\n" % _("(no help text available)"))
710 if callable(doc): 897 if callable(doc):
711 rst += [" %s\n" % l for l in doc(ui).splitlines()] 898 rst += [" %s\n" % l for l in doc(ui).splitlines()]
712 899
713 if not ui.verbose: 900 if not ui.verbose:
714 omitted = _('(some details hidden, use --verbose' 901 omitted = _(
715 ' to show complete help)') 902 '(some details hidden, use --verbose' ' to show complete help)'
903 )
716 indicateomitted(rst, omitted) 904 indicateomitted(rst, omitted)
717 905
718 try: 906 try:
719 cmdutil.findcmd(name, commands.table) 907 cmdutil.findcmd(name, commands.table)
720 rst.append(_("\nuse 'hg help -c %s' to see help for " 908 rst.append(
721 "the %s command\n") % (name, name)) 909 _("\nuse 'hg help -c %s' to see help for " "the %s command\n")
910 % (name, name)
911 )
722 except error.UnknownCommand: 912 except error.UnknownCommand:
723 pass 913 pass
724 return rst 914 return rst
725 915
726 def helpext(name, subtopic=None): 916 def helpext(name, subtopic=None):
741 if tail: 931 if tail:
742 rst.extend(tail.splitlines(True)) 932 rst.extend(tail.splitlines(True))
743 rst.append('\n') 933 rst.append('\n')
744 934
745 if not ui.verbose: 935 if not ui.verbose:
746 omitted = _('(some details hidden, use --verbose' 936 omitted = _(
747 ' to show complete help)') 937 '(some details hidden, use --verbose' ' to show complete help)'
938 )
748 indicateomitted(rst, omitted) 939 indicateomitted(rst, omitted)
749 940
750 if mod: 941 if mod:
751 try: 942 try:
752 ct = mod.cmdtable 943 ct = mod.cmdtable
753 except AttributeError: 944 except AttributeError:
754 ct = {} 945 ct = {}
755 modcmds = {c.partition('|')[0] for c in ct} 946 modcmds = {c.partition('|')[0] for c in ct}
756 rst.extend(helplist(modcmds.__contains__)) 947 rst.extend(helplist(modcmds.__contains__))
757 else: 948 else:
758 rst.append(_("(use 'hg help extensions' for information on enabling" 949 rst.append(
759 " extensions)\n")) 950 _(
951 "(use 'hg help extensions' for information on enabling"
952 " extensions)\n"
953 )
954 )
760 return rst 955 return rst
761 956
762 def helpextcmd(name, subtopic=None): 957 def helpextcmd(name, subtopic=None):
763 cmd, ext, doc = extensions.disabledcmd(ui, name, 958 cmd, ext, doc = extensions.disabledcmd(
764 ui.configbool('ui', 'strict')) 959 ui, name, ui.configbool('ui', 'strict')
960 )
765 doc = doc.splitlines()[0] 961 doc = doc.splitlines()[0]
766 962
767 rst = listexts(_("'%s' is provided by the following " 963 rst = listexts(
768 "extension:") % cmd, {ext: doc}, indent=4, 964 _("'%s' is provided by the following " "extension:") % cmd,
769 showdeprecated=True) 965 {ext: doc},
966 indent=4,
967 showdeprecated=True,
968 )
770 rst.append('\n') 969 rst.append('\n')
771 rst.append(_("(use 'hg help extensions' for information on enabling " 970 rst.append(
772 "extensions)\n")) 971 _(
972 "(use 'hg help extensions' for information on enabling "
973 "extensions)\n"
974 )
975 )
773 return rst 976 return rst
774
775 977
776 rst = [] 978 rst = []
777 kw = opts.get('keyword') 979 kw = opts.get('keyword')
778 if kw or name is None and any(opts[o] for o in opts): 980 if kw or name is None and any(opts[o] for o in opts):
779 matches = topicmatch(ui, commands, name or '') 981 matches = topicmatch(ui, commands, name or '')
781 if opts.get('extension'): 983 if opts.get('extension'):
782 helpareas += [('extensions', _('Extensions'))] 984 helpareas += [('extensions', _('Extensions'))]
783 if opts.get('command'): 985 if opts.get('command'):
784 helpareas += [('commands', _('Commands'))] 986 helpareas += [('commands', _('Commands'))]
785 if not helpareas: 987 if not helpareas:
786 helpareas = [('topics', _('Topics')), 988 helpareas = [
787 ('commands', _('Commands')), 989 ('topics', _('Topics')),
788 ('extensions', _('Extensions')), 990 ('commands', _('Commands')),
789 ('extensioncommands', _('Extension Commands'))] 991 ('extensions', _('Extensions')),
992 ('extensioncommands', _('Extension Commands')),
993 ]
790 for t, title in helpareas: 994 for t, title in helpareas:
791 if matches[t]: 995 if matches[t]:
792 rst.append('%s:\n\n' % title) 996 rst.append('%s:\n\n' % title)
793 rst.extend(minirst.maketable(sorted(matches[t]), 1)) 997 rst.extend(minirst.maketable(sorted(matches[t]), 1))
794 rst.append('\n') 998 rst.append('\n')
833 rst = [_("Mercurial Distributed SCM\n"), '\n'] 1037 rst = [_("Mercurial Distributed SCM\n"), '\n']
834 rst.extend(helplist(None, **pycompat.strkwargs(opts))) 1038 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
835 1039
836 return ''.join(rst) 1040 return ''.join(rst)
837 1041
838 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False, 1042
839 full=True, **opts): 1043 def formattedhelp(
1044 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts
1045 ):
840 """get help for a given topic (as a dotted name) as rendered rst 1046 """get help for a given topic (as a dotted name) as rendered rst
841 1047
842 Either returns the rendered help text or raises an exception. 1048 Either returns the rendered help text or raises an exception.
843 """ 1049 """
844 if keep is None: 1050 if keep is None:
845 keep = [] 1051 keep = []
846 else: 1052 else:
847 keep = list(keep) # make a copy so we can mutate this later 1053 keep = list(keep) # make a copy so we can mutate this later
848 1054
849 # <fullname> := <name>[.<subtopic][.<section>] 1055 # <fullname> := <name>[.<subtopic][.<section>]
850 name = subtopic = section = None 1056 name = subtopic = section = None
851 if fullname is not None: 1057 if fullname is not None:
852 nameparts = fullname.split('.') 1058 nameparts = fullname.split('.')
858 1064
859 textwidth = ui.configint('ui', 'textwidth') 1065 textwidth = ui.configint('ui', 'textwidth')
860 termwidth = ui.termwidth() - 2 1066 termwidth = ui.termwidth() - 2
861 if textwidth <= 0 or termwidth < textwidth: 1067 if textwidth <= 0 or termwidth < textwidth:
862 textwidth = termwidth 1068 textwidth = termwidth
863 text = help_(ui, commands, name, fullname=fullname, 1069 text = help_(
864 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts) 1070 ui,
1071 commands,
1072 name,
1073 fullname=fullname,
1074 subtopic=subtopic,
1075 unknowncmd=unknowncmd,
1076 full=full,
1077 **opts
1078 )
865 1079
866 blocks, pruned = minirst.parse(text, keep=keep) 1080 blocks, pruned = minirst.parse(text, keep=keep)
867 if 'verbose' in pruned: 1081 if 'verbose' in pruned:
868 keep.append('omitted') 1082 keep.append('omitted')
869 else: 1083 else: