Patchwork [3,of,5] registrar: add decorator class to register a function as web command (API)

login
register
mail settings
Submitter Katsunori FUJIWARA
Date Aug. 22, 2018, 2:21 a.m.
Message ID <15909a34b98c7d07d7c7.1534904514@blacknile>
Download mbox | patch
Permalink /patch/33954/
State New
Headers show

Comments

Katsunori FUJIWARA - Aug. 22, 2018, 2:21 a.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1534511486 -32400
#      Fri Aug 17 22:11:26 2018 +0900
# Node ID 15909a34b98c7d07d7c7fe1eb2fd4a466a7f41e8
# Parent  eafbb78561cfd9d299f72fca659bf4747c4808c6
# Available At https://bitbucket.org/foozy/mercurial-wip
#              hg pull https://bitbucket.org/foozy/mercurial-wip -r 15909a34b98c
# EXP-Topic use-decorator-for-webcommand
registrar: add decorator class to register a function as web command (API)

Using decorator can localize changes for adding (or removing) a web
command function in source code.

This patch also makes extensions._loadextra() directly invoke
loadermod if name of loader function is not specified, in order to
delay importing webcommands, because web server feature is not used in
almost all usecases of 'hg' command.

This change requires that 'webcommand' attribute of (3rd party)
extension is registrar.webcommand or so. This is reason why this patch
is marked with "(API)".

  .. api::

     attribute name 'webcommand' of an extension is reserved to
     load webcommand automatically
Yuya Nishihara - Aug. 23, 2018, 12:27 p.m.
On Wed, 22 Aug 2018 11:21:54 +0900, FUJIWARA Katsunori wrote:
> +    # delay importing webcommands, because it implies evaluation of
> +    # hgweb/__init__.py, even though web server feature is not used in
> +    # almost all client usecases
> +    def loadwebcommand(ui, extname, registrarobj):
> +        from .hgweb import webcommands
> +        webcommands.loadcommand(ui, extname, registrarobj)

Nit: if it's really expensive to import hgweb, we'll probably need to
move the slow parts out of hgweb/__init__.py.
Katsunori FUJIWARA - Aug. 24, 2018, 10:56 a.m.
At Thu, 23 Aug 2018 21:27:40 +0900,
Yuya Nishihara wrote:
> 
> On Wed, 22 Aug 2018 11:21:54 +0900, FUJIWARA Katsunori wrote:
> > +    # delay importing webcommands, because it implies evaluation of
> > +    # hgweb/__init__.py, even though web server feature is not used in
> > +    # almost all client usecases
> > +    def loadwebcommand(ui, extname, registrarobj):
> > +        from .hgweb import webcommands
> > +        webcommands.loadcommand(ui, extname, registrarobj)
> 
> Nit: if it's really expensive to import hgweb, we'll probably need to
> move the slow parts out of hgweb/__init__.py.
> 

hgweb/__init__.py has only some import statements, and a few function
definitions. There is no access to property of imported modules in
global scope.

Therefore, importing it is not so expensive, maybe. But on the other
hand, "hgweb" functionality is not needed in many (or almost all)
usecases of "hg" command CUI, obviously.

If I was too afraid of adding new "import", OK, I'll treat importing
hgweb module as same as others in extensions.py.

Patch

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -389,6 +389,13 @@  def loadall(ui, whitelist=None):
         templatekw,
     )
 
+    # delay importing webcommands, because it implies evaluation of
+    # hgweb/__init__.py, even though web server feature is not used in
+    # almost all client usecases
+    def loadwebcommand(ui, extname, registrarobj):
+        from .hgweb import webcommands
+        webcommands.loadcommand(ui, extname, registrarobj)
+
     # list of (objname, loadermod, loadername) tuple:
     # - objname is the name of an object in extension module,
     #   from which extra information is loaded
@@ -405,6 +412,7 @@  def loadall(ui, whitelist=None):
         ('templatefilter', templatefilters, 'loadfilter'),
         ('templatefunc', templatefuncs, 'loadfunction'),
         ('templatekeyword', templatekw, 'loadkeyword'),
+        ('webcommand', loadwebcommand, None),
     ]
     with util.timedcm() as stats:
         _loadextra(ui, newindex, extraloaders)
@@ -420,7 +428,11 @@  def _loadextra(ui, newindex, extraloader
         for objname, loadermod, loadername in extraloaders:
             extraobj = getattr(module, objname, None)
             if extraobj is not None:
-                getattr(loadermod, loadername)(ui, name, extraobj)
+                if loadername:
+                    loader = getattr(loadermod, loadername)
+                else:
+                    loader = loadermod
+                loader(ui, name, extraobj)
 
 def afterloaded(extension, callback):
     '''Run the specified function after a named extension is loaded.
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -1474,5 +1474,11 @@  def help(web):
         topic=topicname,
         doc=doc)
 
+def loadcommand(ui, extname, registrarobj):
+    """Load web command from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        commands[name] = func
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = commands.values()
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -604,3 +604,64 @@  class internalmerge(_funcregistrarbase):
 
         # actual capabilities, which this internal merge tool has
         func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
+
+class webcommand(_funcregistrarbase):
+    """Decorator to register web command
+
+    Usage::
+
+        webcommand = registrar.webcommand()
+
+        @webcommand('/cmd[/additional/path]')
+        def mycommand(web, req, tmpl):
+            '''Explanation of this web command ....
+            '''
+            pass
+
+    The first string argument is used also in online help, if it
+    matches against '/(\w+)' regexp pattern.
+
+    In this case, this string argument and subsequent section title
+    marker in RST syntax are placed at the beginning of doc
+    string. For example, sample code above causes doc string below::
+
+        /cmd[/additional/path]
+        ----------------------
+
+        Explanation of this web command ....
+
+    Specify just a name of your web command to this decorator, and
+    write full doc string of webcommand function manually, if you want
+    to register web command with a path not matching against the
+    regexp above (e.g. including '-' or other non alpha numeric
+    characters).
+
+    'webcommand' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'webcommand' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'webcommands.loadcommand()' is needed.
+    """
+    # this regexp is enough for Mercurial itself
+    _urlpat = util.re.compile(r'/(\w+)')
+
+    def _getname(self, decl):
+        matched = self._urlpat.match(decl)
+        if matched:
+            return matched.group(1)
+        else:
+            return decl
+
+    def _formatdoc(self, decl, doc):
+        matched = self._urlpat.match(decl)
+        if matched:
+            return """
+    %s
+    %s
+
+    %s""" % (decl, '-' * len(decl), doc)
+
+        return doc