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