##// END OF EJS Templates
exthelper: switch to using the registrar merging method
Matt Harbison -
r41113:4d40f6bb default
parent child Browse files
Show More
@@ -1,330 +1,330 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 error,
16 error,
17 extensions,
17 extensions,
18 registrar,
18 registrar,
19 )
19 )
20
20
21 class exthelper(object):
21 class exthelper(object):
22 """Helper for modular extension setup
22 """Helper for modular extension setup
23
23
24 A single helper should be instantiated for each module of an
24 A single helper should be instantiated for each module of an
25 extension, where a command or function needs to be wrapped, or a
25 extension, where a command or function needs to be wrapped, or a
26 command, extension hook, fileset, revset or template needs to be
26 command, extension hook, fileset, revset or template needs to be
27 registered. Helper methods are then used as decorators for
27 registered. Helper methods are then used as decorators for
28 these various purposes. If an extension spans multiple modules,
28 these various purposes. If an extension spans multiple modules,
29 all helper instances should be merged in the main module.
29 all helper instances should be merged in the main module.
30
30
31 All decorators return the original function and may be chained.
31 All decorators return the original function and may be chained.
32
32
33 Aside from the helper functions with examples below, several
33 Aside from the helper functions with examples below, several
34 registrar method aliases are available for adding commands,
34 registrar method aliases are available for adding commands,
35 configitems, filesets, revsets, and templates. Simply decorate
35 configitems, filesets, revsets, and templates. Simply decorate
36 the appropriate methods, and assign the corresponding exthelper
36 the appropriate methods, and assign the corresponding exthelper
37 variable to a module level variable of the extension. The
37 variable to a module level variable of the extension. The
38 extension loading mechanism will handle the rest.
38 extension loading mechanism will handle the rest.
39
39
40 example::
40 example::
41
41
42 # ext.py
42 # ext.py
43 eh = exthelper.exthelper()
43 eh = exthelper.exthelper()
44
44
45 # As needed:
45 # As needed:
46 cmdtable = eh.cmdtable
46 cmdtable = eh.cmdtable
47 configtable = eh.configtable
47 configtable = eh.configtable
48 filesetpredicate = eh.filesetpredicate
48 filesetpredicate = eh.filesetpredicate
49 revsetpredicate = eh.revsetpredicate
49 revsetpredicate = eh.revsetpredicate
50 templatekeyword = eh.templatekeyword
50 templatekeyword = eh.templatekeyword
51
51
52 @eh.command('mynewcommand',
52 @eh.command('mynewcommand',
53 [('r', 'rev', [], _('operate on these revisions'))],
53 [('r', 'rev', [], _('operate on these revisions'))],
54 _('-r REV...'),
54 _('-r REV...'),
55 helpcategory=command.CATEGORY_XXX)
55 helpcategory=command.CATEGORY_XXX)
56 def newcommand(ui, repo, *revs, **opts):
56 def newcommand(ui, repo, *revs, **opts):
57 # implementation goes here
57 # implementation goes here
58
58
59 eh.configitem('experimental', 'foo',
59 eh.configitem('experimental', 'foo',
60 default=False,
60 default=False,
61 )
61 )
62
62
63 @eh.filesetpredicate('lfs()')
63 @eh.filesetpredicate('lfs()')
64 def filesetbabar(mctx, x):
64 def filesetbabar(mctx, x):
65 return mctx.predicate(...)
65 return mctx.predicate(...)
66
66
67 @eh.revsetpredicate('hidden')
67 @eh.revsetpredicate('hidden')
68 def revsetbabar(repo, subset, x):
68 def revsetbabar(repo, subset, x):
69 args = revset.getargs(x, 0, 0, 'babar accept no argument')
69 args = revset.getargs(x, 0, 0, 'babar accept no argument')
70 return [r for r in subset if 'babar' in repo[r].description()]
70 return [r for r in subset if 'babar' in repo[r].description()]
71
71
72 @eh.templatekeyword('babar')
72 @eh.templatekeyword('babar')
73 def kwbabar(ctx):
73 def kwbabar(ctx):
74 return 'babar'
74 return 'babar'
75 """
75 """
76
76
77 def __init__(self):
77 def __init__(self):
78 self._uipopulatecallables = []
78 self._uipopulatecallables = []
79 self._uicallables = []
79 self._uicallables = []
80 self._extcallables = []
80 self._extcallables = []
81 self._repocallables = []
81 self._repocallables = []
82 self._commandwrappers = []
82 self._commandwrappers = []
83 self._extcommandwrappers = []
83 self._extcommandwrappers = []
84 self._functionwrappers = []
84 self._functionwrappers = []
85 self._duckpunchers = []
85 self._duckpunchers = []
86 self.cmdtable = {}
86 self.cmdtable = {}
87 self.command = registrar.command(self.cmdtable)
87 self.command = registrar.command(self.cmdtable)
88 self.configtable = {}
88 self.configtable = {}
89 self.configitem = registrar.configitem(self.configtable)
89 self.configitem = registrar.configitem(self.configtable)
90 self.filesetpredicate = registrar.filesetpredicate()
90 self.filesetpredicate = registrar.filesetpredicate()
91 self.revsetpredicate = registrar.revsetpredicate()
91 self.revsetpredicate = registrar.revsetpredicate()
92 self.templatekeyword = registrar.templatekeyword()
92 self.templatekeyword = registrar.templatekeyword()
93
93
94 def merge(self, other):
94 def merge(self, other):
95 self._uicallables.extend(other._uicallables)
95 self._uicallables.extend(other._uicallables)
96 self._uipopulatecallables.extend(other._uipopulatecallables)
96 self._uipopulatecallables.extend(other._uipopulatecallables)
97 self._extcallables.extend(other._extcallables)
97 self._extcallables.extend(other._extcallables)
98 self._repocallables.extend(other._repocallables)
98 self._repocallables.extend(other._repocallables)
99 self.filesetpredicate._table.update(other.filesetpredicate._table)
99 self.filesetpredicate._merge(other.filesetpredicate)
100 self.revsetpredicate._table.update(other.revsetpredicate._table)
100 self.revsetpredicate._merge(other.revsetpredicate)
101 self.templatekeyword._table.update(other.templatekeyword._table)
101 self.templatekeyword._merge(other.templatekeyword)
102 self._commandwrappers.extend(other._commandwrappers)
102 self._commandwrappers.extend(other._commandwrappers)
103 self._extcommandwrappers.extend(other._extcommandwrappers)
103 self._extcommandwrappers.extend(other._extcommandwrappers)
104 self._functionwrappers.extend(other._functionwrappers)
104 self._functionwrappers.extend(other._functionwrappers)
105 self._duckpunchers.extend(other._duckpunchers)
105 self._duckpunchers.extend(other._duckpunchers)
106 self.cmdtable.update(other.cmdtable)
106 self.cmdtable.update(other.cmdtable)
107 for section, items in other.configtable.iteritems():
107 for section, items in other.configtable.iteritems():
108 if section in self.configtable:
108 if section in self.configtable:
109 self.configtable[section].update(items)
109 self.configtable[section].update(items)
110 else:
110 else:
111 self.configtable[section] = items
111 self.configtable[section] = items
112
112
113 def finaluisetup(self, ui):
113 def finaluisetup(self, ui):
114 """Method to be used as the extension uisetup
114 """Method to be used as the extension uisetup
115
115
116 The following operations belong here:
116 The following operations belong here:
117
117
118 - Changes to ui.__class__ . The ui object that will be used to run the
118 - Changes to ui.__class__ . The ui object that will be used to run the
119 command has not yet been created. Changes made here will affect ui
119 command has not yet been created. Changes made here will affect ui
120 objects created after this, and in particular the ui that will be
120 objects created after this, and in particular the ui that will be
121 passed to runcommand
121 passed to runcommand
122 - Command wraps (extensions.wrapcommand)
122 - Command wraps (extensions.wrapcommand)
123 - Changes that need to be visible to other extensions: because
123 - Changes that need to be visible to other extensions: because
124 initialization occurs in phases (all extensions run uisetup, then all
124 initialization occurs in phases (all extensions run uisetup, then all
125 run extsetup), a change made here will be visible to other extensions
125 run extsetup), a change made here will be visible to other extensions
126 during extsetup
126 during extsetup
127 - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
127 - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
128 module members
128 module members
129 - Setup of pre-* and post-* hooks
129 - Setup of pre-* and post-* hooks
130 - pushkey setup
130 - pushkey setup
131 """
131 """
132 for cont, funcname, func in self._duckpunchers:
132 for cont, funcname, func in self._duckpunchers:
133 setattr(cont, funcname, func)
133 setattr(cont, funcname, func)
134 for command, wrapper, opts in self._commandwrappers:
134 for command, wrapper, opts in self._commandwrappers:
135 entry = extensions.wrapcommand(commands.table, command, wrapper)
135 entry = extensions.wrapcommand(commands.table, command, wrapper)
136 if opts:
136 if opts:
137 for opt in opts:
137 for opt in opts:
138 entry[1].append(opt)
138 entry[1].append(opt)
139 for cont, funcname, wrapper in self._functionwrappers:
139 for cont, funcname, wrapper in self._functionwrappers:
140 extensions.wrapfunction(cont, funcname, wrapper)
140 extensions.wrapfunction(cont, funcname, wrapper)
141 for c in self._uicallables:
141 for c in self._uicallables:
142 c(ui)
142 c(ui)
143
143
144 def finaluipopulate(self, ui):
144 def finaluipopulate(self, ui):
145 """Method to be used as the extension uipopulate
145 """Method to be used as the extension uipopulate
146
146
147 This is called once per ui instance to:
147 This is called once per ui instance to:
148
148
149 - Set up additional ui members
149 - Set up additional ui members
150 - Update configuration by ``ui.setconfig()``
150 - Update configuration by ``ui.setconfig()``
151 - Extend the class dynamically
151 - Extend the class dynamically
152 """
152 """
153 for c in self._uipopulatecallables:
153 for c in self._uipopulatecallables:
154 c(ui)
154 c(ui)
155
155
156 def finalextsetup(self, ui):
156 def finalextsetup(self, ui):
157 """Method to be used as a the extension extsetup
157 """Method to be used as a the extension extsetup
158
158
159 The following operations belong here:
159 The following operations belong here:
160
160
161 - Changes depending on the status of other extensions. (if
161 - Changes depending on the status of other extensions. (if
162 extensions.find('mq'))
162 extensions.find('mq'))
163 - Add a global option to all commands
163 - Add a global option to all commands
164 """
164 """
165 knownexts = {}
165 knownexts = {}
166
166
167 for ext, command, wrapper, opts in self._extcommandwrappers:
167 for ext, command, wrapper, opts in self._extcommandwrappers:
168 if ext not in knownexts:
168 if ext not in knownexts:
169 try:
169 try:
170 e = extensions.find(ext)
170 e = extensions.find(ext)
171 except KeyError:
171 except KeyError:
172 # Extension isn't enabled, so don't bother trying to wrap
172 # Extension isn't enabled, so don't bother trying to wrap
173 # it.
173 # it.
174 continue
174 continue
175 knownexts[ext] = e.cmdtable
175 knownexts[ext] = e.cmdtable
176 entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
176 entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
177 if opts:
177 if opts:
178 for opt in opts:
178 for opt in opts:
179 entry[1].append(opt)
179 entry[1].append(opt)
180
180
181 for c in self._extcallables:
181 for c in self._extcallables:
182 c(ui)
182 c(ui)
183
183
184 def finalreposetup(self, ui, repo):
184 def finalreposetup(self, ui, repo):
185 """Method to be used as the extension reposetup
185 """Method to be used as the extension reposetup
186
186
187 The following operations belong here:
187 The following operations belong here:
188
188
189 - All hooks but pre-* and post-*
189 - All hooks but pre-* and post-*
190 - Modify configuration variables
190 - Modify configuration variables
191 - Changes to repo.__class__, repo.dirstate.__class__
191 - Changes to repo.__class__, repo.dirstate.__class__
192 """
192 """
193 for c in self._repocallables:
193 for c in self._repocallables:
194 c(ui, repo)
194 c(ui, repo)
195
195
196 def uisetup(self, call):
196 def uisetup(self, call):
197 """Decorated function will be executed during uisetup
197 """Decorated function will be executed during uisetup
198
198
199 example::
199 example::
200
200
201 @eh.uisetup
201 @eh.uisetup
202 def setupbabar(ui):
202 def setupbabar(ui):
203 print 'this is uisetup!'
203 print 'this is uisetup!'
204 """
204 """
205 self._uicallables.append(call)
205 self._uicallables.append(call)
206 return call
206 return call
207
207
208 def uipopulate(self, call):
208 def uipopulate(self, call):
209 """Decorated function will be executed during uipopulate
209 """Decorated function will be executed during uipopulate
210
210
211 example::
211 example::
212
212
213 @eh.uipopulate
213 @eh.uipopulate
214 def setupfoo(ui):
214 def setupfoo(ui):
215 print 'this is uipopulate!'
215 print 'this is uipopulate!'
216 """
216 """
217 self._uipopulatecallables.append(call)
217 self._uipopulatecallables.append(call)
218 return call
218 return call
219
219
220 def extsetup(self, call):
220 def extsetup(self, call):
221 """Decorated function will be executed during extsetup
221 """Decorated function will be executed during extsetup
222
222
223 example::
223 example::
224
224
225 @eh.extsetup
225 @eh.extsetup
226 def setupcelestine(ui):
226 def setupcelestine(ui):
227 print 'this is extsetup!'
227 print 'this is extsetup!'
228 """
228 """
229 self._extcallables.append(call)
229 self._extcallables.append(call)
230 return call
230 return call
231
231
232 def reposetup(self, call):
232 def reposetup(self, call):
233 """Decorated function will be executed during reposetup
233 """Decorated function will be executed during reposetup
234
234
235 example::
235 example::
236
236
237 @eh.reposetup
237 @eh.reposetup
238 def setupzephir(ui, repo):
238 def setupzephir(ui, repo):
239 print 'this is reposetup!'
239 print 'this is reposetup!'
240 """
240 """
241 self._repocallables.append(call)
241 self._repocallables.append(call)
242 return call
242 return call
243
243
244 def wrapcommand(self, command, extension=None, opts=None):
244 def wrapcommand(self, command, extension=None, opts=None):
245 """Decorated function is a command wrapper
245 """Decorated function is a command wrapper
246
246
247 The name of the command must be given as the decorator argument.
247 The name of the command must be given as the decorator argument.
248 The wrapping is installed during `uisetup`.
248 The wrapping is installed during `uisetup`.
249
249
250 If the second option `extension` argument is provided, the wrapping
250 If the second option `extension` argument is provided, the wrapping
251 will be applied in the extension commandtable. This argument must be a
251 will be applied in the extension commandtable. This argument must be a
252 string that will be searched using `extension.find` if not found and
252 string that will be searched using `extension.find` if not found and
253 Abort error is raised. If the wrapping applies to an extension, it is
253 Abort error is raised. If the wrapping applies to an extension, it is
254 installed during `extsetup`.
254 installed during `extsetup`.
255
255
256 example::
256 example::
257
257
258 @eh.wrapcommand('summary')
258 @eh.wrapcommand('summary')
259 def wrapsummary(orig, ui, repo, *args, **kwargs):
259 def wrapsummary(orig, ui, repo, *args, **kwargs):
260 ui.note('Barry!')
260 ui.note('Barry!')
261 return orig(ui, repo, *args, **kwargs)
261 return orig(ui, repo, *args, **kwargs)
262
262
263 The `opts` argument allows specifying a list of tuples for additional
263 The `opts` argument allows specifying a list of tuples for additional
264 arguments for the command. See ``mercurial.fancyopts.fancyopts()`` for
264 arguments for the command. See ``mercurial.fancyopts.fancyopts()`` for
265 the format of the tuple.
265 the format of the tuple.
266
266
267 """
267 """
268 if opts is None:
268 if opts is None:
269 opts = []
269 opts = []
270 else:
270 else:
271 for opt in opts:
271 for opt in opts:
272 if not isinstance(opt, tuple):
272 if not isinstance(opt, tuple):
273 raise error.ProgrammingError('opts must be list of tuples')
273 raise error.ProgrammingError('opts must be list of tuples')
274 if len(opt) not in (4, 5):
274 if len(opt) not in (4, 5):
275 msg = 'each opt tuple must contain 4 or 5 values'
275 msg = 'each opt tuple must contain 4 or 5 values'
276 raise error.ProgrammingError(msg)
276 raise error.ProgrammingError(msg)
277
277
278 def dec(wrapper):
278 def dec(wrapper):
279 if extension is None:
279 if extension is None:
280 self._commandwrappers.append((command, wrapper, opts))
280 self._commandwrappers.append((command, wrapper, opts))
281 else:
281 else:
282 self._extcommandwrappers.append((extension, command, wrapper,
282 self._extcommandwrappers.append((extension, command, wrapper,
283 opts))
283 opts))
284 return wrapper
284 return wrapper
285 return dec
285 return dec
286
286
287 def wrapfunction(self, container, funcname):
287 def wrapfunction(self, container, funcname):
288 """Decorated function is a function wrapper
288 """Decorated function is a function wrapper
289
289
290 This function takes two arguments, the container and the name of the
290 This function takes two arguments, the container and the name of the
291 function to wrap. The wrapping is performed during `uisetup`.
291 function to wrap. The wrapping is performed during `uisetup`.
292 (there is no extension support)
292 (there is no extension support)
293
293
294 example::
294 example::
295
295
296 @eh.function(discovery, 'checkheads')
296 @eh.function(discovery, 'checkheads')
297 def wrapfunction(orig, *args, **kwargs):
297 def wrapfunction(orig, *args, **kwargs):
298 ui.note('His head smashed in and his heart cut out')
298 ui.note('His head smashed in and his heart cut out')
299 return orig(*args, **kwargs)
299 return orig(*args, **kwargs)
300 """
300 """
301 def dec(wrapper):
301 def dec(wrapper):
302 self._functionwrappers.append((container, funcname, wrapper))
302 self._functionwrappers.append((container, funcname, wrapper))
303 return wrapper
303 return wrapper
304 return dec
304 return dec
305
305
306 def addattr(self, container, funcname):
306 def addattr(self, container, funcname):
307 """Decorated function is to be added to the container
307 """Decorated function is to be added to the container
308
308
309 This function takes two arguments, the container and the name of the
309 This function takes two arguments, the container and the name of the
310 function to wrap. The wrapping is performed during `uisetup`.
310 function to wrap. The wrapping is performed during `uisetup`.
311
311
312 Adding attributes to a container like this is discouraged, because the
312 Adding attributes to a container like this is discouraged, because the
313 container modification is visible even in repositories that do not
313 container modification is visible even in repositories that do not
314 have the extension loaded. Therefore, care must be taken that the
314 have the extension loaded. Therefore, care must be taken that the
315 function doesn't make assumptions that the extension was loaded for the
315 function doesn't make assumptions that the extension was loaded for the
316 current repository. For `ui` and `repo` instances, a better option is
316 current repository. For `ui` and `repo` instances, a better option is
317 to subclass the instance in `uipopulate` and `reposetup` respectively.
317 to subclass the instance in `uipopulate` and `reposetup` respectively.
318
318
319 https://www.mercurial-scm.org/wiki/WritingExtensions
319 https://www.mercurial-scm.org/wiki/WritingExtensions
320
320
321 example::
321 example::
322
322
323 @eh.addattr(context.changectx, 'babar')
323 @eh.addattr(context.changectx, 'babar')
324 def babar(ctx):
324 def babar(ctx):
325 return 'babar' in ctx.description
325 return 'babar' in ctx.description
326 """
326 """
327 def dec(func):
327 def dec(func):
328 self._duckpunchers.append((container, funcname, func))
328 self._duckpunchers.append((container, funcname, func))
329 return func
329 return func
330 return dec
330 return dec
General Comments 0
You need to be logged in to leave comments. Login now