Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/exthelper.py @ 41047: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
41046:ce0bc2952e2a | 41047: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 |