summaryrefslogtreecommitdiff
path: root/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py
diff options
context:
space:
mode:
Diffstat (limited to 'eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py')
-rw-r--r--eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py289
1 files changed, 289 insertions, 0 deletions
diff --git a/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py
new file mode 100644
index 0000000..5b3a5ad
--- /dev/null
+++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/templater.py
@@ -0,0 +1,289 @@
+# templater.py - template expansion for output
+#
+# Copyright 2005, 2006 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.
+
+from i18n import _
+import sys, os
+import util, config, templatefilters
+
+path = ['templates', '../templates']
+stringify = templatefilters.stringify
+
+def _flatten(thing):
+ '''yield a single stream from a possibly nested set of iterators'''
+ if isinstance(thing, str):
+ yield thing
+ elif not hasattr(thing, '__iter__'):
+ if thing is not None:
+ yield str(thing)
+ else:
+ for i in thing:
+ if isinstance(i, str):
+ yield i
+ elif not hasattr(i, '__iter__'):
+ if i is not None:
+ yield str(i)
+ elif i is not None:
+ for j in _flatten(i):
+ yield j
+
+def parsestring(s, quoted=True):
+ '''parse a string using simple c-like syntax.
+ string must be in quotes if quoted is True.'''
+ if quoted:
+ if len(s) < 2 or s[0] != s[-1]:
+ raise SyntaxError(_('unmatched quotes'))
+ return s[1:-1].decode('string_escape')
+
+ return s.decode('string_escape')
+
+class engine(object):
+ '''template expansion engine.
+
+ template expansion works like this. a map file contains key=value
+ pairs. if value is quoted, it is treated as string. otherwise, it
+ is treated as name of template file.
+
+ templater is asked to expand a key in map. it looks up key, and
+ looks for strings like this: {foo}. it expands {foo} by looking up
+ foo in map, and substituting it. expansion is recursive: it stops
+ when there is no more {foo} to replace.
+
+ expansion also allows formatting and filtering.
+
+ format uses key to expand each item in list. syntax is
+ {key%format}.
+
+ filter uses function to transform value. syntax is
+ {key|filter1|filter2|...}.'''
+
+ def __init__(self, loader, filters={}, defaults={}):
+ self._loader = loader
+ self._filters = filters
+ self._defaults = defaults
+ self._cache = {}
+
+ def process(self, t, mapping):
+ '''Perform expansion. t is name of map element to expand.
+ mapping contains added elements for use during expansion. Is a
+ generator.'''
+ return _flatten(self._process(self._load(t), mapping))
+
+ def _load(self, t):
+ '''load, parse, and cache a template'''
+ if t not in self._cache:
+ self._cache[t] = self._parse(self._loader(t))
+ return self._cache[t]
+
+ def _get(self, mapping, key):
+ v = mapping.get(key)
+ if v is None:
+ v = self._defaults.get(key, '')
+ if hasattr(v, '__call__'):
+ v = v(**mapping)
+ return v
+
+ def _filter(self, mapping, parts):
+ filters, val = parts
+ x = self._get(mapping, val)
+ for f in filters:
+ x = f(x)
+ return x
+
+ def _format(self, mapping, args):
+ key, parsed = args
+ v = self._get(mapping, key)
+ if not hasattr(v, '__iter__'):
+ raise SyntaxError(_("error expanding '%s%%%s'")
+ % (key, parsed))
+ lm = mapping.copy()
+ for i in v:
+ if isinstance(i, dict):
+ lm.update(i)
+ yield self._process(parsed, lm)
+ else:
+ # v is not an iterable of dicts, this happen when 'key'
+ # has been fully expanded already and format is useless.
+ # If so, return the expanded value.
+ yield i
+
+ def _parse(self, tmpl):
+ '''preparse a template'''
+ parsed = []
+ pos, stop = 0, len(tmpl)
+ while pos < stop:
+ n = tmpl.find('{', pos)
+ if n < 0:
+ parsed.append((None, tmpl[pos:stop]))
+ break
+ if n > 0 and tmpl[n - 1] == '\\':
+ # escaped
+ parsed.append((None, tmpl[pos:n - 1] + "{"))
+ pos = n + 1
+ continue
+ if n > pos:
+ parsed.append((None, tmpl[pos:n]))
+
+ pos = n
+ n = tmpl.find('}', pos)
+ if n < 0:
+ # no closing
+ parsed.append((None, tmpl[pos:stop]))
+ break
+
+ expr = tmpl[pos + 1:n]
+ pos = n + 1
+
+ if '%' in expr:
+ # the keyword should be formatted with a template
+ key, t = expr.split('%')
+ parsed.append((self._format, (key.strip(),
+ self._load(t.strip()))))
+ elif '|' in expr:
+ # process the keyword value with one or more filters
+ parts = expr.split('|')
+ val = parts[0].strip()
+ try:
+ filters = [self._filters[f.strip()] for f in parts[1:]]
+ except KeyError, i:
+ raise SyntaxError(_("unknown filter '%s'") % i[0])
+ parsed.append((self._filter, (filters, val)))
+ else:
+ # just get the keyword
+ parsed.append((self._get, expr.strip()))
+
+ return parsed
+
+ def _process(self, parsed, mapping):
+ '''Render a template. Returns a generator.'''
+ for f, e in parsed:
+ if f:
+ yield f(mapping, e)
+ else:
+ yield e
+
+engines = {'default': engine}
+
+class templater(object):
+
+ def __init__(self, mapfile, filters={}, defaults={}, cache={},
+ minchunk=1024, maxchunk=65536):
+ '''set up template engine.
+ mapfile is name of file to read map definitions from.
+ filters is dict of functions. each transforms a value into another.
+ defaults is dict of default map definitions.'''
+ self.mapfile = mapfile or 'template'
+ self.cache = cache.copy()
+ self.map = {}
+ self.base = (mapfile and os.path.dirname(mapfile)) or ''
+ self.filters = templatefilters.filters.copy()
+ self.filters.update(filters)
+ self.defaults = defaults
+ self.minchunk, self.maxchunk = minchunk, maxchunk
+ self.engines = {}
+
+ if not mapfile:
+ return
+ if not os.path.exists(mapfile):
+ raise util.Abort(_('style not found: %s') % mapfile)
+
+ conf = config.config()
+ conf.read(mapfile)
+
+ for key, val in conf[''].items():
+ if val[0] in "'\"":
+ try:
+ self.cache[key] = parsestring(val)
+ except SyntaxError, inst:
+ raise SyntaxError('%s: %s' %
+ (conf.source('', key), inst.args[0]))
+ else:
+ val = 'default', val
+ if ':' in val[1]:
+ val = val[1].split(':', 1)
+ self.map[key] = val[0], os.path.join(self.base, val[1])
+
+ def __contains__(self, key):
+ return key in self.cache or key in self.map
+
+ def load(self, t):
+ '''Get the template for the given template name. Use a local cache.'''
+ if not t in self.cache:
+ try:
+ self.cache[t] = open(self.map[t][1]).read()
+ except IOError, inst:
+ raise IOError(inst.args[0], _('template file %s: %s') %
+ (self.map[t][1], inst.args[1]))
+ return self.cache[t]
+
+ def __call__(self, t, **mapping):
+ ttype = t in self.map and self.map[t][0] or 'default'
+ proc = self.engines.get(ttype)
+ if proc is None:
+ proc = engines[ttype](self.load, self.filters, self.defaults)
+ self.engines[ttype] = proc
+
+ stream = proc.process(t, mapping)
+ if self.minchunk:
+ stream = util.increasingchunks(stream, min=self.minchunk,
+ max=self.maxchunk)
+ return stream
+
+def templatepath(name=None):
+ '''return location of template file or directory (if no name).
+ returns None if not found.'''
+ normpaths = []
+
+ # executable version (py2exe) doesn't support __file__
+ if hasattr(sys, 'frozen'):
+ module = sys.executable
+ else:
+ module = __file__
+ for f in path:
+ if f.startswith('/'):
+ p = f
+ else:
+ fl = f.split('/')
+ p = os.path.join(os.path.dirname(module), *fl)
+ if name:
+ p = os.path.join(p, name)
+ if name and os.path.exists(p):
+ return os.path.normpath(p)
+ elif os.path.isdir(p):
+ normpaths.append(os.path.normpath(p))
+
+ return normpaths
+
+def stylemap(styles, paths=None):
+ """Return path to mapfile for a given style.
+
+ Searches mapfile in the following locations:
+ 1. templatepath/style/map
+ 2. templatepath/map-style
+ 3. templatepath/map
+ """
+
+ if paths is None:
+ paths = templatepath()
+ elif isinstance(paths, str):
+ paths = [paths]
+
+ if isinstance(styles, str):
+ styles = [styles]
+
+ for style in styles:
+ if not style:
+ continue
+ locations = [os.path.join(style, 'map'), 'map-' + style]
+ locations.append('map')
+
+ for path in paths:
+ for location in locations:
+ mapfile = os.path.join(path, location)
+ if os.path.isfile(mapfile):
+ return style, mapfile
+
+ raise RuntimeError("No hgweb templates found in %r" % paths)