diff --git a/contrib/churn.py b/contrib/churn.py --- a/contrib/churn.py +++ b/contrib/churn.py @@ -12,7 +12,7 @@ # from mercurial.i18n import gettext as _ -from mercurial import hg, mdiff, cmdutil, ui, util, templater, node +from mercurial import hg, mdiff, cmdutil, ui, util, templatefilters, node import os, sys def get_tty_width(): diff --git a/hgext/interhg.py b/hgext/interhg.py --- a/hgext/interhg.py +++ b/hgext/interhg.py @@ -27,9 +27,9 @@ import re from mercurial.hgweb import hgweb_mod -from mercurial import templater +from mercurial import templatefilters -orig_escape = templater.common_filters["escape"] +orig_escape = templatefilters.filters["escape"] interhg_table = [] @@ -39,7 +39,7 @@ def interhg_escape(x): escstr = regexp.sub(format, escstr) return escstr -templater.common_filters["escape"] = interhg_escape +templatefilters.filters["escape"] = interhg_escape orig_refresh = hgweb_mod.hgweb.refresh diff --git a/hgext/keyword.py b/hgext/keyword.py --- a/hgext/keyword.py +++ b/hgext/keyword.py @@ -78,8 +78,8 @@ like CVS' $Log$, are not supported. A ke "Log = {desc}" expands to the first line of the changeset description. ''' -from mercurial import commands, cmdutil, context, dispatch, filelog -from mercurial import patch, localrepo, revlog, templater, util +from mercurial import commands, cmdutil, context, dispatch, filelog, revlog +from mercurial import patch, localrepo, templater, templatefilters, util from mercurial.node import * from mercurial.i18n import _ import re, shutil, sys, tempfile, time @@ -130,7 +130,7 @@ class kwtemplater(object): kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) self.re_kw = re.compile(kwpat) - templater.common_filters['utcdate'] = utcdate + templatefilters.filters['utcdate'] = utcdate self.ct = cmdutil.changeset_templater(self.ui, self.repo, False, '', False) @@ -149,7 +149,8 @@ class kwtemplater(object): self.ct.use_template(self.templates[kw]) self.ui.pushbuffer() self.ct.show(changenode=fnode, root=self.repo.root, file=self.path) - return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer())) + return '$%s: %s $' % (kw, templatefilters.firstline( + self.ui.popbuffer())) return subfunc(kwsub, data) diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -8,7 +8,7 @@ from node import * from i18n import _ import os, sys, bisect, stat -import mdiff, bdiff, util, templater, patch, errno +import mdiff, bdiff, util, templater, templatefilters, patch, errno revrangesep = ':' @@ -673,7 +673,7 @@ class changeset_templater(changeset_prin def __init__(self, ui, repo, patch, mapfile, buffered): changeset_printer.__init__(self, ui, repo, patch, buffered) - filters = templater.common_filters.copy() + filters = templatefilters.filters.copy() filters['formatnode'] = (ui.debugflag and (lambda x: x) or (lambda x: x[:12])) self.t = templater.templater(mapfile, filters, diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -9,7 +9,7 @@ import os, mimetypes, re from mercurial.node import * from mercurial import mdiff, ui, hg, util, archival, patch, hook -from mercurial import revlog, templater +from mercurial import revlog, templater, templatefilters from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact from request import wsgirequest import webcommands, protocol @@ -288,7 +288,7 @@ class hgweb(object): # create the templater - tmpl = templater.templater(mapfile, templater.common_filters, + tmpl = templater.templater(mapfile, templatefilters.filters, defaults={"url": req.url, "staticurl": staticurl, "urlbase": urlbase, diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -8,8 +8,8 @@ import os from mercurial.i18n import gettext as _ -from mercurial import ui, hg, util, templater -from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen, \ +from mercurial import ui, hg, util, templater, templatefilters +from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\ get_contact from hgweb_mod import hgweb from request import wsgirequest @@ -266,7 +266,7 @@ class hgwebdir(object): if self.stripecount is None: self.stripecount = int(config('web', 'stripes', 1)) mapfile = style_map(templater.templatepath(), style) - tmpl = templater.templater(mapfile, templater.common_filters, + tmpl = templater.templater(mapfile, templatefilters.filters, defaults={"header": header, "footer": footer, "motd": motd, diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py new file mode 100644 --- /dev/null +++ b/mercurial/templatefilters.py @@ -0,0 +1,155 @@ +# template-filters.py - common template expansion filters +# +# Copyright 2005-2008 Matt Mackall +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import cgi, re, os, time, urllib, textwrap +import util, templater + +agescales = [("second", 1), + ("minute", 60), + ("hour", 3600), + ("day", 3600 * 24), + ("week", 3600 * 24 * 7), + ("month", 3600 * 24 * 30), + ("year", 3600 * 24 * 365)] + +agescales.reverse() + +def age(date): + '''turn a (timestamp, tzoff) tuple into an age string.''' + + def plural(t, c): + if c == 1: + return t + return t + "s" + def fmt(t, c): + return "%d %s" % (c, plural(t, c)) + + now = time.time() + then = date[0] + delta = max(1, int(now - then)) + + for t, s in agescales: + n = delta / s + if n >= 2 or s == 1: + return fmt(t, n) + +para_re = None +space_re = None + +def fill(text, width): + '''fill many paragraphs.''' + global para_re, space_re + if para_re is None: + para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) + space_re = re.compile(r' +') + + def findparas(): + start = 0 + while True: + m = para_re.search(text, start) + if not m: + w = len(text) + while w > start and text[w-1].isspace(): w -= 1 + yield text[start:w], text[w:] + break + yield text[start:m.start(0)], m.group(1) + start = m.end(1) + + return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest + for para, rest in findparas()]) + +def firstline(text): + '''return the first line of text''' + try: + return text.splitlines(1)[0].rstrip('\r\n') + except IndexError: + return '' + +def isodate(date): + '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' + return util.datestr(date, format='%Y-%m-%d %H:%M') + +def hgdate(date): + '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.''' + return "%d %d" % date + +def nl2br(text): + '''replace raw newlines with xhtml line breaks.''' + return text.replace('\n', '
\n') + +def obfuscate(text): + text = unicode(text, util._encoding, 'replace') + return ''.join(['&#%d;' % ord(c) for c in text]) + +def domain(author): + '''get domain of author, or empty string if none.''' + f = author.find('@') + if f == -1: return '' + author = author[f+1:] + f = author.find('>') + if f >= 0: author = author[:f] + return author + +def person(author): + '''get name of author, or else username.''' + f = author.find('<') + if f == -1: return util.shortuser(author) + return author[:f].rstrip() + +def shortdate(date): + '''turn (timestamp, tzoff) tuple into iso 8631 date.''' + return util.datestr(date, format='%Y-%m-%d', timezone=False) + +def indent(text, prefix): + '''indent each non-empty line of text after first with prefix.''' + lines = text.splitlines() + num_lines = len(lines) + def indenter(): + for i in xrange(num_lines): + l = lines[i] + if i and l.strip(): + yield prefix + yield l + if i < num_lines - 1 or text.endswith('\n'): + yield '\n' + return "".join(indenter()) + +def permissions(flags): + if "l" in flags: + return "lrwxrwxrwx" + if "x" in flags: + return "-rwxr-xr-x" + return "-rw-r--r--" + +filters = { + "addbreaks": nl2br, + "basename": os.path.basename, + "age": age, + "date": lambda x: util.datestr(x), + "domain": domain, + "email": util.email, + "escape": lambda x: cgi.escape(x, True), + "fill68": lambda x: fill(x, width=68), + "fill76": lambda x: fill(x, width=76), + "firstline": firstline, + "tabindent": lambda x: indent(x, '\t'), + "hgdate": hgdate, + "isodate": isodate, + "obfuscate": obfuscate, + "permissions": permissions, + "person": person, + "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), + "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S", True, "%+03d:%02d"), + "short": lambda x: x[:12], + "shortdate": shortdate, + "stringify": templater.stringify, + "strip": lambda x: x.strip(), + "urlescape": lambda x: urllib.quote(x), + "user": lambda x: util.shortuser(x), + "stringescape": lambda x: x.encode('string_escape'), + } + diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -6,7 +6,7 @@ # of the GNU General Public License, incorporated herein by reference. from i18n import _ -import cgi, re, sys, os, time, urllib, util, textwrap +import re, sys, os def parsestring(s, quoted=True): '''parse a string using simple c-like syntax. @@ -122,157 +122,6 @@ class templater(object): v = self.filters[f](v) yield v -agescales = [("second", 1), - ("minute", 60), - ("hour", 3600), - ("day", 3600 * 24), - ("week", 3600 * 24 * 7), - ("month", 3600 * 24 * 30), - ("year", 3600 * 24 * 365)] - -agescales.reverse() - -def age(date): - '''turn a (timestamp, tzoff) tuple into an age string.''' - - def plural(t, c): - if c == 1: - return t - return t + "s" - def fmt(t, c): - return "%d %s" % (c, plural(t, c)) - - now = time.time() - then = date[0] - delta = max(1, int(now - then)) - - for t, s in agescales: - n = delta / s - if n >= 2 or s == 1: - return fmt(t, n) - -def stringify(thing): - '''turn nested template iterator into string.''' - if hasattr(thing, '__iter__'): - return "".join([stringify(t) for t in thing if t is not None]) - return str(thing) - -para_re = None -space_re = None - -def fill(text, width): - '''fill many paragraphs.''' - global para_re, space_re - if para_re is None: - para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) - space_re = re.compile(r' +') - - def findparas(): - start = 0 - while True: - m = para_re.search(text, start) - if not m: - w = len(text) - while w > start and text[w-1].isspace(): w -= 1 - yield text[start:w], text[w:] - break - yield text[start:m.start(0)], m.group(1) - start = m.end(1) - - return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest - for para, rest in findparas()]) - -def firstline(text): - '''return the first line of text''' - try: - return text.splitlines(1)[0].rstrip('\r\n') - except IndexError: - return '' - -def isodate(date): - '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' - return util.datestr(date, format='%Y-%m-%d %H:%M') - -def hgdate(date): - '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.''' - return "%d %d" % date - -def nl2br(text): - '''replace raw newlines with xhtml line breaks.''' - return text.replace('\n', '
\n') - -def obfuscate(text): - text = unicode(text, util._encoding, 'replace') - return ''.join(['&#%d;' % ord(c) for c in text]) - -def domain(author): - '''get domain of author, or empty string if none.''' - f = author.find('@') - if f == -1: return '' - author = author[f+1:] - f = author.find('>') - if f >= 0: author = author[:f] - return author - -def person(author): - '''get name of author, or else username.''' - f = author.find('<') - if f == -1: return util.shortuser(author) - return author[:f].rstrip() - -def shortdate(date): - '''turn (timestamp, tzoff) tuple into iso 8631 date.''' - return util.datestr(date, format='%Y-%m-%d', timezone=False) - -def indent(text, prefix): - '''indent each non-empty line of text after first with prefix.''' - lines = text.splitlines() - num_lines = len(lines) - def indenter(): - for i in xrange(num_lines): - l = lines[i] - if i and l.strip(): - yield prefix - yield l - if i < num_lines - 1 or text.endswith('\n'): - yield '\n' - return "".join(indenter()) - -def permissions(flags): - if "l" in flags: - return "lrwxrwxrwx" - if "x" in flags: - return "-rwxr-xr-x" - return "-rw-r--r--" - -common_filters = { - "addbreaks": nl2br, - "basename": os.path.basename, - "age": age, - "date": lambda x: util.datestr(x), - "domain": domain, - "email": util.email, - "escape": lambda x: cgi.escape(x, True), - "fill68": lambda x: fill(x, width=68), - "fill76": lambda x: fill(x, width=76), - "firstline": firstline, - "tabindent": lambda x: indent(x, '\t'), - "hgdate": hgdate, - "isodate": isodate, - "obfuscate": obfuscate, - "permissions": permissions, - "person": person, - "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), - "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S", True, "%+03d:%02d"), - "short": lambda x: x[:12], - "shortdate": shortdate, - "stringify": stringify, - "strip": lambda x: x.strip(), - "urlescape": lambda x: urllib.quote(x), - "user": lambda x: util.shortuser(x), - "stringescape": lambda x: x.encode('string_escape'), - } - def templatepath(name=None): '''return location of template file or directory (if no name). returns None if not found.''' @@ -289,3 +138,9 @@ def templatepath(name=None): if (name and os.path.exists(p)) or os.path.isdir(p): return os.path.normpath(p) +def stringify(thing): + '''turn nested template iterator into string.''' + if hasattr(thing, '__iter__'): + return "".join([stringify(t) for t in thing if t is not None]) + return str(thing) +