##// END OF EJS Templates
help: extract items doc generation function
Patrick Mezard -
r13593:cc4721ed default
parent child Browse files
Show More
@@ -1,117 +1,133 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import gettext, _
8 from i18n import gettext, _
9 import sys, os
9 import sys, os
10 import extensions
10 import extensions
11
11
12
12
13 def moduledoc(file):
13 def moduledoc(file):
14 '''return the top-level python documentation for the given file
14 '''return the top-level python documentation for the given file
15
15
16 Loosely inspired by pydoc.source_synopsis(), but rewritten to
16 Loosely inspired by pydoc.source_synopsis(), but rewritten to
17 handle triple quotes and to return the whole text instead of just
17 handle triple quotes and to return the whole text instead of just
18 the synopsis'''
18 the synopsis'''
19 result = []
19 result = []
20
20
21 line = file.readline()
21 line = file.readline()
22 while line[:1] == '#' or not line.strip():
22 while line[:1] == '#' or not line.strip():
23 line = file.readline()
23 line = file.readline()
24 if not line:
24 if not line:
25 break
25 break
26
26
27 start = line[:3]
27 start = line[:3]
28 if start == '"""' or start == "'''":
28 if start == '"""' or start == "'''":
29 line = line[3:]
29 line = line[3:]
30 while line:
30 while line:
31 if line.rstrip().endswith(start):
31 if line.rstrip().endswith(start):
32 line = line.split(start)[0]
32 line = line.split(start)[0]
33 if line:
33 if line:
34 result.append(line)
34 result.append(line)
35 break
35 break
36 elif not line:
36 elif not line:
37 return None # unmatched delimiter
37 return None # unmatched delimiter
38 result.append(line)
38 result.append(line)
39 line = file.readline()
39 line = file.readline()
40 else:
40 else:
41 return None
41 return None
42
42
43 return ''.join(result)
43 return ''.join(result)
44
44
45 def listexts(header, exts, maxlength, indent=1):
45 def listexts(header, exts, maxlength, indent=1):
46 '''return a text listing of the given extensions'''
46 '''return a text listing of the given extensions'''
47 if not exts:
47 if not exts:
48 return ''
48 return ''
49 result = '\n%s\n\n' % header
49 result = '\n%s\n\n' % header
50 for name, desc in sorted(exts.iteritems()):
50 for name, desc in sorted(exts.iteritems()):
51 result += '%s%-*s %s\n' % (' ' * indent, maxlength + 2,
51 result += '%s%-*s %s\n' % (' ' * indent, maxlength + 2,
52 ':%s:' % name, desc)
52 ':%s:' % name, desc)
53 return result
53 return result
54
54
55 def extshelp():
55 def extshelp():
56 doc = loaddoc('extensions')()
56 doc = loaddoc('extensions')()
57
57
58 exts, maxlength = extensions.enabled()
58 exts, maxlength = extensions.enabled()
59 doc += listexts(_('enabled extensions:'), exts, maxlength)
59 doc += listexts(_('enabled extensions:'), exts, maxlength)
60
60
61 exts, maxlength = extensions.disabled()
61 exts, maxlength = extensions.disabled()
62 doc += listexts(_('disabled extensions:'), exts, maxlength)
62 doc += listexts(_('disabled extensions:'), exts, maxlength)
63
63
64 return doc
64 return doc
65
65
66 def loaddoc(topic):
66 def loaddoc(topic):
67 """Return a delayed loader for help/topic.txt."""
67 """Return a delayed loader for help/topic.txt."""
68
68
69 def loader():
69 def loader():
70 if hasattr(sys, 'frozen'):
70 if hasattr(sys, 'frozen'):
71 module = sys.executable
71 module = sys.executable
72 else:
72 else:
73 module = __file__
73 module = __file__
74 base = os.path.dirname(module)
74 base = os.path.dirname(module)
75
75
76 for dir in ('.', '..'):
76 for dir in ('.', '..'):
77 docdir = os.path.join(base, dir, 'help')
77 docdir = os.path.join(base, dir, 'help')
78 if os.path.isdir(docdir):
78 if os.path.isdir(docdir):
79 break
79 break
80
80
81 path = os.path.join(docdir, topic + ".txt")
81 path = os.path.join(docdir, topic + ".txt")
82 doc = gettext(open(path).read())
82 doc = gettext(open(path).read())
83 for rewriter in helphooks.get(topic, []):
83 for rewriter in helphooks.get(topic, []):
84 doc = rewriter(topic, doc)
84 doc = rewriter(topic, doc)
85 return doc
85 return doc
86
86
87 return loader
87 return loader
88
88
89 helptable = [
89 helptable = [
90 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
90 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
91 (["dates"], _("Date Formats"), loaddoc('dates')),
91 (["dates"], _("Date Formats"), loaddoc('dates')),
92 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
92 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
93 (['environment', 'env'], _('Environment Variables'),
93 (['environment', 'env'], _('Environment Variables'),
94 loaddoc('environment')),
94 loaddoc('environment')),
95 (['revs', 'revisions'], _('Specifying Single Revisions'),
95 (['revs', 'revisions'], _('Specifying Single Revisions'),
96 loaddoc('revisions')),
96 loaddoc('revisions')),
97 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
97 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
98 loaddoc('multirevs')),
98 loaddoc('multirevs')),
99 (['revset', 'revsets'], _("Specifying Revision Sets"), loaddoc('revsets')),
99 (['revset', 'revsets'], _("Specifying Revision Sets"), loaddoc('revsets')),
100 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
100 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
101 (['merge-tools'], _('Merge Tools'), loaddoc('merge-tools')),
101 (['merge-tools'], _('Merge Tools'), loaddoc('merge-tools')),
102 (['templating', 'templates'], _('Template Usage'),
102 (['templating', 'templates'], _('Template Usage'),
103 loaddoc('templates')),
103 loaddoc('templates')),
104 (['urls'], _('URL Paths'), loaddoc('urls')),
104 (['urls'], _('URL Paths'), loaddoc('urls')),
105 (["extensions"], _("Using additional features"), extshelp),
105 (["extensions"], _("Using additional features"), extshelp),
106 (["subrepo", "subrepos"], _("Subrepositories"), loaddoc('subrepos')),
106 (["subrepo", "subrepos"], _("Subrepositories"), loaddoc('subrepos')),
107 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
107 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
108 (["glossary"], _("Glossary"), loaddoc('glossary')),
108 (["glossary"], _("Glossary"), loaddoc('glossary')),
109 ]
109 ]
110
110
111 # Map topics to lists of callable taking the current topic help and
111 # Map topics to lists of callable taking the current topic help and
112 # returning the updated version
112 # returning the updated version
113 helphooks = {
113 helphooks = {
114 }
114 }
115
115
116 def addtopichook(topic, rewriter):
116 def addtopichook(topic, rewriter):
117 helphooks.setdefault(topic, []).append(rewriter)
117 helphooks.setdefault(topic, []).append(rewriter)
118
119 def makeitemsdoc(topic, doc, marker, items):
120 """Extract docstring from the items key to function mapping, build a
121 .single documentation block and use it to overwrite the marker in doc
122 """
123 entries = []
124 for name in sorted(items):
125 text = (items[name].__doc__ or '').rstrip()
126 if not text:
127 continue
128 text = gettext(text)
129 lines = text.splitlines()
130 lines[1:] = [(' ' + l.strip()) for l in lines[1:]]
131 entries.append('\n'.join(lines))
132 entries = '\n\n'.join(entries)
133 return doc.replace(marker, entries)
@@ -1,833 +1,821 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, util, error, discovery
9 import parser, util, error, discovery, help
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _, gettext
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "-": (5, ("negate", 19), ("minus", 5)),
16 "-": (5, ("negate", 19), ("minus", 5)),
17 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
17 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
18 ("dagrangepost", 17)),
18 ("dagrangepost", 17)),
19 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
21 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
22 "not": (10, ("not", 10)),
22 "not": (10, ("not", 10)),
23 "!": (10, ("not", 10)),
23 "!": (10, ("not", 10)),
24 "and": (5, None, ("and", 5)),
24 "and": (5, None, ("and", 5)),
25 "&": (5, None, ("and", 5)),
25 "&": (5, None, ("and", 5)),
26 "or": (4, None, ("or", 4)),
26 "or": (4, None, ("or", 4)),
27 "|": (4, None, ("or", 4)),
27 "|": (4, None, ("or", 4)),
28 "+": (4, None, ("or", 4)),
28 "+": (4, None, ("or", 4)),
29 ",": (2, None, ("list", 2)),
29 ",": (2, None, ("list", 2)),
30 ")": (0, None, None),
30 ")": (0, None, None),
31 "symbol": (0, ("symbol",), None),
31 "symbol": (0, ("symbol",), None),
32 "string": (0, ("string",), None),
32 "string": (0, ("string",), None),
33 "end": (0, None, None),
33 "end": (0, None, None),
34 }
34 }
35
35
36 keywords = set(['and', 'or', 'not'])
36 keywords = set(['and', 'or', 'not'])
37
37
38 def tokenize(program):
38 def tokenize(program):
39 pos, l = 0, len(program)
39 pos, l = 0, len(program)
40 while pos < l:
40 while pos < l:
41 c = program[pos]
41 c = program[pos]
42 if c.isspace(): # skip inter-token whitespace
42 if c.isspace(): # skip inter-token whitespace
43 pass
43 pass
44 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
44 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
45 yield ('::', None, pos)
45 yield ('::', None, pos)
46 pos += 1 # skip ahead
46 pos += 1 # skip ahead
47 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
47 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
48 yield ('..', None, pos)
48 yield ('..', None, pos)
49 pos += 1 # skip ahead
49 pos += 1 # skip ahead
50 elif c in "():,-|&+!": # handle simple operators
50 elif c in "():,-|&+!": # handle simple operators
51 yield (c, None, pos)
51 yield (c, None, pos)
52 elif (c in '"\'' or c == 'r' and
52 elif (c in '"\'' or c == 'r' and
53 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
53 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
54 if c == 'r':
54 if c == 'r':
55 pos += 1
55 pos += 1
56 c = program[pos]
56 c = program[pos]
57 decode = lambda x: x
57 decode = lambda x: x
58 else:
58 else:
59 decode = lambda x: x.decode('string-escape')
59 decode = lambda x: x.decode('string-escape')
60 pos += 1
60 pos += 1
61 s = pos
61 s = pos
62 while pos < l: # find closing quote
62 while pos < l: # find closing quote
63 d = program[pos]
63 d = program[pos]
64 if d == '\\': # skip over escaped characters
64 if d == '\\': # skip over escaped characters
65 pos += 2
65 pos += 2
66 continue
66 continue
67 if d == c:
67 if d == c:
68 yield ('string', decode(program[s:pos]), s)
68 yield ('string', decode(program[s:pos]), s)
69 break
69 break
70 pos += 1
70 pos += 1
71 else:
71 else:
72 raise error.ParseError(_("unterminated string"), s)
72 raise error.ParseError(_("unterminated string"), s)
73 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
73 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
74 s = pos
74 s = pos
75 pos += 1
75 pos += 1
76 while pos < l: # find end of symbol
76 while pos < l: # find end of symbol
77 d = program[pos]
77 d = program[pos]
78 if not (d.isalnum() or d in "._" or ord(d) > 127):
78 if not (d.isalnum() or d in "._" or ord(d) > 127):
79 break
79 break
80 if d == '.' and program[pos - 1] == '.': # special case for ..
80 if d == '.' and program[pos - 1] == '.': # special case for ..
81 pos -= 1
81 pos -= 1
82 break
82 break
83 pos += 1
83 pos += 1
84 sym = program[s:pos]
84 sym = program[s:pos]
85 if sym in keywords: # operator keywords
85 if sym in keywords: # operator keywords
86 yield (sym, None, s)
86 yield (sym, None, s)
87 else:
87 else:
88 yield ('symbol', sym, s)
88 yield ('symbol', sym, s)
89 pos -= 1
89 pos -= 1
90 else:
90 else:
91 raise error.ParseError(_("syntax error"), pos)
91 raise error.ParseError(_("syntax error"), pos)
92 pos += 1
92 pos += 1
93 yield ('end', None, pos)
93 yield ('end', None, pos)
94
94
95 # helpers
95 # helpers
96
96
97 def getstring(x, err):
97 def getstring(x, err):
98 if x and (x[0] == 'string' or x[0] == 'symbol'):
98 if x and (x[0] == 'string' or x[0] == 'symbol'):
99 return x[1]
99 return x[1]
100 raise error.ParseError(err)
100 raise error.ParseError(err)
101
101
102 def getlist(x):
102 def getlist(x):
103 if not x:
103 if not x:
104 return []
104 return []
105 if x[0] == 'list':
105 if x[0] == 'list':
106 return getlist(x[1]) + [x[2]]
106 return getlist(x[1]) + [x[2]]
107 return [x]
107 return [x]
108
108
109 def getargs(x, min, max, err):
109 def getargs(x, min, max, err):
110 l = getlist(x)
110 l = getlist(x)
111 if len(l) < min or len(l) > max:
111 if len(l) < min or len(l) > max:
112 raise error.ParseError(err)
112 raise error.ParseError(err)
113 return l
113 return l
114
114
115 def getset(repo, subset, x):
115 def getset(repo, subset, x):
116 if not x:
116 if not x:
117 raise error.ParseError(_("missing argument"))
117 raise error.ParseError(_("missing argument"))
118 return methods[x[0]](repo, subset, *x[1:])
118 return methods[x[0]](repo, subset, *x[1:])
119
119
120 # operator methods
120 # operator methods
121
121
122 def stringset(repo, subset, x):
122 def stringset(repo, subset, x):
123 x = repo[x].rev()
123 x = repo[x].rev()
124 if x == -1 and len(subset) == len(repo):
124 if x == -1 and len(subset) == len(repo):
125 return [-1]
125 return [-1]
126 if x in subset:
126 if x in subset:
127 return [x]
127 return [x]
128 return []
128 return []
129
129
130 def symbolset(repo, subset, x):
130 def symbolset(repo, subset, x):
131 if x in symbols:
131 if x in symbols:
132 raise error.ParseError(_("can't use %s here") % x)
132 raise error.ParseError(_("can't use %s here") % x)
133 return stringset(repo, subset, x)
133 return stringset(repo, subset, x)
134
134
135 def rangeset(repo, subset, x, y):
135 def rangeset(repo, subset, x, y):
136 m = getset(repo, subset, x)
136 m = getset(repo, subset, x)
137 if not m:
137 if not m:
138 m = getset(repo, range(len(repo)), x)
138 m = getset(repo, range(len(repo)), x)
139
139
140 n = getset(repo, subset, y)
140 n = getset(repo, subset, y)
141 if not n:
141 if not n:
142 n = getset(repo, range(len(repo)), y)
142 n = getset(repo, range(len(repo)), y)
143
143
144 if not m or not n:
144 if not m or not n:
145 return []
145 return []
146 m, n = m[0], n[-1]
146 m, n = m[0], n[-1]
147
147
148 if m < n:
148 if m < n:
149 r = range(m, n + 1)
149 r = range(m, n + 1)
150 else:
150 else:
151 r = range(m, n - 1, -1)
151 r = range(m, n - 1, -1)
152 s = set(subset)
152 s = set(subset)
153 return [x for x in r if x in s]
153 return [x for x in r if x in s]
154
154
155 def andset(repo, subset, x, y):
155 def andset(repo, subset, x, y):
156 return getset(repo, getset(repo, subset, x), y)
156 return getset(repo, getset(repo, subset, x), y)
157
157
158 def orset(repo, subset, x, y):
158 def orset(repo, subset, x, y):
159 s = set(getset(repo, subset, x))
159 s = set(getset(repo, subset, x))
160 s |= set(getset(repo, [r for r in subset if r not in s], y))
160 s |= set(getset(repo, [r for r in subset if r not in s], y))
161 return [r for r in subset if r in s]
161 return [r for r in subset if r in s]
162
162
163 def notset(repo, subset, x):
163 def notset(repo, subset, x):
164 s = set(getset(repo, subset, x))
164 s = set(getset(repo, subset, x))
165 return [r for r in subset if r not in s]
165 return [r for r in subset if r not in s]
166
166
167 def listset(repo, subset, a, b):
167 def listset(repo, subset, a, b):
168 raise error.ParseError(_("can't use a list in this context"))
168 raise error.ParseError(_("can't use a list in this context"))
169
169
170 def func(repo, subset, a, b):
170 def func(repo, subset, a, b):
171 if a[0] == 'symbol' and a[1] in symbols:
171 if a[0] == 'symbol' and a[1] in symbols:
172 return symbols[a[1]](repo, subset, b)
172 return symbols[a[1]](repo, subset, b)
173 raise error.ParseError(_("not a function: %s") % a[1])
173 raise error.ParseError(_("not a function: %s") % a[1])
174
174
175 # functions
175 # functions
176
176
177 def node(repo, subset, x):
177 def node(repo, subset, x):
178 """``id(string)``
178 """``id(string)``
179 Revision non-ambiguously specified by the given hex string prefix.
179 Revision non-ambiguously specified by the given hex string prefix.
180 """
180 """
181 # i18n: "id" is a keyword
181 # i18n: "id" is a keyword
182 l = getargs(x, 1, 1, _("id requires one argument"))
182 l = getargs(x, 1, 1, _("id requires one argument"))
183 # i18n: "id" is a keyword
183 # i18n: "id" is a keyword
184 n = getstring(l[0], _("id requires a string"))
184 n = getstring(l[0], _("id requires a string"))
185 if len(n) == 40:
185 if len(n) == 40:
186 rn = repo[n].rev()
186 rn = repo[n].rev()
187 else:
187 else:
188 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
188 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
189 return [r for r in subset if r == rn]
189 return [r for r in subset if r == rn]
190
190
191 def rev(repo, subset, x):
191 def rev(repo, subset, x):
192 """``rev(number)``
192 """``rev(number)``
193 Revision with the given numeric identifier.
193 Revision with the given numeric identifier.
194 """
194 """
195 # i18n: "rev" is a keyword
195 # i18n: "rev" is a keyword
196 l = getargs(x, 1, 1, _("rev requires one argument"))
196 l = getargs(x, 1, 1, _("rev requires one argument"))
197 try:
197 try:
198 # i18n: "rev" is a keyword
198 # i18n: "rev" is a keyword
199 l = int(getstring(l[0], _("rev requires a number")))
199 l = int(getstring(l[0], _("rev requires a number")))
200 except ValueError:
200 except ValueError:
201 # i18n: "rev" is a keyword
201 # i18n: "rev" is a keyword
202 raise error.ParseError(_("rev expects a number"))
202 raise error.ParseError(_("rev expects a number"))
203 return [r for r in subset if r == l]
203 return [r for r in subset if r == l]
204
204
205 def p1(repo, subset, x):
205 def p1(repo, subset, x):
206 """``p1([set])``
206 """``p1([set])``
207 First parent of changesets in set, or the working directory.
207 First parent of changesets in set, or the working directory.
208 """
208 """
209 if x is None:
209 if x is None:
210 p = repo[x].parents()[0].rev()
210 p = repo[x].parents()[0].rev()
211 return [r for r in subset if r == p]
211 return [r for r in subset if r == p]
212
212
213 ps = set()
213 ps = set()
214 cl = repo.changelog
214 cl = repo.changelog
215 for r in getset(repo, range(len(repo)), x):
215 for r in getset(repo, range(len(repo)), x):
216 ps.add(cl.parentrevs(r)[0])
216 ps.add(cl.parentrevs(r)[0])
217 return [r for r in subset if r in ps]
217 return [r for r in subset if r in ps]
218
218
219 def p2(repo, subset, x):
219 def p2(repo, subset, x):
220 """``p2([set])``
220 """``p2([set])``
221 Second parent of changesets in set, or the working directory.
221 Second parent of changesets in set, or the working directory.
222 """
222 """
223 if x is None:
223 if x is None:
224 ps = repo[x].parents()
224 ps = repo[x].parents()
225 try:
225 try:
226 p = ps[1].rev()
226 p = ps[1].rev()
227 return [r for r in subset if r == p]
227 return [r for r in subset if r == p]
228 except IndexError:
228 except IndexError:
229 return []
229 return []
230
230
231 ps = set()
231 ps = set()
232 cl = repo.changelog
232 cl = repo.changelog
233 for r in getset(repo, range(len(repo)), x):
233 for r in getset(repo, range(len(repo)), x):
234 ps.add(cl.parentrevs(r)[1])
234 ps.add(cl.parentrevs(r)[1])
235 return [r for r in subset if r in ps]
235 return [r for r in subset if r in ps]
236
236
237 def parents(repo, subset, x):
237 def parents(repo, subset, x):
238 """``parents([set])``
238 """``parents([set])``
239 The set of all parents for all changesets in set, or the working directory.
239 The set of all parents for all changesets in set, or the working directory.
240 """
240 """
241 if x is None:
241 if x is None:
242 ps = tuple(p.rev() for p in repo[x].parents())
242 ps = tuple(p.rev() for p in repo[x].parents())
243 return [r for r in subset if r in ps]
243 return [r for r in subset if r in ps]
244
244
245 ps = set()
245 ps = set()
246 cl = repo.changelog
246 cl = repo.changelog
247 for r in getset(repo, range(len(repo)), x):
247 for r in getset(repo, range(len(repo)), x):
248 ps.update(cl.parentrevs(r))
248 ps.update(cl.parentrevs(r))
249 return [r for r in subset if r in ps]
249 return [r for r in subset if r in ps]
250
250
251 def maxrev(repo, subset, x):
251 def maxrev(repo, subset, x):
252 """``max(set)``
252 """``max(set)``
253 Changeset with highest revision number in set.
253 Changeset with highest revision number in set.
254 """
254 """
255 s = getset(repo, subset, x)
255 s = getset(repo, subset, x)
256 if s:
256 if s:
257 m = max(s)
257 m = max(s)
258 if m in subset:
258 if m in subset:
259 return [m]
259 return [m]
260 return []
260 return []
261
261
262 def minrev(repo, subset, x):
262 def minrev(repo, subset, x):
263 """``min(set)``
263 """``min(set)``
264 Changeset with lowest revision number in set.
264 Changeset with lowest revision number in set.
265 """
265 """
266 s = getset(repo, subset, x)
266 s = getset(repo, subset, x)
267 if s:
267 if s:
268 m = min(s)
268 m = min(s)
269 if m in subset:
269 if m in subset:
270 return [m]
270 return [m]
271 return []
271 return []
272
272
273 def limit(repo, subset, x):
273 def limit(repo, subset, x):
274 """``limit(set, n)``
274 """``limit(set, n)``
275 First n members of set.
275 First n members of set.
276 """
276 """
277 # i18n: "limit" is a keyword
277 # i18n: "limit" is a keyword
278 l = getargs(x, 2, 2, _("limit requires two arguments"))
278 l = getargs(x, 2, 2, _("limit requires two arguments"))
279 try:
279 try:
280 # i18n: "limit" is a keyword
280 # i18n: "limit" is a keyword
281 lim = int(getstring(l[1], _("limit requires a number")))
281 lim = int(getstring(l[1], _("limit requires a number")))
282 except ValueError:
282 except ValueError:
283 # i18n: "limit" is a keyword
283 # i18n: "limit" is a keyword
284 raise error.ParseError(_("limit expects a number"))
284 raise error.ParseError(_("limit expects a number"))
285 return getset(repo, subset, l[0])[:lim]
285 return getset(repo, subset, l[0])[:lim]
286
286
287 def children(repo, subset, x):
287 def children(repo, subset, x):
288 """``children(set)``
288 """``children(set)``
289 Child changesets of changesets in set.
289 Child changesets of changesets in set.
290 """
290 """
291 cs = set()
291 cs = set()
292 cl = repo.changelog
292 cl = repo.changelog
293 s = set(getset(repo, range(len(repo)), x))
293 s = set(getset(repo, range(len(repo)), x))
294 for r in xrange(0, len(repo)):
294 for r in xrange(0, len(repo)):
295 for p in cl.parentrevs(r):
295 for p in cl.parentrevs(r):
296 if p in s:
296 if p in s:
297 cs.add(r)
297 cs.add(r)
298 return [r for r in subset if r in cs]
298 return [r for r in subset if r in cs]
299
299
300 def branch(repo, subset, x):
300 def branch(repo, subset, x):
301 """``branch(set)``
301 """``branch(set)``
302 All changesets belonging to the branches of changesets in set.
302 All changesets belonging to the branches of changesets in set.
303 """
303 """
304 s = getset(repo, range(len(repo)), x)
304 s = getset(repo, range(len(repo)), x)
305 b = set()
305 b = set()
306 for r in s:
306 for r in s:
307 b.add(repo[r].branch())
307 b.add(repo[r].branch())
308 s = set(s)
308 s = set(s)
309 return [r for r in subset if r in s or repo[r].branch() in b]
309 return [r for r in subset if r in s or repo[r].branch() in b]
310
310
311 def ancestor(repo, subset, x):
311 def ancestor(repo, subset, x):
312 """``ancestor(single, single)``
312 """``ancestor(single, single)``
313 Greatest common ancestor of the two changesets.
313 Greatest common ancestor of the two changesets.
314 """
314 """
315 # i18n: "ancestor" is a keyword
315 # i18n: "ancestor" is a keyword
316 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
316 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
317 r = range(len(repo))
317 r = range(len(repo))
318 a = getset(repo, r, l[0])
318 a = getset(repo, r, l[0])
319 b = getset(repo, r, l[1])
319 b = getset(repo, r, l[1])
320 if len(a) != 1 or len(b) != 1:
320 if len(a) != 1 or len(b) != 1:
321 # i18n: "ancestor" is a keyword
321 # i18n: "ancestor" is a keyword
322 raise error.ParseError(_("ancestor arguments must be single revisions"))
322 raise error.ParseError(_("ancestor arguments must be single revisions"))
323 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
323 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
324
324
325 return [r for r in an if r in subset]
325 return [r for r in an if r in subset]
326
326
327 def ancestors(repo, subset, x):
327 def ancestors(repo, subset, x):
328 """``ancestors(set)``
328 """``ancestors(set)``
329 Changesets that are ancestors of a changeset in set.
329 Changesets that are ancestors of a changeset in set.
330 """
330 """
331 args = getset(repo, range(len(repo)), x)
331 args = getset(repo, range(len(repo)), x)
332 if not args:
332 if not args:
333 return []
333 return []
334 s = set(repo.changelog.ancestors(*args)) | set(args)
334 s = set(repo.changelog.ancestors(*args)) | set(args)
335 return [r for r in subset if r in s]
335 return [r for r in subset if r in s]
336
336
337 def descendants(repo, subset, x):
337 def descendants(repo, subset, x):
338 """``descendants(set)``
338 """``descendants(set)``
339 Changesets which are descendants of changesets in set.
339 Changesets which are descendants of changesets in set.
340 """
340 """
341 args = getset(repo, range(len(repo)), x)
341 args = getset(repo, range(len(repo)), x)
342 if not args:
342 if not args:
343 return []
343 return []
344 s = set(repo.changelog.descendants(*args)) | set(args)
344 s = set(repo.changelog.descendants(*args)) | set(args)
345 return [r for r in subset if r in s]
345 return [r for r in subset if r in s]
346
346
347 def follow(repo, subset, x):
347 def follow(repo, subset, x):
348 """``follow()``
348 """``follow()``
349 An alias for ``::.`` (ancestors of the working copy's first parent).
349 An alias for ``::.`` (ancestors of the working copy's first parent).
350 """
350 """
351 # i18n: "follow" is a keyword
351 # i18n: "follow" is a keyword
352 getargs(x, 0, 0, _("follow takes no arguments"))
352 getargs(x, 0, 0, _("follow takes no arguments"))
353 p = repo['.'].rev()
353 p = repo['.'].rev()
354 s = set(repo.changelog.ancestors(p)) | set([p])
354 s = set(repo.changelog.ancestors(p)) | set([p])
355 return [r for r in subset if r in s]
355 return [r for r in subset if r in s]
356
356
357 def date(repo, subset, x):
357 def date(repo, subset, x):
358 """``date(interval)``
358 """``date(interval)``
359 Changesets within the interval, see :hg:`help dates`.
359 Changesets within the interval, see :hg:`help dates`.
360 """
360 """
361 # i18n: "date" is a keyword
361 # i18n: "date" is a keyword
362 ds = getstring(x, _("date requires a string"))
362 ds = getstring(x, _("date requires a string"))
363 dm = util.matchdate(ds)
363 dm = util.matchdate(ds)
364 return [r for r in subset if dm(repo[r].date()[0])]
364 return [r for r in subset if dm(repo[r].date()[0])]
365
365
366 def keyword(repo, subset, x):
366 def keyword(repo, subset, x):
367 """``keyword(string)``
367 """``keyword(string)``
368 Search commit message, user name, and names of changed files for
368 Search commit message, user name, and names of changed files for
369 string.
369 string.
370 """
370 """
371 # i18n: "keyword" is a keyword
371 # i18n: "keyword" is a keyword
372 kw = getstring(x, _("keyword requires a string")).lower()
372 kw = getstring(x, _("keyword requires a string")).lower()
373 l = []
373 l = []
374 for r in subset:
374 for r in subset:
375 c = repo[r]
375 c = repo[r]
376 t = " ".join(c.files() + [c.user(), c.description()])
376 t = " ".join(c.files() + [c.user(), c.description()])
377 if kw in t.lower():
377 if kw in t.lower():
378 l.append(r)
378 l.append(r)
379 return l
379 return l
380
380
381 def grep(repo, subset, x):
381 def grep(repo, subset, x):
382 """``grep(regex)``
382 """``grep(regex)``
383 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
383 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
384 to ensure special escape characters are handled correctly.
384 to ensure special escape characters are handled correctly.
385 """
385 """
386 try:
386 try:
387 # i18n: "grep" is a keyword
387 # i18n: "grep" is a keyword
388 gr = re.compile(getstring(x, _("grep requires a string")))
388 gr = re.compile(getstring(x, _("grep requires a string")))
389 except re.error, e:
389 except re.error, e:
390 raise error.ParseError(_('invalid match pattern: %s') % e)
390 raise error.ParseError(_('invalid match pattern: %s') % e)
391 l = []
391 l = []
392 for r in subset:
392 for r in subset:
393 c = repo[r]
393 c = repo[r]
394 for e in c.files() + [c.user(), c.description()]:
394 for e in c.files() + [c.user(), c.description()]:
395 if gr.search(e):
395 if gr.search(e):
396 l.append(r)
396 l.append(r)
397 continue
397 continue
398 return l
398 return l
399
399
400 def author(repo, subset, x):
400 def author(repo, subset, x):
401 """``author(string)``
401 """``author(string)``
402 Alias for ``user(string)``.
402 Alias for ``user(string)``.
403 """
403 """
404 # i18n: "author" is a keyword
404 # i18n: "author" is a keyword
405 n = getstring(x, _("author requires a string")).lower()
405 n = getstring(x, _("author requires a string")).lower()
406 return [r for r in subset if n in repo[r].user().lower()]
406 return [r for r in subset if n in repo[r].user().lower()]
407
407
408 def user(repo, subset, x):
408 def user(repo, subset, x):
409 """``user(string)``
409 """``user(string)``
410 User name is string.
410 User name is string.
411 """
411 """
412 return author(repo, subset, x)
412 return author(repo, subset, x)
413
413
414 def hasfile(repo, subset, x):
414 def hasfile(repo, subset, x):
415 """``file(pattern)``
415 """``file(pattern)``
416 Changesets affecting files matched by pattern.
416 Changesets affecting files matched by pattern.
417 """
417 """
418 # i18n: "file" is a keyword
418 # i18n: "file" is a keyword
419 pat = getstring(x, _("file requires a pattern"))
419 pat = getstring(x, _("file requires a pattern"))
420 m = matchmod.match(repo.root, repo.getcwd(), [pat])
420 m = matchmod.match(repo.root, repo.getcwd(), [pat])
421 s = []
421 s = []
422 for r in subset:
422 for r in subset:
423 for f in repo[r].files():
423 for f in repo[r].files():
424 if m(f):
424 if m(f):
425 s.append(r)
425 s.append(r)
426 continue
426 continue
427 return s
427 return s
428
428
429 def contains(repo, subset, x):
429 def contains(repo, subset, x):
430 """``contains(pattern)``
430 """``contains(pattern)``
431 Revision contains pattern.
431 Revision contains pattern.
432 """
432 """
433 # i18n: "contains" is a keyword
433 # i18n: "contains" is a keyword
434 pat = getstring(x, _("contains requires a pattern"))
434 pat = getstring(x, _("contains requires a pattern"))
435 m = matchmod.match(repo.root, repo.getcwd(), [pat])
435 m = matchmod.match(repo.root, repo.getcwd(), [pat])
436 s = []
436 s = []
437 if m.files() == [pat]:
437 if m.files() == [pat]:
438 for r in subset:
438 for r in subset:
439 if pat in repo[r]:
439 if pat in repo[r]:
440 s.append(r)
440 s.append(r)
441 continue
441 continue
442 else:
442 else:
443 for r in subset:
443 for r in subset:
444 for f in repo[r].manifest():
444 for f in repo[r].manifest():
445 if m(f):
445 if m(f):
446 s.append(r)
446 s.append(r)
447 continue
447 continue
448 return s
448 return s
449
449
450 def checkstatus(repo, subset, pat, field):
450 def checkstatus(repo, subset, pat, field):
451 m = matchmod.match(repo.root, repo.getcwd(), [pat])
451 m = matchmod.match(repo.root, repo.getcwd(), [pat])
452 s = []
452 s = []
453 fast = (m.files() == [pat])
453 fast = (m.files() == [pat])
454 for r in subset:
454 for r in subset:
455 c = repo[r]
455 c = repo[r]
456 if fast:
456 if fast:
457 if pat not in c.files():
457 if pat not in c.files():
458 continue
458 continue
459 else:
459 else:
460 for f in c.files():
460 for f in c.files():
461 if m(f):
461 if m(f):
462 break
462 break
463 else:
463 else:
464 continue
464 continue
465 files = repo.status(c.p1().node(), c.node())[field]
465 files = repo.status(c.p1().node(), c.node())[field]
466 if fast:
466 if fast:
467 if pat in files:
467 if pat in files:
468 s.append(r)
468 s.append(r)
469 continue
469 continue
470 else:
470 else:
471 for f in files:
471 for f in files:
472 if m(f):
472 if m(f):
473 s.append(r)
473 s.append(r)
474 continue
474 continue
475 return s
475 return s
476
476
477 def modifies(repo, subset, x):
477 def modifies(repo, subset, x):
478 """``modifies(pattern)``
478 """``modifies(pattern)``
479 Changesets modifying files matched by pattern.
479 Changesets modifying files matched by pattern.
480 """
480 """
481 # i18n: "modifies" is a keyword
481 # i18n: "modifies" is a keyword
482 pat = getstring(x, _("modifies requires a pattern"))
482 pat = getstring(x, _("modifies requires a pattern"))
483 return checkstatus(repo, subset, pat, 0)
483 return checkstatus(repo, subset, pat, 0)
484
484
485 def adds(repo, subset, x):
485 def adds(repo, subset, x):
486 """``adds(pattern)``
486 """``adds(pattern)``
487 Changesets that add a file matching pattern.
487 Changesets that add a file matching pattern.
488 """
488 """
489 # i18n: "adds" is a keyword
489 # i18n: "adds" is a keyword
490 pat = getstring(x, _("adds requires a pattern"))
490 pat = getstring(x, _("adds requires a pattern"))
491 return checkstatus(repo, subset, pat, 1)
491 return checkstatus(repo, subset, pat, 1)
492
492
493 def removes(repo, subset, x):
493 def removes(repo, subset, x):
494 """``removes(pattern)``
494 """``removes(pattern)``
495 Changesets which remove files matching pattern.
495 Changesets which remove files matching pattern.
496 """
496 """
497 # i18n: "removes" is a keyword
497 # i18n: "removes" is a keyword
498 pat = getstring(x, _("removes requires a pattern"))
498 pat = getstring(x, _("removes requires a pattern"))
499 return checkstatus(repo, subset, pat, 2)
499 return checkstatus(repo, subset, pat, 2)
500
500
501 def merge(repo, subset, x):
501 def merge(repo, subset, x):
502 """``merge()``
502 """``merge()``
503 Changeset is a merge changeset.
503 Changeset is a merge changeset.
504 """
504 """
505 # i18n: "merge" is a keyword
505 # i18n: "merge" is a keyword
506 getargs(x, 0, 0, _("merge takes no arguments"))
506 getargs(x, 0, 0, _("merge takes no arguments"))
507 cl = repo.changelog
507 cl = repo.changelog
508 return [r for r in subset if cl.parentrevs(r)[1] != -1]
508 return [r for r in subset if cl.parentrevs(r)[1] != -1]
509
509
510 def closed(repo, subset, x):
510 def closed(repo, subset, x):
511 """``closed()``
511 """``closed()``
512 Changeset is closed.
512 Changeset is closed.
513 """
513 """
514 # i18n: "closed" is a keyword
514 # i18n: "closed" is a keyword
515 getargs(x, 0, 0, _("closed takes no arguments"))
515 getargs(x, 0, 0, _("closed takes no arguments"))
516 return [r for r in subset if repo[r].extra().get('close')]
516 return [r for r in subset if repo[r].extra().get('close')]
517
517
518 def head(repo, subset, x):
518 def head(repo, subset, x):
519 """``head()``
519 """``head()``
520 Changeset is a named branch head.
520 Changeset is a named branch head.
521 """
521 """
522 # i18n: "head" is a keyword
522 # i18n: "head" is a keyword
523 getargs(x, 0, 0, _("head takes no arguments"))
523 getargs(x, 0, 0, _("head takes no arguments"))
524 hs = set()
524 hs = set()
525 for b, ls in repo.branchmap().iteritems():
525 for b, ls in repo.branchmap().iteritems():
526 hs.update(repo[h].rev() for h in ls)
526 hs.update(repo[h].rev() for h in ls)
527 return [r for r in subset if r in hs]
527 return [r for r in subset if r in hs]
528
528
529 def reverse(repo, subset, x):
529 def reverse(repo, subset, x):
530 """``reverse(set)``
530 """``reverse(set)``
531 Reverse order of set.
531 Reverse order of set.
532 """
532 """
533 l = getset(repo, subset, x)
533 l = getset(repo, subset, x)
534 l.reverse()
534 l.reverse()
535 return l
535 return l
536
536
537 def present(repo, subset, x):
537 def present(repo, subset, x):
538 """``present(set)``
538 """``present(set)``
539 An empty set, if any revision in set isn't found; otherwise,
539 An empty set, if any revision in set isn't found; otherwise,
540 all revisions in set.
540 all revisions in set.
541 """
541 """
542 try:
542 try:
543 return getset(repo, subset, x)
543 return getset(repo, subset, x)
544 except error.RepoLookupError:
544 except error.RepoLookupError:
545 return []
545 return []
546
546
547 def sort(repo, subset, x):
547 def sort(repo, subset, x):
548 """``sort(set[, [-]key...])``
548 """``sort(set[, [-]key...])``
549 Sort set by keys. The default sort order is ascending, specify a key
549 Sort set by keys. The default sort order is ascending, specify a key
550 as ``-key`` to sort in descending order.
550 as ``-key`` to sort in descending order.
551
551
552 The keys can be:
552 The keys can be:
553
553
554 - ``rev`` for the revision number,
554 - ``rev`` for the revision number,
555 - ``branch`` for the branch name,
555 - ``branch`` for the branch name,
556 - ``desc`` for the commit message (description),
556 - ``desc`` for the commit message (description),
557 - ``user`` for user name (``author`` can be used as an alias),
557 - ``user`` for user name (``author`` can be used as an alias),
558 - ``date`` for the commit date
558 - ``date`` for the commit date
559 """
559 """
560 # i18n: "sort" is a keyword
560 # i18n: "sort" is a keyword
561 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
561 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
562 keys = "rev"
562 keys = "rev"
563 if len(l) == 2:
563 if len(l) == 2:
564 keys = getstring(l[1], _("sort spec must be a string"))
564 keys = getstring(l[1], _("sort spec must be a string"))
565
565
566 s = l[0]
566 s = l[0]
567 keys = keys.split()
567 keys = keys.split()
568 l = []
568 l = []
569 def invert(s):
569 def invert(s):
570 return "".join(chr(255 - ord(c)) for c in s)
570 return "".join(chr(255 - ord(c)) for c in s)
571 for r in getset(repo, subset, s):
571 for r in getset(repo, subset, s):
572 c = repo[r]
572 c = repo[r]
573 e = []
573 e = []
574 for k in keys:
574 for k in keys:
575 if k == 'rev':
575 if k == 'rev':
576 e.append(r)
576 e.append(r)
577 elif k == '-rev':
577 elif k == '-rev':
578 e.append(-r)
578 e.append(-r)
579 elif k == 'branch':
579 elif k == 'branch':
580 e.append(c.branch())
580 e.append(c.branch())
581 elif k == '-branch':
581 elif k == '-branch':
582 e.append(invert(c.branch()))
582 e.append(invert(c.branch()))
583 elif k == 'desc':
583 elif k == 'desc':
584 e.append(c.description())
584 e.append(c.description())
585 elif k == '-desc':
585 elif k == '-desc':
586 e.append(invert(c.description()))
586 e.append(invert(c.description()))
587 elif k in 'user author':
587 elif k in 'user author':
588 e.append(c.user())
588 e.append(c.user())
589 elif k in '-user -author':
589 elif k in '-user -author':
590 e.append(invert(c.user()))
590 e.append(invert(c.user()))
591 elif k == 'date':
591 elif k == 'date':
592 e.append(c.date()[0])
592 e.append(c.date()[0])
593 elif k == '-date':
593 elif k == '-date':
594 e.append(-c.date()[0])
594 e.append(-c.date()[0])
595 else:
595 else:
596 raise error.ParseError(_("unknown sort key %r") % k)
596 raise error.ParseError(_("unknown sort key %r") % k)
597 e.append(r)
597 e.append(r)
598 l.append(e)
598 l.append(e)
599 l.sort()
599 l.sort()
600 return [e[-1] for e in l]
600 return [e[-1] for e in l]
601
601
602 def getall(repo, subset, x):
602 def getall(repo, subset, x):
603 """``all()``
603 """``all()``
604 All changesets, the same as ``0:tip``.
604 All changesets, the same as ``0:tip``.
605 """
605 """
606 # i18n: "all" is a keyword
606 # i18n: "all" is a keyword
607 getargs(x, 0, 0, _("all takes no arguments"))
607 getargs(x, 0, 0, _("all takes no arguments"))
608 return subset
608 return subset
609
609
610 def heads(repo, subset, x):
610 def heads(repo, subset, x):
611 """``heads(set)``
611 """``heads(set)``
612 Members of set with no children in set.
612 Members of set with no children in set.
613 """
613 """
614 s = getset(repo, subset, x)
614 s = getset(repo, subset, x)
615 ps = set(parents(repo, subset, x))
615 ps = set(parents(repo, subset, x))
616 return [r for r in s if r not in ps]
616 return [r for r in s if r not in ps]
617
617
618 def roots(repo, subset, x):
618 def roots(repo, subset, x):
619 """``roots(set)``
619 """``roots(set)``
620 Changesets with no parent changeset in set.
620 Changesets with no parent changeset in set.
621 """
621 """
622 s = getset(repo, subset, x)
622 s = getset(repo, subset, x)
623 cs = set(children(repo, subset, x))
623 cs = set(children(repo, subset, x))
624 return [r for r in s if r not in cs]
624 return [r for r in s if r not in cs]
625
625
626 def outgoing(repo, subset, x):
626 def outgoing(repo, subset, x):
627 """``outgoing([path])``
627 """``outgoing([path])``
628 Changesets not found in the specified destination repository, or the
628 Changesets not found in the specified destination repository, or the
629 default push location.
629 default push location.
630 """
630 """
631 import hg # avoid start-up nasties
631 import hg # avoid start-up nasties
632 # i18n: "outgoing" is a keyword
632 # i18n: "outgoing" is a keyword
633 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
633 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
634 # i18n: "outgoing" is a keyword
634 # i18n: "outgoing" is a keyword
635 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
635 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
636 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
636 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
637 dest, branches = hg.parseurl(dest)
637 dest, branches = hg.parseurl(dest)
638 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
638 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
639 if revs:
639 if revs:
640 revs = [repo.lookup(rev) for rev in revs]
640 revs = [repo.lookup(rev) for rev in revs]
641 other = hg.repository(hg.remoteui(repo, {}), dest)
641 other = hg.repository(hg.remoteui(repo, {}), dest)
642 repo.ui.pushbuffer()
642 repo.ui.pushbuffer()
643 o = discovery.findoutgoing(repo, other)
643 o = discovery.findoutgoing(repo, other)
644 repo.ui.popbuffer()
644 repo.ui.popbuffer()
645 cl = repo.changelog
645 cl = repo.changelog
646 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
646 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
647 return [r for r in subset if r in o]
647 return [r for r in subset if r in o]
648
648
649 def tag(repo, subset, x):
649 def tag(repo, subset, x):
650 """``tag(name)``
650 """``tag(name)``
651 The specified tag by name, or all tagged revisions if no name is given.
651 The specified tag by name, or all tagged revisions if no name is given.
652 """
652 """
653 # i18n: "tag" is a keyword
653 # i18n: "tag" is a keyword
654 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
654 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
655 cl = repo.changelog
655 cl = repo.changelog
656 if args:
656 if args:
657 tn = getstring(args[0],
657 tn = getstring(args[0],
658 # i18n: "tag" is a keyword
658 # i18n: "tag" is a keyword
659 _('the argument to tag must be a string'))
659 _('the argument to tag must be a string'))
660 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
660 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
661 else:
661 else:
662 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
662 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
663 return [r for r in subset if r in s]
663 return [r for r in subset if r in s]
664
664
665 def tagged(repo, subset, x):
665 def tagged(repo, subset, x):
666 return tag(repo, subset, x)
666 return tag(repo, subset, x)
667
667
668 def bookmark(repo, subset, x):
668 def bookmark(repo, subset, x):
669 """``bookmark([name])``
669 """``bookmark([name])``
670 The named bookmark or all bookmarks.
670 The named bookmark or all bookmarks.
671 """
671 """
672 # i18n: "bookmark" is a keyword
672 # i18n: "bookmark" is a keyword
673 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
673 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
674 if args:
674 if args:
675 bm = getstring(args[0],
675 bm = getstring(args[0],
676 # i18n: "bookmark" is a keyword
676 # i18n: "bookmark" is a keyword
677 _('the argument to bookmark must be a string'))
677 _('the argument to bookmark must be a string'))
678 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
678 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
679 if bmrev:
679 if bmrev:
680 bmrev = repo[bmrev].rev()
680 bmrev = repo[bmrev].rev()
681 return [r for r in subset if r == bmrev]
681 return [r for r in subset if r == bmrev]
682 bms = set([repo[r].rev()
682 bms = set([repo[r].rev()
683 for r in bookmarksmod.listbookmarks(repo).values()])
683 for r in bookmarksmod.listbookmarks(repo).values()])
684 return [r for r in subset if r in bms]
684 return [r for r in subset if r in bms]
685
685
686 symbols = {
686 symbols = {
687 "adds": adds,
687 "adds": adds,
688 "all": getall,
688 "all": getall,
689 "ancestor": ancestor,
689 "ancestor": ancestor,
690 "ancestors": ancestors,
690 "ancestors": ancestors,
691 "author": author,
691 "author": author,
692 "bookmark": bookmark,
692 "bookmark": bookmark,
693 "branch": branch,
693 "branch": branch,
694 "children": children,
694 "children": children,
695 "closed": closed,
695 "closed": closed,
696 "contains": contains,
696 "contains": contains,
697 "date": date,
697 "date": date,
698 "descendants": descendants,
698 "descendants": descendants,
699 "file": hasfile,
699 "file": hasfile,
700 "follow": follow,
700 "follow": follow,
701 "grep": grep,
701 "grep": grep,
702 "head": head,
702 "head": head,
703 "heads": heads,
703 "heads": heads,
704 "keyword": keyword,
704 "keyword": keyword,
705 "limit": limit,
705 "limit": limit,
706 "max": maxrev,
706 "max": maxrev,
707 "min": minrev,
707 "min": minrev,
708 "merge": merge,
708 "merge": merge,
709 "modifies": modifies,
709 "modifies": modifies,
710 "id": node,
710 "id": node,
711 "outgoing": outgoing,
711 "outgoing": outgoing,
712 "p1": p1,
712 "p1": p1,
713 "p2": p2,
713 "p2": p2,
714 "parents": parents,
714 "parents": parents,
715 "present": present,
715 "present": present,
716 "removes": removes,
716 "removes": removes,
717 "reverse": reverse,
717 "reverse": reverse,
718 "rev": rev,
718 "rev": rev,
719 "roots": roots,
719 "roots": roots,
720 "sort": sort,
720 "sort": sort,
721 "tag": tag,
721 "tag": tag,
722 "tagged": tagged,
722 "tagged": tagged,
723 "user": user,
723 "user": user,
724 }
724 }
725
725
726 methods = {
726 methods = {
727 "range": rangeset,
727 "range": rangeset,
728 "string": stringset,
728 "string": stringset,
729 "symbol": symbolset,
729 "symbol": symbolset,
730 "and": andset,
730 "and": andset,
731 "or": orset,
731 "or": orset,
732 "not": notset,
732 "not": notset,
733 "list": listset,
733 "list": listset,
734 "func": func,
734 "func": func,
735 }
735 }
736
736
737 def optimize(x, small):
737 def optimize(x, small):
738 if x is None:
738 if x is None:
739 return 0, x
739 return 0, x
740
740
741 smallbonus = 1
741 smallbonus = 1
742 if small:
742 if small:
743 smallbonus = .5
743 smallbonus = .5
744
744
745 op = x[0]
745 op = x[0]
746 if op == 'minus':
746 if op == 'minus':
747 return optimize(('and', x[1], ('not', x[2])), small)
747 return optimize(('and', x[1], ('not', x[2])), small)
748 elif op == 'dagrange':
748 elif op == 'dagrange':
749 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
749 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
750 ('func', ('symbol', 'ancestors'), x[2])), small)
750 ('func', ('symbol', 'ancestors'), x[2])), small)
751 elif op == 'dagrangepre':
751 elif op == 'dagrangepre':
752 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
752 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
753 elif op == 'dagrangepost':
753 elif op == 'dagrangepost':
754 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
754 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
755 elif op == 'rangepre':
755 elif op == 'rangepre':
756 return optimize(('range', ('string', '0'), x[1]), small)
756 return optimize(('range', ('string', '0'), x[1]), small)
757 elif op == 'rangepost':
757 elif op == 'rangepost':
758 return optimize(('range', x[1], ('string', 'tip')), small)
758 return optimize(('range', x[1], ('string', 'tip')), small)
759 elif op == 'negate':
759 elif op == 'negate':
760 return optimize(('string',
760 return optimize(('string',
761 '-' + getstring(x[1], _("can't negate that"))), small)
761 '-' + getstring(x[1], _("can't negate that"))), small)
762 elif op in 'string symbol negate':
762 elif op in 'string symbol negate':
763 return smallbonus, x # single revisions are small
763 return smallbonus, x # single revisions are small
764 elif op == 'and' or op == 'dagrange':
764 elif op == 'and' or op == 'dagrange':
765 wa, ta = optimize(x[1], True)
765 wa, ta = optimize(x[1], True)
766 wb, tb = optimize(x[2], True)
766 wb, tb = optimize(x[2], True)
767 w = min(wa, wb)
767 w = min(wa, wb)
768 if wa > wb:
768 if wa > wb:
769 return w, (op, tb, ta)
769 return w, (op, tb, ta)
770 return w, (op, ta, tb)
770 return w, (op, ta, tb)
771 elif op == 'or':
771 elif op == 'or':
772 wa, ta = optimize(x[1], False)
772 wa, ta = optimize(x[1], False)
773 wb, tb = optimize(x[2], False)
773 wb, tb = optimize(x[2], False)
774 if wb < wa:
774 if wb < wa:
775 wb, wa = wa, wb
775 wb, wa = wa, wb
776 return max(wa, wb), (op, ta, tb)
776 return max(wa, wb), (op, ta, tb)
777 elif op == 'not':
777 elif op == 'not':
778 o = optimize(x[1], not small)
778 o = optimize(x[1], not small)
779 return o[0], (op, o[1])
779 return o[0], (op, o[1])
780 elif op == 'group':
780 elif op == 'group':
781 return optimize(x[1], small)
781 return optimize(x[1], small)
782 elif op in 'range list':
782 elif op in 'range list':
783 wa, ta = optimize(x[1], small)
783 wa, ta = optimize(x[1], small)
784 wb, tb = optimize(x[2], small)
784 wb, tb = optimize(x[2], small)
785 return wa + wb, (op, ta, tb)
785 return wa + wb, (op, ta, tb)
786 elif op == 'func':
786 elif op == 'func':
787 f = getstring(x[1], _("not a symbol"))
787 f = getstring(x[1], _("not a symbol"))
788 wa, ta = optimize(x[2], small)
788 wa, ta = optimize(x[2], small)
789 if f in "grep date user author keyword branch file outgoing":
789 if f in "grep date user author keyword branch file outgoing":
790 w = 10 # slow
790 w = 10 # slow
791 elif f in "modifies adds removes":
791 elif f in "modifies adds removes":
792 w = 30 # slower
792 w = 30 # slower
793 elif f == "contains":
793 elif f == "contains":
794 w = 100 # very slow
794 w = 100 # very slow
795 elif f == "ancestor":
795 elif f == "ancestor":
796 w = 1 * smallbonus
796 w = 1 * smallbonus
797 elif f in "reverse limit":
797 elif f in "reverse limit":
798 w = 0
798 w = 0
799 elif f in "sort":
799 elif f in "sort":
800 w = 10 # assume most sorts look at changelog
800 w = 10 # assume most sorts look at changelog
801 else:
801 else:
802 w = 1
802 w = 1
803 return w + wa, (op, x[1], ta)
803 return w + wa, (op, x[1], ta)
804 return 1, x
804 return 1, x
805
805
806 parse = parser.parser(tokenize, elements).parse
806 parse = parser.parser(tokenize, elements).parse
807
807
808 def match(spec):
808 def match(spec):
809 if not spec:
809 if not spec:
810 raise error.ParseError(_("empty query"))
810 raise error.ParseError(_("empty query"))
811 tree = parse(spec)
811 tree = parse(spec)
812 weight, tree = optimize(tree, True)
812 weight, tree = optimize(tree, True)
813 def mfunc(repo, subset):
813 def mfunc(repo, subset):
814 return getset(repo, subset, tree)
814 return getset(repo, subset, tree)
815 return mfunc
815 return mfunc
816
816
817 def makedoc(topic, doc):
817 def makedoc(topic, doc):
818 """Generate and include predicates help in revsets topic."""
818 return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols)
819 predicates = []
820 for name in sorted(symbols):
821 text = symbols[name].__doc__
822 if not text:
823 continue
824 text = gettext(text.rstrip())
825 lines = text.splitlines()
826 lines[1:] = [(' ' + l.strip()) for l in lines[1:]]
827 predicates.append('\n'.join(lines))
828 predicates = '\n\n'.join(predicates)
829 doc = doc.replace('.. predicatesmarker', predicates)
830 return doc
831
819
832 # tell hggettext to extract docstrings from these functions:
820 # tell hggettext to extract docstrings from these functions:
833 i18nfunctions = symbols.values()
821 i18nfunctions = symbols.values()
@@ -1,371 +1,358 b''
1 # template-filters.py - common template expansion filters
1 # template-filters.py - common template expansion filters
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import cgi, re, os, time, urllib
8 import cgi, re, os, time, urllib
9 import encoding, node, util
9 import encoding, node, util, help
10 from i18n import gettext
11
10
12 def addbreaks(text):
11 def addbreaks(text):
13 """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
12 """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
14 every line except the last.
13 every line except the last.
15 """
14 """
16 return text.replace('\n', '<br/>\n')
15 return text.replace('\n', '<br/>\n')
17
16
18 agescales = [("year", 3600 * 24 * 365),
17 agescales = [("year", 3600 * 24 * 365),
19 ("month", 3600 * 24 * 30),
18 ("month", 3600 * 24 * 30),
20 ("week", 3600 * 24 * 7),
19 ("week", 3600 * 24 * 7),
21 ("day", 3600 * 24),
20 ("day", 3600 * 24),
22 ("hour", 3600),
21 ("hour", 3600),
23 ("minute", 60),
22 ("minute", 60),
24 ("second", 1)]
23 ("second", 1)]
25
24
26 def age(date):
25 def age(date):
27 """:age: Date. Returns a human-readable date/time difference between the
26 """:age: Date. Returns a human-readable date/time difference between the
28 given date/time and the current date/time.
27 given date/time and the current date/time.
29 """
28 """
30
29
31 def plural(t, c):
30 def plural(t, c):
32 if c == 1:
31 if c == 1:
33 return t
32 return t
34 return t + "s"
33 return t + "s"
35 def fmt(t, c):
34 def fmt(t, c):
36 return "%d %s" % (c, plural(t, c))
35 return "%d %s" % (c, plural(t, c))
37
36
38 now = time.time()
37 now = time.time()
39 then = date[0]
38 then = date[0]
40 if then > now:
39 if then > now:
41 return 'in the future'
40 return 'in the future'
42
41
43 delta = max(1, int(now - then))
42 delta = max(1, int(now - then))
44 if delta > agescales[0][1] * 2:
43 if delta > agescales[0][1] * 2:
45 return util.shortdate(date)
44 return util.shortdate(date)
46
45
47 for t, s in agescales:
46 for t, s in agescales:
48 n = delta // s
47 n = delta // s
49 if n >= 2 or s == 1:
48 if n >= 2 or s == 1:
50 return '%s ago' % fmt(t, n)
49 return '%s ago' % fmt(t, n)
51
50
52 def basename(path):
51 def basename(path):
53 """:basename: Any text. Treats the text as a path, and returns the last
52 """:basename: Any text. Treats the text as a path, and returns the last
54 component of the path after splitting by the path separator
53 component of the path after splitting by the path separator
55 (ignoring trailing separators). For example, "foo/bar/baz" becomes
54 (ignoring trailing separators). For example, "foo/bar/baz" becomes
56 "baz" and "foo/bar//" becomes "bar".
55 "baz" and "foo/bar//" becomes "bar".
57 """
56 """
58 return os.path.basename(path)
57 return os.path.basename(path)
59
58
60 def datefilter(text):
59 def datefilter(text):
61 """:date: Date. Returns a date in a Unix date format, including the
60 """:date: Date. Returns a date in a Unix date format, including the
62 timezone: "Mon Sep 04 15:13:13 2006 0700".
61 timezone: "Mon Sep 04 15:13:13 2006 0700".
63 """
62 """
64 return util.datestr(text)
63 return util.datestr(text)
65
64
66 def domain(author):
65 def domain(author):
67 """:domain: Any text. Finds the first string that looks like an email
66 """:domain: Any text. Finds the first string that looks like an email
68 address, and extracts just the domain component. Example: ``User
67 address, and extracts just the domain component. Example: ``User
69 <user@example.com>`` becomes ``example.com``.
68 <user@example.com>`` becomes ``example.com``.
70 """
69 """
71 f = author.find('@')
70 f = author.find('@')
72 if f == -1:
71 if f == -1:
73 return ''
72 return ''
74 author = author[f + 1:]
73 author = author[f + 1:]
75 f = author.find('>')
74 f = author.find('>')
76 if f >= 0:
75 if f >= 0:
77 author = author[:f]
76 author = author[:f]
78 return author
77 return author
79
78
80 def email(text):
79 def email(text):
81 """:email: Any text. Extracts the first string that looks like an email
80 """:email: Any text. Extracts the first string that looks like an email
82 address. Example: ``User <user@example.com>`` becomes
81 address. Example: ``User <user@example.com>`` becomes
83 ``user@example.com``.
82 ``user@example.com``.
84 """
83 """
85 return util.email(text)
84 return util.email(text)
86
85
87 def escape(text):
86 def escape(text):
88 """:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
87 """:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
89 and ">" with XML entities.
88 and ">" with XML entities.
90 """
89 """
91 return cgi.escape(text, True)
90 return cgi.escape(text, True)
92
91
93 para_re = None
92 para_re = None
94 space_re = None
93 space_re = None
95
94
96 def fill(text, width):
95 def fill(text, width):
97 '''fill many paragraphs.'''
96 '''fill many paragraphs.'''
98 global para_re, space_re
97 global para_re, space_re
99 if para_re is None:
98 if para_re is None:
100 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
99 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
101 space_re = re.compile(r' +')
100 space_re = re.compile(r' +')
102
101
103 def findparas():
102 def findparas():
104 start = 0
103 start = 0
105 while True:
104 while True:
106 m = para_re.search(text, start)
105 m = para_re.search(text, start)
107 if not m:
106 if not m:
108 uctext = unicode(text[start:], encoding.encoding)
107 uctext = unicode(text[start:], encoding.encoding)
109 w = len(uctext)
108 w = len(uctext)
110 while 0 < w and uctext[w - 1].isspace():
109 while 0 < w and uctext[w - 1].isspace():
111 w -= 1
110 w -= 1
112 yield (uctext[:w].encode(encoding.encoding),
111 yield (uctext[:w].encode(encoding.encoding),
113 uctext[w:].encode(encoding.encoding))
112 uctext[w:].encode(encoding.encoding))
114 break
113 break
115 yield text[start:m.start(0)], m.group(1)
114 yield text[start:m.start(0)], m.group(1)
116 start = m.end(1)
115 start = m.end(1)
117
116
118 return "".join([space_re.sub(' ', util.wrap(para, width=width)) + rest
117 return "".join([space_re.sub(' ', util.wrap(para, width=width)) + rest
119 for para, rest in findparas()])
118 for para, rest in findparas()])
120
119
121 def fill68(text):
120 def fill68(text):
122 """:fill68: Any text. Wraps the text to fit in 68 columns."""
121 """:fill68: Any text. Wraps the text to fit in 68 columns."""
123 return fill(text, 68)
122 return fill(text, 68)
124
123
125 def fill76(text):
124 def fill76(text):
126 """:fill76: Any text. Wraps the text to fit in 76 columns."""
125 """:fill76: Any text. Wraps the text to fit in 76 columns."""
127 return fill(text, 76)
126 return fill(text, 76)
128
127
129 def firstline(text):
128 def firstline(text):
130 """:firstline: Any text. Returns the first line of text."""
129 """:firstline: Any text. Returns the first line of text."""
131 try:
130 try:
132 return text.splitlines(True)[0].rstrip('\r\n')
131 return text.splitlines(True)[0].rstrip('\r\n')
133 except IndexError:
132 except IndexError:
134 return ''
133 return ''
135
134
136 def hexfilter(text):
135 def hexfilter(text):
137 """:hex: Any text. Convert a binary Mercurial node identifier into
136 """:hex: Any text. Convert a binary Mercurial node identifier into
138 its long hexadecimal representation.
137 its long hexadecimal representation.
139 """
138 """
140 return node.hex(text)
139 return node.hex(text)
141
140
142 def hgdate(text):
141 def hgdate(text):
143 """:hgdate: Date. Returns the date as a pair of numbers: "1157407993
142 """:hgdate: Date. Returns the date as a pair of numbers: "1157407993
144 25200" (Unix timestamp, timezone offset).
143 25200" (Unix timestamp, timezone offset).
145 """
144 """
146 return "%d %d" % text
145 return "%d %d" % text
147
146
148 def isodate(text):
147 def isodate(text):
149 """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
148 """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
150 +0200".
149 +0200".
151 """
150 """
152 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
151 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
153
152
154 def isodatesec(text):
153 def isodatesec(text):
155 """:isodatesec: Date. Returns the date in ISO 8601 format, including
154 """:isodatesec: Date. Returns the date in ISO 8601 format, including
156 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
155 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
157 filter.
156 filter.
158 """
157 """
159 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
158 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
160
159
161 def indent(text, prefix):
160 def indent(text, prefix):
162 '''indent each non-empty line of text after first with prefix.'''
161 '''indent each non-empty line of text after first with prefix.'''
163 lines = text.splitlines()
162 lines = text.splitlines()
164 num_lines = len(lines)
163 num_lines = len(lines)
165 endswithnewline = text[-1:] == '\n'
164 endswithnewline = text[-1:] == '\n'
166 def indenter():
165 def indenter():
167 for i in xrange(num_lines):
166 for i in xrange(num_lines):
168 l = lines[i]
167 l = lines[i]
169 if i and l.strip():
168 if i and l.strip():
170 yield prefix
169 yield prefix
171 yield l
170 yield l
172 if i < num_lines - 1 or endswithnewline:
171 if i < num_lines - 1 or endswithnewline:
173 yield '\n'
172 yield '\n'
174 return "".join(indenter())
173 return "".join(indenter())
175
174
176 def json(obj):
175 def json(obj):
177 if obj is None or obj is False or obj is True:
176 if obj is None or obj is False or obj is True:
178 return {None: 'null', False: 'false', True: 'true'}[obj]
177 return {None: 'null', False: 'false', True: 'true'}[obj]
179 elif isinstance(obj, int) or isinstance(obj, float):
178 elif isinstance(obj, int) or isinstance(obj, float):
180 return str(obj)
179 return str(obj)
181 elif isinstance(obj, str):
180 elif isinstance(obj, str):
182 u = unicode(obj, encoding.encoding, 'replace')
181 u = unicode(obj, encoding.encoding, 'replace')
183 return '"%s"' % jsonescape(u)
182 return '"%s"' % jsonescape(u)
184 elif isinstance(obj, unicode):
183 elif isinstance(obj, unicode):
185 return '"%s"' % jsonescape(obj)
184 return '"%s"' % jsonescape(obj)
186 elif hasattr(obj, 'keys'):
185 elif hasattr(obj, 'keys'):
187 out = []
186 out = []
188 for k, v in obj.iteritems():
187 for k, v in obj.iteritems():
189 s = '%s: %s' % (json(k), json(v))
188 s = '%s: %s' % (json(k), json(v))
190 out.append(s)
189 out.append(s)
191 return '{' + ', '.join(out) + '}'
190 return '{' + ', '.join(out) + '}'
192 elif hasattr(obj, '__iter__'):
191 elif hasattr(obj, '__iter__'):
193 out = []
192 out = []
194 for i in obj:
193 for i in obj:
195 out.append(json(i))
194 out.append(json(i))
196 return '[' + ', '.join(out) + ']'
195 return '[' + ', '.join(out) + ']'
197 else:
196 else:
198 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
197 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
199
198
200 def _uescape(c):
199 def _uescape(c):
201 if ord(c) < 0x80:
200 if ord(c) < 0x80:
202 return c
201 return c
203 else:
202 else:
204 return '\\u%04x' % ord(c)
203 return '\\u%04x' % ord(c)
205
204
206 _escapes = [
205 _escapes = [
207 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
206 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
208 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
207 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
209 ]
208 ]
210
209
211 def jsonescape(s):
210 def jsonescape(s):
212 for k, v in _escapes:
211 for k, v in _escapes:
213 s = s.replace(k, v)
212 s = s.replace(k, v)
214 return ''.join(_uescape(c) for c in s)
213 return ''.join(_uescape(c) for c in s)
215
214
216 def localdate(text):
215 def localdate(text):
217 """:localdate: Date. Converts a date to local date."""
216 """:localdate: Date. Converts a date to local date."""
218 return (text[0], util.makedate()[1])
217 return (text[0], util.makedate()[1])
219
218
220 def nonempty(str):
219 def nonempty(str):
221 """:nonempty: Any text. Returns '(none)' if the string is empty."""
220 """:nonempty: Any text. Returns '(none)' if the string is empty."""
222 return str or "(none)"
221 return str or "(none)"
223
222
224 def obfuscate(text):
223 def obfuscate(text):
225 """:obfuscate: Any text. Returns the input text rendered as a sequence of
224 """:obfuscate: Any text. Returns the input text rendered as a sequence of
226 XML entities.
225 XML entities.
227 """
226 """
228 text = unicode(text, encoding.encoding, 'replace')
227 text = unicode(text, encoding.encoding, 'replace')
229 return ''.join(['&#%d;' % ord(c) for c in text])
228 return ''.join(['&#%d;' % ord(c) for c in text])
230
229
231 def permissions(flags):
230 def permissions(flags):
232 if "l" in flags:
231 if "l" in flags:
233 return "lrwxrwxrwx"
232 return "lrwxrwxrwx"
234 if "x" in flags:
233 if "x" in flags:
235 return "-rwxr-xr-x"
234 return "-rwxr-xr-x"
236 return "-rw-r--r--"
235 return "-rw-r--r--"
237
236
238 def person(author):
237 def person(author):
239 """:person: Any text. Returns the text before an email address."""
238 """:person: Any text. Returns the text before an email address."""
240 if not '@' in author:
239 if not '@' in author:
241 return author
240 return author
242 f = author.find('<')
241 f = author.find('<')
243 if f == -1:
242 if f == -1:
244 return util.shortuser(author)
243 return util.shortuser(author)
245 return author[:f].rstrip()
244 return author[:f].rstrip()
246
245
247 def rfc3339date(text):
246 def rfc3339date(text):
248 """:rfc3339date: Date. Returns a date using the Internet date format
247 """:rfc3339date: Date. Returns a date using the Internet date format
249 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
248 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
250 """
249 """
251 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
250 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
252
251
253 def rfc822date(text):
252 def rfc822date(text):
254 """:rfc822date: Date. Returns a date using the same format used in email
253 """:rfc822date: Date. Returns a date using the same format used in email
255 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
254 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
256 """
255 """
257 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
256 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
258
257
259 def short(text):
258 def short(text):
260 """:short: Changeset hash. Returns the short form of a changeset hash,
259 """:short: Changeset hash. Returns the short form of a changeset hash,
261 i.e. a 12 hexadecimal digit string.
260 i.e. a 12 hexadecimal digit string.
262 """
261 """
263 return text[:12]
262 return text[:12]
264
263
265 def shortdate(text):
264 def shortdate(text):
266 """:shortdate: Date. Returns a date like "2006-09-18"."""
265 """:shortdate: Date. Returns a date like "2006-09-18"."""
267 return util.shortdate(text)
266 return util.shortdate(text)
268
267
269 def stringescape(text):
268 def stringescape(text):
270 return text.encode('string_escape')
269 return text.encode('string_escape')
271
270
272 def stringify(thing):
271 def stringify(thing):
273 """:stringify: Any type. Turns the value into text by converting values into
272 """:stringify: Any type. Turns the value into text by converting values into
274 text and concatenating them.
273 text and concatenating them.
275 """
274 """
276 if hasattr(thing, '__iter__') and not isinstance(thing, str):
275 if hasattr(thing, '__iter__') and not isinstance(thing, str):
277 return "".join([stringify(t) for t in thing if t is not None])
276 return "".join([stringify(t) for t in thing if t is not None])
278 return str(thing)
277 return str(thing)
279
278
280 def strip(text):
279 def strip(text):
281 """:strip: Any text. Strips all leading and trailing whitespace."""
280 """:strip: Any text. Strips all leading and trailing whitespace."""
282 return text.strip()
281 return text.strip()
283
282
284 def stripdir(text):
283 def stripdir(text):
285 """:stripdir: Treat the text as path and strip a directory level, if
284 """:stripdir: Treat the text as path and strip a directory level, if
286 possible. For example, "foo" and "foo/bar" becomes "foo".
285 possible. For example, "foo" and "foo/bar" becomes "foo".
287 """
286 """
288 dir = os.path.dirname(text)
287 dir = os.path.dirname(text)
289 if dir == "":
288 if dir == "":
290 return os.path.basename(text)
289 return os.path.basename(text)
291 else:
290 else:
292 return dir
291 return dir
293
292
294 def tabindent(text):
293 def tabindent(text):
295 """:tabindent: Any text. Returns the text, with every line except the
294 """:tabindent: Any text. Returns the text, with every line except the
296 first starting with a tab character.
295 first starting with a tab character.
297 """
296 """
298 return indent(text, '\t')
297 return indent(text, '\t')
299
298
300 def urlescape(text):
299 def urlescape(text):
301 """:urlescape: Any text. Escapes all "special" characters. For example,
300 """:urlescape: Any text. Escapes all "special" characters. For example,
302 "foo bar" becomes "foo%20bar".
301 "foo bar" becomes "foo%20bar".
303 """
302 """
304 return urllib.quote(text)
303 return urllib.quote(text)
305
304
306 def userfilter(text):
305 def userfilter(text):
307 """:user: Any text. Returns the user portion of an email address."""
306 """:user: Any text. Returns the user portion of an email address."""
308 return util.shortuser(text)
307 return util.shortuser(text)
309
308
310 def xmlescape(text):
309 def xmlescape(text):
311 text = (text
310 text = (text
312 .replace('&', '&amp;')
311 .replace('&', '&amp;')
313 .replace('<', '&lt;')
312 .replace('<', '&lt;')
314 .replace('>', '&gt;')
313 .replace('>', '&gt;')
315 .replace('"', '&quot;')
314 .replace('"', '&quot;')
316 .replace("'", '&#39;')) # &apos; invalid in HTML
315 .replace("'", '&#39;')) # &apos; invalid in HTML
317 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
316 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
318
317
319 filters = {
318 filters = {
320 "addbreaks": addbreaks,
319 "addbreaks": addbreaks,
321 "age": age,
320 "age": age,
322 "basename": basename,
321 "basename": basename,
323 "date": datefilter,
322 "date": datefilter,
324 "domain": domain,
323 "domain": domain,
325 "email": email,
324 "email": email,
326 "escape": escape,
325 "escape": escape,
327 "fill68": fill68,
326 "fill68": fill68,
328 "fill76": fill76,
327 "fill76": fill76,
329 "firstline": firstline,
328 "firstline": firstline,
330 "hex": hexfilter,
329 "hex": hexfilter,
331 "hgdate": hgdate,
330 "hgdate": hgdate,
332 "isodate": isodate,
331 "isodate": isodate,
333 "isodatesec": isodatesec,
332 "isodatesec": isodatesec,
334 "json": json,
333 "json": json,
335 "jsonescape": jsonescape,
334 "jsonescape": jsonescape,
336 "localdate": localdate,
335 "localdate": localdate,
337 "nonempty": nonempty,
336 "nonempty": nonempty,
338 "obfuscate": obfuscate,
337 "obfuscate": obfuscate,
339 "permissions": permissions,
338 "permissions": permissions,
340 "person": person,
339 "person": person,
341 "rfc3339date": rfc3339date,
340 "rfc3339date": rfc3339date,
342 "rfc822date": rfc822date,
341 "rfc822date": rfc822date,
343 "short": short,
342 "short": short,
344 "shortdate": shortdate,
343 "shortdate": shortdate,
345 "stringescape": stringescape,
344 "stringescape": stringescape,
346 "stringify": stringify,
345 "stringify": stringify,
347 "strip": strip,
346 "strip": strip,
348 "stripdir": stripdir,
347 "stripdir": stripdir,
349 "tabindent": tabindent,
348 "tabindent": tabindent,
350 "urlescape": urlescape,
349 "urlescape": urlescape,
351 "user": userfilter,
350 "user": userfilter,
352 "xmlescape": xmlescape,
351 "xmlescape": xmlescape,
353 }
352 }
354
353
355 def makedoc(topic, doc):
354 def makedoc(topic, doc):
356 """Generate and include templatefilters help in templating topic."""
355 return help.makeitemsdoc(topic, doc, '.. filtersmarker', filters)
357 entries = []
358 for name in sorted(filters):
359 text = (filters[name].__doc__ or '').rstrip()
360 if not text:
361 continue
362 text = gettext(text)
363 lines = text.splitlines()
364 lines[1:] = [(' ' + l.strip()) for l in lines[1:]]
365 entries.append('\n'.join(lines))
366 entries = '\n\n'.join(entries)
367 doc = doc.replace('.. filtersmarker', entries)
368 return doc
369
356
370 # tell hggettext to extract docstrings from these functions:
357 # tell hggettext to extract docstrings from these functions:
371 i18nfunctions = filters.values()
358 i18nfunctions = filters.values()
@@ -1,334 +1,321 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex
8 from node import hex
9 import encoding, patch, util, error
9 import encoding, patch, util, error, help
10 from i18n import gettext
11
10
12 def showlist(name, values, plural=None, **args):
11 def showlist(name, values, plural=None, **args):
13 '''expand set of values.
12 '''expand set of values.
14 name is name of key in template map.
13 name is name of key in template map.
15 values is list of strings or dicts.
14 values is list of strings or dicts.
16 plural is plural of name, if not simply name + 's'.
15 plural is plural of name, if not simply name + 's'.
17
16
18 expansion works like this, given name 'foo'.
17 expansion works like this, given name 'foo'.
19
18
20 if values is empty, expand 'no_foos'.
19 if values is empty, expand 'no_foos'.
21
20
22 if 'foo' not in template map, return values as a string,
21 if 'foo' not in template map, return values as a string,
23 joined by space.
22 joined by space.
24
23
25 expand 'start_foos'.
24 expand 'start_foos'.
26
25
27 for each value, expand 'foo'. if 'last_foo' in template
26 for each value, expand 'foo'. if 'last_foo' in template
28 map, expand it instead of 'foo' for last key.
27 map, expand it instead of 'foo' for last key.
29
28
30 expand 'end_foos'.
29 expand 'end_foos'.
31 '''
30 '''
32 templ = args['templ']
31 templ = args['templ']
33 if plural:
32 if plural:
34 names = plural
33 names = plural
35 else: names = name + 's'
34 else: names = name + 's'
36 if not values:
35 if not values:
37 noname = 'no_' + names
36 noname = 'no_' + names
38 if noname in templ:
37 if noname in templ:
39 yield templ(noname, **args)
38 yield templ(noname, **args)
40 return
39 return
41 if name not in templ:
40 if name not in templ:
42 if isinstance(values[0], str):
41 if isinstance(values[0], str):
43 yield ' '.join(values)
42 yield ' '.join(values)
44 else:
43 else:
45 for v in values:
44 for v in values:
46 yield dict(v, **args)
45 yield dict(v, **args)
47 return
46 return
48 startname = 'start_' + names
47 startname = 'start_' + names
49 if startname in templ:
48 if startname in templ:
50 yield templ(startname, **args)
49 yield templ(startname, **args)
51 vargs = args.copy()
50 vargs = args.copy()
52 def one(v, tag=name):
51 def one(v, tag=name):
53 try:
52 try:
54 vargs.update(v)
53 vargs.update(v)
55 except (AttributeError, ValueError):
54 except (AttributeError, ValueError):
56 try:
55 try:
57 for a, b in v:
56 for a, b in v:
58 vargs[a] = b
57 vargs[a] = b
59 except ValueError:
58 except ValueError:
60 vargs[name] = v
59 vargs[name] = v
61 return templ(tag, **vargs)
60 return templ(tag, **vargs)
62 lastname = 'last_' + name
61 lastname = 'last_' + name
63 if lastname in templ:
62 if lastname in templ:
64 last = values.pop()
63 last = values.pop()
65 else:
64 else:
66 last = None
65 last = None
67 for v in values:
66 for v in values:
68 yield one(v)
67 yield one(v)
69 if last is not None:
68 if last is not None:
70 yield one(last, tag=lastname)
69 yield one(last, tag=lastname)
71 endname = 'end_' + names
70 endname = 'end_' + names
72 if endname in templ:
71 if endname in templ:
73 yield templ(endname, **args)
72 yield templ(endname, **args)
74
73
75 def getfiles(repo, ctx, revcache):
74 def getfiles(repo, ctx, revcache):
76 if 'files' not in revcache:
75 if 'files' not in revcache:
77 revcache['files'] = repo.status(ctx.parents()[0].node(),
76 revcache['files'] = repo.status(ctx.parents()[0].node(),
78 ctx.node())[:3]
77 ctx.node())[:3]
79 return revcache['files']
78 return revcache['files']
80
79
81 def getlatesttags(repo, ctx, cache):
80 def getlatesttags(repo, ctx, cache):
82 '''return date, distance and name for the latest tag of rev'''
81 '''return date, distance and name for the latest tag of rev'''
83
82
84 if 'latesttags' not in cache:
83 if 'latesttags' not in cache:
85 # Cache mapping from rev to a tuple with tag date, tag
84 # Cache mapping from rev to a tuple with tag date, tag
86 # distance and tag name
85 # distance and tag name
87 cache['latesttags'] = {-1: (0, 0, 'null')}
86 cache['latesttags'] = {-1: (0, 0, 'null')}
88 latesttags = cache['latesttags']
87 latesttags = cache['latesttags']
89
88
90 rev = ctx.rev()
89 rev = ctx.rev()
91 todo = [rev]
90 todo = [rev]
92 while todo:
91 while todo:
93 rev = todo.pop()
92 rev = todo.pop()
94 if rev in latesttags:
93 if rev in latesttags:
95 continue
94 continue
96 ctx = repo[rev]
95 ctx = repo[rev]
97 tags = [t for t in ctx.tags() if repo.tagtype(t) == 'global']
96 tags = [t for t in ctx.tags() if repo.tagtype(t) == 'global']
98 if tags:
97 if tags:
99 latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
98 latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
100 continue
99 continue
101 try:
100 try:
102 # The tuples are laid out so the right one can be found by
101 # The tuples are laid out so the right one can be found by
103 # comparison.
102 # comparison.
104 pdate, pdist, ptag = max(
103 pdate, pdist, ptag = max(
105 latesttags[p.rev()] for p in ctx.parents())
104 latesttags[p.rev()] for p in ctx.parents())
106 except KeyError:
105 except KeyError:
107 # Cache miss - recurse
106 # Cache miss - recurse
108 todo.append(rev)
107 todo.append(rev)
109 todo.extend(p.rev() for p in ctx.parents())
108 todo.extend(p.rev() for p in ctx.parents())
110 continue
109 continue
111 latesttags[rev] = pdate, pdist + 1, ptag
110 latesttags[rev] = pdate, pdist + 1, ptag
112 return latesttags[rev]
111 return latesttags[rev]
113
112
114 def getrenamedfn(repo, endrev=None):
113 def getrenamedfn(repo, endrev=None):
115 rcache = {}
114 rcache = {}
116 if endrev is None:
115 if endrev is None:
117 endrev = len(repo)
116 endrev = len(repo)
118
117
119 def getrenamed(fn, rev):
118 def getrenamed(fn, rev):
120 '''looks up all renames for a file (up to endrev) the first
119 '''looks up all renames for a file (up to endrev) the first
121 time the file is given. It indexes on the changerev and only
120 time the file is given. It indexes on the changerev and only
122 parses the manifest if linkrev != changerev.
121 parses the manifest if linkrev != changerev.
123 Returns rename info for fn at changerev rev.'''
122 Returns rename info for fn at changerev rev.'''
124 if fn not in rcache:
123 if fn not in rcache:
125 rcache[fn] = {}
124 rcache[fn] = {}
126 fl = repo.file(fn)
125 fl = repo.file(fn)
127 for i in fl:
126 for i in fl:
128 lr = fl.linkrev(i)
127 lr = fl.linkrev(i)
129 renamed = fl.renamed(fl.node(i))
128 renamed = fl.renamed(fl.node(i))
130 rcache[fn][lr] = renamed
129 rcache[fn][lr] = renamed
131 if lr >= endrev:
130 if lr >= endrev:
132 break
131 break
133 if rev in rcache[fn]:
132 if rev in rcache[fn]:
134 return rcache[fn][rev]
133 return rcache[fn][rev]
135
134
136 # If linkrev != rev (i.e. rev not found in rcache) fallback to
135 # If linkrev != rev (i.e. rev not found in rcache) fallback to
137 # filectx logic.
136 # filectx logic.
138 try:
137 try:
139 return repo[rev][fn].renamed()
138 return repo[rev][fn].renamed()
140 except error.LookupError:
139 except error.LookupError:
141 return None
140 return None
142
141
143 return getrenamed
142 return getrenamed
144
143
145
144
146 def showauthor(repo, ctx, templ, **args):
145 def showauthor(repo, ctx, templ, **args):
147 """:author: String. The unmodified author of the changeset."""
146 """:author: String. The unmodified author of the changeset."""
148 return ctx.user()
147 return ctx.user()
149
148
150 def showbranch(**args):
149 def showbranch(**args):
151 """:branch: String. The name of the branch on which the changeset was
150 """:branch: String. The name of the branch on which the changeset was
152 committed.
151 committed.
153 """
152 """
154 return args['ctx'].branch()
153 return args['ctx'].branch()
155
154
156 def showbranches(**args):
155 def showbranches(**args):
157 """:branches: List of strings. The name of the branch on which the
156 """:branches: List of strings. The name of the branch on which the
158 changeset was committed. Will be empty if the branch name was
157 changeset was committed. Will be empty if the branch name was
159 default.
158 default.
160 """
159 """
161 branch = args['ctx'].branch()
160 branch = args['ctx'].branch()
162 if branch != 'default':
161 if branch != 'default':
163 return showlist('branch', [branch], plural='branches', **args)
162 return showlist('branch', [branch], plural='branches', **args)
164
163
165 def showbookmarks(**args):
164 def showbookmarks(**args):
166 """:bookmarks: List of strings. Any bookmarks associated with the
165 """:bookmarks: List of strings. Any bookmarks associated with the
167 changeset.
166 changeset.
168 """
167 """
169 bookmarks = args['ctx'].bookmarks()
168 bookmarks = args['ctx'].bookmarks()
170 return showlist('bookmark', bookmarks, **args)
169 return showlist('bookmark', bookmarks, **args)
171
170
172 def showchildren(**args):
171 def showchildren(**args):
173 """:children: List of strings. The children of the changeset."""
172 """:children: List of strings. The children of the changeset."""
174 ctx = args['ctx']
173 ctx = args['ctx']
175 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
174 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
176 return showlist('children', childrevs, **args)
175 return showlist('children', childrevs, **args)
177
176
178 def showdate(repo, ctx, templ, **args):
177 def showdate(repo, ctx, templ, **args):
179 """:date: Date information. The date when the changeset was committed."""
178 """:date: Date information. The date when the changeset was committed."""
180 return ctx.date()
179 return ctx.date()
181
180
182 def showdescription(repo, ctx, templ, **args):
181 def showdescription(repo, ctx, templ, **args):
183 """:desc: String. The text of the changeset description."""
182 """:desc: String. The text of the changeset description."""
184 return ctx.description().strip()
183 return ctx.description().strip()
185
184
186 def showdiffstat(repo, ctx, templ, **args):
185 def showdiffstat(repo, ctx, templ, **args):
187 """:diffstat: String. Statistics of changes with the following format:
186 """:diffstat: String. Statistics of changes with the following format:
188 "modified files: +added/-removed lines"
187 "modified files: +added/-removed lines"
189 """
188 """
190 files, adds, removes = 0, 0, 0
189 files, adds, removes = 0, 0, 0
191 for i in patch.diffstatdata(util.iterlines(ctx.diff())):
190 for i in patch.diffstatdata(util.iterlines(ctx.diff())):
192 files += 1
191 files += 1
193 adds += i[1]
192 adds += i[1]
194 removes += i[2]
193 removes += i[2]
195 return '%s: +%s/-%s' % (files, adds, removes)
194 return '%s: +%s/-%s' % (files, adds, removes)
196
195
197 def showextras(**args):
196 def showextras(**args):
198 templ = args['templ']
197 templ = args['templ']
199 for key, value in sorted(args['ctx'].extra().items()):
198 for key, value in sorted(args['ctx'].extra().items()):
200 args = args.copy()
199 args = args.copy()
201 args.update(dict(key=key, value=value))
200 args.update(dict(key=key, value=value))
202 yield templ('extra', **args)
201 yield templ('extra', **args)
203
202
204 def showfileadds(**args):
203 def showfileadds(**args):
205 """:file_adds: List of strings. Files added by this changeset."""
204 """:file_adds: List of strings. Files added by this changeset."""
206 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
205 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
207 return showlist('file_add', getfiles(repo, ctx, revcache)[1], **args)
206 return showlist('file_add', getfiles(repo, ctx, revcache)[1], **args)
208
207
209 def showfilecopies(**args):
208 def showfilecopies(**args):
210 """:file_copies: List of strings. Files copied in this changeset with
209 """:file_copies: List of strings. Files copied in this changeset with
211 their sources.
210 their sources.
212 """
211 """
213 cache, ctx = args['cache'], args['ctx']
212 cache, ctx = args['cache'], args['ctx']
214 copies = args['revcache'].get('copies')
213 copies = args['revcache'].get('copies')
215 if copies is None:
214 if copies is None:
216 if 'getrenamed' not in cache:
215 if 'getrenamed' not in cache:
217 cache['getrenamed'] = getrenamedfn(args['repo'])
216 cache['getrenamed'] = getrenamedfn(args['repo'])
218 copies = []
217 copies = []
219 getrenamed = cache['getrenamed']
218 getrenamed = cache['getrenamed']
220 for fn in ctx.files():
219 for fn in ctx.files():
221 rename = getrenamed(fn, ctx.rev())
220 rename = getrenamed(fn, ctx.rev())
222 if rename:
221 if rename:
223 copies.append((fn, rename[0]))
222 copies.append((fn, rename[0]))
224
223
225 c = [{'name': x[0], 'source': x[1]} for x in copies]
224 c = [{'name': x[0], 'source': x[1]} for x in copies]
226 return showlist('file_copy', c, plural='file_copies', **args)
225 return showlist('file_copy', c, plural='file_copies', **args)
227
226
228 # showfilecopiesswitch() displays file copies only if copy records are
227 # showfilecopiesswitch() displays file copies only if copy records are
229 # provided before calling the templater, usually with a --copies
228 # provided before calling the templater, usually with a --copies
230 # command line switch.
229 # command line switch.
231 def showfilecopiesswitch(**args):
230 def showfilecopiesswitch(**args):
232 """:file_copies_switch: List of strings. Like "file_copies" but displayed
231 """:file_copies_switch: List of strings. Like "file_copies" but displayed
233 only if the --copied switch is set.
232 only if the --copied switch is set.
234 """
233 """
235 copies = args['revcache'].get('copies') or []
234 copies = args['revcache'].get('copies') or []
236 c = [{'name': x[0], 'source': x[1]} for x in copies]
235 c = [{'name': x[0], 'source': x[1]} for x in copies]
237 return showlist('file_copy', c, plural='file_copies', **args)
236 return showlist('file_copy', c, plural='file_copies', **args)
238
237
239 def showfiledels(**args):
238 def showfiledels(**args):
240 """:file_dels: List of strings. Files removed by this changeset."""
239 """:file_dels: List of strings. Files removed by this changeset."""
241 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
240 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
242 return showlist('file_del', getfiles(repo, ctx, revcache)[2], **args)
241 return showlist('file_del', getfiles(repo, ctx, revcache)[2], **args)
243
242
244 def showfilemods(**args):
243 def showfilemods(**args):
245 """:file_mods: List of strings. Files modified by this changeset."""
244 """:file_mods: List of strings. Files modified by this changeset."""
246 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
245 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
247 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], **args)
246 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], **args)
248
247
249 def showfiles(**args):
248 def showfiles(**args):
250 """:files: List of strings. All files modified, added, or removed by this
249 """:files: List of strings. All files modified, added, or removed by this
251 changeset.
250 changeset.
252 """
251 """
253 return showlist('file', args['ctx'].files(), **args)
252 return showlist('file', args['ctx'].files(), **args)
254
253
255 def showlatesttag(repo, ctx, templ, cache, **args):
254 def showlatesttag(repo, ctx, templ, cache, **args):
256 """:latesttag: String. Most recent global tag in the ancestors of this
255 """:latesttag: String. Most recent global tag in the ancestors of this
257 changeset.
256 changeset.
258 """
257 """
259 return getlatesttags(repo, ctx, cache)[2]
258 return getlatesttags(repo, ctx, cache)[2]
260
259
261 def showlatesttagdistance(repo, ctx, templ, cache, **args):
260 def showlatesttagdistance(repo, ctx, templ, cache, **args):
262 """:latesttagdistance: Integer. Longest path to the latest tag."""
261 """:latesttagdistance: Integer. Longest path to the latest tag."""
263 return getlatesttags(repo, ctx, cache)[1]
262 return getlatesttags(repo, ctx, cache)[1]
264
263
265 def showmanifest(**args):
264 def showmanifest(**args):
266 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
265 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
267 args = args.copy()
266 args = args.copy()
268 args.update(dict(rev=repo.manifest.rev(ctx.changeset()[0]),
267 args.update(dict(rev=repo.manifest.rev(ctx.changeset()[0]),
269 node=hex(ctx.changeset()[0])))
268 node=hex(ctx.changeset()[0])))
270 return templ('manifest', **args)
269 return templ('manifest', **args)
271
270
272 def shownode(repo, ctx, templ, **args):
271 def shownode(repo, ctx, templ, **args):
273 """:node: String. The changeset identification hash, as a 40 hexadecimal
272 """:node: String. The changeset identification hash, as a 40 hexadecimal
274 digit string.
273 digit string.
275 """
274 """
276 return ctx.hex()
275 return ctx.hex()
277
276
278 def showrev(repo, ctx, templ, **args):
277 def showrev(repo, ctx, templ, **args):
279 """:rev: Integer. The repository-local changeset revision number."""
278 """:rev: Integer. The repository-local changeset revision number."""
280 return ctx.rev()
279 return ctx.rev()
281
280
282 def showtags(**args):
281 def showtags(**args):
283 """:tags: List of strings. Any tags associated with the changeset."""
282 """:tags: List of strings. Any tags associated with the changeset."""
284 return showlist('tag', args['ctx'].tags(), **args)
283 return showlist('tag', args['ctx'].tags(), **args)
285
284
286 # keywords are callables like:
285 # keywords are callables like:
287 # fn(repo, ctx, templ, cache, revcache, **args)
286 # fn(repo, ctx, templ, cache, revcache, **args)
288 # with:
287 # with:
289 # repo - current repository instance
288 # repo - current repository instance
290 # ctx - the changectx being displayed
289 # ctx - the changectx being displayed
291 # templ - the templater instance
290 # templ - the templater instance
292 # cache - a cache dictionary for the whole templater run
291 # cache - a cache dictionary for the whole templater run
293 # revcache - a cache dictionary for the current revision
292 # revcache - a cache dictionary for the current revision
294 keywords = {
293 keywords = {
295 'author': showauthor,
294 'author': showauthor,
296 'branch': showbranch,
295 'branch': showbranch,
297 'branches': showbranches,
296 'branches': showbranches,
298 'bookmarks': showbookmarks,
297 'bookmarks': showbookmarks,
299 'children': showchildren,
298 'children': showchildren,
300 'date': showdate,
299 'date': showdate,
301 'desc': showdescription,
300 'desc': showdescription,
302 'diffstat': showdiffstat,
301 'diffstat': showdiffstat,
303 'extras': showextras,
302 'extras': showextras,
304 'file_adds': showfileadds,
303 'file_adds': showfileadds,
305 'file_copies': showfilecopies,
304 'file_copies': showfilecopies,
306 'file_copies_switch': showfilecopiesswitch,
305 'file_copies_switch': showfilecopiesswitch,
307 'file_dels': showfiledels,
306 'file_dels': showfiledels,
308 'file_mods': showfilemods,
307 'file_mods': showfilemods,
309 'files': showfiles,
308 'files': showfiles,
310 'latesttag': showlatesttag,
309 'latesttag': showlatesttag,
311 'latesttagdistance': showlatesttagdistance,
310 'latesttagdistance': showlatesttagdistance,
312 'manifest': showmanifest,
311 'manifest': showmanifest,
313 'node': shownode,
312 'node': shownode,
314 'rev': showrev,
313 'rev': showrev,
315 'tags': showtags,
314 'tags': showtags,
316 }
315 }
317
316
318 def makedoc(topic, doc):
317 def makedoc(topic, doc):
319 """Generate and include keyword help in templating topic."""
318 return help.makeitemsdoc(topic, doc, '.. keywordsmarker', keywords)
320 kw = []
321 for name in sorted(keywords):
322 text = (keywords[name].__doc__ or '').rstrip()
323 if not text:
324 continue
325 text = gettext(text)
326 lines = text.splitlines()
327 lines[1:] = [(' ' + l.strip()) for l in lines[1:]]
328 kw.append('\n'.join(lines))
329 kw = '\n\n'.join(kw)
330 doc = doc.replace('.. keywordsmarker', kw)
331 return doc
332
319
333 # tell hggettext to extract docstrings from these functions:
320 # tell hggettext to extract docstrings from these functions:
334 i18nfunctions = keywords.values()
321 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now