[PATCH 1 of 5] registrar: add base classes to wrap functions, commands, and so on (API)

FUJIWARA Katsunori foozy at lares.dti.ne.jp
Wed Aug 22 02:21:52 UTC 2018


# HG changeset patch
# User FUJIWARA Katsunori <foozy at lares.dti.ne.jp>
# Date 1534511513 -32400
#      Fri Aug 17 22:11:53 2018 +0900
# Node ID f52f112594f5d27067446280a685fba3a9053a13
# Parent  7a759ad2d06dcf6548a8cc19fcd773eaa943f7ac
# Available At https://bitbucket.org/foozy/mercurial-wip
#              hg pull https://bitbucket.org/foozy/mercurial-wip -r f52f112594f5
# EXP-Topic use-decorator-for-webcommand
registrar: add base classes to wrap functions, commands, and so on (API)

This patch adds base classes to wrap functions, commands, and so on,
and adds functions to execute wrapping procedures. The latter is
useful to understand how added base classes are processed.

This change requires that 'hgwrapperlist' attribute of (3rd party)
extension is a list of registrar._wrapentrybase instances. This is
reason why this patch is marked with "(API)".

This patch chooses 'hgwrapperlist' instead of 'wrapperlist', because
the latter seems too generic to treat it as reserved.

  .. api::

     attribute name 'hgwrapperlist' of an extension is reserved to
     load wrapping automatically

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -231,6 +231,38 @@ def _runextsetup(name, ui):
             return False
     return True
 
+def loadwrapping(wrapperlist):
+    """Execute wrapping procedures registered in wrapperlist
+    """
+    for wrapentry in wrapperlist:
+        extmod = None
+        if wrapentry.extname:
+            if wrapentry.extname not in _extensions:
+                # target extension is not loaded
+                continue
+            extmod = _extensions[wrapentry.extname]
+
+        container = wrapentry.getcontainer(extmod)
+        name = wrapentry.name
+        wrapper = wrapentry.wrapper
+
+        raise NotImplemented(wrapentry.target)
+
+def _runwrapping(name, ui):
+    """Execute wrapping procedures in 'name' extension
+    """
+    wrapperlist = getattr(_extensions[name], 'hgwrapperlist', None)
+    if wrapperlist:
+        try:
+            loadwrapping(wrapperlist)
+        except Exception as inst:
+            ui.traceback(force=True)
+            msg = stringutil.forcebytestr(inst)
+            ui.warn(_("*** failed to wrap functions by extension %s: %s\n")
+                    % (name, msg))
+            return False
+    return True
+
 def loadall(ui, whitelist=None):
     if ui.configbool('devel', 'debug.extensions'):
         log = lambda msg, *values: ui.debug('debug.extensions: ',
@@ -303,6 +335,18 @@ def loadall(ui, whitelist=None):
                 broken.add(name)
         log('  > extsetup for %r took %s\n', name, stats)
 
+    log('- executing wrapping functions\n')
+    for name in _order[newindex:]:
+        if name in broken:
+            continue
+        log('  - running wrapping functions for %r\n', name)
+        with util.timedcm() as stats:
+            if not _runwrapping(name, ui):
+                log('    - wrapping functions for the %r extension failed\n',
+                    name)
+                broken.add(name)
+        log('  > wrapping functions for %r took %s\n', name, stats)
+
     for name in broken:
         log('    - disabling broken %r extension\n', name)
         _extensions[name] = None
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -103,6 +103,83 @@ class _funcregistrarbase(object):
         """Execute exra setup for registered function, if needed
         """
 
+class _wrapentrybase(object):
+    """Base of the object to encapsulate information for wrapping
+
+    Targets of wrapping are:
+
+    - a normal function
+    - a filecache property of a class
+    - a command function in a command table
+    - a function in a dispatch table (e.g. fileset, revset, and so on)
+
+    These exist in:
+
+    - core modules of Mercurial
+    - extensions loaded at runtime
+    """
+    target = None
+
+    def __init__(self, name, extname):
+        self.name = name
+        self.extname = extname
+
+    def __call__(self, wrapper):
+        self.wrapper = wrapper
+        return wrapper
+
+    def getcontainer(self, extmod):
+        """Get a container of the function to be wrapped
+
+        If `self.extname` is not None, `extmod` should be the
+        extension module object corresponded to it.
+        """
+        raise NotImplemented()
+
+    def extrasetup(self, funcentry, wrap):
+        """Do extra setup for wrapping
+
+        `funcentry` is the original object, which is looked up with
+        `self.name` from the object returned by `getcontainer()`.
+        Usually, it is a function to be wrapped itself (aka "origfn").
+
+        `wrap` is the result of `bind(wrapper, origfn)` or so.
+
+        Typically, this method is used to copy some specific
+        information from `funcentry` to `wrap`. For example, `norepo`,
+        `optionalrepo`, and such attributes of original "command"
+        function should be visible via `wrap` at
+        commands.loadcmdtable().
+
+        The result of this method is stored into the object returned
+        by `getcontainer()` with `self.name`.
+
+        """
+        return wrap
+
+    @staticmethod
+    def lookup(basecontainer, path):
+        components = []
+        if path:
+            components.extend(path.split('.'))
+        container = basecontainer
+        while components:
+            container = getattr(container, components.pop(0))
+        return container
+
+class _wrapdecoratorbase(object):
+    """Base of decorator to store wrapping information into the list
+    """
+    _entrycls = None
+
+    def __init__(self, entrylist):
+        self.entrylist = entrylist
+
+    def __call__(self, name, *args, **kwargs):
+        entry = self._entrycls(name, *args, **kwargs)
+        self.entrylist.append(entry)
+        return entry
+
 class command(_funcregistrarbase):
     """Decorator to register a command function to table
 
diff --git a/tests/test-bad-extension.t b/tests/test-bad-extension.t
--- a/tests/test-bad-extension.t
+++ b/tests/test-bad-extension.t
@@ -116,6 +116,11 @@ show traceback for ImportError of hgext.
   debug.extensions:   > extsetup for 'gpg' took * (glob)
   debug.extensions:   - running extsetup for 'baddocext'
   debug.extensions:   > extsetup for 'baddocext' took * (glob)
+  debug.extensions: - executing wrapping functions
+  debug.extensions:   - running wrapping functions for 'gpg'
+  debug.extensions:   > wrapping functions for 'gpg' took * us (glob)
+  debug.extensions:   - running wrapping functions for 'baddocext'
+  debug.extensions:   > wrapping functions for 'baddocext' took * us (glob)
   debug.extensions: - executing remaining aftercallbacks
   debug.extensions: > remaining aftercallbacks completed in * (glob)
   debug.extensions: - loading extension registration objects


More information about the Mercurial-devel mailing list