mercurial/templatekw.py
changeset 36921 32f9b7e3f056
parent 36634 cafd0586876b
child 37019 c97b936d8bb5
equal deleted inserted replaced
36920:6ff6e1d6b5b8 36921:32f9b7e3f056
    21     obsutil,
    21     obsutil,
    22     patch,
    22     patch,
    23     pycompat,
    23     pycompat,
    24     registrar,
    24     registrar,
    25     scmutil,
    25     scmutil,
       
    26     templateutil,
    26     util,
    27     util,
    27 )
    28 )
    28 
    29 
    29 class _hybrid(object):
    30 _hybrid = templateutil.hybrid
    30     """Wrapper for list or dict to support legacy template
    31 _mappable = templateutil.mappable
    31 
    32 _showlist = templateutil._showlist
    32     This class allows us to handle both:
    33 hybriddict = templateutil.hybriddict
    33     - "{files}" (legacy command-line-specific list hack) and
    34 hybridlist = templateutil.hybridlist
    34     - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
    35 compatdict = templateutil.compatdict
    35     and to access raw values:
    36 compatlist = templateutil.compatlist
    36     - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
       
    37     - "{get(extras, key)}"
       
    38     - "{files|json}"
       
    39     """
       
    40 
       
    41     def __init__(self, gen, values, makemap, joinfmt, keytype=None):
       
    42         if gen is not None:
       
    43             self.gen = gen  # generator or function returning generator
       
    44         self._values = values
       
    45         self._makemap = makemap
       
    46         self.joinfmt = joinfmt
       
    47         self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
       
    48     def gen(self):
       
    49         """Default generator to stringify this as {join(self, ' ')}"""
       
    50         for i, x in enumerate(self._values):
       
    51             if i > 0:
       
    52                 yield ' '
       
    53             yield self.joinfmt(x)
       
    54     def itermaps(self):
       
    55         makemap = self._makemap
       
    56         for x in self._values:
       
    57             yield makemap(x)
       
    58     def __contains__(self, x):
       
    59         return x in self._values
       
    60     def __getitem__(self, key):
       
    61         return self._values[key]
       
    62     def __len__(self):
       
    63         return len(self._values)
       
    64     def __iter__(self):
       
    65         return iter(self._values)
       
    66     def __getattr__(self, name):
       
    67         if name not in (r'get', r'items', r'iteritems', r'iterkeys',
       
    68                         r'itervalues', r'keys', r'values'):
       
    69             raise AttributeError(name)
       
    70         return getattr(self._values, name)
       
    71 
       
    72 class _mappable(object):
       
    73     """Wrapper for non-list/dict object to support map operation
       
    74 
       
    75     This class allows us to handle both:
       
    76     - "{manifest}"
       
    77     - "{manifest % '{rev}:{node}'}"
       
    78     - "{manifest.rev}"
       
    79 
       
    80     Unlike a _hybrid, this does not simulate the behavior of the underling
       
    81     value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
       
    82     """
       
    83 
       
    84     def __init__(self, gen, key, value, makemap):
       
    85         if gen is not None:
       
    86             self.gen = gen  # generator or function returning generator
       
    87         self._key = key
       
    88         self._value = value  # may be generator of strings
       
    89         self._makemap = makemap
       
    90 
       
    91     def gen(self):
       
    92         yield pycompat.bytestr(self._value)
       
    93 
       
    94     def tomap(self):
       
    95         return self._makemap(self._key)
       
    96 
       
    97     def itermaps(self):
       
    98         yield self.tomap()
       
    99 
       
   100 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
       
   101     """Wrap data to support both dict-like and string-like operations"""
       
   102     prefmt = pycompat.identity
       
   103     if fmt is None:
       
   104         fmt = '%s=%s'
       
   105         prefmt = pycompat.bytestr
       
   106     return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
       
   107                    lambda k: fmt % (prefmt(k), prefmt(data[k])))
       
   108 
       
   109 def hybridlist(data, name, fmt=None, gen=None):
       
   110     """Wrap data to support both list-like and string-like operations"""
       
   111     prefmt = pycompat.identity
       
   112     if fmt is None:
       
   113         fmt = '%s'
       
   114         prefmt = pycompat.bytestr
       
   115     return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
       
   116 
       
   117 def unwraphybrid(thing):
       
   118     """Return an object which can be stringified possibly by using a legacy
       
   119     template"""
       
   120     gen = getattr(thing, 'gen', None)
       
   121     if gen is None:
       
   122         return thing
       
   123     if callable(gen):
       
   124         return gen()
       
   125     return gen
       
   126 
       
   127 def unwrapvalue(thing):
       
   128     """Move the inner value object out of the wrapper"""
       
   129     if not util.safehasattr(thing, '_value'):
       
   130         return thing
       
   131     return thing._value
       
   132 
       
   133 def wraphybridvalue(container, key, value):
       
   134     """Wrap an element of hybrid container to be mappable
       
   135 
       
   136     The key is passed to the makemap function of the given container, which
       
   137     should be an item generated by iter(container).
       
   138     """
       
   139     makemap = getattr(container, '_makemap', None)
       
   140     if makemap is None:
       
   141         return value
       
   142     if util.safehasattr(value, '_makemap'):
       
   143         # a nested hybrid list/dict, which has its own way of map operation
       
   144         return value
       
   145     return _mappable(None, key, value, makemap)
       
   146 
       
   147 def compatdict(context, mapping, name, data, key='key', value='value',
       
   148                fmt=None, plural=None, separator=' '):
       
   149     """Wrap data like hybriddict(), but also supports old-style list template
       
   150 
       
   151     This exists for backward compatibility with the old-style template. Use
       
   152     hybriddict() for new template keywords.
       
   153     """
       
   154     c = [{key: k, value: v} for k, v in data.iteritems()]
       
   155     t = context.resource(mapping, 'templ')
       
   156     f = _showlist(name, c, t, mapping, plural, separator)
       
   157     return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
       
   158 
       
   159 def compatlist(context, mapping, name, data, element=None, fmt=None,
       
   160                plural=None, separator=' '):
       
   161     """Wrap data like hybridlist(), but also supports old-style list template
       
   162 
       
   163     This exists for backward compatibility with the old-style template. Use
       
   164     hybridlist() for new template keywords.
       
   165     """
       
   166     t = context.resource(mapping, 'templ')
       
   167     f = _showlist(name, data, t, mapping, plural, separator)
       
   168     return hybridlist(data, name=element or name, fmt=fmt, gen=f)
       
   169 
    37 
   170 def showdict(name, data, mapping, plural=None, key='key', value='value',
    38 def showdict(name, data, mapping, plural=None, key='key', value='value',
   171              fmt=None, separator=' '):
    39              fmt=None, separator=' '):
   172     ui = mapping.get('ui')
    40     ui = mapping.get('ui')
   173     if ui:
    41     if ui:
   174         ui.deprecwarn("templatekw.showdict() is deprecated, use compatdict()",
    42         ui.deprecwarn("templatekw.showdict() is deprecated, use "
   175                       '4.6')
    43                       "templateutil.compatdict()", '4.6')
   176     c = [{key: k, value: v} for k, v in data.iteritems()]
    44     c = [{key: k, value: v} for k, v in data.iteritems()]
   177     f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
    45     f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
   178     return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
    46     return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
   179 
    47 
   180 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
    48 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
   181     ui = mapping.get('ui')
    49     ui = mapping.get('ui')
   182     if ui:
    50     if ui:
   183         ui.deprecwarn("templatekw.showlist() is deprecated, use compatlist()",
    51         ui.deprecwarn("templatekw.showlist() is deprecated, use "
   184                       '4.6')
    52                       "templateutil.compatlist()", '4.6')
   185     if not element:
    53     if not element:
   186         element = name
    54         element = name
   187     f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
    55     f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
   188     return hybridlist(values, name=element, gen=f)
    56     return hybridlist(values, name=element, gen=f)
   189 
       
   190 def _showlist(name, values, templ, mapping, plural=None, separator=' '):
       
   191     '''expand set of values.
       
   192     name is name of key in template map.
       
   193     values is list of strings or dicts.
       
   194     plural is plural of name, if not simply name + 's'.
       
   195     separator is used to join values as a string
       
   196 
       
   197     expansion works like this, given name 'foo'.
       
   198 
       
   199     if values is empty, expand 'no_foos'.
       
   200 
       
   201     if 'foo' not in template map, return values as a string,
       
   202     joined by 'separator'.
       
   203 
       
   204     expand 'start_foos'.
       
   205 
       
   206     for each value, expand 'foo'. if 'last_foo' in template
       
   207     map, expand it instead of 'foo' for last key.
       
   208 
       
   209     expand 'end_foos'.
       
   210     '''
       
   211     strmapping = pycompat.strkwargs(mapping)
       
   212     if not plural:
       
   213         plural = name + 's'
       
   214     if not values:
       
   215         noname = 'no_' + plural
       
   216         if noname in templ:
       
   217             yield templ(noname, **strmapping)
       
   218         return
       
   219     if name not in templ:
       
   220         if isinstance(values[0], bytes):
       
   221             yield separator.join(values)
       
   222         else:
       
   223             for v in values:
       
   224                 r = dict(v)
       
   225                 r.update(mapping)
       
   226                 yield r
       
   227         return
       
   228     startname = 'start_' + plural
       
   229     if startname in templ:
       
   230         yield templ(startname, **strmapping)
       
   231     vmapping = mapping.copy()
       
   232     def one(v, tag=name):
       
   233         try:
       
   234             vmapping.update(v)
       
   235         # Python 2 raises ValueError if the type of v is wrong. Python
       
   236         # 3 raises TypeError.
       
   237         except (AttributeError, TypeError, ValueError):
       
   238             try:
       
   239                 # Python 2 raises ValueError trying to destructure an e.g.
       
   240                 # bytes. Python 3 raises TypeError.
       
   241                 for a, b in v:
       
   242                     vmapping[a] = b
       
   243             except (TypeError, ValueError):
       
   244                 vmapping[name] = v
       
   245         return templ(tag, **pycompat.strkwargs(vmapping))
       
   246     lastname = 'last_' + name
       
   247     if lastname in templ:
       
   248         last = values.pop()
       
   249     else:
       
   250         last = None
       
   251     for v in values:
       
   252         yield one(v)
       
   253     if last is not None:
       
   254         yield one(last, tag=lastname)
       
   255     endname = 'end_' + plural
       
   256     if endname in templ:
       
   257         yield templ(endname, **strmapping)
       
   258 
    57 
   259 def getlatesttags(context, mapping, pattern=None):
    58 def getlatesttags(context, mapping, pattern=None):
   260     '''return date, distance and name for the latest tag of rev'''
    59     '''return date, distance and name for the latest tag of rev'''
   261     repo = context.resource(mapping, 'repo')
    60     repo = context.resource(mapping, 'repo')
   262     ctx = context.resource(mapping, 'ctx')
    61     ctx = context.resource(mapping, 'ctx')