Patchwork [1,of,3] registrar: add templatekeyword to mark a function as template keyword (API)

login
register
mail settings
Submitter Katsunori FUJIWARA
Date March 12, 2016, 8:20 p.m.
Message ID <dec6fedbede5d40088f1.1457814045@feefifofum>
Download mbox | patch
Permalink /patch/13830/
State Accepted
Delegated to: Yuya Nishihara
Headers show

Comments

Katsunori FUJIWARA - March 12, 2016, 8:20 p.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1457813826 -32400
#      Sun Mar 13 05:17:06 2016 +0900
# Node ID dec6fedbede5d40088f18057f49ae5c4ebd634ef
# Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
registrar: add templatekeyword to mark a function as template keyword (API)

_templateregistrarbase is defined as a super class of templatekeyword,
for ease of adding template common features between "keyword",
"filter" and "function".

This patch also adds loadkeyword() to templatekw, because this
combination helps to figure out how they cooperate with each other.

Listing up loadkeyword() in dispatch.extraloaders causes implicit
loading template keyword functions at loading (3rd party) extension.

This change requires that "templatekeyword" attribute of (3rd party)
extension is registrar.templatekeyword or so.
Sean Farley - March 12, 2016, 8:48 p.m.
FUJIWARA Katsunori <foozy@lares.dti.ne.jp> writes:

> # HG changeset patch
> # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> # Date 1457813826 -32400
> #      Sun Mar 13 05:17:06 2016 +0900
> # Node ID dec6fedbede5d40088f18057f49ae5c4ebd634ef
> # Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
> registrar: add templatekeyword to mark a function as template keyword (API)
>
> _templateregistrarbase is defined as a super class of templatekeyword,
> for ease of adding template common features between "keyword",
> "filter" and "function".
>
> This patch also adds loadkeyword() to templatekw, because this
> combination helps to figure out how they cooperate with each other.
>
> Listing up loadkeyword() in dispatch.extraloaders causes implicit
> loading template keyword functions at loading (3rd party) extension.
>
> This change requires that "templatekeyword" attribute of (3rd party)
> extension is registrar.templatekeyword or so.

A few things are a little unclear to me:

- these patches are needed so that template keywords in 3rd party
  extensions aren't implicitly loaded? How is that bad?

- how will extensions support both 3.7 and 3.8 with your changes?

- how can an extension dynamically create a template keyword with your
  changes?
Katsunori FUJIWARA - March 13, 2016, 1:57 p.m.
At Sat, 12 Mar 2016 12:48:36 -0800,
Sean Farley wrote:
> 
> 
> FUJIWARA Katsunori <foozy@lares.dti.ne.jp> writes:
> 
> > # HG changeset patch
> > # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> > # Date 1457813826 -32400
> > #      Sun Mar 13 05:17:06 2016 +0900
> > # Node ID dec6fedbede5d40088f18057f49ae5c4ebd634ef
> > # Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
> > registrar: add templatekeyword to mark a function as template keyword (API)
> >
> > _templateregistrarbase is defined as a super class of templatekeyword,
> > for ease of adding template common features between "keyword",
> > "filter" and "function".
> >
> > This patch also adds loadkeyword() to templatekw, because this
> > combination helps to figure out how they cooperate with each other.
> >
> > Listing up loadkeyword() in dispatch.extraloaders causes implicit
> > loading template keyword functions at loading (3rd party) extension.
> >
> > This change requires that "templatekeyword" attribute of (3rd party)
> > extension is registrar.templatekeyword or so.
> 
> A few things are a little unclear to me:
> 
> - these patches are needed so that template keywords in 3rd party
>   extensions aren't implicitly loaded? How is that bad?

Main purpose of this (, previous and subsequent) series is increasing
maintainability of function registration in Mercurial source. For
example, usage of annotation can:

  - localize "function definition" and "putting it into table"

  - hide internal implementation of optional attributes
    (e.g. "safe" of revset, "callstatus" and "callexisting" of fileset)

Loading template keyword function from 3rd party extension implicitly
is a side effect.


> - how will extensions support both 3.7 and 3.8 with your changes?

