comparison mercurial/exthelper.py @ 41044:fe606f2dcae9

extensions: import the exthelper class from evolve 980565468003 (API) This should help make extensions that wrap a lot of stuff more comprehendible. It was copied unmodified, except: - fix up the imports - rename final_xxxsetup() -> finalxxxsetup() to appease checkcode - avoid a [] default arg to wrapcommand() .. api:: Add `exthelper` class to simplify extension writing by allowing functions, commands, and configitems to be registered via annotations. The previous APIs are still available for use.
author Matt Harbison <matt_harbison@yahoo.com>
date Sat, 22 Dec 2018 21:06:24 -0500
parents
children c1476d095d57
comparison
equal deleted inserted replaced
41043:ce0bc2952e2a 41044:fe606f2dcae9
1 # Copyright 2012 Logilab SA <contact@logilab.fr>
2 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Octobus <contact@octobus.net>
4 #
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.
7
8 #####################################################################
9 ### Extension helper ###
10 #####################################################################
11
12 from __future__ import absolute_import
13
14 from . import (
15 commands,
16 configitems,
17 extensions,
18 fileset as filesetmod,
19 registrar,
20 revset as revsetmod,
21 templatekw as templatekwmod,
22 )
23
24 class exthelper(object):
25 """Helper for modular extension setup
26
27 A single helper should be instantiated for each extension. Helper
28 methods are then used as decorators for various purpose.
29
30 All decorators return the original function and may be chained.
31 """
32
33 def __init__(self):
34 self._uipopulatecallables = []
35 self._uicallables = []
36 self._extcallables = []
37 self._repocallables = []
38 self._revsetsymbols = []
39 self._filesetsymbols = []
40 self._templatekws = []
41 self._commandwrappers = []
42 self._extcommandwrappers = []
43 self._functionwrappers = []
44 self._duckpunchers = []
45 self.cmdtable = {}
46 self.command = registrar.command(self.cmdtable)
47 if '^init' in commands.table:
48 olddoregister = self.command._doregister
49
50 def _newdoregister(self, name, *args, **kwargs):
51 if kwargs.pop('helpbasic', False):
52 name = '^' + name
53 return olddoregister(self, name, *args, **kwargs)
54 self.command._doregister = _newdoregister
55
56 self.configtable = {}
57 self._configitem = registrar.configitem(self.configtable)
58
59 def configitem(self, section, config, default=configitems.dynamicdefault):
60 """Register a config item.
61 """
62 self._configitem(section, config, default=default)
63
64 def merge(self, other):
65 self._uicallables.extend(other._uicallables)
66 self._uipopulatecallables.extend(other._uipopulatecallables)
67 self._extcallables.extend(other._extcallables)
68 self._repocallables.extend(other._repocallables)
69 self._revsetsymbols.extend(other._revsetsymbols)
70 self._filesetsymbols.extend(other._filesetsymbols)
71 self._templatekws.extend(other._templatekws)
72 self._commandwrappers.extend(other._commandwrappers)
73 self._extcommandwrappers.extend(other._extcommandwrappers)
74 self._functionwrappers.extend(other._functionwrappers)
75 self._duckpunchers.extend(other._duckpunchers)
76 self.cmdtable.update(other.cmdtable)
77 for section, items in other.configtable.iteritems():
78 if section in self.configtable:
79 self.configtable[section].update(items)
80 else:
81 self.configtable[section] = items
82
83 def finaluisetup(self, ui):
84 """Method to be used as the extension uisetup
85
86 The following operations belong here:
87
88 - Changes to ui.__class__ . The ui object that will be used to run the
89 command has not yet been created. Changes made here will affect ui
90 objects created after this, and in particular the ui that will be
91 passed to runcommand
92 - Command wraps (extensions.wrapcommand)
93 - Changes that need to be visible to other extensions: because
94 initialization occurs in phases (all extensions run uisetup, then all
95 run extsetup), a change made here will be visible to other extensions
96 during extsetup
97 - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
98 module members
99 - Setup of pre-* and post-* hooks
100 - pushkey setup
101 """
102 for cont, funcname, func in self._duckpunchers:
103 setattr(cont, funcname, func)
104 for command, wrapper, opts in self._commandwrappers:
105 entry = extensions.wrapcommand(commands.table, command, wrapper)
106 if opts:
107 for short, long, val, msg in opts:
108 entry[1].append((short, long, val, msg))
109 for cont, funcname, wrapper in self._functionwrappers:
110 extensions.wrapfunction(cont, funcname, wrapper)
111 for c in self._uicallables:
112 c(ui)
113
114 def finaluipopulate(self, ui):
115 """Method to be used as the extension uipopulate
116
117 This is called once per ui instance to:
118
119 - Set up additional ui members
120 - Update configuration by ``ui.setconfig()``
121 - Extend the class dynamically
122 """
123 for c in self._uipopulatecallables:
124 c(ui)
125
126 def finalextsetup(self, ui):
127 """Method to be used as a the extension extsetup
128
129 The following operations belong here:
130
131 - Changes depending on the status of other extensions. (if
132 extensions.find('mq'))
133 - Add a global option to all commands
134 - Register revset functions
135 """
136 knownexts = {}
137
138 revsetpredicate = registrar.revsetpredicate()
139 for name, symbol in self._revsetsymbols:
140 revsetpredicate(name)(symbol)
141 revsetmod.loadpredicate(ui, 'evolve', revsetpredicate)
142
143 filesetpredicate = registrar.filesetpredicate()
144 for name, symbol in self._filesetsymbols:
145 filesetpredicate(name)(symbol)
146 # TODO: Figure out the calling extension name
147 filesetmod.loadpredicate(ui, 'exthelper', filesetpredicate)
148
149 templatekeyword = registrar.templatekeyword()
150 for name, kw, requires in self._templatekws:
151 if requires is not None:
152 templatekeyword(name, requires=requires)(kw)
153 else:
154 templatekeyword(name)(kw)
155 templatekwmod.loadkeyword(ui, 'evolve', templatekeyword)
156
157 for ext, command, wrapper, opts in self._extcommandwrappers:
158 if ext not in knownexts:
159 try:
160 e = extensions.find(ext)
161 except KeyError:
162 # Extension isn't enabled, so don't bother trying to wrap
163 # it.
164 continue
165 knownexts[ext] = e.cmdtable
166 entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
167 if opts:
168 for short, long, val, msg in opts:
169 entry[1].append((short, long, val, msg))
170
171 for c in self._extcallables:
172 c(ui)
173
174 def finalreposetup(self, ui, repo):
175 """Method to be used as the extension reposetup
176
177 The following operations belong here:
178
179 - All hooks but pre-* and post-*
180 - Modify configuration variables
181 - Changes to repo.__class__, repo.dirstate.__class__
182 """
183 for c in self._repocallables:
184 c(ui, repo)
185
186 def uisetup(self, call):
187 """Decorated function will be executed during uisetup
188
189 example::
190
191 @eh.uisetup
192 def setupbabar(ui):
193 print 'this is uisetup!'
194 """
195 self._uicallables.append(call)
196 return call
197
198 def uipopulate(self, call):
199 """Decorated function will be executed during uipopulate
200
201 example::
202
203 @eh.uipopulate
204 def setupfoo(ui):
205 print 'this is uipopulate!'
206 """
207 self._uipopulatecallables.append(call)
208 return call
209
210 def extsetup(self, call):
211 """Decorated function will be executed during extsetup
212
213 example::
214
215 @eh.extsetup
216 def setupcelestine(ui):
217 print 'this is extsetup!'
218 """
219 self._extcallables.append(call)
220 return call
221
222 def reposetup(self, call):
223 """Decorated function will be executed during reposetup
224
225 example::
226
227 @eh.reposetup
228 def setupzephir(ui, repo):
229 print 'this is reposetup!'
230 """
231 self._repocallables.append(call)
232 return call
233
234 def revset(self, symbolname):
235 """Decorated function is a revset symbol
236
237 The name of the symbol must be given as the decorator argument.
238 The symbol is added during `extsetup`.
239
240 example::
241
242 @eh.revset('hidden')
243 def revsetbabar(repo, subset, x):
244 args = revset.getargs(x, 0, 0, 'babar accept no argument')
245 return [r for r in subset if 'babar' in repo[r].description()]
246 """
247 def dec(symbol):
248 self._revsetsymbols.append((symbolname, symbol))
249 return symbol
250 return dec
251
252 def fileset(self, symbolname):
253 """Decorated function is a fileset symbol
254
255 The name of the symbol must be given as the decorator argument.
256 The symbol is added during `extsetup`.
257
258 example::
259
260 @eh.fileset('lfs()')
261 def filesetbabar(mctx, x):
262 return mctx.predicate(...)
263 """
264 def dec(symbol):
265 self._filesetsymbols.append((symbolname, symbol))
266 return symbol
267 return dec
268
269 def templatekw(self, keywordname, requires=None):
270 """Decorated function is a template keyword
271
272 The name of the keyword must be given as the decorator argument.
273 The symbol is added during `extsetup`.
274
275 example::
276
277 @eh.templatekw('babar')
278 def kwbabar(ctx):
279 return 'babar'
280 """
281 def dec(keyword):
282 self._templatekws.append((keywordname, keyword, requires))
283 return keyword
284 return dec
285
286 def wrapcommand(self, command, extension=None, opts=None):
287 """Decorated function is a command wrapper
288
289 The name of the command must be given as the decorator argument.
290 The wrapping is installed during `uisetup`.
291
292 If the second option `extension` argument is provided, the wrapping
293 will be applied in the extension commandtable. This argument must be a
294 string that will be searched using `extension.find` if not found and
295 Abort error is raised. If the wrapping applies to an extension, it is
296 installed during `extsetup`.
297
298 example::
299
300 @eh.wrapcommand('summary')
301 def wrapsummary(orig, ui, repo, *args, **kwargs):
302 ui.note('Barry!')
303 return orig(ui, repo, *args, **kwargs)
304
305 The `opts` argument allows specifying additional arguments for the
306 command.
307
308 """
309 if opts is None:
310 opts = []
311 def dec(wrapper):
312 if extension is None:
313 self._commandwrappers.append((command, wrapper, opts))
314 else:
315 self._extcommandwrappers.append((extension, command, wrapper,
316 opts))
317 return wrapper
318 return dec
319
320 def wrapfunction(self, container, funcname):
321 """Decorated function is a function wrapper
322
323 This function takes two arguments, the container and the name of the
324 function to wrap. The wrapping is performed during `uisetup`.
325 (there is no extension support)
326
327 example::
328
329 @eh.function(discovery, 'checkheads')
330 def wrapfunction(orig, *args, **kwargs):
331 ui.note('His head smashed in and his heart cut out')
332 return orig(*args, **kwargs)
333 """
334 def dec(wrapper):
335 self._functionwrappers.append((container, funcname, wrapper))
336 return wrapper
337 return dec
338
339 def addattr(self, container, funcname):
340 """Decorated function is to be added to the container
341
342 This function takes two arguments, the container and the name of the
343 function to wrap. The wrapping is performed during `uisetup`.
344
345 example::
346
347 @eh.function(context.changectx, 'babar')
348 def babar(ctx):
349 return 'babar' in ctx.description
350 """
351 def dec(func):
352 self._duckpunchers.append((container, funcname, func))
353 return func
354 return dec