##// END OF EJS Templates
help: adding support for command categories...
rdamazio@google.com -
r40327:170926ca default
parent child Browse files
Show More
@@ -1,228 +1,229 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """usage: %s DOC ...
2 """usage: %s DOC ...
3
3
4 where DOC is the name of a document
4 where DOC is the name of a document
5 """
5 """
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import textwrap
11 import textwrap
12
12
13 # This script is executed during installs and may not have C extensions
13 # This script is executed during installs and may not have C extensions
14 # available. Relax C module requirements.
14 # available. Relax C module requirements.
15 os.environ['HGMODULEPOLICY'] = 'allow'
15 os.environ['HGMODULEPOLICY'] = 'allow'
16 # import from the live mercurial repo
16 # import from the live mercurial repo
17 sys.path.insert(0, "..")
17 sys.path.insert(0, "..")
18 from mercurial import demandimport; demandimport.enable()
18 from mercurial import demandimport; demandimport.enable()
19 # Load util so that the locale path is set by i18n.setdatapath() before
19 # Load util so that the locale path is set by i18n.setdatapath() before
20 # calling _().
20 # calling _().
21 from mercurial import util
21 from mercurial import util
22 util.datapath
22 util.datapath
23 from mercurial import (
23 from mercurial import (
24 commands,
24 commands,
25 extensions,
25 extensions,
26 help,
26 help,
27 minirst,
27 minirst,
28 ui as uimod,
28 ui as uimod,
29 )
29 )
30 from mercurial.i18n import (
30 from mercurial.i18n import (
31 gettext,
31 gettext,
32 _,
32 _,
33 )
33 )
34
34
35 table = commands.table
35 table = commands.table
36 globalopts = commands.globalopts
36 globalopts = commands.globalopts
37 helptable = help.helptable
37 helptable = help.helptable
38 loaddoc = help.loaddoc
38 loaddoc = help.loaddoc
39
39
40 def get_desc(docstr):
40 def get_desc(docstr):
41 if not docstr:
41 if not docstr:
42 return "", ""
42 return "", ""
43 # sanitize
43 # sanitize
44 docstr = docstr.strip("\n")
44 docstr = docstr.strip("\n")
45 docstr = docstr.rstrip()
45 docstr = docstr.rstrip()
46 shortdesc = docstr.splitlines()[0].strip()
46 shortdesc = docstr.splitlines()[0].strip()
47
47
48 i = docstr.find("\n")
48 i = docstr.find("\n")
49 if i != -1:
49 if i != -1:
50 desc = docstr[i + 2:]
50 desc = docstr[i + 2:]
51 else:
51 else:
52 desc = shortdesc
52 desc = shortdesc
53
53
54 desc = textwrap.dedent(desc)
54 desc = textwrap.dedent(desc)
55
55
56 return (shortdesc, desc)
56 return (shortdesc, desc)
57
57
58 def get_opts(opts):
58 def get_opts(opts):
59 for opt in opts:
59 for opt in opts:
60 if len(opt) == 5:
60 if len(opt) == 5:
61 shortopt, longopt, default, desc, optlabel = opt
61 shortopt, longopt, default, desc, optlabel = opt
62 else:
62 else:
63 shortopt, longopt, default, desc = opt
63 shortopt, longopt, default, desc = opt
64 optlabel = _("VALUE")
64 optlabel = _("VALUE")
65 allopts = []
65 allopts = []
66 if shortopt:
66 if shortopt:
67 allopts.append("-%s" % shortopt)
67 allopts.append("-%s" % shortopt)
68 if longopt:
68 if longopt:
69 allopts.append("--%s" % longopt)
69 allopts.append("--%s" % longopt)
70 if isinstance(default, list):
70 if isinstance(default, list):
71 allopts[-1] += " <%s[+]>" % optlabel
71 allopts[-1] += " <%s[+]>" % optlabel
72 elif (default is not None) and not isinstance(default, bool):
72 elif (default is not None) and not isinstance(default, bool):
73 allopts[-1] += " <%s>" % optlabel
73 allopts[-1] += " <%s>" % optlabel
74 if '\n' in desc:
74 if '\n' in desc:
75 # only remove line breaks and indentation
75 # only remove line breaks and indentation
76 desc = ' '.join(l.lstrip() for l in desc.split('\n'))
76 desc = ' '.join(l.lstrip() for l in desc.split('\n'))
77 desc += default and _(" (default: %s)") % default or ""
77 desc += default and _(" (default: %s)") % default or ""
78 yield (", ".join(allopts), desc)
78 yield (", ".join(allopts), desc)
79
79
80 def get_cmd(cmd, cmdtable):
80 def get_cmd(cmd, cmdtable):
81 d = {}
81 d = {}
82 attr = cmdtable[cmd]
82 attr = cmdtable[cmd]
83 cmds = cmd.lstrip("^").split("|")
83 cmds = cmd.lstrip("^").split("|")
84
84
85 d['cmd'] = cmds[0]
85 d['cmd'] = cmds[0]
86 d['aliases'] = cmd.split("|")[1:]
86 d['aliases'] = cmd.split("|")[1:]
87 d['desc'] = get_desc(gettext(attr[0].__doc__))
87 d['desc'] = get_desc(gettext(attr[0].__doc__))
88 d['opts'] = list(get_opts(attr[1]))
88 d['opts'] = list(get_opts(attr[1]))
89
89
90 s = 'hg ' + cmds[0]
90 s = 'hg ' + cmds[0]
91 if len(attr) > 2:
91 if len(attr) > 2:
92 if not attr[2].startswith('hg'):
92 if not attr[2].startswith('hg'):
93 s += ' ' + attr[2]
93 s += ' ' + attr[2]
94 else:
94 else:
95 s = attr[2]
95 s = attr[2]
96 d['synopsis'] = s.strip()
96 d['synopsis'] = s.strip()
97
97
98 return d
98 return d
99
99
100 def showdoc(ui):
100 def showdoc(ui):
101 # print options
101 # print options
102 ui.write(minirst.section(_("Options")))
102 ui.write(minirst.section(_("Options")))
103 multioccur = False
103 multioccur = False
104 for optstr, desc in get_opts(globalopts):
104 for optstr, desc in get_opts(globalopts):
105 ui.write("%s\n %s\n\n" % (optstr, desc))
105 ui.write("%s\n %s\n\n" % (optstr, desc))
106 if optstr.endswith("[+]>"):
106 if optstr.endswith("[+]>"):
107 multioccur = True
107 multioccur = True
108 if multioccur:
108 if multioccur:
109 ui.write(_("\n[+] marked option can be specified multiple times\n"))
109 ui.write(_("\n[+] marked option can be specified multiple times\n"))
110 ui.write("\n")
110 ui.write("\n")
111
111
112 # print cmds
112 # print cmds
113 ui.write(minirst.section(_("Commands")))
113 ui.write(minirst.section(_("Commands")))
114 commandprinter(ui, table, minirst.subsection)
114 commandprinter(ui, table, minirst.subsection)
115
115
116 # print help topics
116 # print help topics
117 # The config help topic is included in the hgrc.5 man page.
117 # The config help topic is included in the hgrc.5 man page.
118 helpprinter(ui, helptable, minirst.section, exclude=['config'])
118 helpprinter(ui, helptable, minirst.section, exclude=['config'])
119
119
120 ui.write(minirst.section(_("Extensions")))
120 ui.write(minirst.section(_("Extensions")))
121 ui.write(_("This section contains help for extensions that are "
121 ui.write(_("This section contains help for extensions that are "
122 "distributed together with Mercurial. Help for other "
122 "distributed together with Mercurial. Help for other "
123 "extensions is available in the help system."))
123 "extensions is available in the help system."))
124 ui.write(("\n\n"
124 ui.write(("\n\n"
125 ".. contents::\n"
125 ".. contents::\n"
126 " :class: htmlonly\n"
126 " :class: htmlonly\n"
127 " :local:\n"
127 " :local:\n"
128 " :depth: 1\n\n"))
128 " :depth: 1\n\n"))
129
129
130 for extensionname in sorted(allextensionnames()):
130 for extensionname in sorted(allextensionnames()):
131 mod = extensions.load(ui, extensionname, None)
131 mod = extensions.load(ui, extensionname, None)
132 ui.write(minirst.subsection(extensionname))
132 ui.write(minirst.subsection(extensionname))
133 ui.write("%s\n\n" % gettext(mod.__doc__))
133 ui.write("%s\n\n" % gettext(mod.__doc__))
134 cmdtable = getattr(mod, 'cmdtable', None)
134 cmdtable = getattr(mod, 'cmdtable', None)
135 if cmdtable:
135 if cmdtable:
136 ui.write(minirst.subsubsection(_('Commands')))
136 ui.write(minirst.subsubsection(_('Commands')))
137 commandprinter(ui, cmdtable, minirst.subsubsubsection)
137 commandprinter(ui, cmdtable, minirst.subsubsubsection)
138
138
139 def showtopic(ui, topic):
139 def showtopic(ui, topic):
140 extrahelptable = [
140 extrahelptable = [
141 (["common"], '', loaddoc('common')),
141 (["common"], '', loaddoc('common')),
142 (["hg.1"], '', loaddoc('hg.1')),
142 (["hg.1"], '', loaddoc('hg.1')),
143 (["hg-ssh.8"], '', loaddoc('hg-ssh.8')),
143 (["hg-ssh.8"], '', loaddoc('hg-ssh.8')),
144 (["hgignore.5"], '', loaddoc('hgignore.5')),
144 (["hgignore.5"], '', loaddoc('hgignore.5')),
145 (["hgrc.5"], '', loaddoc('hgrc.5')),
145 (["hgrc.5"], '', loaddoc('hgrc.5')),
146 (["hgignore.5.gendoc"], '', loaddoc('hgignore')),
146 (["hgignore.5.gendoc"], '', loaddoc('hgignore')),
147 (["hgrc.5.gendoc"], '', loaddoc('config')),
147 (["hgrc.5.gendoc"], '', loaddoc('config')),
148 ]
148 ]
149 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
149 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
150
150
151 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
151 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
152 for names, sec, doc in helptable:
152 for h in helptable:
153 names, sec, doc = h[0:3]
153 if exclude and names[0] in exclude:
154 if exclude and names[0] in exclude:
154 continue
155 continue
155 if include and names[0] not in include:
156 if include and names[0] not in include:
156 continue
157 continue
157 for name in names:
158 for name in names:
158 ui.write(".. _%s:\n" % name)
159 ui.write(".. _%s:\n" % name)
159 ui.write("\n")
160 ui.write("\n")
160 if sectionfunc:
161 if sectionfunc:
161 ui.write(sectionfunc(sec))
162 ui.write(sectionfunc(sec))
162 if callable(doc):
163 if callable(doc):
163 doc = doc(ui)
164 doc = doc(ui)
164 ui.write(doc)
165 ui.write(doc)
165 ui.write("\n")
166 ui.write("\n")
166
167
167 def commandprinter(ui, cmdtable, sectionfunc):
168 def commandprinter(ui, cmdtable, sectionfunc):
168 h = {}
169 h = {}
169 for c, attr in cmdtable.items():
170 for c, attr in cmdtable.items():
170 f = c.split("|")[0]
171 f = c.split("|")[0]
171 f = f.lstrip("^")
172 f = f.lstrip("^")
172 h[f] = c
173 h[f] = c
173 cmds = h.keys()
174 cmds = h.keys()
174 cmds.sort()
175 cmds.sort()
175
176
176 for f in cmds:
177 for f in cmds:
177 if f.startswith("debug"):
178 if f.startswith("debug"):
178 continue
179 continue
179 d = get_cmd(h[f], cmdtable)
180 d = get_cmd(h[f], cmdtable)
180 ui.write(sectionfunc(d['cmd']))
181 ui.write(sectionfunc(d['cmd']))
181 # short description
182 # short description
182 ui.write(d['desc'][0])
183 ui.write(d['desc'][0])
183 # synopsis
184 # synopsis
184 ui.write("::\n\n")
185 ui.write("::\n\n")
185 synopsislines = d['synopsis'].splitlines()
186 synopsislines = d['synopsis'].splitlines()
186 for line in synopsislines:
187 for line in synopsislines:
187 # some commands (such as rebase) have a multi-line
188 # some commands (such as rebase) have a multi-line
188 # synopsis
189 # synopsis
189 ui.write(" %s\n" % line)
190 ui.write(" %s\n" % line)
190 ui.write('\n')
191 ui.write('\n')
191 # description
192 # description
192 ui.write("%s\n\n" % d['desc'][1])
193 ui.write("%s\n\n" % d['desc'][1])
193 # options
194 # options
194 opt_output = list(d['opts'])
195 opt_output = list(d['opts'])
195 if opt_output:
196 if opt_output:
196 opts_len = max([len(line[0]) for line in opt_output])
197 opts_len = max([len(line[0]) for line in opt_output])
197 ui.write(_("Options:\n\n"))
198 ui.write(_("Options:\n\n"))
198 multioccur = False
199 multioccur = False
199 for optstr, desc in opt_output:
200 for optstr, desc in opt_output:
200 if desc:
201 if desc:
201 s = "%-*s %s" % (opts_len, optstr, desc)
202 s = "%-*s %s" % (opts_len, optstr, desc)
202 else:
203 else:
203 s = optstr
204 s = optstr
204 ui.write("%s\n" % s)
205 ui.write("%s\n" % s)
205 if optstr.endswith("[+]>"):
206 if optstr.endswith("[+]>"):
206 multioccur = True
207 multioccur = True
207 if multioccur:
208 if multioccur:
208 ui.write(_("\n[+] marked option can be specified"
209 ui.write(_("\n[+] marked option can be specified"
209 " multiple times\n"))
210 " multiple times\n"))
210 ui.write("\n")
211 ui.write("\n")
211 # aliases
212 # aliases
212 if d['aliases']:
213 if d['aliases']:
213 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
214 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
214
215
215
216
216 def allextensionnames():
217 def allextensionnames():
217 return extensions.enabled().keys() + extensions.disabled().keys()
218 return extensions.enabled().keys() + extensions.disabled().keys()
218
219
219 if __name__ == "__main__":
220 if __name__ == "__main__":
220 doc = 'hg.1.gendoc'
221 doc = 'hg.1.gendoc'
221 if len(sys.argv) > 1:
222 if len(sys.argv) > 1:
222 doc = sys.argv[1]
223 doc = sys.argv[1]
223
224
224 ui = uimod.ui.load()
225 ui = uimod.ui.load()
225 if doc == 'hg.1.gendoc':
226 if doc == 'hg.1.gendoc':
226 showdoc(ui)
227 showdoc(ui)
227 else:
228 else:
228 showtopic(ui, sys.argv[1])
229 showtopic(ui, sys.argv[1])
@@ -1,694 +1,736 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import textwrap
12 import textwrap
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 encoding,
20 encoding,
21 error,
21 error,
22 extensions,
22 extensions,
23 fancyopts,
23 fancyopts,
24 filemerge,
24 filemerge,
25 fileset,
25 fileset,
26 minirst,
26 minirst,
27 pycompat,
27 pycompat,
28 registrar,
28 revset,
29 revset,
29 templatefilters,
30 templatefilters,
30 templatefuncs,
31 templatefuncs,
31 templatekw,
32 templatekw,
32 util,
33 util,
33 )
34 )
34 from .hgweb import (
35 from .hgweb import (
35 webcommands,
36 webcommands,
36 )
37 )
37
38
38 _exclkeywords = {
39 _exclkeywords = {
39 "(ADVANCED)",
40 "(ADVANCED)",
40 "(DEPRECATED)",
41 "(DEPRECATED)",
41 "(EXPERIMENTAL)",
42 "(EXPERIMENTAL)",
42 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
43 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
43 _("(ADVANCED)"),
44 _("(ADVANCED)"),
44 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
45 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
45 _("(DEPRECATED)"),
46 _("(DEPRECATED)"),
46 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
47 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
47 _("(EXPERIMENTAL)"),
48 _("(EXPERIMENTAL)"),
48 }
49 }
49
50
51 # The order in which command categories will be displayed.
52 # Extensions with custom categories should insert them into this list
53 # after/before the appropriate item, rather than replacing the list or
54 # assuming absolute positions.
55 CATEGORY_ORDER = [
56 registrar.command.CATEGORY_NONE,
57 ]
58
59 # Human-readable category names. These are translated.
60 # Extensions with custom categories should add their names here.
61 CATEGORY_NAMES = {
62 registrar.command.CATEGORY_NONE: 'Uncategorized commands',
63 }
64
50 def listexts(header, exts, indent=1, showdeprecated=False):
65 def listexts(header, exts, indent=1, showdeprecated=False):
51 '''return a text listing of the given extensions'''
66 '''return a text listing of the given extensions'''
52 rst = []
67 rst = []
53 if exts:
68 if exts:
54 for name, desc in sorted(exts.iteritems()):
69 for name, desc in sorted(exts.iteritems()):
55 if not showdeprecated and any(w in desc for w in _exclkeywords):
70 if not showdeprecated and any(w in desc for w in _exclkeywords):
56 continue
71 continue
57 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
72 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
58 if rst:
73 if rst:
59 rst.insert(0, '\n%s\n\n' % header)
74 rst.insert(0, '\n%s\n\n' % header)
60 return rst
75 return rst
61
76
62 def extshelp(ui):
77 def extshelp(ui):
63 rst = loaddoc('extensions')(ui).splitlines(True)
78 rst = loaddoc('extensions')(ui).splitlines(True)
64 rst.extend(listexts(
79 rst.extend(listexts(
65 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
80 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
66 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
81 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
67 showdeprecated=ui.verbose))
82 showdeprecated=ui.verbose))
68 doc = ''.join(rst)
83 doc = ''.join(rst)
69 return doc
84 return doc
70
85
71 def optrst(header, options, verbose):
86 def optrst(header, options, verbose):
72 data = []
87 data = []
73 multioccur = False
88 multioccur = False
74 for option in options:
89 for option in options:
75 if len(option) == 5:
90 if len(option) == 5:
76 shortopt, longopt, default, desc, optlabel = option
91 shortopt, longopt, default, desc, optlabel = option
77 else:
92 else:
78 shortopt, longopt, default, desc = option
93 shortopt, longopt, default, desc = option
79 optlabel = _("VALUE") # default label
94 optlabel = _("VALUE") # default label
80
95
81 if not verbose and any(w in desc for w in _exclkeywords):
96 if not verbose and any(w in desc for w in _exclkeywords):
82 continue
97 continue
83
98
84 so = ''
99 so = ''
85 if shortopt:
100 if shortopt:
86 so = '-' + shortopt
101 so = '-' + shortopt
87 lo = '--' + longopt
102 lo = '--' + longopt
88
103
89 if isinstance(default, fancyopts.customopt):
104 if isinstance(default, fancyopts.customopt):
90 default = default.getdefaultvalue()
105 default = default.getdefaultvalue()
91 if default and not callable(default):
106 if default and not callable(default):
92 # default is of unknown type, and in Python 2 we abused
107 # default is of unknown type, and in Python 2 we abused
93 # the %s-shows-repr property to handle integers etc. To
108 # the %s-shows-repr property to handle integers etc. To
94 # match that behavior on Python 3, we do str(default) and
109 # match that behavior on Python 3, we do str(default) and
95 # then convert it to bytes.
110 # then convert it to bytes.
96 desc += _(" (default: %s)") % pycompat.bytestr(default)
111 desc += _(" (default: %s)") % pycompat.bytestr(default)
97
112
98 if isinstance(default, list):
113 if isinstance(default, list):
99 lo += " %s [+]" % optlabel
114 lo += " %s [+]" % optlabel
100 multioccur = True
115 multioccur = True
101 elif (default is not None) and not isinstance(default, bool):
116 elif (default is not None) and not isinstance(default, bool):
102 lo += " %s" % optlabel
117 lo += " %s" % optlabel
103
118
104 data.append((so, lo, desc))
119 data.append((so, lo, desc))
105
120
106 if multioccur:
121 if multioccur:
107 header += (_(" ([+] can be repeated)"))
122 header += (_(" ([+] can be repeated)"))
108
123
109 rst = ['\n%s:\n\n' % header]
124 rst = ['\n%s:\n\n' % header]
110 rst.extend(minirst.maketable(data, 1))
125 rst.extend(minirst.maketable(data, 1))
111
126
112 return ''.join(rst)
127 return ''.join(rst)
113
128
114 def indicateomitted(rst, omitted, notomitted=None):
129 def indicateomitted(rst, omitted, notomitted=None):
115 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
130 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
116 if notomitted:
131 if notomitted:
117 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
132 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
118
133
119 def filtercmd(ui, cmd, kw, doc):
134 def filtercmd(ui, cmd, kw, doc):
120 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
135 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
121 return True
136 return True
122 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
137 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
123 return True
138 return True
124 return False
139 return False
125
140
126 def topicmatch(ui, commands, kw):
141 def topicmatch(ui, commands, kw):
127 """Return help topics matching kw.
142 """Return help topics matching kw.
128
143
129 Returns {'section': [(name, summary), ...], ...} where section is
144 Returns {'section': [(name, summary), ...], ...} where section is
130 one of topics, commands, extensions, or extensioncommands.
145 one of topics, commands, extensions, or extensioncommands.
131 """
146 """
132 kw = encoding.lower(kw)
147 kw = encoding.lower(kw)
133 def lowercontains(container):
148 def lowercontains(container):
134 return kw in encoding.lower(container) # translated in helptable
149 return kw in encoding.lower(container) # translated in helptable
135 results = {'topics': [],
150 results = {'topics': [],
136 'commands': [],
151 'commands': [],
137 'extensions': [],
152 'extensions': [],
138 'extensioncommands': [],
153 'extensioncommands': [],
139 }
154 }
140 for names, header, doc in helptable:
155 for names, header, doc in helptable:
141 # Old extensions may use a str as doc.
156 # Old extensions may use a str as doc.
142 if (sum(map(lowercontains, names))
157 if (sum(map(lowercontains, names))
143 or lowercontains(header)
158 or lowercontains(header)
144 or (callable(doc) and lowercontains(doc(ui)))):
159 or (callable(doc) and lowercontains(doc(ui)))):
145 results['topics'].append((names[0], header))
160 results['topics'].append((names[0], header))
146 for cmd, entry in commands.table.iteritems():
161 for cmd, entry in commands.table.iteritems():
147 if len(entry) == 3:
162 if len(entry) == 3:
148 summary = entry[2]
163 summary = entry[2]
149 else:
164 else:
150 summary = ''
165 summary = ''
151 # translate docs *before* searching there
166 # translate docs *before* searching there
152 docs = _(pycompat.getdoc(entry[0])) or ''
167 docs = _(pycompat.getdoc(entry[0])) or ''
153 if kw in cmd or lowercontains(summary) or lowercontains(docs):
168 if kw in cmd or lowercontains(summary) or lowercontains(docs):
154 doclines = docs.splitlines()
169 doclines = docs.splitlines()
155 if doclines:
170 if doclines:
156 summary = doclines[0]
171 summary = doclines[0]
157 cmdname = cmdutil.parsealiases(cmd)[0]
172 cmdname = cmdutil.parsealiases(cmd)[0]
158 if filtercmd(ui, cmdname, kw, docs):
173 if filtercmd(ui, cmdname, kw, docs):
159 continue
174 continue
160 results['commands'].append((cmdname, summary))
175 results['commands'].append((cmdname, summary))
161 for name, docs in itertools.chain(
176 for name, docs in itertools.chain(
162 extensions.enabled(False).iteritems(),
177 extensions.enabled(False).iteritems(),
163 extensions.disabled().iteritems()):
178 extensions.disabled().iteritems()):
164 if not docs:
179 if not docs:
165 continue
180 continue
166 name = name.rpartition('.')[-1]
181 name = name.rpartition('.')[-1]
167 if lowercontains(name) or lowercontains(docs):
182 if lowercontains(name) or lowercontains(docs):
168 # extension docs are already translated
183 # extension docs are already translated
169 results['extensions'].append((name, docs.splitlines()[0]))
184 results['extensions'].append((name, docs.splitlines()[0]))
170 try:
185 try:
171 mod = extensions.load(ui, name, '')
186 mod = extensions.load(ui, name, '')
172 except ImportError:
187 except ImportError:
173 # debug message would be printed in extensions.load()
188 # debug message would be printed in extensions.load()
174 continue
189 continue
175 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
190 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
176 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
191 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
177 cmdname = cmdutil.parsealiases(cmd)[0]
192 cmdname = cmdutil.parsealiases(cmd)[0]
178 cmddoc = pycompat.getdoc(entry[0])
193 cmddoc = pycompat.getdoc(entry[0])
179 if cmddoc:
194 if cmddoc:
180 cmddoc = gettext(cmddoc).splitlines()[0]
195 cmddoc = gettext(cmddoc).splitlines()[0]
181 else:
196 else:
182 cmddoc = _('(no help text available)')
197 cmddoc = _('(no help text available)')
183 if filtercmd(ui, cmdname, kw, cmddoc):
198 if filtercmd(ui, cmdname, kw, cmddoc):
184 continue
199 continue
185 results['extensioncommands'].append((cmdname, cmddoc))
200 results['extensioncommands'].append((cmdname, cmddoc))
186 return results
201 return results
187
202
188 def loaddoc(topic, subdir=None):
203 def loaddoc(topic, subdir=None):
189 """Return a delayed loader for help/topic.txt."""
204 """Return a delayed loader for help/topic.txt."""
190
205
191 def loader(ui):
206 def loader(ui):
192 docdir = os.path.join(util.datapath, 'help')
207 docdir = os.path.join(util.datapath, 'help')
193 if subdir:
208 if subdir:
194 docdir = os.path.join(docdir, subdir)
209 docdir = os.path.join(docdir, subdir)
195 path = os.path.join(docdir, topic + ".txt")
210 path = os.path.join(docdir, topic + ".txt")
196 doc = gettext(util.readfile(path))
211 doc = gettext(util.readfile(path))
197 for rewriter in helphooks.get(topic, []):
212 for rewriter in helphooks.get(topic, []):
198 doc = rewriter(ui, topic, doc)
213 doc = rewriter(ui, topic, doc)
199 return doc
214 return doc
200
215
201 return loader
216 return loader
202
217
203 internalstable = sorted([
218 internalstable = sorted([
204 (['bundle2'], _('Bundle2'),
219 (['bundle2'], _('Bundle2'),
205 loaddoc('bundle2', subdir='internals')),
220 loaddoc('bundle2', subdir='internals')),
206 (['bundles'], _('Bundles'),
221 (['bundles'], _('Bundles'),
207 loaddoc('bundles', subdir='internals')),
222 loaddoc('bundles', subdir='internals')),
208 (['cbor'], _('CBOR'),
223 (['cbor'], _('CBOR'),
209 loaddoc('cbor', subdir='internals')),
224 loaddoc('cbor', subdir='internals')),
210 (['censor'], _('Censor'),
225 (['censor'], _('Censor'),
211 loaddoc('censor', subdir='internals')),
226 loaddoc('censor', subdir='internals')),
212 (['changegroups'], _('Changegroups'),
227 (['changegroups'], _('Changegroups'),
213 loaddoc('changegroups', subdir='internals')),
228 loaddoc('changegroups', subdir='internals')),
214 (['config'], _('Config Registrar'),
229 (['config'], _('Config Registrar'),
215 loaddoc('config', subdir='internals')),
230 loaddoc('config', subdir='internals')),
216 (['requirements'], _('Repository Requirements'),
231 (['requirements'], _('Repository Requirements'),
217 loaddoc('requirements', subdir='internals')),
232 loaddoc('requirements', subdir='internals')),
218 (['revlogs'], _('Revision Logs'),
233 (['revlogs'], _('Revision Logs'),
219 loaddoc('revlogs', subdir='internals')),
234 loaddoc('revlogs', subdir='internals')),
220 (['wireprotocol'], _('Wire Protocol'),
235 (['wireprotocol'], _('Wire Protocol'),
221 loaddoc('wireprotocol', subdir='internals')),
236 loaddoc('wireprotocol', subdir='internals')),
222 (['wireprotocolrpc'], _('Wire Protocol RPC'),
237 (['wireprotocolrpc'], _('Wire Protocol RPC'),
223 loaddoc('wireprotocolrpc', subdir='internals')),
238 loaddoc('wireprotocolrpc', subdir='internals')),
224 (['wireprotocolv2'], _('Wire Protocol Version 2'),
239 (['wireprotocolv2'], _('Wire Protocol Version 2'),
225 loaddoc('wireprotocolv2', subdir='internals')),
240 loaddoc('wireprotocolv2', subdir='internals')),
226 ])
241 ])
227
242
228 def internalshelp(ui):
243 def internalshelp(ui):
229 """Generate the index for the "internals" topic."""
244 """Generate the index for the "internals" topic."""
230 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
245 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
231 '\n']
246 '\n']
232 for names, header, doc in internalstable:
247 for names, header, doc in internalstable:
233 lines.append(' :%s: %s\n' % (names[0], header))
248 lines.append(' :%s: %s\n' % (names[0], header))
234
249
235 return ''.join(lines)
250 return ''.join(lines)
236
251
237 helptable = sorted([
252 helptable = sorted([
238 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
253 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
239 (['color'], _("Colorizing Outputs"), loaddoc('color')),
254 (['color'], _("Colorizing Outputs"), loaddoc('color')),
240 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
255 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
241 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated')),
256 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated')),
242 (["dates"], _("Date Formats"), loaddoc('dates')),
257 (["dates"], _("Date Formats"), loaddoc('dates')),
243 (["flags"], _("Command-line flags"), loaddoc('flags')),
258 (["flags"], _("Command-line flags"), loaddoc('flags')),
244 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
259 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
245 (['environment', 'env'], _('Environment Variables'),
260 (['environment', 'env'], _('Environment Variables'),
246 loaddoc('environment')),
261 loaddoc('environment')),
247 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
262 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
248 _('Specifying Revisions'), loaddoc('revisions')),
263 _('Specifying Revisions'), loaddoc('revisions')),
249 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
264 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
250 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
265 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
251 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
266 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
252 loaddoc('merge-tools')),
267 loaddoc('merge-tools')),
253 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
268 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
254 loaddoc('templates')),
269 loaddoc('templates')),
255 (['urls'], _('URL Paths'), loaddoc('urls')),
270 (['urls'], _('URL Paths'), loaddoc('urls')),
256 (["extensions"], _("Using Additional Features"), extshelp),
271 (["extensions"], _("Using Additional Features"), extshelp),
257 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
272 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
258 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
273 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
259 (["glossary"], _("Glossary"), loaddoc('glossary')),
274 (["glossary"], _("Glossary"), loaddoc('glossary')),
260 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
275 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
261 loaddoc('hgignore')),
276 loaddoc('hgignore')),
262 (["phases"], _("Working with Phases"), loaddoc('phases')),
277 (["phases"], _("Working with Phases"), loaddoc('phases')),
263 (['scripting'], _('Using Mercurial from scripts and automation'),
278 (['scripting'], _('Using Mercurial from scripts and automation'),
264 loaddoc('scripting')),
279 loaddoc('scripting')),
265 (['internals'], _("Technical implementation topics"),
280 (['internals'], _("Technical implementation topics"),
266 internalshelp),
281 internalshelp),
267 (['pager'], _("Pager Support"), loaddoc('pager')),
282 (['pager'], _("Pager Support"), loaddoc('pager')),
268 ])
283 ])
269
284
270 # Maps topics with sub-topics to a list of their sub-topics.
285 # Maps topics with sub-topics to a list of their sub-topics.
271 subtopics = {
286 subtopics = {
272 'internals': internalstable,
287 'internals': internalstable,
273 }
288 }
274
289
275 # Map topics to lists of callable taking the current topic help and
290 # Map topics to lists of callable taking the current topic help and
276 # returning the updated version
291 # returning the updated version
277 helphooks = {}
292 helphooks = {}
278
293
279 def addtopichook(topic, rewriter):
294 def addtopichook(topic, rewriter):
280 helphooks.setdefault(topic, []).append(rewriter)
295 helphooks.setdefault(topic, []).append(rewriter)
281
296
282 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
297 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
283 """Extract docstring from the items key to function mapping, build a
298 """Extract docstring from the items key to function mapping, build a
284 single documentation block and use it to overwrite the marker in doc.
299 single documentation block and use it to overwrite the marker in doc.
285 """
300 """
286 entries = []
301 entries = []
287 for name in sorted(items):
302 for name in sorted(items):
288 text = (pycompat.getdoc(items[name]) or '').rstrip()
303 text = (pycompat.getdoc(items[name]) or '').rstrip()
289 if (not text
304 if (not text
290 or not ui.verbose and any(w in text for w in _exclkeywords)):
305 or not ui.verbose and any(w in text for w in _exclkeywords)):
291 continue
306 continue
292 text = gettext(text)
307 text = gettext(text)
293 if dedent:
308 if dedent:
294 # Abuse latin1 to use textwrap.dedent() on bytes.
309 # Abuse latin1 to use textwrap.dedent() on bytes.
295 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
310 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
296 lines = text.splitlines()
311 lines = text.splitlines()
297 doclines = [(lines[0])]
312 doclines = [(lines[0])]
298 for l in lines[1:]:
313 for l in lines[1:]:
299 # Stop once we find some Python doctest
314 # Stop once we find some Python doctest
300 if l.strip().startswith('>>>'):
315 if l.strip().startswith('>>>'):
301 break
316 break
302 if dedent:
317 if dedent:
303 doclines.append(l.rstrip())
318 doclines.append(l.rstrip())
304 else:
319 else:
305 doclines.append(' ' + l.strip())
320 doclines.append(' ' + l.strip())
306 entries.append('\n'.join(doclines))
321 entries.append('\n'.join(doclines))
307 entries = '\n\n'.join(entries)
322 entries = '\n\n'.join(entries)
308 return doc.replace(marker, entries)
323 return doc.replace(marker, entries)
309
324
310 def addtopicsymbols(topic, marker, symbols, dedent=False):
325 def addtopicsymbols(topic, marker, symbols, dedent=False):
311 def add(ui, topic, doc):
326 def add(ui, topic, doc):
312 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
327 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
313 addtopichook(topic, add)
328 addtopichook(topic, add)
314
329
315 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
330 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
316 util.bundlecompressiontopics())
331 util.bundlecompressiontopics())
317 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
332 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
318 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
333 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
319 filemerge.internalsdoc)
334 filemerge.internalsdoc)
320 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
335 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
321 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
336 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
322 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
337 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
323 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
338 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
324 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
339 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
325 dedent=True)
340 dedent=True)
326
341
327 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
342 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
328 **opts):
343 **opts):
329 '''
344 '''
330 Generate the help for 'name' as unformatted restructured text. If
345 Generate the help for 'name' as unformatted restructured text. If
331 'name' is None, describe the commands available.
346 'name' is None, describe the commands available.
332 '''
347 '''
333
348
334 opts = pycompat.byteskwargs(opts)
349 opts = pycompat.byteskwargs(opts)
335
350
336 def helpcmd(name, subtopic=None):
351 def helpcmd(name, subtopic=None):
337 try:
352 try:
338 aliases, entry = cmdutil.findcmd(name, commands.table,
353 aliases, entry = cmdutil.findcmd(name, commands.table,
339 strict=unknowncmd)
354 strict=unknowncmd)
340 except error.AmbiguousCommand as inst:
355 except error.AmbiguousCommand as inst:
341 # py3 fix: except vars can't be used outside the scope of the
356 # py3 fix: except vars can't be used outside the scope of the
342 # except block, nor can be used inside a lambda. python issue4617
357 # except block, nor can be used inside a lambda. python issue4617
343 prefix = inst.args[0]
358 prefix = inst.args[0]
344 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
359 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
345 rst = helplist(select)
360 rst = helplist(select)
346 return rst
361 return rst
347
362
348 rst = []
363 rst = []
349
364
350 # check if it's an invalid alias and display its error if it is
365 # check if it's an invalid alias and display its error if it is
351 if getattr(entry[0], 'badalias', None):
366 if getattr(entry[0], 'badalias', None):
352 rst.append(entry[0].badalias + '\n')
367 rst.append(entry[0].badalias + '\n')
353 if entry[0].unknowncmd:
368 if entry[0].unknowncmd:
354 try:
369 try:
355 rst.extend(helpextcmd(entry[0].cmdname))
370 rst.extend(helpextcmd(entry[0].cmdname))
356 except error.UnknownCommand:
371 except error.UnknownCommand:
357 pass
372 pass
358 return rst
373 return rst
359
374
360 # synopsis
375 # synopsis
361 if len(entry) > 2:
376 if len(entry) > 2:
362 if entry[2].startswith('hg'):
377 if entry[2].startswith('hg'):
363 rst.append("%s\n" % entry[2])
378 rst.append("%s\n" % entry[2])
364 else:
379 else:
365 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
380 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
366 else:
381 else:
367 rst.append('hg %s\n' % aliases[0])
382 rst.append('hg %s\n' % aliases[0])
368 # aliases
383 # aliases
369 if full and not ui.quiet and len(aliases) > 1:
384 if full and not ui.quiet and len(aliases) > 1:
370 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
385 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
371 rst.append('\n')
386 rst.append('\n')
372
387
373 # description
388 # description
374 doc = gettext(pycompat.getdoc(entry[0]))
389 doc = gettext(pycompat.getdoc(entry[0]))
375 if not doc:
390 if not doc:
376 doc = _("(no help text available)")
391 doc = _("(no help text available)")
377 if util.safehasattr(entry[0], 'definition'): # aliased command
392 if util.safehasattr(entry[0], 'definition'): # aliased command
378 source = entry[0].source
393 source = entry[0].source
379 if entry[0].definition.startswith('!'): # shell alias
394 if entry[0].definition.startswith('!'): # shell alias
380 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
395 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
381 (entry[0].definition[1:], doc, source))
396 (entry[0].definition[1:], doc, source))
382 else:
397 else:
383 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
398 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
384 (entry[0].definition, doc, source))
399 (entry[0].definition, doc, source))
385 doc = doc.splitlines(True)
400 doc = doc.splitlines(True)
386 if ui.quiet or not full:
401 if ui.quiet or not full:
387 rst.append(doc[0])
402 rst.append(doc[0])
388 else:
403 else:
389 rst.extend(doc)
404 rst.extend(doc)
390 rst.append('\n')
405 rst.append('\n')
391
406
392 # check if this command shadows a non-trivial (multi-line)
407 # check if this command shadows a non-trivial (multi-line)
393 # extension help text
408 # extension help text
394 try:
409 try:
395 mod = extensions.find(name)
410 mod = extensions.find(name)
396 doc = gettext(pycompat.getdoc(mod)) or ''
411 doc = gettext(pycompat.getdoc(mod)) or ''
397 if '\n' in doc.strip():
412 if '\n' in doc.strip():
398 msg = _("(use 'hg help -e %s' to show help for "
413 msg = _("(use 'hg help -e %s' to show help for "
399 "the %s extension)") % (name, name)
414 "the %s extension)") % (name, name)
400 rst.append('\n%s\n' % msg)
415 rst.append('\n%s\n' % msg)
401 except KeyError:
416 except KeyError:
402 pass
417 pass
403
418
404 # options
419 # options
405 if not ui.quiet and entry[1]:
420 if not ui.quiet and entry[1]:
406 rst.append(optrst(_("options"), entry[1], ui.verbose))
421 rst.append(optrst(_("options"), entry[1], ui.verbose))
407
422
408 if ui.verbose:
423 if ui.verbose:
409 rst.append(optrst(_("global options"),
424 rst.append(optrst(_("global options"),
410 commands.globalopts, ui.verbose))
425 commands.globalopts, ui.verbose))
411
426
412 if not ui.verbose:
427 if not ui.verbose:
413 if not full:
428 if not full:
414 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
429 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
415 % name)
430 % name)
416 elif not ui.quiet:
431 elif not ui.quiet:
417 rst.append(_('\n(some details hidden, use --verbose '
432 rst.append(_('\n(some details hidden, use --verbose '
418 'to show complete help)'))
433 'to show complete help)'))
419
434
420 return rst
435 return rst
421
436
422
423 def helplist(select=None, **opts):
437 def helplist(select=None, **opts):
424 # list of commands
438 # Category -> list of commands
425 if name == "shortlist":
439 cats = {}
426 header = _('basic commands:\n\n')
440 # Command -> short description
427 elif name == "debug":
428 header = _('debug commands (internal and unsupported):\n\n')
429 else:
430 header = _('list of commands:\n\n')
431
432 h = {}
441 h = {}
433 cmds = {}
442 # Command -> string showing synonyms
443 syns = {}
434 for c, e in commands.table.iteritems():
444 for c, e in commands.table.iteritems():
435 fs = cmdutil.parsealiases(c)
445 fs = cmdutil.parsealiases(c)
436 f = fs[0]
446 f = fs[0]
447 syns[f] = ', '.join(fs)
448 func = e[0]
437 p = ''
449 p = ''
438 if c.startswith("^"):
450 if c.startswith("^"):
439 p = '^'
451 p = '^'
440 if select and not select(p + f):
452 if select and not select(p + f):
441 continue
453 continue
442 if (not select and name != 'shortlist' and
454 if (not select and name != 'shortlist' and
443 e[0].__module__ != commands.__name__):
455 func.__module__ != commands.__name__):
444 continue
456 continue
445 if name == "shortlist" and not p:
457 if name == "shortlist" and not p:
446 continue
458 continue
447 doc = pycompat.getdoc(e[0])
459 doc = pycompat.getdoc(func)
448 if filtercmd(ui, f, name, doc):
460 if filtercmd(ui, f, name, doc):
449 continue
461 continue
450 doc = gettext(doc)
462 doc = gettext(doc)
451 if not doc:
463 if not doc:
452 doc = _("(no help text available)")
464 doc = _("(no help text available)")
453 h[f] = doc.splitlines()[0].rstrip()
465 h[f] = doc.splitlines()[0].rstrip()
454 cmds[f] = '|'.join(fs)
466
467 cat = getattr(func, 'helpcategory', None) or (
468 registrar.command.CATEGORY_NONE)
469 cats.setdefault(cat, []).append(f)
455
470
456 rst = []
471 rst = []
457 if not h:
472 if not h:
458 if not ui.quiet:
473 if not ui.quiet:
459 rst.append(_('no commands defined\n'))
474 rst.append(_('no commands defined\n'))
460 return rst
475 return rst
461
476
477 # Output top header.
462 if not ui.quiet:
478 if not ui.quiet:
463 rst.append(header)
479 if name == "shortlist":
464 fns = sorted(h)
480 rst.append(_('basic commands:\n\n'))
465 for f in fns:
481 elif name == "debug":
466 if ui.verbose:
482 rst.append(_('debug commands (internal and unsupported):\n\n'))
467 commacmds = cmds[f].replace("|",", ")
468 rst.append(" :%s: %s\n" % (commacmds, h[f]))
469 else:
483 else:
470 rst.append(' :%s: %s\n' % (f, h[f]))
484 rst.append(_('list of commands:\n'))
485
486 def appendcmds(cmds):
487 cmds = sorted(cmds)
488 for c in cmds:
489 if ui.verbose:
490 rst.append(" :%s: %s\n" % (syns[c], h[c]))
491 else:
492 rst.append(' :%s: %s\n' % (c, h[c]))
493
494 if name in ('shortlist', 'debug'):
495 # List without categories.
496 appendcmds(h)
497 else:
498 # Check that all categories have an order.
499 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
500 if missing_order:
501 ui.develwarn('help categories missing from CATEGORY_ORDER: %s' %
502 missing_order)
503
504 # List per category.
505 for cat in CATEGORY_ORDER:
506 catfns = cats.get(cat, [])
507 if catfns:
508 if len(cats) > 1:
509 catname = gettext(CATEGORY_NAMES[cat])
510 rst.append("\n%s:\n" % catname)
511 rst.append("\n")
512 appendcmds(catfns)
471
513
472 ex = opts.get
514 ex = opts.get
473 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
515 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
474 if not name and anyopts:
516 if not name and anyopts:
475 exts = listexts(_('enabled extensions:'), extensions.enabled())
517 exts = listexts(_('enabled extensions:'), extensions.enabled())
476 if exts:
518 if exts:
477 rst.append('\n')
519 rst.append('\n')
478 rst.extend(exts)
520 rst.extend(exts)
479
521
480 rst.append(_("\nadditional help topics:\n\n"))
522 rst.append(_("\nadditional help topics:\n\n"))
481 topics = []
523 topics = []
482 for names, header, doc in helptable:
524 for names, header, doc in helptable:
483 topics.append((names[0], header))
525 topics.append((names[0], header))
484 for t, desc in topics:
526 for t, desc in topics:
485 rst.append(" :%s: %s\n" % (t, desc))
527 rst.append(" :%s: %s\n" % (t, desc))
486
528
487 if ui.quiet:
529 if ui.quiet:
488 pass
530 pass
489 elif ui.verbose:
531 elif ui.verbose:
490 rst.append('\n%s\n' % optrst(_("global options"),
532 rst.append('\n%s\n' % optrst(_("global options"),
491 commands.globalopts, ui.verbose))
533 commands.globalopts, ui.verbose))
492 if name == 'shortlist':
534 if name == 'shortlist':
493 rst.append(_("\n(use 'hg help' for the full list "
535 rst.append(_("\n(use 'hg help' for the full list "
494 "of commands)\n"))
536 "of commands)\n"))
495 else:
537 else:
496 if name == 'shortlist':
538 if name == 'shortlist':
497 rst.append(_("\n(use 'hg help' for the full list of commands "
539 rst.append(_("\n(use 'hg help' for the full list of commands "
498 "or 'hg -v' for details)\n"))
540 "or 'hg -v' for details)\n"))
499 elif name and not full:
541 elif name and not full:
500 rst.append(_("\n(use 'hg help %s' to show the full help "
542 rst.append(_("\n(use 'hg help %s' to show the full help "
501 "text)\n") % name)
543 "text)\n") % name)
502 elif name and cmds and name in cmds.keys():
544 elif name and syns and name in syns.keys():
503 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
545 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
504 "aliases and global options)\n") % name)
546 "aliases and global options)\n") % name)
505 else:
547 else:
506 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
548 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
507 "and global options)\n")
549 "and global options)\n")
508 % (name and " " + name or ""))
550 % (name and " " + name or ""))
509 return rst
551 return rst
510
552
511 def helptopic(name, subtopic=None):
553 def helptopic(name, subtopic=None):
512 # Look for sub-topic entry first.
554 # Look for sub-topic entry first.
513 header, doc = None, None
555 header, doc = None, None
514 if subtopic and name in subtopics:
556 if subtopic and name in subtopics:
515 for names, header, doc in subtopics[name]:
557 for names, header, doc in subtopics[name]:
516 if subtopic in names:
558 if subtopic in names:
517 break
559 break
518
560
519 if not header:
561 if not header:
520 for names, header, doc in helptable:
562 for names, header, doc in helptable:
521 if name in names:
563 if name in names:
522 break
564 break
523 else:
565 else:
524 raise error.UnknownCommand(name)
566 raise error.UnknownCommand(name)
525
567
526 rst = [minirst.section(header)]
568 rst = [minirst.section(header)]
527
569
528 # description
570 # description
529 if not doc:
571 if not doc:
530 rst.append(" %s\n" % _("(no help text available)"))
572 rst.append(" %s\n" % _("(no help text available)"))
531 if callable(doc):
573 if callable(doc):
532 rst += [" %s\n" % l for l in doc(ui).splitlines()]
574 rst += [" %s\n" % l for l in doc(ui).splitlines()]
533
575
534 if not ui.verbose:
576 if not ui.verbose:
535 omitted = _('(some details hidden, use --verbose'
577 omitted = _('(some details hidden, use --verbose'
536 ' to show complete help)')
578 ' to show complete help)')
537 indicateomitted(rst, omitted)
579 indicateomitted(rst, omitted)
538
580
539 try:
581 try:
540 cmdutil.findcmd(name, commands.table)
582 cmdutil.findcmd(name, commands.table)
541 rst.append(_("\nuse 'hg help -c %s' to see help for "
583 rst.append(_("\nuse 'hg help -c %s' to see help for "
542 "the %s command\n") % (name, name))
584 "the %s command\n") % (name, name))
543 except error.UnknownCommand:
585 except error.UnknownCommand:
544 pass
586 pass
545 return rst
587 return rst
546
588
547 def helpext(name, subtopic=None):
589 def helpext(name, subtopic=None):
548 try:
590 try:
549 mod = extensions.find(name)
591 mod = extensions.find(name)
550 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
592 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
551 except KeyError:
593 except KeyError:
552 mod = None
594 mod = None
553 doc = extensions.disabledext(name)
595 doc = extensions.disabledext(name)
554 if not doc:
596 if not doc:
555 raise error.UnknownCommand(name)
597 raise error.UnknownCommand(name)
556
598
557 if '\n' not in doc:
599 if '\n' not in doc:
558 head, tail = doc, ""
600 head, tail = doc, ""
559 else:
601 else:
560 head, tail = doc.split('\n', 1)
602 head, tail = doc.split('\n', 1)
561 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
603 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
562 if tail:
604 if tail:
563 rst.extend(tail.splitlines(True))
605 rst.extend(tail.splitlines(True))
564 rst.append('\n')
606 rst.append('\n')
565
607
566 if not ui.verbose:
608 if not ui.verbose:
567 omitted = _('(some details hidden, use --verbose'
609 omitted = _('(some details hidden, use --verbose'
568 ' to show complete help)')
610 ' to show complete help)')
569 indicateomitted(rst, omitted)
611 indicateomitted(rst, omitted)
570
612
571 if mod:
613 if mod:
572 try:
614 try:
573 ct = mod.cmdtable
615 ct = mod.cmdtable
574 except AttributeError:
616 except AttributeError:
575 ct = {}
617 ct = {}
576 modcmds = set([c.partition('|')[0] for c in ct])
618 modcmds = set([c.partition('|')[0] for c in ct])
577 rst.extend(helplist(modcmds.__contains__))
619 rst.extend(helplist(modcmds.__contains__))
578 else:
620 else:
579 rst.append(_("(use 'hg help extensions' for information on enabling"
621 rst.append(_("(use 'hg help extensions' for information on enabling"
580 " extensions)\n"))
622 " extensions)\n"))
581 return rst
623 return rst
582
624
583 def helpextcmd(name, subtopic=None):
625 def helpextcmd(name, subtopic=None):
584 cmd, ext, doc = extensions.disabledcmd(ui, name,
626 cmd, ext, doc = extensions.disabledcmd(ui, name,
585 ui.configbool('ui', 'strict'))
627 ui.configbool('ui', 'strict'))
586 doc = doc.splitlines()[0]
628 doc = doc.splitlines()[0]
587
629
588 rst = listexts(_("'%s' is provided by the following "
630 rst = listexts(_("'%s' is provided by the following "
589 "extension:") % cmd, {ext: doc}, indent=4,
631 "extension:") % cmd, {ext: doc}, indent=4,
590 showdeprecated=True)
632 showdeprecated=True)
591 rst.append('\n')
633 rst.append('\n')
592 rst.append(_("(use 'hg help extensions' for information on enabling "
634 rst.append(_("(use 'hg help extensions' for information on enabling "
593 "extensions)\n"))
635 "extensions)\n"))
594 return rst
636 return rst
595
637
596
638
597 rst = []
639 rst = []
598 kw = opts.get('keyword')
640 kw = opts.get('keyword')
599 if kw or name is None and any(opts[o] for o in opts):
641 if kw or name is None and any(opts[o] for o in opts):
600 matches = topicmatch(ui, commands, name or '')
642 matches = topicmatch(ui, commands, name or '')
601 helpareas = []
643 helpareas = []
602 if opts.get('extension'):
644 if opts.get('extension'):
603 helpareas += [('extensions', _('Extensions'))]
645 helpareas += [('extensions', _('Extensions'))]
604 if opts.get('command'):
646 if opts.get('command'):
605 helpareas += [('commands', _('Commands'))]
647 helpareas += [('commands', _('Commands'))]
606 if not helpareas:
648 if not helpareas:
607 helpareas = [('topics', _('Topics')),
649 helpareas = [('topics', _('Topics')),
608 ('commands', _('Commands')),
650 ('commands', _('Commands')),
609 ('extensions', _('Extensions')),
651 ('extensions', _('Extensions')),
610 ('extensioncommands', _('Extension Commands'))]
652 ('extensioncommands', _('Extension Commands'))]
611 for t, title in helpareas:
653 for t, title in helpareas:
612 if matches[t]:
654 if matches[t]:
613 rst.append('%s:\n\n' % title)
655 rst.append('%s:\n\n' % title)
614 rst.extend(minirst.maketable(sorted(matches[t]), 1))
656 rst.extend(minirst.maketable(sorted(matches[t]), 1))
615 rst.append('\n')
657 rst.append('\n')
616 if not rst:
658 if not rst:
617 msg = _('no matches')
659 msg = _('no matches')
618 hint = _("try 'hg help' for a list of topics")
660 hint = _("try 'hg help' for a list of topics")
619 raise error.Abort(msg, hint=hint)
661 raise error.Abort(msg, hint=hint)
620 elif name and name != 'shortlist':
662 elif name and name != 'shortlist':
621 queries = []
663 queries = []
622 if unknowncmd:
664 if unknowncmd:
623 queries += [helpextcmd]
665 queries += [helpextcmd]
624 if opts.get('extension'):
666 if opts.get('extension'):
625 queries += [helpext]
667 queries += [helpext]
626 if opts.get('command'):
668 if opts.get('command'):
627 queries += [helpcmd]
669 queries += [helpcmd]
628 if not queries:
670 if not queries:
629 queries = (helptopic, helpcmd, helpext, helpextcmd)
671 queries = (helptopic, helpcmd, helpext, helpextcmd)
630 for f in queries:
672 for f in queries:
631 try:
673 try:
632 rst = f(name, subtopic)
674 rst = f(name, subtopic)
633 break
675 break
634 except error.UnknownCommand:
676 except error.UnknownCommand:
635 pass
677 pass
636 else:
678 else:
637 if unknowncmd:
679 if unknowncmd:
638 raise error.UnknownCommand(name)
680 raise error.UnknownCommand(name)
639 else:
681 else:
640 msg = _('no such help topic: %s') % name
682 msg = _('no such help topic: %s') % name
641 hint = _("try 'hg help --keyword %s'") % name
683 hint = _("try 'hg help --keyword %s'") % name
642 raise error.Abort(msg, hint=hint)
684 raise error.Abort(msg, hint=hint)
643 else:
685 else:
644 # program name
686 # program name
645 if not ui.quiet:
687 if not ui.quiet:
646 rst = [_("Mercurial Distributed SCM\n"), '\n']
688 rst = [_("Mercurial Distributed SCM\n"), '\n']
647 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
689 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
648
690
649 return ''.join(rst)
691 return ''.join(rst)
650
692
651 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
693 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
652 full=True, **opts):
694 full=True, **opts):
653 """get help for a given topic (as a dotted name) as rendered rst
695 """get help for a given topic (as a dotted name) as rendered rst
654
696
655 Either returns the rendered help text or raises an exception.
697 Either returns the rendered help text or raises an exception.
656 """
698 """
657 if keep is None:
699 if keep is None:
658 keep = []
700 keep = []
659 else:
701 else:
660 keep = list(keep) # make a copy so we can mutate this later
702 keep = list(keep) # make a copy so we can mutate this later
661
703
662 # <fullname> := <name>[.<subtopic][.<section>]
704 # <fullname> := <name>[.<subtopic][.<section>]
663 name = subtopic = section = None
705 name = subtopic = section = None
664 if fullname is not None:
706 if fullname is not None:
665 nameparts = fullname.split('.')
707 nameparts = fullname.split('.')
666 name = nameparts.pop(0)
708 name = nameparts.pop(0)
667 if nameparts and name in subtopics:
709 if nameparts and name in subtopics:
668 subtopic = nameparts.pop(0)
710 subtopic = nameparts.pop(0)
669 if nameparts:
711 if nameparts:
670 section = encoding.lower('.'.join(nameparts))
712 section = encoding.lower('.'.join(nameparts))
671
713
672 textwidth = ui.configint('ui', 'textwidth')
714 textwidth = ui.configint('ui', 'textwidth')
673 termwidth = ui.termwidth() - 2
715 termwidth = ui.termwidth() - 2
674 if textwidth <= 0 or termwidth < textwidth:
716 if textwidth <= 0 or termwidth < textwidth:
675 textwidth = termwidth
717 textwidth = termwidth
676 text = help_(ui, commands, name,
718 text = help_(ui, commands, name,
677 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
719 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
678
720
679 blocks, pruned = minirst.parse(text, keep=keep)
721 blocks, pruned = minirst.parse(text, keep=keep)
680 if 'verbose' in pruned:
722 if 'verbose' in pruned:
681 keep.append('omitted')
723 keep.append('omitted')
682 else:
724 else:
683 keep.append('notomitted')
725 keep.append('notomitted')
684 blocks, pruned = minirst.parse(text, keep=keep)
726 blocks, pruned = minirst.parse(text, keep=keep)
685 if section:
727 if section:
686 blocks = minirst.filtersections(blocks, section)
728 blocks = minirst.filtersections(blocks, section)
687
729
688 # We could have been given a weird ".foo" section without a name
730 # We could have been given a weird ".foo" section without a name
689 # to look for, or we could have simply failed to found "foo.bar"
731 # to look for, or we could have simply failed to found "foo.bar"
690 # because bar isn't a section of foo
732 # because bar isn't a section of foo
691 if section and not (blocks and name):
733 if section and not (blocks and name):
692 raise error.Abort(_("help section not found: %s") % fullname)
734 raise error.Abort(_("help section not found: %s") % fullname)
693
735
694 return minirst.formatplain(blocks, textwidth)
736 return minirst.formatplain(blocks, textwidth)
@@ -1,467 +1,474 b''
1 # registrar.py - utilities to register function for specific purpose
1 # registrar.py - utilities to register function for specific purpose
2 #
2 #
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 configitems,
11 configitems,
12 error,
12 error,
13 pycompat,
13 pycompat,
14 util,
14 util,
15 )
15 )
16
16
17 # unlike the other registered items, config options are neither functions or
17 # unlike the other registered items, config options are neither functions or
18 # classes. Registering the option is just small function call.
18 # classes. Registering the option is just small function call.
19 #
19 #
20 # We still add the official API to the registrar module for consistency with
20 # We still add the official API to the registrar module for consistency with
21 # the other items extensions want might to register.
21 # the other items extensions want might to register.
22 configitem = configitems.getitemregister
22 configitem = configitems.getitemregister
23
23
24 class _funcregistrarbase(object):
24 class _funcregistrarbase(object):
25 """Base of decorator to register a function for specific purpose
25 """Base of decorator to register a function for specific purpose
26
26
27 This decorator stores decorated functions into own dict 'table'.
27 This decorator stores decorated functions into own dict 'table'.
28
28
29 The least derived class can be defined by overriding 'formatdoc',
29 The least derived class can be defined by overriding 'formatdoc',
30 for example::
30 for example::
31
31
32 class keyword(_funcregistrarbase):
32 class keyword(_funcregistrarbase):
33 _docformat = ":%s: %s"
33 _docformat = ":%s: %s"
34
34
35 This should be used as below:
35 This should be used as below:
36
36
37 keyword = registrar.keyword()
37 keyword = registrar.keyword()
38
38
39 @keyword('bar')
39 @keyword('bar')
40 def barfunc(*args, **kwargs):
40 def barfunc(*args, **kwargs):
41 '''Explanation of bar keyword ....
41 '''Explanation of bar keyword ....
42 '''
42 '''
43 pass
43 pass
44
44
45 In this case:
45 In this case:
46
46
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 """
49 """
50 def __init__(self, table=None):
50 def __init__(self, table=None):
51 if table is None:
51 if table is None:
52 self._table = {}
52 self._table = {}
53 else:
53 else:
54 self._table = table
54 self._table = table
55
55
56 def __call__(self, decl, *args, **kwargs):
56 def __call__(self, decl, *args, **kwargs):
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
58
58
59 def _doregister(self, func, decl, *args, **kwargs):
59 def _doregister(self, func, decl, *args, **kwargs):
60 name = self._getname(decl)
60 name = self._getname(decl)
61
61
62 if name in self._table:
62 if name in self._table:
63 msg = 'duplicate registration for name: "%s"' % name
63 msg = 'duplicate registration for name: "%s"' % name
64 raise error.ProgrammingError(msg)
64 raise error.ProgrammingError(msg)
65
65
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 doc = pycompat.sysbytes(func.__doc__).strip()
67 doc = pycompat.sysbytes(func.__doc__).strip()
68 func._origdoc = doc
68 func._origdoc = doc
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70
70
71 self._table[name] = func
71 self._table[name] = func
72 self._extrasetup(name, func, *args, **kwargs)
72 self._extrasetup(name, func, *args, **kwargs)
73
73
74 return func
74 return func
75
75
76 def _parsefuncdecl(self, decl):
76 def _parsefuncdecl(self, decl):
77 """Parse function declaration and return the name of function in it
77 """Parse function declaration and return the name of function in it
78 """
78 """
79 i = decl.find('(')
79 i = decl.find('(')
80 if i >= 0:
80 if i >= 0:
81 return decl[:i]
81 return decl[:i]
82 else:
82 else:
83 return decl
83 return decl
84
84
85 def _getname(self, decl):
85 def _getname(self, decl):
86 """Return the name of the registered function from decl
86 """Return the name of the registered function from decl
87
87
88 Derived class should override this, if it allows more
88 Derived class should override this, if it allows more
89 descriptive 'decl' string than just a name.
89 descriptive 'decl' string than just a name.
90 """
90 """
91 return decl
91 return decl
92
92
93 _docformat = None
93 _docformat = None
94
94
95 def _formatdoc(self, decl, doc):
95 def _formatdoc(self, decl, doc):
96 """Return formatted document of the registered function for help
96 """Return formatted document of the registered function for help
97
97
98 'doc' is '__doc__.strip()' of the registered function.
98 'doc' is '__doc__.strip()' of the registered function.
99 """
99 """
100 return self._docformat % (decl, doc)
100 return self._docformat % (decl, doc)
101
101
102 def _extrasetup(self, name, func):
102 def _extrasetup(self, name, func):
103 """Execute exra setup for registered function, if needed
103 """Execute exra setup for registered function, if needed
104 """
104 """
105
105
106 class command(_funcregistrarbase):
106 class command(_funcregistrarbase):
107 """Decorator to register a command function to table
107 """Decorator to register a command function to table
108
108
109 This class receives a command table as its argument. The table should
109 This class receives a command table as its argument. The table should
110 be a dict.
110 be a dict.
111
111
112 The created object can be used as a decorator for adding commands to
112 The created object can be used as a decorator for adding commands to
113 that command table. This accepts multiple arguments to define a command.
113 that command table. This accepts multiple arguments to define a command.
114
114
115 The first argument is the command name (as bytes).
115 The first argument is the command name (as bytes).
116
116
117 The `options` keyword argument is an iterable of tuples defining command
117 The `options` keyword argument is an iterable of tuples defining command
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
119 tuple.
119 tuple.
120
120
121 The `synopsis` argument defines a short, one line summary of how to use the
121 The `synopsis` argument defines a short, one line summary of how to use the
122 command. This shows up in the help output.
122 command. This shows up in the help output.
123
123
124 There are three arguments that control what repository (if any) is found
124 There are three arguments that control what repository (if any) is found
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
126 `inferrepo`.
126 `inferrepo`.
127
127
128 The `norepo` argument defines whether the command does not require a
128 The `norepo` argument defines whether the command does not require a
129 local repository. Most commands operate against a repository, thus the
129 local repository. Most commands operate against a repository, thus the
130 default is False. When True, no repository will be passed.
130 default is False. When True, no repository will be passed.
131
131
132 The `optionalrepo` argument defines whether the command optionally requires
132 The `optionalrepo` argument defines whether the command optionally requires
133 a local repository. If no repository can be found, None will be passed
133 a local repository. If no repository can be found, None will be passed
134 to the decorated function.
134 to the decorated function.
135
135
136 The `inferrepo` argument defines whether to try to find a repository from
136 The `inferrepo` argument defines whether to try to find a repository from
137 the command line arguments. If True, arguments will be examined for
137 the command line arguments. If True, arguments will be examined for
138 potential repository locations. See ``findrepo()``. If a repository is
138 potential repository locations. See ``findrepo()``. If a repository is
139 found, it will be used and passed to the decorated function.
139 found, it will be used and passed to the decorated function.
140
140
141 The `intents` argument defines a set of intended actions or capabilities
141 The `intents` argument defines a set of intended actions or capabilities
142 the command is taking. These intents can be used to affect the construction
142 the command is taking. These intents can be used to affect the construction
143 of the repository object passed to the command. For example, commands
143 of the repository object passed to the command. For example, commands
144 declaring that they are read-only could receive a repository that doesn't
144 declaring that they are read-only could receive a repository that doesn't
145 have any methods allowing repository mutation. Other intents could be used
145 have any methods allowing repository mutation. Other intents could be used
146 to prevent the command from running if the requested intent could not be
146 to prevent the command from running if the requested intent could not be
147 fulfilled.
147 fulfilled.
148
148
149 If `helpcategory` is set (usually to one of the constants in the help
150 module), the command will be displayed under that category in the help's
151 list of commands.
152
149 The following intents are defined:
153 The following intents are defined:
150
154
151 readonly
155 readonly
152 The command is read-only
156 The command is read-only
153
157
154 The signature of the decorated function looks like this:
158 The signature of the decorated function looks like this:
155 def cmd(ui[, repo] [, <args>] [, <options>])
159 def cmd(ui[, repo] [, <args>] [, <options>])
156
160
157 `repo` is required if `norepo` is False.
161 `repo` is required if `norepo` is False.
158 `<args>` are positional args (or `*args`) arguments, of non-option
162 `<args>` are positional args (or `*args`) arguments, of non-option
159 arguments from the command line.
163 arguments from the command line.
160 `<options>` are keyword arguments (or `**options`) of option arguments
164 `<options>` are keyword arguments (or `**options`) of option arguments
161 from the command line.
165 from the command line.
162
166
163 See the WritingExtensions and MercurialApi documentation for more exhaustive
167 See the WritingExtensions and MercurialApi documentation for more exhaustive
164 descriptions and examples.
168 descriptions and examples.
165 """
169 """
166
170
171 # Command categories for grouping them in help output.
172 CATEGORY_NONE = 'none'
173
167 def _doregister(self, func, name, options=(), synopsis=None,
174 def _doregister(self, func, name, options=(), synopsis=None,
168 norepo=False, optionalrepo=False, inferrepo=False,
175 norepo=False, optionalrepo=False, inferrepo=False,
169 intents=None):
176 intents=None, helpcategory=None):
170
171 func.norepo = norepo
177 func.norepo = norepo
172 func.optionalrepo = optionalrepo
178 func.optionalrepo = optionalrepo
173 func.inferrepo = inferrepo
179 func.inferrepo = inferrepo
174 func.intents = intents or set()
180 func.intents = intents or set()
181 func.helpcategory = helpcategory
175 if synopsis:
182 if synopsis:
176 self._table[name] = func, list(options), synopsis
183 self._table[name] = func, list(options), synopsis
177 else:
184 else:
178 self._table[name] = func, list(options)
185 self._table[name] = func, list(options)
179 return func
186 return func
180
187
181 INTENT_READONLY = b'readonly'
188 INTENT_READONLY = b'readonly'
182
189
183 class revsetpredicate(_funcregistrarbase):
190 class revsetpredicate(_funcregistrarbase):
184 """Decorator to register revset predicate
191 """Decorator to register revset predicate
185
192
186 Usage::
193 Usage::
187
194
188 revsetpredicate = registrar.revsetpredicate()
195 revsetpredicate = registrar.revsetpredicate()
189
196
190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
197 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
191 def mypredicatefunc(repo, subset, x):
198 def mypredicatefunc(repo, subset, x):
192 '''Explanation of this revset predicate ....
199 '''Explanation of this revset predicate ....
193 '''
200 '''
194 pass
201 pass
195
202
196 The first string argument is used also in online help.
203 The first string argument is used also in online help.
197
204
198 Optional argument 'safe' indicates whether a predicate is safe for
205 Optional argument 'safe' indicates whether a predicate is safe for
199 DoS attack (False by default).
206 DoS attack (False by default).
200
207
201 Optional argument 'takeorder' indicates whether a predicate function
208 Optional argument 'takeorder' indicates whether a predicate function
202 takes ordering policy as the last argument.
209 takes ordering policy as the last argument.
203
210
204 Optional argument 'weight' indicates the estimated run-time cost, useful
211 Optional argument 'weight' indicates the estimated run-time cost, useful
205 for static optimization, default is 1. Higher weight means more expensive.
212 for static optimization, default is 1. Higher weight means more expensive.
206 Usually, revsets that are fast and return only one revision has a weight of
213 Usually, revsets that are fast and return only one revision has a weight of
207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
214 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
215 changelog have weight 10 (ex. author); revsets reading manifest deltas have
209 weight 30 (ex. adds); revset reading manifest contents have weight 100
216 weight 30 (ex. adds); revset reading manifest contents have weight 100
210 (ex. contains). Note: those values are flexible. If the revset has a
217 (ex. contains). Note: those values are flexible. If the revset has a
211 same big-O time complexity as 'contains', but with a smaller constant, it
218 same big-O time complexity as 'contains', but with a smaller constant, it
212 might have a weight of 90.
219 might have a weight of 90.
213
220
214 'revsetpredicate' instance in example above can be used to
221 'revsetpredicate' instance in example above can be used to
215 decorate multiple functions.
222 decorate multiple functions.
216
223
217 Decorated functions are registered automatically at loading
224 Decorated functions are registered automatically at loading
218 extension, if an instance named as 'revsetpredicate' is used for
225 extension, if an instance named as 'revsetpredicate' is used for
219 decorating in extension.
226 decorating in extension.
220
227
221 Otherwise, explicit 'revset.loadpredicate()' is needed.
228 Otherwise, explicit 'revset.loadpredicate()' is needed.
222 """
229 """
223 _getname = _funcregistrarbase._parsefuncdecl
230 _getname = _funcregistrarbase._parsefuncdecl
224 _docformat = "``%s``\n %s"
231 _docformat = "``%s``\n %s"
225
232
226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
233 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
227 func._safe = safe
234 func._safe = safe
228 func._takeorder = takeorder
235 func._takeorder = takeorder
229 func._weight = weight
236 func._weight = weight
230
237
231 class filesetpredicate(_funcregistrarbase):
238 class filesetpredicate(_funcregistrarbase):
232 """Decorator to register fileset predicate
239 """Decorator to register fileset predicate
233
240
234 Usage::
241 Usage::
235
242
236 filesetpredicate = registrar.filesetpredicate()
243 filesetpredicate = registrar.filesetpredicate()
237
244
238 @filesetpredicate('mypredicate()')
245 @filesetpredicate('mypredicate()')
239 def mypredicatefunc(mctx, x):
246 def mypredicatefunc(mctx, x):
240 '''Explanation of this fileset predicate ....
247 '''Explanation of this fileset predicate ....
241 '''
248 '''
242 pass
249 pass
243
250
244 The first string argument is used also in online help.
251 The first string argument is used also in online help.
245
252
246 Optional argument 'callstatus' indicates whether a predicate
253 Optional argument 'callstatus' indicates whether a predicate
247 implies 'matchctx.status()' at runtime or not (False, by
254 implies 'matchctx.status()' at runtime or not (False, by
248 default).
255 default).
249
256
250 Optional argument 'weight' indicates the estimated run-time cost, useful
257 Optional argument 'weight' indicates the estimated run-time cost, useful
251 for static optimization, default is 1. Higher weight means more expensive.
258 for static optimization, default is 1. Higher weight means more expensive.
252 There are predefined weights in the 'filesetlang' module.
259 There are predefined weights in the 'filesetlang' module.
253
260
254 ====== =============================================================
261 ====== =============================================================
255 Weight Description and examples
262 Weight Description and examples
256 ====== =============================================================
263 ====== =============================================================
257 0.5 basic match patterns (e.g. a symbol)
264 0.5 basic match patterns (e.g. a symbol)
258 10 computing status (e.g. added()) or accessing a few files
265 10 computing status (e.g. added()) or accessing a few files
259 30 reading file content for each (e.g. grep())
266 30 reading file content for each (e.g. grep())
260 50 scanning working directory (ignored())
267 50 scanning working directory (ignored())
261 ====== =============================================================
268 ====== =============================================================
262
269
263 'filesetpredicate' instance in example above can be used to
270 'filesetpredicate' instance in example above can be used to
264 decorate multiple functions.
271 decorate multiple functions.
265
272
266 Decorated functions are registered automatically at loading
273 Decorated functions are registered automatically at loading
267 extension, if an instance named as 'filesetpredicate' is used for
274 extension, if an instance named as 'filesetpredicate' is used for
268 decorating in extension.
275 decorating in extension.
269
276
270 Otherwise, explicit 'fileset.loadpredicate()' is needed.
277 Otherwise, explicit 'fileset.loadpredicate()' is needed.
271 """
278 """
272 _getname = _funcregistrarbase._parsefuncdecl
279 _getname = _funcregistrarbase._parsefuncdecl
273 _docformat = "``%s``\n %s"
280 _docformat = "``%s``\n %s"
274
281
275 def _extrasetup(self, name, func, callstatus=False, weight=1):
282 def _extrasetup(self, name, func, callstatus=False, weight=1):
276 func._callstatus = callstatus
283 func._callstatus = callstatus
277 func._weight = weight
284 func._weight = weight
278
285
279 class _templateregistrarbase(_funcregistrarbase):
286 class _templateregistrarbase(_funcregistrarbase):
280 """Base of decorator to register functions as template specific one
287 """Base of decorator to register functions as template specific one
281 """
288 """
282 _docformat = ":%s: %s"
289 _docformat = ":%s: %s"
283
290
284 class templatekeyword(_templateregistrarbase):
291 class templatekeyword(_templateregistrarbase):
285 """Decorator to register template keyword
292 """Decorator to register template keyword
286
293
287 Usage::
294 Usage::
288
295
289 templatekeyword = registrar.templatekeyword()
296 templatekeyword = registrar.templatekeyword()
290
297
291 # new API (since Mercurial 4.6)
298 # new API (since Mercurial 4.6)
292 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
299 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
293 def mykeywordfunc(context, mapping):
300 def mykeywordfunc(context, mapping):
294 '''Explanation of this template keyword ....
301 '''Explanation of this template keyword ....
295 '''
302 '''
296 pass
303 pass
297
304
298 # old API (DEPRECATED)
305 # old API (DEPRECATED)
299 @templatekeyword('mykeyword')
306 @templatekeyword('mykeyword')
300 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
307 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
301 '''Explanation of this template keyword ....
308 '''Explanation of this template keyword ....
302 '''
309 '''
303 pass
310 pass
304
311
305 The first string argument is used also in online help.
312 The first string argument is used also in online help.
306
313
307 Optional argument 'requires' should be a collection of resource names
314 Optional argument 'requires' should be a collection of resource names
308 which the template keyword depends on. This also serves as a flag to
315 which the template keyword depends on. This also serves as a flag to
309 switch to the new API. If 'requires' is unspecified, all template
316 switch to the new API. If 'requires' is unspecified, all template
310 keywords and resources are expanded to the function arguments.
317 keywords and resources are expanded to the function arguments.
311
318
312 'templatekeyword' instance in example above can be used to
319 'templatekeyword' instance in example above can be used to
313 decorate multiple functions.
320 decorate multiple functions.
314
321
315 Decorated functions are registered automatically at loading
322 Decorated functions are registered automatically at loading
316 extension, if an instance named as 'templatekeyword' is used for
323 extension, if an instance named as 'templatekeyword' is used for
317 decorating in extension.
324 decorating in extension.
318
325
319 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
326 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
320 """
327 """
321
328
322 def _extrasetup(self, name, func, requires=None):
329 def _extrasetup(self, name, func, requires=None):
323 func._requires = requires
330 func._requires = requires
324
331
325 class templatefilter(_templateregistrarbase):
332 class templatefilter(_templateregistrarbase):
326 """Decorator to register template filer
333 """Decorator to register template filer
327
334
328 Usage::
335 Usage::
329
336
330 templatefilter = registrar.templatefilter()
337 templatefilter = registrar.templatefilter()
331
338
332 @templatefilter('myfilter', intype=bytes)
339 @templatefilter('myfilter', intype=bytes)
333 def myfilterfunc(text):
340 def myfilterfunc(text):
334 '''Explanation of this template filter ....
341 '''Explanation of this template filter ....
335 '''
342 '''
336 pass
343 pass
337
344
338 The first string argument is used also in online help.
345 The first string argument is used also in online help.
339
346
340 Optional argument 'intype' defines the type of the input argument,
347 Optional argument 'intype' defines the type of the input argument,
341 which should be (bytes, int, templateutil.date, or None for any.)
348 which should be (bytes, int, templateutil.date, or None for any.)
342
349
343 'templatefilter' instance in example above can be used to
350 'templatefilter' instance in example above can be used to
344 decorate multiple functions.
351 decorate multiple functions.
345
352
346 Decorated functions are registered automatically at loading
353 Decorated functions are registered automatically at loading
347 extension, if an instance named as 'templatefilter' is used for
354 extension, if an instance named as 'templatefilter' is used for
348 decorating in extension.
355 decorating in extension.
349
356
350 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
357 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
351 """
358 """
352
359
353 def _extrasetup(self, name, func, intype=None):
360 def _extrasetup(self, name, func, intype=None):
354 func._intype = intype
361 func._intype = intype
355
362
356 class templatefunc(_templateregistrarbase):
363 class templatefunc(_templateregistrarbase):
357 """Decorator to register template function
364 """Decorator to register template function
358
365
359 Usage::
366 Usage::
360
367
361 templatefunc = registrar.templatefunc()
368 templatefunc = registrar.templatefunc()
362
369
363 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
370 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
364 requires={'ctx'})
371 requires={'ctx'})
365 def myfuncfunc(context, mapping, args):
372 def myfuncfunc(context, mapping, args):
366 '''Explanation of this template function ....
373 '''Explanation of this template function ....
367 '''
374 '''
368 pass
375 pass
369
376
370 The first string argument is used also in online help.
377 The first string argument is used also in online help.
371
378
372 If optional 'argspec' is defined, the function will receive 'args' as
379 If optional 'argspec' is defined, the function will receive 'args' as
373 a dict of named arguments. Otherwise 'args' is a list of positional
380 a dict of named arguments. Otherwise 'args' is a list of positional
374 arguments.
381 arguments.
375
382
376 Optional argument 'requires' should be a collection of resource names
383 Optional argument 'requires' should be a collection of resource names
377 which the template function depends on.
384 which the template function depends on.
378
385
379 'templatefunc' instance in example above can be used to
386 'templatefunc' instance in example above can be used to
380 decorate multiple functions.
387 decorate multiple functions.
381
388
382 Decorated functions are registered automatically at loading
389 Decorated functions are registered automatically at loading
383 extension, if an instance named as 'templatefunc' is used for
390 extension, if an instance named as 'templatefunc' is used for
384 decorating in extension.
391 decorating in extension.
385
392
386 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
393 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
387 """
394 """
388 _getname = _funcregistrarbase._parsefuncdecl
395 _getname = _funcregistrarbase._parsefuncdecl
389
396
390 def _extrasetup(self, name, func, argspec=None, requires=()):
397 def _extrasetup(self, name, func, argspec=None, requires=()):
391 func._argspec = argspec
398 func._argspec = argspec
392 func._requires = requires
399 func._requires = requires
393
400
394 class internalmerge(_funcregistrarbase):
401 class internalmerge(_funcregistrarbase):
395 """Decorator to register in-process merge tool
402 """Decorator to register in-process merge tool
396
403
397 Usage::
404 Usage::
398
405
399 internalmerge = registrar.internalmerge()
406 internalmerge = registrar.internalmerge()
400
407
401 @internalmerge('mymerge', internalmerge.mergeonly,
408 @internalmerge('mymerge', internalmerge.mergeonly,
402 onfailure=None, precheck=None,
409 onfailure=None, precheck=None,
403 binary=False, symlink=False):
410 binary=False, symlink=False):
404 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
411 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
405 toolconf, files, labels=None):
412 toolconf, files, labels=None):
406 '''Explanation of this internal merge tool ....
413 '''Explanation of this internal merge tool ....
407 '''
414 '''
408 return 1, False # means "conflicted", "no deletion needed"
415 return 1, False # means "conflicted", "no deletion needed"
409
416
410 The first string argument is used to compose actual merge tool name,
417 The first string argument is used to compose actual merge tool name,
411 ":name" and "internal:name" (the latter is historical one).
418 ":name" and "internal:name" (the latter is historical one).
412
419
413 The second argument is one of merge types below:
420 The second argument is one of merge types below:
414
421
415 ========== ======== ======== =========
422 ========== ======== ======== =========
416 merge type precheck premerge fullmerge
423 merge type precheck premerge fullmerge
417 ========== ======== ======== =========
424 ========== ======== ======== =========
418 nomerge x x x
425 nomerge x x x
419 mergeonly o x o
426 mergeonly o x o
420 fullmerge o o o
427 fullmerge o o o
421 ========== ======== ======== =========
428 ========== ======== ======== =========
422
429
423 Optional argument 'onfailure' is the format of warning message
430 Optional argument 'onfailure' is the format of warning message
424 to be used at failure of merging (target filename is specified
431 to be used at failure of merging (target filename is specified
425 at formatting). Or, None or so, if warning message should be
432 at formatting). Or, None or so, if warning message should be
426 suppressed.
433 suppressed.
427
434
428 Optional argument 'precheck' is the function to be used
435 Optional argument 'precheck' is the function to be used
429 before actual invocation of internal merge tool itself.
436 before actual invocation of internal merge tool itself.
430 It takes as same arguments as internal merge tool does, other than
437 It takes as same arguments as internal merge tool does, other than
431 'files' and 'labels'. If it returns false value, merging is aborted
438 'files' and 'labels'. If it returns false value, merging is aborted
432 immediately (and file is marked as "unresolved").
439 immediately (and file is marked as "unresolved").
433
440
434 Optional argument 'binary' is a binary files capability of internal
441 Optional argument 'binary' is a binary files capability of internal
435 merge tool. 'nomerge' merge type implies binary=True.
442 merge tool. 'nomerge' merge type implies binary=True.
436
443
437 Optional argument 'symlink' is a symlinks capability of inetrnal
444 Optional argument 'symlink' is a symlinks capability of inetrnal
438 merge function. 'nomerge' merge type implies symlink=True.
445 merge function. 'nomerge' merge type implies symlink=True.
439
446
440 'internalmerge' instance in example above can be used to
447 'internalmerge' instance in example above can be used to
441 decorate multiple functions.
448 decorate multiple functions.
442
449
443 Decorated functions are registered automatically at loading
450 Decorated functions are registered automatically at loading
444 extension, if an instance named as 'internalmerge' is used for
451 extension, if an instance named as 'internalmerge' is used for
445 decorating in extension.
452 decorating in extension.
446
453
447 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
454 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
448 """
455 """
449 _docformat = "``:%s``\n %s"
456 _docformat = "``:%s``\n %s"
450
457
451 # merge type definitions:
458 # merge type definitions:
452 nomerge = None
459 nomerge = None
453 mergeonly = 'mergeonly' # just the full merge, no premerge
460 mergeonly = 'mergeonly' # just the full merge, no premerge
454 fullmerge = 'fullmerge' # both premerge and merge
461 fullmerge = 'fullmerge' # both premerge and merge
455
462
456 def _extrasetup(self, name, func, mergetype,
463 def _extrasetup(self, name, func, mergetype,
457 onfailure=None, precheck=None,
464 onfailure=None, precheck=None,
458 binary=False, symlink=False):
465 binary=False, symlink=False):
459 func.mergetype = mergetype
466 func.mergetype = mergetype
460 func.onfailure = onfailure
467 func.onfailure = onfailure
461 func.precheck = precheck
468 func.precheck = precheck
462
469
463 binarycap = binary or mergetype == self.nomerge
470 binarycap = binary or mergetype == self.nomerge
464 symlinkcap = symlink or mergetype == self.nomerge
471 symlinkcap = symlink or mergetype == self.nomerge
465
472
466 # actual capabilities, which this internal merge tool has
473 # actual capabilities, which this internal merge tool has
467 func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
474 func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
General Comments 0
You need to be logged in to leave comments. Login now