##// END OF EJS Templates
templater: add hex filter.
Dan Villiom Podlaski Christiansen -
r12371:48a4acd1 default
parent child Browse files
Show More
@@ -1,156 +1,159 b''
1 1 Mercurial allows you to customize output of commands through
2 2 templates. You can either pass in a template from the command
3 3 line, via the --template option, or select an existing
4 4 template-style (--style).
5 5
6 6 You can customize output for any "log-like" command: log,
7 7 outgoing, incoming, tip, parents, heads and glog.
8 8
9 9 Four styles are packaged with Mercurial: default (the style used
10 10 when no explicit preference is passed), compact, changelog,
11 11 and xml.
12 12 Usage::
13 13
14 14 $ hg log -r1 --style changelog
15 15
16 16 A template is a piece of text, with markup to invoke variable
17 17 expansion::
18 18
19 19 $ hg log -r1 --template "{node}\n"
20 20 b56ce7b07c52de7d5fd79fb89701ea538af65746
21 21
22 22 Strings in curly braces are called keywords. The availability of
23 23 keywords depends on the exact context of the templater. These
24 24 keywords are usually available for templating a log-like command:
25 25
26 26 :author: String. The unmodified author of the changeset.
27 27
28 28 :branches: String. The name of the branch on which the changeset was
29 29 committed. Will be empty if the branch name was default.
30 30
31 31 :children: List of strings. The children of the changeset.
32 32
33 33 :date: Date information. The date when the changeset was committed.
34 34
35 35 :desc: String. The text of the changeset description.
36 36
37 37 :diffstat: String. Statistics of changes with the following format:
38 38 "modified files: +added/-removed lines"
39 39
40 40 :files: List of strings. All files modified, added, or removed by this
41 41 changeset.
42 42
43 43 :file_adds: List of strings. Files added by this changeset.
44 44
45 45 :file_copies: List of strings. Files copied in this changeset with
46 46 their sources.
47 47
48 48 :file_copies_switch: List of strings. Like "file_copies" but displayed
49 49 only if the --copied switch is set.
50 50
51 51 :file_mods: List of strings. Files modified by this changeset.
52 52
53 53 :file_dels: List of strings. Files removed by this changeset.
54 54
55 55 :node: String. The changeset identification hash, as a 40 hexadecimal
56 56 digit string.
57 57
58 58 :parents: List of strings. The parents of the changeset.
59 59
60 60 :rev: Integer. The repository-local changeset revision number.
61 61
62 62 :tags: List of strings. Any tags associated with the changeset.
63 63
64 64 :latesttag: String. Most recent global tag in the ancestors of this
65 65 changeset.
66 66
67 67 :latesttagdistance: Integer. Longest path to the latest tag.
68 68
69 69 The "date" keyword does not produce human-readable output. If you
70 70 want to use a date in your output, you can use a filter to process
71 71 it. Filters are functions which return a string based on the input
72 72 variable. Be sure to use the stringify filter first when you're
73 73 applying a string-input filter to a list-like input variable.
74 74 You can also use a chain of filters to get the desired output::
75 75
76 76 $ hg tip --template "{date|isodate}\n"
77 77 2008-08-21 18:22 +0000
78 78
79 79 List of filters:
80 80
81 81 :addbreaks: Any text. Add an XHTML "<br />" tag before the end of
82 82 every line except the last.
83 83
84 84 :age: Date. Returns a human-readable date/time difference between the
85 85 given date/time and the current date/time.
86 86
87 87 :basename: Any text. Treats the text as a path, and returns the last
88 88 component of the path after splitting by the path separator
89 89 (ignoring trailing separators). For example, "foo/bar/baz" becomes
90 90 "baz" and "foo/bar//" becomes "bar".
91 91
92 92 :stripdir: Treat the text as path and strip a directory level, if
93 93 possible. For example, "foo" and "foo/bar" becomes "foo".
94 94
95 95 :date: Date. Returns a date in a Unix date format, including the
96 96 timezone: "Mon Sep 04 15:13:13 2006 0700".
97 97
98 98 :domain: Any text. Finds the first string that looks like an email
99 99 address, and extracts just the domain component. Example: ``User
100 100 <user@example.com>`` becomes ``example.com``.
101 101
102 102 :email: Any text. Extracts the first string that looks like an email
103 103 address. Example: ``User <user@example.com>`` becomes
104 104 ``user@example.com``.
105 105
106 106 :escape: Any text. Replaces the special XML/XHTML characters "&", "<"
107 107 and ">" with XML entities.
108 108
109 :hex: Any text. Convert a binary Mercurial node identifier into
110 its long hexadecimal representation.
111
109 112 :fill68: Any text. Wraps the text to fit in 68 columns.
110 113
111 114 :fill76: Any text. Wraps the text to fit in 76 columns.
112 115
113 116 :firstline: Any text. Returns the first line of text.
114 117
115 118 :nonempty: Any text. Returns '(none)' if the string is empty.
116 119
117 120 :hgdate: Date. Returns the date as a pair of numbers: "1157407993
118 121 25200" (Unix timestamp, timezone offset).
119 122
120 123 :isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
121 124 +0200".
122 125
123 126 :isodatesec: Date. Returns the date in ISO 8601 format, including
124 127 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
125 128 filter.
126 129
127 130 :localdate: Date. Converts a date to local date.
128 131
129 132 :obfuscate: Any text. Returns the input text rendered as a sequence of
130 133 XML entities.
131 134
132 135 :person: Any text. Returns the text before an email address.
133 136
134 137 :rfc822date: Date. Returns a date using the same format used in email
135 138 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
136 139
137 140 :rfc3339date: Date. Returns a date using the Internet date format
138 141 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
139 142
140 143 :short: Changeset hash. Returns the short form of a changeset hash,
141 144 i.e. a 12 hexadecimal digit string.
142 145
143 146 :shortdate: Date. Returns a date like "2006-09-18".
144 147
145 148 :stringify: Any type. Turns the value into text by converting values into
146 149 text and concatenating them.
147 150
148 151 :strip: Any text. Strips all leading and trailing whitespace.
149 152
150 153 :tabindent: Any text. Returns the text, with every line except the
151 154 first starting with a tab character.
152 155
153 156 :urlescape: Any text. Escapes all "special" characters. For example,
154 157 "foo bar" becomes "foo%20bar".
155 158
156 159 :user: Any text. Returns the user portion of an email address.
@@ -1,227 +1,228 b''
1 1 # template-filters.py - common template expansion filters
2 2 #
3 3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import cgi, re, os, time, urllib
9 import util, encoding
9 import encoding, node, util
10 10
11 11 def stringify(thing):
12 12 '''turn nested template iterator into string.'''
13 13 if hasattr(thing, '__iter__') and not isinstance(thing, str):
14 14 return "".join([stringify(t) for t in thing if t is not None])
15 15 return str(thing)
16 16
17 17 agescales = [("year", 3600 * 24 * 365),
18 18 ("month", 3600 * 24 * 30),
19 19 ("week", 3600 * 24 * 7),
20 20 ("day", 3600 * 24),
21 21 ("hour", 3600),
22 22 ("minute", 60),
23 23 ("second", 1)]
24 24
25 25 def age(date):
26 26 '''turn a (timestamp, tzoff) tuple into an age string.'''
27 27
28 28 def plural(t, c):
29 29 if c == 1:
30 30 return t
31 31 return t + "s"
32 32 def fmt(t, c):
33 33 return "%d %s" % (c, plural(t, c))
34 34
35 35 now = time.time()
36 36 then = date[0]
37 37 if then > now:
38 38 return 'in the future'
39 39
40 40 delta = max(1, int(now - then))
41 41 if delta > agescales[0][1] * 2:
42 42 return util.shortdate(date)
43 43
44 44 for t, s in agescales:
45 45 n = delta // s
46 46 if n >= 2 or s == 1:
47 47 return '%s ago' % fmt(t, n)
48 48
49 49 para_re = None
50 50 space_re = None
51 51
52 52 def fill(text, width):
53 53 '''fill many paragraphs.'''
54 54 global para_re, space_re
55 55 if para_re is None:
56 56 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
57 57 space_re = re.compile(r' +')
58 58
59 59 def findparas():
60 60 start = 0
61 61 while True:
62 62 m = para_re.search(text, start)
63 63 if not m:
64 64 uctext = unicode(text[start:], encoding.encoding)
65 65 w = len(uctext)
66 66 while 0 < w and uctext[w - 1].isspace():
67 67 w -= 1
68 68 yield (uctext[:w].encode(encoding.encoding),
69 69 uctext[w:].encode(encoding.encoding))
70 70 break
71 71 yield text[start:m.start(0)], m.group(1)
72 72 start = m.end(1)
73 73
74 74 return "".join([space_re.sub(' ', util.wrap(para, width=width)) + rest
75 75 for para, rest in findparas()])
76 76
77 77 def firstline(text):
78 78 '''return the first line of text'''
79 79 try:
80 80 return text.splitlines(True)[0].rstrip('\r\n')
81 81 except IndexError:
82 82 return ''
83 83
84 84 def nl2br(text):
85 85 '''replace raw newlines with xhtml line breaks.'''
86 86 return text.replace('\n', '<br/>\n')
87 87
88 88 def obfuscate(text):
89 89 text = unicode(text, encoding.encoding, 'replace')
90 90 return ''.join(['&#%d;' % ord(c) for c in text])
91 91
92 92 def domain(author):
93 93 '''get domain of author, or empty string if none.'''
94 94 f = author.find('@')
95 95 if f == -1:
96 96 return ''
97 97 author = author[f + 1:]
98 98 f = author.find('>')
99 99 if f >= 0:
100 100 author = author[:f]
101 101 return author
102 102
103 103 def person(author):
104 104 '''get name of author, or else username.'''
105 105 if not '@' in author:
106 106 return author
107 107 f = author.find('<')
108 108 if f == -1:
109 109 return util.shortuser(author)
110 110 return author[:f].rstrip()
111 111
112 112 def indent(text, prefix):
113 113 '''indent each non-empty line of text after first with prefix.'''
114 114 lines = text.splitlines()
115 115 num_lines = len(lines)
116 116 endswithnewline = text[-1:] == '\n'
117 117 def indenter():
118 118 for i in xrange(num_lines):
119 119 l = lines[i]
120 120 if i and l.strip():
121 121 yield prefix
122 122 yield l
123 123 if i < num_lines - 1 or endswithnewline:
124 124 yield '\n'
125 125 return "".join(indenter())
126 126
127 127 def permissions(flags):
128 128 if "l" in flags:
129 129 return "lrwxrwxrwx"
130 130 if "x" in flags:
131 131 return "-rwxr-xr-x"
132 132 return "-rw-r--r--"
133 133
134 134 def xmlescape(text):
135 135 text = (text
136 136 .replace('&', '&amp;')
137 137 .replace('<', '&lt;')
138 138 .replace('>', '&gt;')
139 139 .replace('"', '&quot;')
140 140 .replace("'", '&#39;')) # &apos; invalid in HTML
141 141 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
142 142
143 143 def uescape(c):
144 144 if ord(c) < 0x80:
145 145 return c
146 146 else:
147 147 return '\\u%04x' % ord(c)
148 148
149 149 _escapes = [
150 150 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
151 151 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
152 152 ]
153 153
154 154 def jsonescape(s):
155 155 for k, v in _escapes:
156 156 s = s.replace(k, v)
157 157 return ''.join(uescape(c) for c in s)
158 158
159 159 def json(obj):
160 160 if obj is None or obj is False or obj is True:
161 161 return {None: 'null', False: 'false', True: 'true'}[obj]
162 162 elif isinstance(obj, int) or isinstance(obj, float):
163 163 return str(obj)
164 164 elif isinstance(obj, str):
165 165 u = unicode(obj, encoding.encoding, 'replace')
166 166 return '"%s"' % jsonescape(u)
167 167 elif isinstance(obj, unicode):
168 168 return '"%s"' % jsonescape(obj)
169 169 elif hasattr(obj, 'keys'):
170 170 out = []
171 171 for k, v in obj.iteritems():
172 172 s = '%s: %s' % (json(k), json(v))
173 173 out.append(s)
174 174 return '{' + ', '.join(out) + '}'
175 175 elif hasattr(obj, '__iter__'):
176 176 out = []
177 177 for i in obj:
178 178 out.append(json(i))
179 179 return '[' + ', '.join(out) + ']'
180 180 else:
181 181 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
182 182
183 183 def stripdir(text):
184 184 '''Treat the text as path and strip a directory level, if possible.'''
185 185 dir = os.path.dirname(text)
186 186 if dir == "":
187 187 return os.path.basename(text)
188 188 else:
189 189 return dir
190 190
191 191 def nonempty(str):
192 192 return str or "(none)"
193 193
194 194 filters = {
195 195 "addbreaks": nl2br,
196 196 "basename": os.path.basename,
197 197 "stripdir": stripdir,
198 198 "age": age,
199 199 "date": lambda x: util.datestr(x),
200 200 "domain": domain,
201 201 "email": util.email,
202 202 "escape": lambda x: cgi.escape(x, True),
203 203 "fill68": lambda x: fill(x, width=68),
204 204 "fill76": lambda x: fill(x, width=76),
205 205 "firstline": firstline,
206 206 "tabindent": lambda x: indent(x, '\t'),
207 207 "hgdate": lambda x: "%d %d" % x,
208 208 "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
209 209 "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
210 210 "json": json,
211 211 "jsonescape": jsonescape,
212 212 "localdate": lambda x: (x[0], util.makedate()[1]),
213 213 "nonempty": nonempty,
214 214 "obfuscate": obfuscate,
215 215 "permissions": permissions,
216 216 "person": person,
217 217 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
218 218 "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
219 "hex": node.hex,
219 220 "short": lambda x: x[:12],
220 221 "shortdate": util.shortdate,
221 222 "stringify": stringify,
222 223 "strip": lambda x: x.strip(),
223 224 "urlescape": lambda x: urllib.quote(x),
224 225 "user": lambda x: util.shortuser(x),
225 226 "stringescape": lambda x: x.encode('string_escape'),
226 227 "xmlescape": xmlescape,
227 228 }
General Comments 0
You need to be logged in to leave comments. Login now