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