It might be like as below:

    # code for portability >>>>
    try:
        from mercurial import registrar
        if not util.safehasattr(registrar, 'templatekeyword'):
            raise ImportError()

        templatekeyword = registrar.templatekeyword()
        loadkeyword = lambda registrarobj: None # nop
    except ImportError:
        # registrar.templatekeyword isn't available = loading by old hg

        # minimum copy from registrar._funcregistrarbase
        class _funcregistrarbase(object):
            ....

        templatekeyword = _funcregistrarbase()
        templatekeyword._docformat = ":%s: %s"

        # minimum copy from templatekw.loadkeyword
        def loadkeyword(registrarobj):
            from mercurial import templatekw
            for name, func in registrarobj._table.iteritems():
                templatekw.keywords[name] = func
    # <<<< code for portability


    # use templatekeyword to mark function as template keyword
    @templatekeywrd('xxxx')
    def xxxx(...):
        """Explanation of keyword xxxx
        """

        :
        :


    def extsetup(ui):
        # load keyword function, if registrar.templatekeyword not available
        loadkeyword(templatekeyword)


I'm planning to write detail of this into WritingExtensions wiki page,
and to provide an utility module as a part of "a skeleton extension"
proposed as issue4677 by Matt.

    https://bz.mercurial-scm.org/show_bug.cgi?id=4677


> - how can an extension dynamically create a template keyword with your
>   changes?

It can dynamically create template keyword by code path like as below:

    dynpredicate = registrar.templatekeyword()

    # mark function as template keyword by dynpredicate
    dynpredicate("name_of_keyword")(keyword_func)
        :
        :

    # register marked functions as template keyword
    templatekw.loadkeyword(ui, 'name_of_extension', dynpredicate)


----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp
Yuya Nishihara - March 13, 2016, 3:40 p.m.
On Sun, 13 Mar 2016 22:57:56 +0900, FUJIWARA Katsunori wrote:
>     # code for portability >>>>
>     try:
>         from mercurial import registrar
>         if not util.safehasattr(registrar, 'templatekeyword'):
>             raise ImportError()
> 
>         templatekeyword = registrar.templatekeyword()
>         loadkeyword = lambda registrarobj: None # nop
>     except ImportError:
>         # registrar.templatekeyword isn't available = loading by old hg
> 
>         # minimum copy from registrar._funcregistrarbase
>         class _funcregistrarbase(object):
>             ....
> 
>         templatekeyword = _funcregistrarbase()
>         templatekeyword._docformat = ":%s: %s"
> 
>         # minimum copy from templatekw.loadkeyword
>         def loadkeyword(registrarobj):
>             from mercurial import templatekw
>             for name, func in registrarobj._table.iteritems():
>                 templatekw.keywords[name] = func
>     # <<<< code for portability

or simply use noop decorator:

    _registrarloaded = False
    try:
        from mercurial import registrar
        templatekeyword = registrar.templatekeyword()
        _registrarloaded = True
    except (ImportError, AttributeError):
        def templatekeyword(decl):
            return lambda func: func

    def extsetup(ui):
        if not _registrarloaded:
            templatekw.keywords['foo'] = kwfoo
Sean Farley - March 13, 2016, 10:03 p.m.
FUJIWARA Katsunori <foozy@lares.dti.ne.jp> writes:

