##// END OF EJS Templates
merge with stable
merge with stable

File last commit:

r42163:77ef3498 default
r42612:c77ab654 merge default
Show More
formatter.py
675 lines | 22.9 KiB | text/x-python | PythonLexer
Matt Mackall
formatter: add basic formatters
r16134 # formatter.py - generic output formatting for mercurial
#
# Copyright 2012 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 """Generic output formatting for Mercurial
The formatter provides API to show data in various ways. The following
functions should be used in place of ui.write():
- fm.write() for unconditional output
- fm.condwrite() to show some extra data conditionally in plain output
Yuya Nishihara
formatter: add support for changeset templating...
r31172 - fm.context() to provide changectx to template output
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 - fm.data() to provide extra data to JSON or template output
- fm.plain() to show raw text that isn't provided to JSON or template output
To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
beforehand so the data is converted to the appropriate data type. Use
fm.isplain() if you need to convert or format data conditionally which isn't
supported by the formatter API.
To build nested structure (i.e. a list of dicts), use fm.nested().
See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
fm.condwrite() vs 'if cond:':
In most cases, use fm.condwrite() so users can selectively show the data
in template output. If it's costly to build data, use plain 'if cond:' with
fm.write().
fm.nested() vs fm.formatdict() (or fm.formatlist()):
fm.nested() should be used to form a tree structure (a list of dicts of
lists of dicts...) which can be accessed through template keywords, e.g.
"{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
exports a dict-type object to template, which can be accessed by e.g.
"{get(foo, key)}" function.
Doctest helper:
>>> def show(fn, verbose=False, **opts):
... import sys
... from . import ui as uimod
... ui = uimod.ui()
... ui.verbose = verbose
Yuya Nishihara
py3: rewrite stdout hack of doctest by using ui.pushbuffer()...
r34256 ... ui.pushbuffer()
... try:
Yuya Nishihara
py3: convert system strings to bytes in doctest of formatter.py
r34257 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
... pycompat.byteskwargs(opts)))
Yuya Nishihara
py3: rewrite stdout hack of doctest by using ui.pushbuffer()...
r34256 ... finally:
... print(pycompat.sysstr(ui.popbuffer()), end='')
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560
Basic example:
>>> def files(ui, fm):
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 ... for f in files:
... fm.startitem()
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 ... fm.write(b'path', b'%s', f[0])
... fm.condwrite(ui.verbose, b'date', b' %s',
... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 ... fm.data(size=f[1])
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 ... fm.plain(b'\\n')
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 ... fm.end()
>>> show(files)
foo
bar
>>> show(files, verbose=True)
foo 1970-01-01 00:00:00
bar 1970-01-01 00:00:01
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> show(files, template=b'json')
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 [
{
"date": [0, 0],
"path": "foo",
"size": 123
},
{
"date": [1, 0],
"path": "bar",
"size": 456
}
]
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 path: foo
date: 1970-01-01T00:00:00+00:00
path: bar
date: 1970-01-01T00:00:01+00:00
Nested example:
>>> def subrepos(ui, fm):
... fm.startitem()
Yuya Nishihara
templater: look up symbols/resources as if they were separated (issue5699)...
r35486 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 ... fm.end()
>>> show(subrepos)
[baz]
foo
bar
Yuya Nishihara
templater: look up symbols/resources as if they were separated (issue5699)...
r35486 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
Yuya Nishihara
formatter: add overview of API and example as doctest
r30560 baz: foo, bar
"""
Yuya Nishihara
py3: rewrite stdout hack of doctest by using ui.pushbuffer()...
r34256 from __future__ import absolute_import, print_function
Gregory Szorc
formatter: use absolute_import
r25950
Yuya Nishihara
formatter: add helper to create a formatter optionally backed by file...
r32574 import contextlib
Yuya Nishihara
templater: provide loop counter as "index" keyword...
r31807 import itertools
Matt Mackall
formatter: move most of template option helper to formatter...
r25511 import os
Matt Mackall
formatter: add json formatter
r22428
Gregory Szorc
formatter: use absolute_import
r25950 from .i18n import _
from .node import (
hex,
short,
)
Yuya Nishihara
formatter: ditch namedtuple in favor of attr
r37860 from .thirdparty import (
attr,
)
Gregory Szorc
formatter: use absolute_import
r25950
from . import (
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 error,
Pulkit Goyal
py3: use pycompat.byteskwargs to converts kwargs to bytes...
r32159 pycompat,
Yuya Nishihara
formatter: use templatefilters.json()...
r31782 templatefilters,
Yuya Nishihara
formatter: add function to convert list to appropriate format (issue5217)...
r29676 templatekw,
Gregory Szorc
formatter: use absolute_import
r25950 templater,
Yuya Nishihara
templater: move stringify() to templateutil module...
r36938 templateutil,
Pulkit Goyal
py3: conditionalize cPickle import by adding in util...
r29324 util,
Gregory Szorc
formatter: use absolute_import
r25950 )
Yuya Nishihara
formatter: use stringutil.pprint() in debug output to drop b''
r40310 from .utils import (
Yuya Nishihara
template: add CBOR output format...
r42163 cborutil,
Yuya Nishihara
formatter: use stringutil.pprint() in debug output to drop b''
r40310 dateutil,
stringutil,
)
Gregory Szorc
formatter: use absolute_import
r25950
Pulkit Goyal
py3: conditionalize cPickle import by adding in util...
r29324 pickle = util.pickle
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 class _nullconverter(object):
'''convert non-primitive data types to be processed by formatter'''
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090
# set to True if context object should be stored as item
storecontext = False
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 def wrapnested(data, tmpl, sep):
'''wrap nested data by appropriate type'''
return data
@staticmethod
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 def formatdate(date, fmt):
'''convert date tuple to appropriate format'''
Yuya Nishihara
formatter: convert timestamp to int...
r37788 # timestamp can be float, but the canonical form should be int
ts, tz = date
return (int(ts), tz)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
def formatdict(data, key, value, fmt, sep):
'''convert dict or key-value pairs to appropriate dict format'''
# use plain dict instead of util.sortdict so that data can be
# serialized as a builtin dict in pickle output
return dict(data)
@staticmethod
def formatlist(data, name, fmt, sep):
'''convert iterable to appropriate list format'''
return list(data)
Matt Mackall
formatter: add basic formatters
r16134 class baseformatter(object):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 def __init__(self, ui, topic, opts, converter):
Matt Mackall
formatter: add basic formatters
r16134 self._ui = ui
self._topic = topic
Yuya Nishihara
formatter: carry opts to file-based formatters by basefm...
r37615 self._opts = opts
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 self._converter = converter
Matt Mackall
formatter: add basic formatters
r16134 self._item = None
Yuya Nishihara
formatter: add general way to switch hex/short functions...
r22701 # function to convert node to string suitable for this output
self.hexfunc = hex
Yuya Nishihara
formatter: add context manager interface for convenience...
r29882 def __enter__(self):
return self
def __exit__(self, exctype, excvalue, traceback):
if exctype is None:
self.end()
Matt Mackall
formatter: add basic formatters
r16134 def _showitem(self):
'''show a formatted item once all data is collected'''
def startitem(self):
'''begin an item in the format list'''
if self._item is not None:
self._showitem()
self._item = {}
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
Yuya Nishihara
formatter: add function to convert date tuple to appropriate format
r29678 '''convert date tuple to appropriate format'''
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 return self._converter.formatdate(date, fmt)
Yuya Nishihara
templater: allow dynamically switching the default dict/list formatting...
r36651 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
Yuya Nishihara
formatter: add function to convert dict to appropriate format...
r29794 '''convert dict or key-value pairs to appropriate dict format'''
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 return self._converter.formatdict(data, key, value, fmt, sep)
Yuya Nishihara
templater: allow dynamically switching the default dict/list formatting...
r36651 def formatlist(self, data, name, fmt=None, sep=' '):
Yuya Nishihara
formatter: add function to convert list to appropriate format (issue5217)...
r29676 '''convert iterable to appropriate list format'''
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 # name is mandatory argument for now, but it could be optional if
# we have default template keyword, e.g. {item}
return self._converter.formatlist(data, name, fmt, sep)
Yuya Nishihara
formatter: add support for changeset templating...
r31172 def context(self, **ctxs):
'''insert context objects to be used to render template keywords'''
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090 ctxs = pycompat.byteskwargs(ctxs)
Yuya Nishihara
formatter: fill missing resources by formatter, not by resource mapper...
r39619 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090 if self._converter.storecontext:
Yuya Nishihara
formatter: fill missing resources by formatter, not by resource mapper...
r39619 # populate missing resources in fctx -> ctx -> repo order
if 'fctx' in ctxs and 'ctx' not in ctxs:
ctxs['ctx'] = ctxs['fctx'].changectx()
if 'ctx' in ctxs and 'repo' not in ctxs:
ctxs['repo'] = ctxs['ctx'].repo()
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090 self._item.update(ctxs)
Yuya Nishihara
formatter: provide hint of referenced field names...
r38375 def datahint(self):
'''set of field names to be referenced'''
return set()
Matt Mackall
formatter: add basic formatters
r16134 def data(self, **data):
'''insert data into item that's not shown in default output'''
Pulkit Goyal
py3: use pycompat.byteskwargs to converts kwargs to bytes...
r32159 data = pycompat.byteskwargs(data)
David M. Carr
formatter: improve implementation of data method...
r17630 self._item.update(data)
Matt Mackall
formatter: add basic formatters
r16134 def write(self, fields, deftext, *fielddata, **opts):
'''do default text output while assigning data to item'''
Yuya Nishihara
formatter: verify number of arguments passed to write functions...
r26372 fieldkeys = fields.split()
Boris Feld
formatter: more details on assertion failure...
r40175 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
Yuya Nishihara
formatter: use dict.update() to set arguments passed to write functions...
r26373 self._item.update(zip(fieldkeys, fielddata))
Matt Mackall
formatter: add condwrite method...
r17909 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
'''do conditional write (primarily for plain formatter)'''
Yuya Nishihara
formatter: verify number of arguments passed to write functions...
r26372 fieldkeys = fields.split()
assert len(fieldkeys) == len(fielddata)
Yuya Nishihara
formatter: use dict.update() to set arguments passed to write functions...
r26373 self._item.update(zip(fieldkeys, fielddata))
Matt Mackall
formatter: add basic formatters
r16134 def plain(self, text, **opts):
'''show raw text for non-templated mode'''
Mathias De Maré
formatter: introduce isplain() to replace (the inverse of) __nonzero__() (API)...
r29949 def isplain(self):
'''check for plain formatter usage'''
return False
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 def nested(self, field, tmpl=None, sep=''):
Yuya Nishihara
formatter: add fm.nested(field) to either write or build sub items...
r29837 '''sub formatter to store nested data in the specified field'''
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 data = []
self._item[field] = self._converter.wrapnested(data, tmpl, sep)
Yuya Nishihara
formatter: add fm.nested(field) to either write or build sub items...
r29837 return _nestedformatter(self._ui, self._converter, data)
Matt Mackall
formatter: add basic formatters
r16134 def end(self):
'''end output for the formatter'''
if self._item is not None:
self._showitem()
Yuya Nishihara
formatter: carry opts to file-based formatters by basefm...
r37615 def nullformatter(ui, topic, opts):
Yuya Nishihara
formatter: add nullformatter...
r32575 '''formatter that prints nothing'''
Yuya Nishihara
formatter: carry opts to file-based formatters by basefm...
r37615 return baseformatter(ui, topic, opts, converter=_nullconverter)
Yuya Nishihara
formatter: add nullformatter...
r32575
Yuya Nishihara
formatter: add fm.nested(field) to either write or build sub items...
r29837 class _nestedformatter(baseformatter):
'''build sub items and store them in the parent formatter'''
def __init__(self, ui, converter, data):
baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
self._data = data
def _showitem(self):
self._data.append(self._item)
Yuya Nishihara
formatter: add function to convert dict to appropriate format...
r29794 def _iteritems(data):
'''iterate key-value pairs in stable order'''
if isinstance(data, dict):
return sorted(data.iteritems())
return data
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 class _plainconverter(object):
'''convert non-primitive data types to text'''
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090
storecontext = False
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 def wrapnested(data, tmpl, sep):
raise error.ProgrammingError('plainformatter should never be nested')
@staticmethod
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 def formatdate(date, fmt):
'''stringify date tuple in the given format'''
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return dateutil.datestr(date, fmt)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
def formatdict(data, key, value, fmt, sep):
'''stringify key-value pairs separated by sep'''
Yuya Nishihara
templater: byte-stringify dict/list values before passing to default format...
r36652 prefmt = pycompat.identity
Yuya Nishihara
templater: allow dynamically switching the default dict/list formatting...
r36651 if fmt is None:
fmt = '%s=%s'
Yuya Nishihara
templater: byte-stringify dict/list values before passing to default format...
r36652 prefmt = pycompat.bytestr
return sep.join(fmt % (prefmt(k), prefmt(v))
for k, v in _iteritems(data))
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
def formatlist(data, name, fmt, sep):
'''stringify iterable separated by sep'''
Yuya Nishihara
templater: byte-stringify dict/list values before passing to default format...
r36652 prefmt = pycompat.identity
Yuya Nishihara
templater: allow dynamically switching the default dict/list formatting...
r36651 if fmt is None:
fmt = '%s'
Yuya Nishihara
templater: byte-stringify dict/list values before passing to default format...
r36652 prefmt = pycompat.bytestr
return sep.join(fmt % prefmt(e) for e in data)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836
Matt Mackall
formatter: add basic formatters
r16134 class plainformatter(baseformatter):
'''the default text output scheme'''
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 def __init__(self, ui, out, topic, opts):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
Yuya Nishihara
formatter: add general way to switch hex/short functions...
r22701 if ui.debugflag:
self.hexfunc = hex
else:
self.hexfunc = short
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 if ui is out:
self._write = ui.write
else:
self._write = lambda s, **opts: out.write(s)
Matt Mackall
formatter: add basic formatters
r16134 def startitem(self):
pass
def data(self, **data):
pass
def write(self, fields, deftext, *fielddata, **opts):
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 self._write(deftext % fielddata, **opts)
Matt Mackall
formatter: add condwrite method...
r17909 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
'''do conditional write'''
if cond:
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 self._write(deftext % fielddata, **opts)
Matt Mackall
formatter: add basic formatters
r16134 def plain(self, text, **opts):
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 self._write(text, **opts)
Mathias De Maré
formatter: introduce isplain() to replace (the inverse of) __nonzero__() (API)...
r29949 def isplain(self):
return True
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 def nested(self, field, tmpl=None, sep=''):
Yuya Nishihara
formatter: add fm.nested(field) to either write or build sub items...
r29837 # nested data will be directly written to ui
return self
Matt Mackall
formatter: add basic formatters
r16134 def end(self):
pass
class debugformatter(baseformatter):
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 def __init__(self, ui, out, topic, opts):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out = out
self._out.write("%s = [\n" % self._topic)
Matt Mackall
formatter: add basic formatters
r16134 def _showitem(self):
Yuya Nishihara
formatter: make debug output prettier...
r40313 self._out.write(' %s,\n'
% stringutil.pprint(self._item, indent=4, level=1))
Matt Mackall
formatter: add basic formatters
r16134 def end(self):
baseformatter.end(self)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write("]\n")
Matt Mackall
formatter: add basic formatters
r16134
Matt Mackall
formatter: add pickle format...
r22430 class pickleformatter(baseformatter):
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 def __init__(self, ui, out, topic, opts):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out = out
Matt Mackall
formatter: add pickle format...
r22430 self._data = []
def _showitem(self):
self._data.append(self._item)
def end(self):
baseformatter.end(self)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write(pickle.dumps(self._data))
Matt Mackall
formatter: add pickle format...
r22430
Yuya Nishihara
template: add CBOR output format...
r42163 class cborformatter(baseformatter):
'''serialize items as an indefinite-length CBOR array'''
def __init__(self, ui, out, topic, opts):
baseformatter.__init__(self, ui, topic, opts, _nullconverter)
self._out = out
self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
def _showitem(self):
self._out.write(b''.join(cborutil.streamencode(self._item)))
def end(self):
baseformatter.end(self)
self._out.write(cborutil.BREAK)
Matt Mackall
formatter: add json formatter
r22428 class jsonformatter(baseformatter):
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 def __init__(self, ui, out, topic, opts):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out = out
self._out.write("[")
Martin von Zweigbergk
formatter: set _first on formatter, not ui...
r31298 self._first = True
Matt Mackall
formatter: add json formatter
r22428 def _showitem(self):
Martin von Zweigbergk
formatter: set _first on formatter, not ui...
r31298 if self._first:
self._first = False
Matt Mackall
formatter: add json formatter
r22428 else:
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write(",")
Matt Mackall
formatter: add json formatter
r22428
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write("\n {\n")
Matt Mackall
formatter: add json formatter
r22428 first = True
for k, v in sorted(self._item.items()):
if first:
first = False
else:
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write(",\n")
Yuya Nishihara
formatter: use templatefilters.json()...
r31782 u = templatefilters.json(v, paranoid=False)
self._out.write(' "%s": %s' % (k, u))
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write("\n }")
Matt Mackall
formatter: add json formatter
r22428 def end(self):
baseformatter.end(self)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out.write("\n]\n")
Matt Mackall
formatter: add json formatter
r22428
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 class _templateconverter(object):
'''convert non-primitive data types to be processed by templater'''
Yuya Nishihara
formatter: proxy fm.context() through converter...
r33090
storecontext = True
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
Yuya Nishihara
formatter: make nested items somewhat readable in template output
r37518 def wrapnested(data, tmpl, sep):
'''wrap nested data by templatable type'''
return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
@staticmethod
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 def formatdate(date, fmt):
'''return date tuple'''
Yuya Nishihara
templater: introduce a wrapper for date tuple (BC)...
r38304 return templateutil.date(date)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
def formatdict(data, key, value, fmt, sep):
'''build object that can be evaluated as either plain string or dict'''
data = util.sortdict(_iteritems(data))
def f():
yield _plainconverter.formatdict(data, key, value, fmt, sep)
Yuya Nishihara
templater: move hybrid class and functions to templateutil module...
r36939 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
gen=f)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 @staticmethod
def formatlist(data, name, fmt, sep):
'''build object that can be evaluated as either plain string or list'''
data = list(data)
def f():
yield _plainconverter.formatlist(data, name, fmt, sep)
Yuya Nishihara
templater: move hybrid class and functions to templateutil module...
r36939 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836
Matt Mackall
formatter: add template support...
r25513 class templateformatter(baseformatter):
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 def __init__(self, ui, out, topic, opts):
Yuya Nishihara
formatter: factor out format*() functions to separate classes...
r29836 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
Yuya Nishihara
formatter: add argument to change output file of non-plain formatter...
r31182 self._out = out
Yuya Nishihara
formatter: inline gettemplater()...
r32832 spec = lookuptemplate(ui, topic, opts.get('template', ''))
Yuya Nishihara
formatter: render template specified by templatespec tuple
r32841 self._tref = spec.ref
Yuya Nishihara
templater: register keywords to defaults table...
r35499 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
resources=templateresources(ui),
Yuya Nishihara
templater: move repo, ui and cache to per-engine resources
r35485 cache=templatekw.defaulttempl)
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 self._parts = templatepartsmap(spec, self._t,
Yuya Nishihara
formatter: add support for separator template...
r32950 ['docheader', 'docfooter', 'separator'])
Yuya Nishihara
templater: provide loop counter as "index" keyword...
r31807 self._counter = itertools.count()
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 self._renderitem('docheader', {})
Matt Mackall
formatter: add template support...
r25513 def _showitem(self):
Yuya Nishihara
formatter: extract helper function to render template
r32948 item = self._item.copy()
Yuya Nishihara
formatter: add support for separator template...
r32950 item['index'] = index = next(self._counter)
if index > 0:
self._renderitem('separator', {})
Yuya Nishihara
formatter: extract helper function to render template
r32948 self._renderitem(self._tref, item)
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 def _renderitem(self, part, item):
if part not in self._parts:
return
ref = self._parts[part]
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 self._out.write(self._t.render(ref, item))
Matt Mackall
formatter: add template support...
r25513
Yuya Nishihara
formatter: provide hint of referenced field names...
r38375 @util.propertycache
def _symbolsused(self):
Yuya Nishihara
formatter: look for template symbols from the associated name...
r38465 return self._t.symbolsused(self._tref)
Yuya Nishihara
formatter: provide hint of referenced field names...
r38375
def datahint(self):
'''set of field names to be referenced from the template'''
return self._symbolsused[0]
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 def end(self):
baseformatter.end(self)
self._renderitem('docfooter', {})
Yuya Nishihara
formatter: ditch namedtuple in favor of attr
r37860 @attr.s(frozen=True)
class templatespec(object):
ref = attr.ib()
tmpl = attr.ib()
mapfile = attr.ib()
Yuya Nishihara
formatter: wrap (tmpl, mapfile) by named tuple...
r32838
Matt Mackall
formatter: move most of template option helper to formatter...
r25511 def lookuptemplate(ui, topic, tmpl):
Yuya Nishihara
formatter: document lookuptemplate()
r32834 """Find the template matching the given -T/--template spec 'tmpl'
'tmpl' can be any of the following:
- a literal template (e.g. '{rev}')
- a map-file name or path (e.g. 'changelog')
- a reference to [templates] in config file
- a path to raw template file
A map file defines a stand-alone template environment. If a map file
selected, all templates defined in the file will be loaded, and the
Yuya Nishihara
templater: load aliases from [templatealias] section in map file...
r34716 template matching the given topic will be rendered. Aliases won't be
loaded from user config, but from the map file.
Yuya Nishihara
formatter: load templates section like a map file...
r32875
If no map file selected, all templates in [templates] section will be
available as well as aliases in [templatealias].
Yuya Nishihara
formatter: document lookuptemplate()
r32834 """
Matt Mackall
formatter: move most of template option helper to formatter...
r25511 # looks like a literal template?
if '{' in tmpl:
Yuya Nishihara
formatter: load templates section like a map file...
r32875 return templatespec('', tmpl, None)
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
# perhaps a stock style?
if not os.path.split(tmpl)[0]:
mapname = (templater.templatepath('map-cmdline.' + tmpl)
or templater.templatepath(tmpl))
if mapname and os.path.isfile(mapname):
Yuya Nishihara
formatter: put topic in templatespec tuple...
r32840 return templatespec(topic, None, mapname)
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
# perhaps it's a reference to [templates]
Yuya Nishihara
formatter: load templates section like a map file...
r32875 if ui.config('templates', tmpl):
return templatespec(tmpl, None, None)
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
if tmpl == 'list':
ui.write(_("available styles: %s\n") % templater.stylelist())
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("specify a template"))
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
# perhaps it's a path to a map or a template
if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
# is it a mapfile for a style?
if os.path.basename(tmpl).startswith("map-"):
Yuya Nishihara
formatter: put topic in templatespec tuple...
r32840 return templatespec(topic, None, os.path.realpath(tmpl))
Yuya Nishihara
formatter: open raw template file in posix semantics...
r32829 with util.posixfile(tmpl, 'rb') as f:
Yuya Nishihara
formatter: close raw template file explicitly
r32827 tmpl = f.read()
Yuya Nishihara
formatter: load templates section like a map file...
r32875 return templatespec('', tmpl, None)
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
# constant string?
Yuya Nishihara
formatter: load templates section like a map file...
r32875 return templatespec('', tmpl, None)
Matt Mackall
formatter: move most of template option helper to formatter...
r25511
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 def templatepartsmap(spec, t, partnames):
"""Create a mapping of {part: ref}"""
partsmap = {spec.ref: spec.ref} # initial ref must exist in t
if spec.mapfile:
partsmap.update((p, p) for p in partnames if p in t)
Yuya Nishihara
formatter: add support for parts map of [templates] section...
r32952 elif spec.ref:
for part in partnames:
ref = '%s:%s' % (spec.ref, part) # select config sub-section
if ref in t:
partsmap[part] = ref
Yuya Nishihara
formatter: add support for docheader and docfooter templates...
r32949 return partsmap
Yuya Nishihara
templater: register keywords to defaults table...
r35499 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
Yuya Nishihara
formatter: factor out function to create templater from literal or map file...
r32831 """Create a templater from either a literal template or loading from
a map file"""
Yuya Nishihara
formatter: wrap (tmpl, mapfile) by named tuple...
r32838 assert not (spec.tmpl and spec.mapfile)
if spec.mapfile:
Yuya Nishihara
templater: keep default resources per template engine (API)...
r35484 frommapfile = templater.templater.frommapfile
Yuya Nishihara
templater: register keywords to defaults table...
r35499 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
cache=cache)
return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
cache=cache)
Yuya Nishihara
templater: factor out function that creates templater from string template...
r28955
Yuya Nishihara
templater: register keywords to defaults table...
r35499 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
Yuya Nishihara
templater: factor out function that creates templater from string template...
r28955 """Create a templater from a string template 'tmpl'"""
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 aliases = ui.configitems('templatealias')
Yuya Nishihara
templater: register keywords to defaults table...
r35499 t = templater.templater(defaults=defaults, resources=resources,
cache=cache, aliases=aliases)
Yuya Nishihara
formatter: load templates section like a map file...
r32875 t.cache.update((k, templater.unquotestring(v))
for k, v in ui.configitems('templates'))
Matt Mackall
formatter: add a method to build a full templater from a -T option
r25512 if tmpl:
Yuya Nishihara
formatter: always store a literal template unnamed...
r32876 t.cache[''] = tmpl
Matt Mackall
formatter: add a method to build a full templater from a -T option
r25512 return t
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 # marker to denote a resource to be loaded on demand based on mapping values
# (e.g. (ctx, path) -> fctx)
_placeholder = object()
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 class templateresources(templater.resourcemapper):
"""Resource mapper designed for the default templatekw and function"""
def __init__(self, ui, repo=None):
self._resmap = {
'cache': {}, # for templatekw/funcs to store reusable data
'repo': repo,
'ui': ui,
}
Yuya Nishihara
templater: move repo, ui and cache to per-engine resources
r35485
Yuya Nishihara
templater: remove unused context argument from most resourcemapper functions...
r39618 def availablekeys(self, mapping):
Yuya Nishihara
formatter: inline _gettermap and _knownkeys
r39620 return {k for k in self.knownkeys()
if self._getsome(mapping, k) is not None}
Yuya Nishihara
templater: drop symbols which should be overridden by new 'ctx' (issue5612)...
r37093
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 def knownkeys(self):
Yuya Nishihara
formatter: inline _gettermap and _knownkeys
r39620 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
Yuya Nishihara
templater: introduce resourcemapper class...
r37091
Yuya Nishihara
templater: remove unused context argument from most resourcemapper functions...
r39618 def lookup(self, mapping, key):
Yuya Nishihara
formatter: inline _gettermap and _knownkeys
r39620 if key not in self.knownkeys():
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 return None
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 v = self._getsome(mapping, key)
if v is _placeholder:
v = mapping[key] = self._loadermap[key](self, mapping)
return v
Yuya Nishihara
templater: introduce resourcemapper class...
r37091
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120 def populatemap(self, context, origmapping, newmapping):
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 mapping = {}
Yuya Nishihara
formatter: factor out function that detects node change and document it...
r39621 if self._hasnodespec(newmapping):
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 mapping['revcache'] = {} # per-ctx cache
Yuya Nishihara
formatter: factor out function that detects node change and document it...
r39621 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
Yuya Nishihara
formatter: make 'originalnode' a thing in log-like templates...
r37123 orignode = templateutil.runsymbol(context, origmapping, 'node')
mapping['originalnode'] = orignode
Yuya Nishihara
formatter: populate ctx from repo and node value...
r39659 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 # its existence to be reported by availablekeys()
Yuya Nishihara
formatter: populate ctx from repo and node value...
r39659 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
mapping['ctx'] = _placeholder
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
mapping['fctx'] = _placeholder
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 return mapping
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120
Yuya Nishihara
templater: remove unused context argument from most resourcemapper functions...
r39618 def _getsome(self, mapping, key):
Yuya Nishihara
templater: process mapping dict by resource callables...
r36998 v = mapping.get(key)
if v is not None:
return v
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 return self._resmap.get(key)
Yuya Nishihara
templater: convert resources to a table of callables for future extension...
r36997
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 def _hasliteral(self, mapping, key):
"""Test if a literal value is set or unset in the given mapping"""
return key in mapping and not callable(mapping[key])
def _getliteral(self, mapping, key):
"""Return value of the given name if it is a literal"""
v = mapping.get(key)
if callable(v):
return None
return v
Yuya Nishihara
formatter: factor out function that detects node change and document it...
r39621 def _hasnodespec(self, mapping):
"""Test if context revision is set or unset in the given mapping"""
return 'node' in mapping or 'ctx' in mapping
Yuya Nishihara
formatter: unblock storing fctx as a template resource...
r36999
Yuya Nishihara
formatter: populate ctx from repo and node value...
r39659 def _loadctx(self, mapping):
repo = self._getsome(mapping, 'repo')
node = self._getliteral(mapping, 'node')
if repo is None or node is None:
return
try:
return repo[node]
except error.RepoLookupError:
return None # maybe hidden/non-existent node
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 def _loadfctx(self, mapping):
ctx = self._getsome(mapping, 'ctx')
path = self._getliteral(mapping, 'path')
if ctx is None or path is None:
return None
try:
return ctx[path]
except error.LookupError:
return None # maybe removed file?
_loadermap = {
Yuya Nishihara
formatter: populate ctx from repo and node value...
r39659 'ctx': _loadctx,
Yuya Nishihara
formatter: populate fctx from ctx and path value...
r39622 'fctx': _loadfctx,
}
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 def formatter(ui, out, topic, opts):
Matt Mackall
formatter: add json formatter
r22428 template = opts.get("template", "")
Yuya Nishihara
template: add CBOR output format...
r42163 if template == "cbor":
return cborformatter(ui, out, topic, opts)
elif template == "json":
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return jsonformatter(ui, out, topic, opts)
Matt Mackall
formatter: add pickle format...
r22430 elif template == "pickle":
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return pickleformatter(ui, out, topic, opts)
Matt Mackall
formatter: add json formatter
r22428 elif template == "debug":
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return debugformatter(ui, out, topic, opts)
Matt Mackall
formatter: add json formatter
r22428 elif template != "":
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return templateformatter(ui, out, topic, opts)
Matt Mackall
formatter: mark developer options
r25838 # developer config: ui.formatdebug
Matt Mackall
formatter: add json formatter
r22428 elif ui.configbool('ui', 'formatdebug'):
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return debugformatter(ui, out, topic, opts)
Matt Mackall
formatter: mark developer options
r25838 # deprecated config: ui.formatjson
Matt Mackall
formatter: add json formatter
r22428 elif ui.configbool('ui', 'formatjson'):
Yuya Nishihara
formatter: add option to redirect output to file object...
r32573 return jsonformatter(ui, out, topic, opts)
return plainformatter(ui, out, topic, opts)
Yuya Nishihara
formatter: add helper to create a formatter optionally backed by file...
r32574
@contextlib.contextmanager
def openformatter(ui, filename, topic, opts):
"""Create a formatter that writes outputs to the specified file
Must be invoked using the 'with' statement.
"""
with util.posixfile(filename, 'wb') as out:
with formatter(ui, out, topic, opts) as fm:
yield fm
@contextlib.contextmanager
def _neverending(fm):
yield fm
Yuya Nishihara
formatter: carry opts to file-based formatters by basefm...
r37615 def maybereopen(fm, filename):
Yuya Nishihara
formatter: add helper to create a formatter optionally backed by file...
r32574 """Create a formatter backed by file if filename specified, else return
the given formatter
Must be invoked using the 'with' statement. This will never call fm.end()
of the given formatter.
"""
if filename:
Yuya Nishihara
formatter: carry opts to file-based formatters by basefm...
r37615 return openformatter(fm._ui, filename, fm._topic, fm._opts)
Yuya Nishihara
formatter: add helper to create a formatter optionally backed by file...
r32574 else:
return _neverending(fm)