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