mercurial/templater.py
changeset 1909 37b9f80a5fbb
parent 1906 9dec2479622d
child 1912 b288b4bb8448
equal deleted inserted replaced
1908:be71c04d62c0 1909:37b9f80a5fbb
       
     1 # templater.py - template expansion for output
       
     2 #
       
     3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms
       
     6 # of the GNU General Public License, incorporated herein by reference.
       
     7 
     1 import re
     8 import re
     2 from demandload import demandload
     9 from demandload import demandload
     3 from i18n import gettext as _
    10 from i18n import gettext as _
     4 demandload(globals(), "cStringIO cgi os time urllib util")
    11 demandload(globals(), "cStringIO cgi os time urllib util")
     5 
    12 
    10     'n': '\n',
    17     'n': '\n',
    11     'v': '\v',
    18     'v': '\v',
    12     }
    19     }
    13 
    20 
    14 def parsestring(s, quoted=True):
    21 def parsestring(s, quoted=True):
       
    22     '''parse a string using simple c-like syntax.
       
    23     string must be in quotes if quoted is True.'''
    15     fp = cStringIO.StringIO()
    24     fp = cStringIO.StringIO()
    16     if quoted:
    25     if quoted:
    17         first = s[0]
    26         first = s[0]
    18         if len(s) < 2: raise SyntaxError(_('string too short'))
    27         if len(s) < 2: raise SyntaxError(_('string too short'))
    19         if first not in "'\"": raise SyntaxError(_('invalid quote'))
    28         if first not in "'\"": raise SyntaxError(_('invalid quote'))
    29         else: fp.write(c)
    38         else: fp.write(c)
    30     if escape: raise SyntaxError(_('unterminated escape'))
    39     if escape: raise SyntaxError(_('unterminated escape'))
    31     return fp.getvalue()
    40     return fp.getvalue()
    32 
    41 
    33 class templater(object):
    42 class templater(object):
       
    43     '''template expansion engine.
       
    44 
       
    45     template expansion works like this. a map file contains key=value
       
    46     pairs. if value is quoted, it is treated as string. otherwise, it
       
    47     is treated as name of template file.
       
    48 
       
    49     templater is asked to expand a key in map. it looks up key, and
       
    50     looks for atrings like this: {foo}. it expands {foo} by looking up
       
    51     foo in map, and substituting it. expansion is recursive: it stops
       
    52     when there is no more {foo} to replace.
       
    53 
       
    54     expansion also allows formatting and filtering.
       
    55 
       
    56     format uses key to expand each item in list. syntax is
       
    57     {key%format}.
       
    58 
       
    59     filter uses function to transform value. syntax is
       
    60     {key|filter1|filter2|...}.'''
       
    61 
    34     def __init__(self, mapfile, filters={}, defaults={}):
    62     def __init__(self, mapfile, filters={}, defaults={}):
       
    63         '''set up template engine.
       
    64         mapfile is name of file to read map definitions from.
       
    65         filters is dict of functions. each transforms a value into another.
       
    66         defaults is dict of default map definitions.'''
    35         self.mapfile = mapfile or 'template'
    67         self.mapfile = mapfile or 'template'
    36         self.cache = {}
    68         self.cache = {}
    37         self.map = {}
    69         self.map = {}
    38         self.base = (mapfile and os.path.dirname(mapfile)) or ''
    70         self.base = (mapfile and os.path.dirname(mapfile)) or ''
    39         self.filters = filters
    71         self.filters = filters
    62 
    94 
    63     def __contains__(self, key):
    95     def __contains__(self, key):
    64         return key in self.cache
    96         return key in self.cache
    65 
    97 
    66     def __call__(self, t, **map):
    98     def __call__(self, t, **map):
       
    99         '''perform expansion.
       
   100         t is name of map element to expand.
       
   101         map is added elements to use during expansion.'''
    67         m = self.defaults.copy()
   102         m = self.defaults.copy()
    68         m.update(map)
   103         m.update(map)
    69         try:
   104         try:
    70             tmpl = self.cache[t]
   105             tmpl = self.cache[t]
    71         except KeyError:
   106         except KeyError:
   125              ("month", 3600 * 24 * 30),
   160              ("month", 3600 * 24 * 30),
   126              ("year", 3600 * 24 * 365)]
   161              ("year", 3600 * 24 * 365)]
   127 
   162 
   128 agescales.reverse()
   163 agescales.reverse()
   129 
   164 
   130 def age(x):
   165 def age(date):
       
   166     '''turn a (timestamp, tzoff) tuple into an age string.'''
       
   167 
   131     def plural(t, c):
   168     def plural(t, c):
   132         if c == 1:
   169         if c == 1:
   133             return t
   170             return t
   134         return t + "s"
   171         return t + "s"
   135     def fmt(t, c):
   172     def fmt(t, c):
   136         return "%d %s" % (c, plural(t, c))
   173         return "%d %s" % (c, plural(t, c))
   137 
   174 
   138     now = time.time()
   175     now = time.time()
   139     then = x[0]
   176     then = date[0]
   140     delta = max(1, int(now - then))
   177     delta = max(1, int(now - then))
   141 
   178 
   142     for t, s in agescales:
   179     for t, s in agescales:
   143         n = delta / s
   180         n = delta / s
   144         if n >= 2 or s == 1:
   181         if n >= 2 or s == 1:
   145             return fmt(t, n)
   182             return fmt(t, n)
   146 
   183 
   147 def isodate(date):
   184 def isodate(date):
       
   185     '''turn a (timestamp, tzoff) tuple into an iso 8631 date.'''
   148     return util.datestr(date, format='%Y-%m-%d %H:%M')
   186     return util.datestr(date, format='%Y-%m-%d %H:%M')
   149 
   187 
   150 def nl2br(text):
   188 def nl2br(text):
       
   189     '''replace raw newlines with xhtml line breaks.'''
   151     return text.replace('\n', '<br/>\n')
   190     return text.replace('\n', '<br/>\n')
   152 
   191 
   153 def obfuscate(text):
   192 def obfuscate(text):
   154     return ''.join(['&#%d;' % ord(c) for c in text])
   193     return ''.join(['&#%d;' % ord(c) for c in text])
   155 
   194 
   156 def domain(author):
   195 def domain(author):
       
   196     '''get domain of author, or empty string if none.'''
   157     f = author.find('@')
   197     f = author.find('@')
   158     if f == -1: return ''
   198     if f == -1: return ''
   159     author = author[f+1:]
   199     author = author[f+1:]
   160     f = author.find('>')
   200     f = author.find('>')
   161     if f >= 0: author = author[:f]
   201     if f >= 0: author = author[:f]
   162     return author
   202     return author
   163 
   203 
   164 def person(author):
   204 def person(author):
       
   205     '''get name of author, or else username.'''
   165     f = author.find('<')
   206     f = author.find('<')
   166     if f == -1: return util.shortuser(author)
   207     if f == -1: return util.shortuser(author)
   167     return author[:f].rstrip()
   208     return author[:f].rstrip()
   168 
   209 
   169 common_filters = {
   210 common_filters = {
   183     "urlescape": urllib.quote,
   224     "urlescape": urllib.quote,
   184     "user": util.shortuser,
   225     "user": util.shortuser,
   185     }
   226     }
   186 
   227 
   187 def templatepath(name=None):
   228 def templatepath(name=None):
       
   229     '''return location of template file or directory (if no name).
       
   230     returns None if not found.'''
   188     for f in 'templates', '../templates':
   231     for f in 'templates', '../templates':
   189         fl = f.split('/')
   232         fl = f.split('/')
   190         if name: fl.append(name)
   233         if name: fl.append(name)
   191         p = os.path.join(os.path.dirname(__file__), *fl)
   234         p = os.path.join(os.path.dirname(__file__), *fl)
   192         if (name and os.path.exists(p)) or os.path.isdir(p):
   235         if (name and os.path.exists(p)) or os.path.isdir(p):