Show More
@@ -1,189 +1,193 | |||||
1 | import re |
|
1 | import re | |
2 | from demandload import demandload |
|
2 | from demandload import demandload | |
3 | from i18n import gettext as _ |
|
3 | from i18n import gettext as _ | |
4 | demandload(globals(), "cStringIO cgi os time urllib util") |
|
4 | demandload(globals(), "cStringIO cgi os time urllib util") | |
5 |
|
5 | |||
6 | esctable = { |
|
6 | esctable = { | |
7 | '\\': '\\', |
|
7 | '\\': '\\', | |
8 | 'r': '\r', |
|
8 | 'r': '\r', | |
9 | 't': '\t', |
|
9 | 't': '\t', | |
10 | 'n': '\n', |
|
10 | 'n': '\n', | |
11 | 'v': '\v', |
|
11 | 'v': '\v', | |
12 | } |
|
12 | } | |
13 |
|
13 | |||
14 | def parsestring(s, quoted=True): |
|
14 | def parsestring(s, quoted=True): | |
15 | fp = cStringIO.StringIO() |
|
15 | fp = cStringIO.StringIO() | |
16 | if quoted: |
|
16 | if quoted: | |
17 | first = s[0] |
|
17 | first = s[0] | |
18 | if len(s) < 2: raise SyntaxError(_('string too short')) |
|
18 | if len(s) < 2: raise SyntaxError(_('string too short')) | |
19 | if first not in "'\"": raise SyntaxError(_('invalid quote')) |
|
19 | if first not in "'\"": raise SyntaxError(_('invalid quote')) | |
20 | if s[-1] != first: raise SyntaxError(_('unmatched quotes')) |
|
20 | if s[-1] != first: raise SyntaxError(_('unmatched quotes')) | |
21 | s = s[1:-1] |
|
21 | s = s[1:-1] | |
22 | escape = False |
|
22 | escape = False | |
23 | for c in s: |
|
23 | for c in s: | |
24 | if escape: |
|
24 | if escape: | |
25 | fp.write(esctable.get(c, c)) |
|
25 | fp.write(esctable.get(c, c)) | |
26 | escape = False |
|
26 | escape = False | |
27 | elif c == '\\': escape = True |
|
27 | elif c == '\\': escape = True | |
28 | elif quoted and c == first: raise SyntaxError(_('string ends early')) |
|
28 | elif quoted and c == first: raise SyntaxError(_('string ends early')) | |
29 | else: fp.write(c) |
|
29 | else: fp.write(c) | |
30 | if escape: raise SyntaxError(_('unterminated escape')) |
|
30 | if escape: raise SyntaxError(_('unterminated escape')) | |
31 | return fp.getvalue() |
|
31 | return fp.getvalue() | |
32 |
|
32 | |||
33 | class templater(object): |
|
33 | class templater(object): | |
34 | def __init__(self, mapfile, filters={}, defaults={}): |
|
34 | def __init__(self, mapfile, filters={}, defaults={}): | |
35 | self.mapfile = mapfile or 'template' |
|
35 | self.mapfile = mapfile or 'template' | |
36 | self.cache = {} |
|
36 | self.cache = {} | |
37 | self.map = {} |
|
37 | self.map = {} | |
38 | self.base = (mapfile and os.path.dirname(mapfile)) or '' |
|
38 | self.base = (mapfile and os.path.dirname(mapfile)) or '' | |
39 | self.filters = filters |
|
39 | self.filters = filters | |
40 | self.defaults = defaults |
|
40 | self.defaults = defaults | |
41 |
|
41 | |||
42 | if not mapfile: |
|
42 | if not mapfile: | |
43 | return |
|
43 | return | |
44 | i = 0 |
|
44 | i = 0 | |
45 | for l in file(mapfile): |
|
45 | for l in file(mapfile): | |
46 | l = l.rstrip('\r\n') |
|
46 | l = l.rstrip('\r\n') | |
47 | i += 1 |
|
47 | i += 1 | |
48 | if l.startswith('#') or not l.strip(): continue |
|
48 | if l.startswith('#') or not l.strip(): continue | |
49 | m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l) |
|
49 | m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l) | |
50 | if m: |
|
50 | if m: | |
51 | key, val = m.groups() |
|
51 | key, val = m.groups() | |
52 | if val[0] in "'\"": |
|
52 | if val[0] in "'\"": | |
53 | try: |
|
53 | try: | |
54 | self.cache[key] = parsestring(val) |
|
54 | self.cache[key] = parsestring(val) | |
55 | except SyntaxError, inst: |
|
55 | except SyntaxError, inst: | |
56 | raise SyntaxError('%s:%s: %s' % |
|
56 | raise SyntaxError('%s:%s: %s' % | |
57 | (mapfile, i, inst.args[0])) |
|
57 | (mapfile, i, inst.args[0])) | |
58 | else: |
|
58 | else: | |
59 | self.map[key] = os.path.join(self.base, val) |
|
59 | self.map[key] = os.path.join(self.base, val) | |
60 | else: |
|
60 | else: | |
61 | raise SyntaxError(_("%s:%s: parse error") % (mapfile, i)) |
|
61 | raise SyntaxError(_("%s:%s: parse error") % (mapfile, i)) | |
62 |
|
62 | |||
63 | def __contains__(self, key): |
|
63 | def __contains__(self, key): | |
64 | return key in self.cache |
|
64 | return key in self.cache | |
65 |
|
65 | |||
66 | def __call__(self, t, **map): |
|
66 | def __call__(self, t, **map): | |
67 | m = self.defaults.copy() |
|
67 | m = self.defaults.copy() | |
68 | m.update(map) |
|
68 | m.update(map) | |
69 | try: |
|
69 | try: | |
70 | tmpl = self.cache[t] |
|
70 | tmpl = self.cache[t] | |
71 | except KeyError: |
|
71 | except KeyError: | |
72 | try: |
|
72 | try: | |
73 | tmpl = self.cache[t] = file(self.map[t]).read() |
|
73 | tmpl = self.cache[t] = file(self.map[t]).read() | |
74 | except IOError, inst: |
|
74 | except IOError, inst: | |
75 | raise IOError(inst.args[0], _('template file %s: %s') % |
|
75 | raise IOError(inst.args[0], _('template file %s: %s') % | |
76 | (self.map[t], inst.args[1])) |
|
76 | (self.map[t], inst.args[1])) | |
77 | return self.template(tmpl, self.filters, **m) |
|
77 | return self.template(tmpl, self.filters, **m) | |
78 |
|
78 | |||
79 | template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)" |
|
79 | template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)" | |
80 | r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)" |
|
80 | r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)" | |
81 | r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]") |
|
81 | r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]") | |
82 |
|
82 | |||
83 | def template(self, tmpl, filters={}, **map): |
|
83 | def template(self, tmpl, filters={}, **map): | |
84 | lm = map.copy() |
|
84 | lm = map.copy() | |
85 | while tmpl: |
|
85 | while tmpl: | |
86 | m = self.template_re.search(tmpl) |
|
86 | m = self.template_re.search(tmpl) | |
87 | if m: |
|
87 | if m: | |
88 | start, end = m.span(0) |
|
88 | start, end = m.span(0) | |
89 | s, e = tmpl[start], tmpl[end - 1] |
|
89 | s, e = tmpl[start], tmpl[end - 1] | |
90 | key = m.group(1) |
|
90 | key = m.group(1) | |
91 | if ((s == '#' and e != '#') or (s == '{' and e != '}')): |
|
91 | if ((s == '#' and e != '#') or (s == '{' and e != '}')): | |
92 | raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") % |
|
92 | raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") % | |
93 | (s, e, key)) |
|
93 | (s, e, key)) | |
94 | if start: |
|
94 | if start: | |
95 | yield tmpl[:start] |
|
95 | yield tmpl[:start] | |
96 | v = map.get(key, "") |
|
96 | v = map.get(key, "") | |
97 | v = callable(v) and v(**map) or v |
|
97 | v = callable(v) and v(**map) or v | |
98 |
|
98 | |||
99 | format = m.group(2) |
|
99 | format = m.group(2) | |
100 | fl = m.group(4) |
|
100 | fl = m.group(4) | |
101 |
|
101 | |||
102 | if format: |
|
102 | if format: | |
103 | q = v.__iter__ |
|
103 | q = v.__iter__ | |
104 | for i in q(): |
|
104 | for i in q(): | |
105 | lm.update(i) |
|
105 | lm.update(i) | |
106 | yield self(format[1:], **lm) |
|
106 | yield self(format[1:], **lm) | |
107 |
|
107 | |||
108 | v = "" |
|
108 | v = "" | |
109 |
|
109 | |||
110 | elif fl: |
|
110 | elif fl: | |
111 | for f in fl.split("|")[1:]: |
|
111 | for f in fl.split("|")[1:]: | |
112 | v = filters[f](v) |
|
112 | v = filters[f](v) | |
113 |
|
113 | |||
114 | yield v |
|
114 | yield v | |
115 | tmpl = tmpl[end:] |
|
115 | tmpl = tmpl[end:] | |
116 | else: |
|
116 | else: | |
117 | yield tmpl |
|
117 | yield tmpl | |
118 | break |
|
118 | break | |
119 |
|
119 | |||
120 | agescales = [("second", 1), |
|
120 | agescales = [("second", 1), | |
121 | ("minute", 60), |
|
121 | ("minute", 60), | |
122 | ("hour", 3600), |
|
122 | ("hour", 3600), | |
123 | ("day", 3600 * 24), |
|
123 | ("day", 3600 * 24), | |
124 | ("week", 3600 * 24 * 7), |
|
124 | ("week", 3600 * 24 * 7), | |
125 | ("month", 3600 * 24 * 30), |
|
125 | ("month", 3600 * 24 * 30), | |
126 | ("year", 3600 * 24 * 365)] |
|
126 | ("year", 3600 * 24 * 365)] | |
127 |
|
127 | |||
128 | agescales.reverse() |
|
128 | agescales.reverse() | |
129 |
|
129 | |||
130 | def age(x): |
|
130 | def age(x): | |
131 | def plural(t, c): |
|
131 | def plural(t, c): | |
132 | if c == 1: |
|
132 | if c == 1: | |
133 | return t |
|
133 | return t | |
134 | return t + "s" |
|
134 | return t + "s" | |
135 | def fmt(t, c): |
|
135 | def fmt(t, c): | |
136 | return "%d %s" % (c, plural(t, c)) |
|
136 | return "%d %s" % (c, plural(t, c)) | |
137 |
|
137 | |||
138 | now = time.time() |
|
138 | now = time.time() | |
139 | then = x[0] |
|
139 | then = x[0] | |
140 | delta = max(1, int(now - then)) |
|
140 | delta = max(1, int(now - then)) | |
141 |
|
141 | |||
142 | for t, s in agescales: |
|
142 | for t, s in agescales: | |
143 | n = delta / s |
|
143 | n = delta / s | |
144 | if n >= 2 or s == 1: |
|
144 | if n >= 2 or s == 1: | |
145 | return fmt(t, n) |
|
145 | return fmt(t, n) | |
146 |
|
146 | |||
|
147 | def isodate(date): | |||
|
148 | return util.datestr(date, format='%Y-%m-%d %H:%M') | |||
|
149 | ||||
147 | def nl2br(text): |
|
150 | def nl2br(text): | |
148 | return text.replace('\n', '<br/>\n') |
|
151 | return text.replace('\n', '<br/>\n') | |
149 |
|
152 | |||
150 | def obfuscate(text): |
|
153 | def obfuscate(text): | |
151 | return ''.join(['&#%d;' % ord(c) for c in text]) |
|
154 | return ''.join(['&#%d;' % ord(c) for c in text]) | |
152 |
|
155 | |||
153 | def domain(author): |
|
156 | def domain(author): | |
154 | f = author.find('@') |
|
157 | f = author.find('@') | |
155 | if f == -1: return '' |
|
158 | if f == -1: return '' | |
156 | author = author[f+1:] |
|
159 | author = author[f+1:] | |
157 | f = author.find('>') |
|
160 | f = author.find('>') | |
158 | if f >= 0: author = author[:f] |
|
161 | if f >= 0: author = author[:f] | |
159 | return author |
|
162 | return author | |
160 |
|
163 | |||
161 | def person(author): |
|
164 | def person(author): | |
162 | f = author.find('<') |
|
165 | f = author.find('<') | |
163 | if f == -1: return util.shortuser(author) |
|
166 | if f == -1: return util.shortuser(author) | |
164 | return author[:f].rstrip() |
|
167 | return author[:f].rstrip() | |
165 |
|
168 | |||
166 | common_filters = { |
|
169 | common_filters = { | |
167 | "addbreaks": nl2br, |
|
170 | "addbreaks": nl2br, | |
168 | "age": age, |
|
171 | "age": age, | |
169 | "date": lambda x: util.datestr(x), |
|
172 | "date": lambda x: util.datestr(x), | |
170 | "domain": domain, |
|
173 | "domain": domain, | |
171 | "escape": lambda x: cgi.escape(x, True), |
|
174 | "escape": lambda x: cgi.escape(x, True), | |
172 | "firstline": (lambda x: x.splitlines(1)[0]), |
|
175 | "firstline": (lambda x: x.splitlines(1)[0]), | |
|
176 | "isodate": isodate, | |||
173 | "obfuscate": obfuscate, |
|
177 | "obfuscate": obfuscate, | |
174 | "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"), |
|
178 | "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"), | |
175 | "person": person, |
|
179 | "person": person, | |
176 | "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), |
|
180 | "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), | |
177 | "short": (lambda x: x[:12]), |
|
181 | "short": (lambda x: x[:12]), | |
178 | "strip": lambda x: x.strip(), |
|
182 | "strip": lambda x: x.strip(), | |
179 | "urlescape": urllib.quote, |
|
183 | "urlescape": urllib.quote, | |
180 | "user": util.shortuser, |
|
184 | "user": util.shortuser, | |
181 | } |
|
185 | } | |
182 |
|
186 | |||
183 | def templatepath(name=None): |
|
187 | def templatepath(name=None): | |
184 | for f in 'templates', '../templates': |
|
188 | for f in 'templates', '../templates': | |
185 | fl = f.split('/') |
|
189 | fl = f.split('/') | |
186 | if name: fl.append(name) |
|
190 | if name: fl.append(name) | |
187 | p = os.path.join(os.path.dirname(__file__), *fl) |
|
191 | p = os.path.join(os.path.dirname(__file__), *fl) | |
188 | if (name and os.path.exists(p)) or os.path.isdir(p): |
|
192 | if (name and os.path.exists(p)) or os.path.isdir(p): | |
189 | return os.path.normpath(p) |
|
193 | return os.path.normpath(p) |
General Comments 0
You need to be logged in to leave comments.
Login now