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