> At Sat, 12 Mar 2016 12:48:36 -0800,
> Sean Farley wrote:
>> 
>> 
>> FUJIWARA Katsunori <foozy@lares.dti.ne.jp> writes:
>> 
>> > # HG changeset patch
>> > # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
>> > # Date 1457813826 -32400
>> > #      Sun Mar 13 05:17:06 2016 +0900
>> > # Node ID dec6fedbede5d40088f18057f49ae5c4ebd634ef
>> > # Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
>> > registrar: add templatekeyword to mark a function as template keyword (API)
>> >
>> > _templateregistrarbase is defined as a super class of templatekeyword,
>> > for ease of adding template common features between "keyword",
>> > "filter" and "function".
>> >
>> > This patch also adds loadkeyword() to templatekw, because this
>> > combination helps to figure out how they cooperate with each other.
>> >
>> > Listing up loadkeyword() in dispatch.extraloaders causes implicit
>> > loading template keyword functions at loading (3rd party) extension.
>> >
>> > This change requires that "templatekeyword" attribute of (3rd party)
>> > extension is registrar.templatekeyword or so.
>> 
>> A few things are a little unclear to me:
>> 
>> - these patches are needed so that template keywords in 3rd party
>>   extensions aren't implicitly loaded? How is that bad?
>
> Main purpose of this (, previous and subsequent) series is increasing
> maintainability of function registration in Mercurial source. For
> example, usage of annotation can:
>
>   - localize "function definition" and "putting it into table"
>
>   - hide internal implementation of optional attributes
>     (e.g. "safe" of revset, "callstatus" and "callexisting" of fileset)
>
> Loading template keyword function from 3rd party extension implicitly
> is a side effect.
>
>
>> - how will extensions support both 3.7 and 3.8 with your changes?
>
> It might be like as below:
>
>     # code for portability >>>>
>     try:
>         from mercurial import registrar
>         if not util.safehasattr(registrar, 'templatekeyword'):
>             raise ImportError()
>
>         templatekeyword = registrar.templatekeyword()
>         loadkeyword = lambda registrarobj: None # nop
>     except ImportError:
>         # registrar.templatekeyword isn't available = loading by old hg
>
>         # minimum copy from registrar._funcregistrarbase
>         class _funcregistrarbase(object):
>             ....
>
>         templatekeyword = _funcregistrarbase()
>         templatekeyword._docformat = ":%s: %s"
>
>         # minimum copy from templatekw.loadkeyword
>         def loadkeyword(registrarobj):
>             from mercurial import templatekw
>             for name, func in registrarobj._table.iteritems():
>                 templatekw.keywords[name] = func
>     # <<<< code for portability
>
>
>     # use templatekeyword to mark function as template keyword
>     @templatekeywrd('xxxx')
>     def xxxx(...):
>         """Explanation of keyword xxxx
>         """
>
>         :
>         :
>
>
>     def extsetup(ui):
>         # load keyword function, if registrar.templatekeyword not available
>         loadkeyword(templatekeyword)
>
>
> I'm planning to write detail of this into WritingExtensions wiki page,
> and to provide an utility module as a part of "a skeleton extension"
> proposed as issue4677 by Matt.
>
>     https://bz.mercurial-scm.org/show_bug.cgi?id=4677
>
>
>> - how can an extension dynamically create a template keyword with your
>>   changes?
>
> It can dynamically create template keyword by code path like as below:
>
>     dynpredicate = registrar.templatekeyword()
>
>     # mark function as template keyword by dynpredicate
>     dynpredicate("name_of_keyword")(keyword_func)
>         :
>         :
>
>     # register marked functions as template keyword
>     templatekw.loadkeyword(ui, 'name_of_extension', dynpredicate)

Thanks for the explanation! I like this series now :-)
Yuya Nishihara - March 16, 2016, 3:34 p.m.
On Sun, 13 Mar 2016 05:20:45 +0900, FUJIWARA Katsunori wrote:
> # HG changeset patch
> # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> # Date 1457813826 -32400
> #      Sun Mar 13 05:17:06 2016 +0900
> # Node ID dec6fedbede5d40088f18057f49ae5c4ebd634ef
> # Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
> registrar: add templatekeyword to mark a function as template keyword (API)

Pushed the series to the clowncopter, thanks.

Patch

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -35,6 +35,7 @@  from . import (
     hg,
     hook,
     revset,
+    templatekw,
     ui as uimod,
     util,
 )
@@ -756,6 +757,7 @@  extraloaders = [
     ('cmdtable', commands, 'loadcmdtable'),
     ('filesetpredicate', fileset, 'loadpredicate'),
     ('revsetpredicate', revset, 'loadpredicate'),
+    ('templatekeyword', templatekw, 'loadkeyword'),
 ]
 
 def _dispatch(req):
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -161,3 +161,33 @@  class filesetpredicate(_funcregistrarbas
     def _extrasetup(self, name, func, callstatus=False, callexisting=False):
         func._callstatus = callstatus
         func._callexisting = callexisting
+
+class _templateregistrarbase(_funcregistrarbase):
+    """Base of decorator to register functions as template specific one
+    """
+    _docformat = ":%s: %s"
+
+class templatekeyword(_templateregistrarbase):
+    """Decorator to register template keyword
+
+    Usage::
+
+        templaetkeyword = registrar.templatekeyword()
+
+        @templatekeyword('mykeyword')
+        def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
+            '''Explanation of this template keyword ....
+            '''
+            pass
+
+    The first string argument is used also in online help.
+
+    'templatekeyword' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'templatekeyword' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'templatekw.loadkeyword()' is needed.
+    """
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -574,5 +574,11 @@  keywords = {
     'tags': showtags,
 }
 
+def loadkeyword(ui, extname, registrarobj):
+    """Load template keyword from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        keywords[name] = func
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()