##// END OF EJS Templates
templater: tell hggettext to collect help of template functions
Yuya Nishihara -
r24601:d80819f6 default
parent child Browse files
Show More
@@ -1,214 +1,215 b''
1 # If you want to change PREFIX, do not just edit it below. The changed
1 # If you want to change PREFIX, do not just edit it below. The changed
2 # value wont get passed on to recursive make calls. You should instead
2 # value wont get passed on to recursive make calls. You should instead
3 # override the variable on the command like:
3 # override the variable on the command like:
4 #
4 #
5 # % make PREFIX=/opt/ install
5 # % make PREFIX=/opt/ install
6
6
7 PREFIX=/usr/local
7 PREFIX=/usr/local
8 export PREFIX
8 export PREFIX
9 PYTHON=python
9 PYTHON=python
10 $(eval HGROOT := $(shell pwd))
10 $(eval HGROOT := $(shell pwd))
11 HGPYTHONS ?= $(HGROOT)/build/pythons
11 HGPYTHONS ?= $(HGROOT)/build/pythons
12 PURE=
12 PURE=
13 PYFILES:=$(shell find mercurial hgext doc -name '*.py')
13 PYFILES:=$(shell find mercurial hgext doc -name '*.py')
14 DOCFILES=mercurial/help/*.txt
14 DOCFILES=mercurial/help/*.txt
15 export LANGUAGE=C
15 export LANGUAGE=C
16 export LC_ALL=C
16 export LC_ALL=C
17 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
17 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
18
18
19 # Set this to e.g. "mingw32" to use a non-default compiler.
19 # Set this to e.g. "mingw32" to use a non-default compiler.
20 COMPILER=
20 COMPILER=
21
21
22 help:
22 help:
23 @echo 'Commonly used make targets:'
23 @echo 'Commonly used make targets:'
24 @echo ' all - build program and documentation'
24 @echo ' all - build program and documentation'
25 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
25 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
26 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
26 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
27 @echo ' local - build for inplace usage'
27 @echo ' local - build for inplace usage'
28 @echo ' tests - run all tests in the automatic test suite'
28 @echo ' tests - run all tests in the automatic test suite'
29 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
29 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
30 @echo ' dist - run all tests and create a source tarball in dist/'
30 @echo ' dist - run all tests and create a source tarball in dist/'
31 @echo ' clean - remove files created by other targets'
31 @echo ' clean - remove files created by other targets'
32 @echo ' (except installed files or dist source tarball)'
32 @echo ' (except installed files or dist source tarball)'
33 @echo ' update-pot - update i18n/hg.pot'
33 @echo ' update-pot - update i18n/hg.pot'
34 @echo
34 @echo
35 @echo 'Example for a system-wide installation under /usr/local:'
35 @echo 'Example for a system-wide installation under /usr/local:'
36 @echo ' make all && su -c "make install" && hg version'
36 @echo ' make all && su -c "make install" && hg version'
37 @echo
37 @echo
38 @echo 'Example for a local installation (usable in this directory):'
38 @echo 'Example for a local installation (usable in this directory):'
39 @echo ' make local && ./hg version'
39 @echo ' make local && ./hg version'
40
40
41 all: build doc
41 all: build doc
42
42
43 local:
43 local:
44 $(PYTHON) setup.py $(PURE) \
44 $(PYTHON) setup.py $(PURE) \
45 build_py -c -d . \
45 build_py -c -d . \
46 build_ext $(COMPILER:%=-c %) -i \
46 build_ext $(COMPILER:%=-c %) -i \
47 build_hgexe $(COMPILER:%=-c %) -i \
47 build_hgexe $(COMPILER:%=-c %) -i \
48 build_mo
48 build_mo
49 env HGRCPATH= $(PYTHON) hg version
49 env HGRCPATH= $(PYTHON) hg version
50
50
51 build:
51 build:
52 $(PYTHON) setup.py $(PURE) build $(COMPILER:%=-c %)
52 $(PYTHON) setup.py $(PURE) build $(COMPILER:%=-c %)
53
53
54 doc:
54 doc:
55 $(MAKE) -C doc
55 $(MAKE) -C doc
56
56
57 clean:
57 clean:
58 -$(PYTHON) setup.py clean --all # ignore errors from this command
58 -$(PYTHON) setup.py clean --all # ignore errors from this command
59 find contrib doc hgext i18n mercurial tests \
59 find contrib doc hgext i18n mercurial tests \
60 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
60 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
61 rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py)))
61 rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py)))
62 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
62 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
63 if test -d .hg; then rm -f mercurial/__version__.py; fi
63 if test -d .hg; then rm -f mercurial/__version__.py; fi
64 rm -rf build mercurial/locale
64 rm -rf build mercurial/locale
65 $(MAKE) -C doc clean
65 $(MAKE) -C doc clean
66
66
67 install: install-bin install-doc
67 install: install-bin install-doc
68
68
69 install-bin: build
69 install-bin: build
70 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
70 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
71
71
72 install-doc: doc
72 install-doc: doc
73 cd doc && $(MAKE) $(MFLAGS) install
73 cd doc && $(MAKE) $(MFLAGS) install
74
74
75 install-home: install-home-bin install-home-doc
75 install-home: install-home-bin install-home-doc
76
76
77 install-home-bin: build
77 install-home-bin: build
78 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
78 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
79
79
80 install-home-doc: doc
80 install-home-doc: doc
81 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
81 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
82
82
83 MANIFEST-doc:
83 MANIFEST-doc:
84 $(MAKE) -C doc MANIFEST
84 $(MAKE) -C doc MANIFEST
85
85
86 MANIFEST.in: MANIFEST-doc
86 MANIFEST.in: MANIFEST-doc
87 hg manifest | sed -e 's/^/include /' > MANIFEST.in
87 hg manifest | sed -e 's/^/include /' > MANIFEST.in
88 echo include mercurial/__version__.py >> MANIFEST.in
88 echo include mercurial/__version__.py >> MANIFEST.in
89 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
89 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
90
90
91 dist: tests dist-notests
91 dist: tests dist-notests
92
92
93 dist-notests: doc MANIFEST.in
93 dist-notests: doc MANIFEST.in
94 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
94 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
95
95
96 check: tests
96 check: tests
97
97
98 tests:
98 tests:
99 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
99 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
100
100
101 test-%:
101 test-%:
102 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
102 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
103
103
104 testpy-%:
104 testpy-%:
105 @echo Looking for Python $* in $(HGPYTHONS)
105 @echo Looking for Python $* in $(HGPYTHONS)
106 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
106 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
107 cd $$(mktemp --directory --tmpdir) && \
107 cd $$(mktemp --directory --tmpdir) && \
108 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
108 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
109 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
109 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
110
110
111 check-code:
111 check-code:
112 hg manifest | xargs python contrib/check-code.py
112 hg manifest | xargs python contrib/check-code.py
113
113
114 update-pot: i18n/hg.pot
114 update-pot: i18n/hg.pot
115
115
116 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
116 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
117 $(PYTHON) i18n/hggettext mercurial/commands.py \
117 $(PYTHON) i18n/hggettext mercurial/commands.py \
118 hgext/*.py hgext/*/__init__.py \
118 hgext/*.py hgext/*/__init__.py \
119 mercurial/fileset.py mercurial/revset.py \
119 mercurial/fileset.py mercurial/revset.py \
120 mercurial/templatefilters.py mercurial/templatekw.py \
120 mercurial/templatefilters.py mercurial/templatekw.py \
121 mercurial/templater.py \
121 mercurial/filemerge.py \
122 mercurial/filemerge.py \
122 $(DOCFILES) > i18n/hg.pot.tmp
123 $(DOCFILES) > i18n/hg.pot.tmp
123 # All strings marked for translation in Mercurial contain
124 # All strings marked for translation in Mercurial contain
124 # ASCII characters only. But some files contain string
125 # ASCII characters only. But some files contain string
125 # literals like this '\037\213'. xgettext thinks it has to
126 # literals like this '\037\213'. xgettext thinks it has to
126 # parse them even though they are not marked for translation.
127 # parse them even though they are not marked for translation.
127 # Extracting with an explicit encoding of ISO-8859-1 will make
128 # Extracting with an explicit encoding of ISO-8859-1 will make
128 # xgettext "parse" and ignore them.
129 # xgettext "parse" and ignore them.
129 echo $(PYFILES) | xargs \
130 echo $(PYFILES) | xargs \
130 xgettext --package-name "Mercurial" \
131 xgettext --package-name "Mercurial" \
131 --msgid-bugs-address "<mercurial-devel@selenic.com>" \
132 --msgid-bugs-address "<mercurial-devel@selenic.com>" \
132 --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \
133 --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \
133 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
134 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
134 -d hg -p i18n -o hg.pot.tmp
135 -d hg -p i18n -o hg.pot.tmp
135 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
136 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
136 # The target file is not created before the last step. So it never is in
137 # The target file is not created before the last step. So it never is in
137 # an intermediate state.
138 # an intermediate state.
138 mv -f i18n/hg.pot.tmp i18n/hg.pot
139 mv -f i18n/hg.pot.tmp i18n/hg.pot
139
140
140 %.po: i18n/hg.pot
141 %.po: i18n/hg.pot
141 # work on a temporary copy for never having a half completed target
142 # work on a temporary copy for never having a half completed target
142 cp $@ $@.tmp
143 cp $@ $@.tmp
143 msgmerge --no-location --update $@.tmp $^
144 msgmerge --no-location --update $@.tmp $^
144 mv -f $@.tmp $@
145 mv -f $@.tmp $@
145
146
146 # Packaging targets
147 # Packaging targets
147
148
148 osx:
149 osx:
149 python -c 'import bdist_mpkg.script_bdist_mpkg' || \
150 python -c 'import bdist_mpkg.script_bdist_mpkg' || \
150 (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false)
151 (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false)
151 rm -rf dist/mercurial-*.mpkg
152 rm -rf dist/mercurial-*.mpkg
152 python -m bdist_mpkg.script_bdist_mpkg setup.py --
153 python -m bdist_mpkg.script_bdist_mpkg setup.py --
153 python contrib/fixpax.py dist/mercurial-*.mpkg/Contents/Packages/*.pkg/Contents/Archive.pax.gz
154 python contrib/fixpax.py dist/mercurial-*.mpkg/Contents/Packages/*.pkg/Contents/Archive.pax.gz
154 mkdir -p packages/osx
155 mkdir -p packages/osx
155 N=`cd dist && echo mercurial-*.mpkg | sed 's,\.mpkg$$,,'` && hdiutil create -srcfolder dist/$$N.mpkg/ -scrub -volname "$$N" -ov packages/osx/$$N.dmg
156 N=`cd dist && echo mercurial-*.mpkg | sed 's,\.mpkg$$,,'` && hdiutil create -srcfolder dist/$$N.mpkg/ -scrub -volname "$$N" -ov packages/osx/$$N.dmg
156 rm -rf dist/mercurial-*.mpkg
157 rm -rf dist/mercurial-*.mpkg
157
158
158 fedora20:
159 fedora20:
159 mkdir -p packages/fedora20
160 mkdir -p packages/fedora20
160 contrib/buildrpm
161 contrib/buildrpm
161 cp rpmbuild/RPMS/*/* packages/fedora20
162 cp rpmbuild/RPMS/*/* packages/fedora20
162 cp rpmbuild/SRPMS/* packages/fedora20
163 cp rpmbuild/SRPMS/* packages/fedora20
163 rm -rf rpmbuild
164 rm -rf rpmbuild
164
165
165 docker-fedora20:
166 docker-fedora20:
166 mkdir -p packages/fedora20
167 mkdir -p packages/fedora20
167 contrib/dockerrpm fedora20
168 contrib/dockerrpm fedora20
168
169
169 fedora21:
170 fedora21:
170 mkdir -p packages/fedora21
171 mkdir -p packages/fedora21
171 contrib/buildrpm
172 contrib/buildrpm
172 cp rpmbuild/RPMS/*/* packages/fedora21
173 cp rpmbuild/RPMS/*/* packages/fedora21
173 cp rpmbuild/SRPMS/* packages/fedora21
174 cp rpmbuild/SRPMS/* packages/fedora21
174 rm -rf rpmbuild
175 rm -rf rpmbuild
175
176
176 docker-fedora21:
177 docker-fedora21:
177 mkdir -p packages/fedora21
178 mkdir -p packages/fedora21
178 contrib/dockerrpm fedora21
179 contrib/dockerrpm fedora21
179
180
180 centos5:
181 centos5:
181 mkdir -p packages/centos5
182 mkdir -p packages/centos5
182 contrib/buildrpm --withpython
183 contrib/buildrpm --withpython
183 cp rpmbuild/RPMS/*/* packages/centos5
184 cp rpmbuild/RPMS/*/* packages/centos5
184 cp rpmbuild/SRPMS/* packages/centos5
185 cp rpmbuild/SRPMS/* packages/centos5
185
186
186 docker-centos5:
187 docker-centos5:
187 mkdir -p packages/centos5
188 mkdir -p packages/centos5
188 contrib/dockerrpm centos5 --withpython
189 contrib/dockerrpm centos5 --withpython
189
190
190 centos6:
191 centos6:
191 mkdir -p packages/centos6
192 mkdir -p packages/centos6
192 contrib/buildrpm
193 contrib/buildrpm
193 cp rpmbuild/RPMS/*/* packages/centos6
194 cp rpmbuild/RPMS/*/* packages/centos6
194 cp rpmbuild/SRPMS/* packages/centos6
195 cp rpmbuild/SRPMS/* packages/centos6
195
196
196 docker-centos6:
197 docker-centos6:
197 mkdir -p packages/centos6
198 mkdir -p packages/centos6
198 contrib/dockerrpm centos6
199 contrib/dockerrpm centos6
199
200
200 centos7:
201 centos7:
201 mkdir -p packages/centos7
202 mkdir -p packages/centos7
202 contrib/buildrpm
203 contrib/buildrpm
203 cp rpmbuild/RPMS/*/* packages/centos7
204 cp rpmbuild/RPMS/*/* packages/centos7
204 cp rpmbuild/SRPMS/* packages/centos7
205 cp rpmbuild/SRPMS/* packages/centos7
205
206
206 docker-centos7:
207 docker-centos7:
207 mkdir -p packages/centos7
208 mkdir -p packages/centos7
208 contrib/dockerrpm centos7
209 contrib/dockerrpm centos7
209
210
210 .PHONY: help all local build doc clean install install-bin install-doc \
211 .PHONY: help all local build doc clean install install-bin install-doc \
211 install-home install-home-bin install-home-doc \
212 install-home install-home-bin install-home-doc \
212 dist dist-notests check tests check-code update-pot \
213 dist dist-notests check tests check-code update-pot \
213 osx fedora20 docker-fedora20 fedora21 docker-fedora21 \
214 osx fedora20 docker-fedora20 fedora21 docker-fedora21 \
214 centos5 docker-centos5 centos6 docker-centos6 centos7 docker-centos7
215 centos5 docker-centos5 centos6 docker-centos6 centos7 docker-centos7
@@ -1,795 +1,798 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 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 _
8 from i18n import _
9 import os, re
9 import os, re
10 import util, config, templatefilters, templatekw, parser, error
10 import util, config, templatefilters, templatekw, parser, error
11 import revset as revsetmod
11 import revset as revsetmod
12 import types
12 import types
13 import minirst
13 import minirst
14
14
15 # template parsing
15 # template parsing
16
16
17 elements = {
17 elements = {
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
19 ",": (2, None, ("list", 2)),
19 ",": (2, None, ("list", 2)),
20 "|": (5, None, ("|", 5)),
20 "|": (5, None, ("|", 5)),
21 "%": (6, None, ("%", 6)),
21 "%": (6, None, ("%", 6)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "rawstring": (0, ("rawstring",), None),
25 "rawstring": (0, ("rawstring",), None),
26 "end": (0, None, None),
26 "end": (0, None, None),
27 }
27 }
28
28
29 def tokenizer(data):
29 def tokenizer(data):
30 program, start, end = data
30 program, start, end = data
31 pos = start
31 pos = start
32 while pos < end:
32 while pos < end:
33 c = program[pos]
33 c = program[pos]
34 if c.isspace(): # skip inter-token whitespace
34 if c.isspace(): # skip inter-token whitespace
35 pass
35 pass
36 elif c in "(,)%|": # handle simple operators
36 elif c in "(,)%|": # handle simple operators
37 yield (c, None, pos)
37 yield (c, None, pos)
38 elif (c in '"\'' or c == 'r' and
38 elif (c in '"\'' or c == 'r' and
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
40 if c == 'r':
40 if c == 'r':
41 pos += 1
41 pos += 1
42 c = program[pos]
42 c = program[pos]
43 decode = False
43 decode = False
44 else:
44 else:
45 decode = True
45 decode = True
46 pos += 1
46 pos += 1
47 s = pos
47 s = pos
48 while pos < end: # find closing quote
48 while pos < end: # find closing quote
49 d = program[pos]
49 d = program[pos]
50 if decode and d == '\\': # skip over escaped characters
50 if decode and d == '\\': # skip over escaped characters
51 pos += 2
51 pos += 2
52 continue
52 continue
53 if d == c:
53 if d == c:
54 if not decode:
54 if not decode:
55 yield ('rawstring', program[s:pos], s)
55 yield ('rawstring', program[s:pos], s)
56 break
56 break
57 yield ('string', program[s:pos], s)
57 yield ('string', program[s:pos], s)
58 break
58 break
59 pos += 1
59 pos += 1
60 else:
60 else:
61 raise error.ParseError(_("unterminated string"), s)
61 raise error.ParseError(_("unterminated string"), s)
62 elif c.isalnum() or c in '_':
62 elif c.isalnum() or c in '_':
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < end: # find end of symbol
65 while pos < end: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d == "_"):
67 if not (d.isalnum() or d == "_"):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 yield ('symbol', sym, s)
71 yield ('symbol', sym, s)
72 pos -= 1
72 pos -= 1
73 elif c == '}':
73 elif c == '}':
74 pos += 1
74 pos += 1
75 break
75 break
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 def compiletemplate(tmpl, context, strtoken="string"):
81 def compiletemplate(tmpl, context, strtoken="string"):
82 parsed = []
82 parsed = []
83 pos, stop = 0, len(tmpl)
83 pos, stop = 0, len(tmpl)
84 p = parser.parser(tokenizer, elements)
84 p = parser.parser(tokenizer, elements)
85 while pos < stop:
85 while pos < stop:
86 n = tmpl.find('{', pos)
86 n = tmpl.find('{', pos)
87 if n < 0:
87 if n < 0:
88 parsed.append((strtoken, tmpl[pos:]))
88 parsed.append((strtoken, tmpl[pos:]))
89 break
89 break
90 if n > 0 and tmpl[n - 1] == '\\':
90 if n > 0 and tmpl[n - 1] == '\\':
91 # escaped
91 # escaped
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
93 pos = n + 1
93 pos = n + 1
94 continue
94 continue
95 if n > pos:
95 if n > pos:
96 parsed.append((strtoken, tmpl[pos:n]))
96 parsed.append((strtoken, tmpl[pos:n]))
97
97
98 pd = [tmpl, n + 1, stop]
98 pd = [tmpl, n + 1, stop]
99 parseres, pos = p.parse(pd)
99 parseres, pos = p.parse(pd)
100 parsed.append(parseres)
100 parsed.append(parseres)
101
101
102 return [compileexp(e, context) for e in parsed]
102 return [compileexp(e, context) for e in parsed]
103
103
104 def compileexp(exp, context):
104 def compileexp(exp, context):
105 t = exp[0]
105 t = exp[0]
106 if t in methods:
106 if t in methods:
107 return methods[t](exp, context)
107 return methods[t](exp, context)
108 raise error.ParseError(_("unknown method '%s'") % t)
108 raise error.ParseError(_("unknown method '%s'") % t)
109
109
110 # template evaluation
110 # template evaluation
111
111
112 def getsymbol(exp):
112 def getsymbol(exp):
113 if exp[0] == 'symbol':
113 if exp[0] == 'symbol':
114 return exp[1]
114 return exp[1]
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
116
116
117 def getlist(x):
117 def getlist(x):
118 if not x:
118 if not x:
119 return []
119 return []
120 if x[0] == 'list':
120 if x[0] == 'list':
121 return getlist(x[1]) + [x[2]]
121 return getlist(x[1]) + [x[2]]
122 return [x]
122 return [x]
123
123
124 def getfilter(exp, context):
124 def getfilter(exp, context):
125 f = getsymbol(exp)
125 f = getsymbol(exp)
126 if f not in context._filters:
126 if f not in context._filters:
127 raise error.ParseError(_("unknown function '%s'") % f)
127 raise error.ParseError(_("unknown function '%s'") % f)
128 return context._filters[f]
128 return context._filters[f]
129
129
130 def gettemplate(exp, context):
130 def gettemplate(exp, context):
131 if exp[0] == 'string' or exp[0] == 'rawstring':
131 if exp[0] == 'string' or exp[0] == 'rawstring':
132 return compiletemplate(exp[1], context, strtoken=exp[0])
132 return compiletemplate(exp[1], context, strtoken=exp[0])
133 if exp[0] == 'symbol':
133 if exp[0] == 'symbol':
134 return context._load(exp[1])
134 return context._load(exp[1])
135 raise error.ParseError(_("expected template specifier"))
135 raise error.ParseError(_("expected template specifier"))
136
136
137 def runstring(context, mapping, data):
137 def runstring(context, mapping, data):
138 return data.decode("string-escape")
138 return data.decode("string-escape")
139
139
140 def runrawstring(context, mapping, data):
140 def runrawstring(context, mapping, data):
141 return data
141 return data
142
142
143 def runsymbol(context, mapping, key):
143 def runsymbol(context, mapping, key):
144 v = mapping.get(key)
144 v = mapping.get(key)
145 if v is None:
145 if v is None:
146 v = context._defaults.get(key)
146 v = context._defaults.get(key)
147 if v is None:
147 if v is None:
148 try:
148 try:
149 v = context.process(key, mapping)
149 v = context.process(key, mapping)
150 except TemplateNotFound:
150 except TemplateNotFound:
151 v = ''
151 v = ''
152 if callable(v):
152 if callable(v):
153 return v(**mapping)
153 return v(**mapping)
154 if isinstance(v, types.GeneratorType):
154 if isinstance(v, types.GeneratorType):
155 v = list(v)
155 v = list(v)
156 return v
156 return v
157
157
158 def buildfilter(exp, context):
158 def buildfilter(exp, context):
159 func, data = compileexp(exp[1], context)
159 func, data = compileexp(exp[1], context)
160 filt = getfilter(exp[2], context)
160 filt = getfilter(exp[2], context)
161 return (runfilter, (func, data, filt))
161 return (runfilter, (func, data, filt))
162
162
163 def runfilter(context, mapping, data):
163 def runfilter(context, mapping, data):
164 func, data, filt = data
164 func, data, filt = data
165 # func() may return string, generator of strings or arbitrary object such
165 # func() may return string, generator of strings or arbitrary object such
166 # as date tuple, but filter does not want generator.
166 # as date tuple, but filter does not want generator.
167 thing = func(context, mapping, data)
167 thing = func(context, mapping, data)
168 if isinstance(thing, types.GeneratorType):
168 if isinstance(thing, types.GeneratorType):
169 thing = stringify(thing)
169 thing = stringify(thing)
170 try:
170 try:
171 return filt(thing)
171 return filt(thing)
172 except (ValueError, AttributeError, TypeError):
172 except (ValueError, AttributeError, TypeError):
173 if isinstance(data, tuple):
173 if isinstance(data, tuple):
174 dt = data[1]
174 dt = data[1]
175 else:
175 else:
176 dt = data
176 dt = data
177 raise util.Abort(_("template filter '%s' is not compatible with "
177 raise util.Abort(_("template filter '%s' is not compatible with "
178 "keyword '%s'") % (filt.func_name, dt))
178 "keyword '%s'") % (filt.func_name, dt))
179
179
180 def buildmap(exp, context):
180 def buildmap(exp, context):
181 func, data = compileexp(exp[1], context)
181 func, data = compileexp(exp[1], context)
182 ctmpl = gettemplate(exp[2], context)
182 ctmpl = gettemplate(exp[2], context)
183 return (runmap, (func, data, ctmpl))
183 return (runmap, (func, data, ctmpl))
184
184
185 def runtemplate(context, mapping, template):
185 def runtemplate(context, mapping, template):
186 for func, data in template:
186 for func, data in template:
187 yield func(context, mapping, data)
187 yield func(context, mapping, data)
188
188
189 def runmap(context, mapping, data):
189 def runmap(context, mapping, data):
190 func, data, ctmpl = data
190 func, data, ctmpl = data
191 d = func(context, mapping, data)
191 d = func(context, mapping, data)
192 if callable(d):
192 if callable(d):
193 d = d()
193 d = d()
194
194
195 lm = mapping.copy()
195 lm = mapping.copy()
196
196
197 for i in d:
197 for i in d:
198 if isinstance(i, dict):
198 if isinstance(i, dict):
199 lm.update(i)
199 lm.update(i)
200 lm['originalnode'] = mapping.get('node')
200 lm['originalnode'] = mapping.get('node')
201 yield runtemplate(context, lm, ctmpl)
201 yield runtemplate(context, lm, ctmpl)
202 else:
202 else:
203 # v is not an iterable of dicts, this happen when 'key'
203 # v is not an iterable of dicts, this happen when 'key'
204 # has been fully expanded already and format is useless.
204 # has been fully expanded already and format is useless.
205 # If so, return the expanded value.
205 # If so, return the expanded value.
206 yield i
206 yield i
207
207
208 def buildfunc(exp, context):
208 def buildfunc(exp, context):
209 n = getsymbol(exp[1])
209 n = getsymbol(exp[1])
210 args = [compileexp(x, context) for x in getlist(exp[2])]
210 args = [compileexp(x, context) for x in getlist(exp[2])]
211 if n in funcs:
211 if n in funcs:
212 f = funcs[n]
212 f = funcs[n]
213 return (f, args)
213 return (f, args)
214 if n in context._filters:
214 if n in context._filters:
215 if len(args) != 1:
215 if len(args) != 1:
216 raise error.ParseError(_("filter %s expects one argument") % n)
216 raise error.ParseError(_("filter %s expects one argument") % n)
217 f = context._filters[n]
217 f = context._filters[n]
218 return (runfilter, (args[0][0], args[0][1], f))
218 return (runfilter, (args[0][0], args[0][1], f))
219 raise error.ParseError(_("unknown function '%s'") % n)
219 raise error.ParseError(_("unknown function '%s'") % n)
220
220
221 def date(context, mapping, args):
221 def date(context, mapping, args):
222 """:date(date[, fmt]): Format a date. See :hg:`help dates` for formatting
222 """:date(date[, fmt]): Format a date. See :hg:`help dates` for formatting
223 strings."""
223 strings."""
224 if not (1 <= len(args) <= 2):
224 if not (1 <= len(args) <= 2):
225 # i18n: "date" is a keyword
225 # i18n: "date" is a keyword
226 raise error.ParseError(_("date expects one or two arguments"))
226 raise error.ParseError(_("date expects one or two arguments"))
227
227
228 date = args[0][0](context, mapping, args[0][1])
228 date = args[0][0](context, mapping, args[0][1])
229 if len(args) == 2:
229 if len(args) == 2:
230 fmt = stringify(args[1][0](context, mapping, args[1][1]))
230 fmt = stringify(args[1][0](context, mapping, args[1][1]))
231 return util.datestr(date, fmt)
231 return util.datestr(date, fmt)
232 return util.datestr(date)
232 return util.datestr(date)
233
233
234 def diff(context, mapping, args):
234 def diff(context, mapping, args):
235 """:diff([includepattern [, excludepattern]]): Show a diff, optionally
235 """:diff([includepattern [, excludepattern]]): Show a diff, optionally
236 specifying files to include or exclude."""
236 specifying files to include or exclude."""
237 if len(args) > 2:
237 if len(args) > 2:
238 # i18n: "diff" is a keyword
238 # i18n: "diff" is a keyword
239 raise error.ParseError(_("diff expects one, two or no arguments"))
239 raise error.ParseError(_("diff expects one, two or no arguments"))
240
240
241 def getpatterns(i):
241 def getpatterns(i):
242 if i < len(args):
242 if i < len(args):
243 s = args[i][1].strip()
243 s = args[i][1].strip()
244 if s:
244 if s:
245 return [s]
245 return [s]
246 return []
246 return []
247
247
248 ctx = mapping['ctx']
248 ctx = mapping['ctx']
249 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
249 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
250
250
251 return ''.join(chunks)
251 return ''.join(chunks)
252
252
253 def fill(context, mapping, args):
253 def fill(context, mapping, args):
254 """:fill(text[, width[, initialident[, hangindent]]]): Fill many
254 """:fill(text[, width[, initialident[, hangindent]]]): Fill many
255 paragraphs with optional indentation. See the "fill" filter."""
255 paragraphs with optional indentation. See the "fill" filter."""
256 if not (1 <= len(args) <= 4):
256 if not (1 <= len(args) <= 4):
257 # i18n: "fill" is a keyword
257 # i18n: "fill" is a keyword
258 raise error.ParseError(_("fill expects one to four arguments"))
258 raise error.ParseError(_("fill expects one to four arguments"))
259
259
260 text = stringify(args[0][0](context, mapping, args[0][1]))
260 text = stringify(args[0][0](context, mapping, args[0][1]))
261 width = 76
261 width = 76
262 initindent = ''
262 initindent = ''
263 hangindent = ''
263 hangindent = ''
264 if 2 <= len(args) <= 4:
264 if 2 <= len(args) <= 4:
265 try:
265 try:
266 width = int(stringify(args[1][0](context, mapping, args[1][1])))
266 width = int(stringify(args[1][0](context, mapping, args[1][1])))
267 except ValueError:
267 except ValueError:
268 # i18n: "fill" is a keyword
268 # i18n: "fill" is a keyword
269 raise error.ParseError(_("fill expects an integer width"))
269 raise error.ParseError(_("fill expects an integer width"))
270 try:
270 try:
271 initindent = stringify(_evalifliteral(args[2], context, mapping))
271 initindent = stringify(_evalifliteral(args[2], context, mapping))
272 hangindent = stringify(_evalifliteral(args[3], context, mapping))
272 hangindent = stringify(_evalifliteral(args[3], context, mapping))
273 except IndexError:
273 except IndexError:
274 pass
274 pass
275
275
276 return templatefilters.fill(text, width, initindent, hangindent)
276 return templatefilters.fill(text, width, initindent, hangindent)
277
277
278 def pad(context, mapping, args):
278 def pad(context, mapping, args):
279 """:pad(text, width[, fillchar=' '[, right=False]]): Pad text with a
279 """:pad(text, width[, fillchar=' '[, right=False]]): Pad text with a
280 fill character."""
280 fill character."""
281 if not (2 <= len(args) <= 4):
281 if not (2 <= len(args) <= 4):
282 # i18n: "pad" is a keyword
282 # i18n: "pad" is a keyword
283 raise error.ParseError(_("pad() expects two to four arguments"))
283 raise error.ParseError(_("pad() expects two to four arguments"))
284
284
285 width = int(args[1][1])
285 width = int(args[1][1])
286
286
287 text = stringify(args[0][0](context, mapping, args[0][1]))
287 text = stringify(args[0][0](context, mapping, args[0][1]))
288 if args[0][0] == runstring:
288 if args[0][0] == runstring:
289 text = stringify(runtemplate(context, mapping,
289 text = stringify(runtemplate(context, mapping,
290 compiletemplate(text, context)))
290 compiletemplate(text, context)))
291
291
292 right = False
292 right = False
293 fillchar = ' '
293 fillchar = ' '
294 if len(args) > 2:
294 if len(args) > 2:
295 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
295 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
296 if len(args) > 3:
296 if len(args) > 3:
297 right = util.parsebool(args[3][1])
297 right = util.parsebool(args[3][1])
298
298
299 if right:
299 if right:
300 return text.rjust(width, fillchar)
300 return text.rjust(width, fillchar)
301 else:
301 else:
302 return text.ljust(width, fillchar)
302 return text.ljust(width, fillchar)
303
303
304 def get(context, mapping, args):
304 def get(context, mapping, args):
305 """:get(dict, key): Get an attribute/key from an object. Some keywords
305 """:get(dict, key): Get an attribute/key from an object. Some keywords
306 are complex types. This function allows you to obtain the value of an
306 are complex types. This function allows you to obtain the value of an
307 attribute on these type."""
307 attribute on these type."""
308 if len(args) != 2:
308 if len(args) != 2:
309 # i18n: "get" is a keyword
309 # i18n: "get" is a keyword
310 raise error.ParseError(_("get() expects two arguments"))
310 raise error.ParseError(_("get() expects two arguments"))
311
311
312 dictarg = args[0][0](context, mapping, args[0][1])
312 dictarg = args[0][0](context, mapping, args[0][1])
313 if not util.safehasattr(dictarg, 'get'):
313 if not util.safehasattr(dictarg, 'get'):
314 # i18n: "get" is a keyword
314 # i18n: "get" is a keyword
315 raise error.ParseError(_("get() expects a dict as first argument"))
315 raise error.ParseError(_("get() expects a dict as first argument"))
316
316
317 key = args[1][0](context, mapping, args[1][1])
317 key = args[1][0](context, mapping, args[1][1])
318 yield dictarg.get(key)
318 yield dictarg.get(key)
319
319
320 def _evalifliteral(arg, context, mapping):
320 def _evalifliteral(arg, context, mapping):
321 t = stringify(arg[0](context, mapping, arg[1]))
321 t = stringify(arg[0](context, mapping, arg[1]))
322 if arg[0] == runstring or arg[0] == runrawstring:
322 if arg[0] == runstring or arg[0] == runrawstring:
323 yield runtemplate(context, mapping,
323 yield runtemplate(context, mapping,
324 compiletemplate(t, context, strtoken='rawstring'))
324 compiletemplate(t, context, strtoken='rawstring'))
325 else:
325 else:
326 yield t
326 yield t
327
327
328 def if_(context, mapping, args):
328 def if_(context, mapping, args):
329 """:if(expr, then[, else]): Conditionally execute based on the result of
329 """:if(expr, then[, else]): Conditionally execute based on the result of
330 an expression."""
330 an expression."""
331 if not (2 <= len(args) <= 3):
331 if not (2 <= len(args) <= 3):
332 # i18n: "if" is a keyword
332 # i18n: "if" is a keyword
333 raise error.ParseError(_("if expects two or three arguments"))
333 raise error.ParseError(_("if expects two or three arguments"))
334
334
335 test = stringify(args[0][0](context, mapping, args[0][1]))
335 test = stringify(args[0][0](context, mapping, args[0][1]))
336 if test:
336 if test:
337 yield _evalifliteral(args[1], context, mapping)
337 yield _evalifliteral(args[1], context, mapping)
338 elif len(args) == 3:
338 elif len(args) == 3:
339 yield _evalifliteral(args[2], context, mapping)
339 yield _evalifliteral(args[2], context, mapping)
340
340
341 def ifcontains(context, mapping, args):
341 def ifcontains(context, mapping, args):
342 """:ifcontains(search, thing, then[, else]): Conditionally execute based
342 """:ifcontains(search, thing, then[, else]): Conditionally execute based
343 on whether the item "search" is in "thing"."""
343 on whether the item "search" is in "thing"."""
344 if not (3 <= len(args) <= 4):
344 if not (3 <= len(args) <= 4):
345 # i18n: "ifcontains" is a keyword
345 # i18n: "ifcontains" is a keyword
346 raise error.ParseError(_("ifcontains expects three or four arguments"))
346 raise error.ParseError(_("ifcontains expects three or four arguments"))
347
347
348 item = stringify(args[0][0](context, mapping, args[0][1]))
348 item = stringify(args[0][0](context, mapping, args[0][1]))
349 items = args[1][0](context, mapping, args[1][1])
349 items = args[1][0](context, mapping, args[1][1])
350
350
351 if item in items:
351 if item in items:
352 yield _evalifliteral(args[2], context, mapping)
352 yield _evalifliteral(args[2], context, mapping)
353 elif len(args) == 4:
353 elif len(args) == 4:
354 yield _evalifliteral(args[3], context, mapping)
354 yield _evalifliteral(args[3], context, mapping)
355
355
356 def ifeq(context, mapping, args):
356 def ifeq(context, mapping, args):
357 """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on
357 """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on
358 whether 2 items are equivalent."""
358 whether 2 items are equivalent."""
359 if not (3 <= len(args) <= 4):
359 if not (3 <= len(args) <= 4):
360 # i18n: "ifeq" is a keyword
360 # i18n: "ifeq" is a keyword
361 raise error.ParseError(_("ifeq expects three or four arguments"))
361 raise error.ParseError(_("ifeq expects three or four arguments"))
362
362
363 test = stringify(args[0][0](context, mapping, args[0][1]))
363 test = stringify(args[0][0](context, mapping, args[0][1]))
364 match = stringify(args[1][0](context, mapping, args[1][1]))
364 match = stringify(args[1][0](context, mapping, args[1][1]))
365 if test == match:
365 if test == match:
366 yield _evalifliteral(args[2], context, mapping)
366 yield _evalifliteral(args[2], context, mapping)
367 elif len(args) == 4:
367 elif len(args) == 4:
368 yield _evalifliteral(args[3], context, mapping)
368 yield _evalifliteral(args[3], context, mapping)
369
369
370 def join(context, mapping, args):
370 def join(context, mapping, args):
371 """:join(list, sep): Join items in a list with a delimiter."""
371 """:join(list, sep): Join items in a list with a delimiter."""
372 if not (1 <= len(args) <= 2):
372 if not (1 <= len(args) <= 2):
373 # i18n: "join" is a keyword
373 # i18n: "join" is a keyword
374 raise error.ParseError(_("join expects one or two arguments"))
374 raise error.ParseError(_("join expects one or two arguments"))
375
375
376 joinset = args[0][0](context, mapping, args[0][1])
376 joinset = args[0][0](context, mapping, args[0][1])
377 if callable(joinset):
377 if callable(joinset):
378 jf = joinset.joinfmt
378 jf = joinset.joinfmt
379 joinset = [jf(x) for x in joinset()]
379 joinset = [jf(x) for x in joinset()]
380
380
381 joiner = " "
381 joiner = " "
382 if len(args) > 1:
382 if len(args) > 1:
383 joiner = stringify(args[1][0](context, mapping, args[1][1]))
383 joiner = stringify(args[1][0](context, mapping, args[1][1]))
384
384
385 first = True
385 first = True
386 for x in joinset:
386 for x in joinset:
387 if first:
387 if first:
388 first = False
388 first = False
389 else:
389 else:
390 yield joiner
390 yield joiner
391 yield x
391 yield x
392
392
393 def label(context, mapping, args):
393 def label(context, mapping, args):
394 """:label(label, expr): Apply a label to generated content. Content with
394 """:label(label, expr): Apply a label to generated content. Content with
395 a label applied can result in additional post-processing, such as
395 a label applied can result in additional post-processing, such as
396 automatic colorization."""
396 automatic colorization."""
397 if len(args) != 2:
397 if len(args) != 2:
398 # i18n: "label" is a keyword
398 # i18n: "label" is a keyword
399 raise error.ParseError(_("label expects two arguments"))
399 raise error.ParseError(_("label expects two arguments"))
400
400
401 # ignore args[0] (the label string) since this is supposed to be a a no-op
401 # ignore args[0] (the label string) since this is supposed to be a a no-op
402 yield _evalifliteral(args[1], context, mapping)
402 yield _evalifliteral(args[1], context, mapping)
403
403
404 def revset(context, mapping, args):
404 def revset(context, mapping, args):
405 """:revset(query[, formatargs...]): Execute a revision set query. See
405 """:revset(query[, formatargs...]): Execute a revision set query. See
406 :hg:`help revset`."""
406 :hg:`help revset`."""
407 if not len(args) > 0:
407 if not len(args) > 0:
408 # i18n: "revset" is a keyword
408 # i18n: "revset" is a keyword
409 raise error.ParseError(_("revset expects one or more arguments"))
409 raise error.ParseError(_("revset expects one or more arguments"))
410
410
411 raw = args[0][1]
411 raw = args[0][1]
412 ctx = mapping['ctx']
412 ctx = mapping['ctx']
413 repo = ctx.repo()
413 repo = ctx.repo()
414
414
415 def query(expr):
415 def query(expr):
416 m = revsetmod.match(repo.ui, expr)
416 m = revsetmod.match(repo.ui, expr)
417 return m(repo)
417 return m(repo)
418
418
419 if len(args) > 1:
419 if len(args) > 1:
420 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
420 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
421 revs = query(revsetmod.formatspec(raw, *formatargs))
421 revs = query(revsetmod.formatspec(raw, *formatargs))
422 revs = list([str(r) for r in revs])
422 revs = list([str(r) for r in revs])
423 else:
423 else:
424 revsetcache = mapping['cache'].setdefault("revsetcache", {})
424 revsetcache = mapping['cache'].setdefault("revsetcache", {})
425 if raw in revsetcache:
425 if raw in revsetcache:
426 revs = revsetcache[raw]
426 revs = revsetcache[raw]
427 else:
427 else:
428 revs = query(raw)
428 revs = query(raw)
429 revs = list([str(r) for r in revs])
429 revs = list([str(r) for r in revs])
430 revsetcache[raw] = revs
430 revsetcache[raw] = revs
431
431
432 return templatekw.showlist("revision", revs, **mapping)
432 return templatekw.showlist("revision", revs, **mapping)
433
433
434 def rstdoc(context, mapping, args):
434 def rstdoc(context, mapping, args):
435 """:rstdoc(text, style): Format ReStructuredText."""
435 """:rstdoc(text, style): Format ReStructuredText."""
436 if len(args) != 2:
436 if len(args) != 2:
437 # i18n: "rstdoc" is a keyword
437 # i18n: "rstdoc" is a keyword
438 raise error.ParseError(_("rstdoc expects two arguments"))
438 raise error.ParseError(_("rstdoc expects two arguments"))
439
439
440 text = stringify(args[0][0](context, mapping, args[0][1]))
440 text = stringify(args[0][0](context, mapping, args[0][1]))
441 style = stringify(args[1][0](context, mapping, args[1][1]))
441 style = stringify(args[1][0](context, mapping, args[1][1]))
442
442
443 return minirst.format(text, style=style, keep=['verbose'])
443 return minirst.format(text, style=style, keep=['verbose'])
444
444
445 def shortest(context, mapping, args):
445 def shortest(context, mapping, args):
446 """:shortest(node, minlength=4): Obtain the shortest representation of
446 """:shortest(node, minlength=4): Obtain the shortest representation of
447 a node."""
447 a node."""
448 if not (1 <= len(args) <= 2):
448 if not (1 <= len(args) <= 2):
449 # i18n: "shortest" is a keyword
449 # i18n: "shortest" is a keyword
450 raise error.ParseError(_("shortest() expects one or two arguments"))
450 raise error.ParseError(_("shortest() expects one or two arguments"))
451
451
452 node = stringify(args[0][0](context, mapping, args[0][1]))
452 node = stringify(args[0][0](context, mapping, args[0][1]))
453
453
454 minlength = 4
454 minlength = 4
455 if len(args) > 1:
455 if len(args) > 1:
456 minlength = int(args[1][1])
456 minlength = int(args[1][1])
457
457
458 cl = mapping['ctx']._repo.changelog
458 cl = mapping['ctx']._repo.changelog
459 def isvalid(test):
459 def isvalid(test):
460 try:
460 try:
461 try:
461 try:
462 cl.index.partialmatch(test)
462 cl.index.partialmatch(test)
463 except AttributeError:
463 except AttributeError:
464 # Pure mercurial doesn't support partialmatch on the index.
464 # Pure mercurial doesn't support partialmatch on the index.
465 # Fallback to the slow way.
465 # Fallback to the slow way.
466 if cl._partialmatch(test) is None:
466 if cl._partialmatch(test) is None:
467 return False
467 return False
468
468
469 try:
469 try:
470 i = int(test)
470 i = int(test)
471 # if we are a pure int, then starting with zero will not be
471 # if we are a pure int, then starting with zero will not be
472 # confused as a rev; or, obviously, if the int is larger than
472 # confused as a rev; or, obviously, if the int is larger than
473 # the value of the tip rev
473 # the value of the tip rev
474 if test[0] == '0' or i > len(cl):
474 if test[0] == '0' or i > len(cl):
475 return True
475 return True
476 return False
476 return False
477 except ValueError:
477 except ValueError:
478 return True
478 return True
479 except error.RevlogError:
479 except error.RevlogError:
480 return False
480 return False
481
481
482 shortest = node
482 shortest = node
483 startlength = max(6, minlength)
483 startlength = max(6, minlength)
484 length = startlength
484 length = startlength
485 while True:
485 while True:
486 test = node[:length]
486 test = node[:length]
487 if isvalid(test):
487 if isvalid(test):
488 shortest = test
488 shortest = test
489 if length == minlength or length > startlength:
489 if length == minlength or length > startlength:
490 return shortest
490 return shortest
491 length -= 1
491 length -= 1
492 else:
492 else:
493 length += 1
493 length += 1
494 if len(shortest) <= length:
494 if len(shortest) <= length:
495 return shortest
495 return shortest
496
496
497 def strip(context, mapping, args):
497 def strip(context, mapping, args):
498 """:strip(text[, chars]): Strip characters from a string."""
498 """:strip(text[, chars]): Strip characters from a string."""
499 if not (1 <= len(args) <= 2):
499 if not (1 <= len(args) <= 2):
500 # i18n: "strip" is a keyword
500 # i18n: "strip" is a keyword
501 raise error.ParseError(_("strip expects one or two arguments"))
501 raise error.ParseError(_("strip expects one or two arguments"))
502
502
503 text = stringify(args[0][0](context, mapping, args[0][1]))
503 text = stringify(args[0][0](context, mapping, args[0][1]))
504 if len(args) == 2:
504 if len(args) == 2:
505 chars = stringify(args[1][0](context, mapping, args[1][1]))
505 chars = stringify(args[1][0](context, mapping, args[1][1]))
506 return text.strip(chars)
506 return text.strip(chars)
507 return text.strip()
507 return text.strip()
508
508
509 def sub(context, mapping, args):
509 def sub(context, mapping, args):
510 """:sub(pattern, replacement, expression): Perform text substitution
510 """:sub(pattern, replacement, expression): Perform text substitution
511 using regular expressions."""
511 using regular expressions."""
512 if len(args) != 3:
512 if len(args) != 3:
513 # i18n: "sub" is a keyword
513 # i18n: "sub" is a keyword
514 raise error.ParseError(_("sub expects three arguments"))
514 raise error.ParseError(_("sub expects three arguments"))
515
515
516 pat = stringify(args[0][0](context, mapping, args[0][1]))
516 pat = stringify(args[0][0](context, mapping, args[0][1]))
517 rpl = stringify(args[1][0](context, mapping, args[1][1]))
517 rpl = stringify(args[1][0](context, mapping, args[1][1]))
518 src = stringify(_evalifliteral(args[2], context, mapping))
518 src = stringify(_evalifliteral(args[2], context, mapping))
519 yield re.sub(pat, rpl, src)
519 yield re.sub(pat, rpl, src)
520
520
521 def startswith(context, mapping, args):
521 def startswith(context, mapping, args):
522 """:startswith(pattern, text): Returns the value from the "text" argument
522 """:startswith(pattern, text): Returns the value from the "text" argument
523 if it begins with the content from the "pattern" argument."""
523 if it begins with the content from the "pattern" argument."""
524 if len(args) != 2:
524 if len(args) != 2:
525 # i18n: "startswith" is a keyword
525 # i18n: "startswith" is a keyword
526 raise error.ParseError(_("startswith expects two arguments"))
526 raise error.ParseError(_("startswith expects two arguments"))
527
527
528 patn = stringify(args[0][0](context, mapping, args[0][1]))
528 patn = stringify(args[0][0](context, mapping, args[0][1]))
529 text = stringify(args[1][0](context, mapping, args[1][1]))
529 text = stringify(args[1][0](context, mapping, args[1][1]))
530 if text.startswith(patn):
530 if text.startswith(patn):
531 return text
531 return text
532 return ''
532 return ''
533
533
534
534
535 def word(context, mapping, args):
535 def word(context, mapping, args):
536 """:word(number, text[, separator]): Return the nth word from a string."""
536 """:word(number, text[, separator]): Return the nth word from a string."""
537 if not (2 <= len(args) <= 3):
537 if not (2 <= len(args) <= 3):
538 # i18n: "word" is a keyword
538 # i18n: "word" is a keyword
539 raise error.ParseError(_("word expects two or three arguments, got %d")
539 raise error.ParseError(_("word expects two or three arguments, got %d")
540 % len(args))
540 % len(args))
541
541
542 num = int(stringify(args[0][0](context, mapping, args[0][1])))
542 num = int(stringify(args[0][0](context, mapping, args[0][1])))
543 text = stringify(args[1][0](context, mapping, args[1][1]))
543 text = stringify(args[1][0](context, mapping, args[1][1]))
544 if len(args) == 3:
544 if len(args) == 3:
545 splitter = stringify(args[2][0](context, mapping, args[2][1]))
545 splitter = stringify(args[2][0](context, mapping, args[2][1]))
546 else:
546 else:
547 splitter = None
547 splitter = None
548
548
549 tokens = text.split(splitter)
549 tokens = text.split(splitter)
550 if num >= len(tokens):
550 if num >= len(tokens):
551 return ''
551 return ''
552 else:
552 else:
553 return tokens[num]
553 return tokens[num]
554
554
555 methods = {
555 methods = {
556 "string": lambda e, c: (runstring, e[1]),
556 "string": lambda e, c: (runstring, e[1]),
557 "rawstring": lambda e, c: (runrawstring, e[1]),
557 "rawstring": lambda e, c: (runrawstring, e[1]),
558 "symbol": lambda e, c: (runsymbol, e[1]),
558 "symbol": lambda e, c: (runsymbol, e[1]),
559 "group": lambda e, c: compileexp(e[1], c),
559 "group": lambda e, c: compileexp(e[1], c),
560 # ".": buildmember,
560 # ".": buildmember,
561 "|": buildfilter,
561 "|": buildfilter,
562 "%": buildmap,
562 "%": buildmap,
563 "func": buildfunc,
563 "func": buildfunc,
564 }
564 }
565
565
566 funcs = {
566 funcs = {
567 "date": date,
567 "date": date,
568 "diff": diff,
568 "diff": diff,
569 "fill": fill,
569 "fill": fill,
570 "get": get,
570 "get": get,
571 "if": if_,
571 "if": if_,
572 "ifcontains": ifcontains,
572 "ifcontains": ifcontains,
573 "ifeq": ifeq,
573 "ifeq": ifeq,
574 "join": join,
574 "join": join,
575 "label": label,
575 "label": label,
576 "pad": pad,
576 "pad": pad,
577 "revset": revset,
577 "revset": revset,
578 "rstdoc": rstdoc,
578 "rstdoc": rstdoc,
579 "shortest": shortest,
579 "shortest": shortest,
580 "startswith": startswith,
580 "startswith": startswith,
581 "strip": strip,
581 "strip": strip,
582 "sub": sub,
582 "sub": sub,
583 "word": word,
583 "word": word,
584 }
584 }
585
585
586 # template engine
586 # template engine
587
587
588 stringify = templatefilters.stringify
588 stringify = templatefilters.stringify
589
589
590 def _flatten(thing):
590 def _flatten(thing):
591 '''yield a single stream from a possibly nested set of iterators'''
591 '''yield a single stream from a possibly nested set of iterators'''
592 if isinstance(thing, str):
592 if isinstance(thing, str):
593 yield thing
593 yield thing
594 elif not util.safehasattr(thing, '__iter__'):
594 elif not util.safehasattr(thing, '__iter__'):
595 if thing is not None:
595 if thing is not None:
596 yield str(thing)
596 yield str(thing)
597 else:
597 else:
598 for i in thing:
598 for i in thing:
599 if isinstance(i, str):
599 if isinstance(i, str):
600 yield i
600 yield i
601 elif not util.safehasattr(i, '__iter__'):
601 elif not util.safehasattr(i, '__iter__'):
602 if i is not None:
602 if i is not None:
603 yield str(i)
603 yield str(i)
604 elif i is not None:
604 elif i is not None:
605 for j in _flatten(i):
605 for j in _flatten(i):
606 yield j
606 yield j
607
607
608 def parsestring(s, quoted=True):
608 def parsestring(s, quoted=True):
609 '''parse a string using simple c-like syntax.
609 '''parse a string using simple c-like syntax.
610 string must be in quotes if quoted is True.'''
610 string must be in quotes if quoted is True.'''
611 if quoted:
611 if quoted:
612 if len(s) < 2 or s[0] != s[-1]:
612 if len(s) < 2 or s[0] != s[-1]:
613 raise SyntaxError(_('unmatched quotes'))
613 raise SyntaxError(_('unmatched quotes'))
614 return s[1:-1].decode('string_escape')
614 return s[1:-1].decode('string_escape')
615
615
616 return s.decode('string_escape')
616 return s.decode('string_escape')
617
617
618 class engine(object):
618 class engine(object):
619 '''template expansion engine.
619 '''template expansion engine.
620
620
621 template expansion works like this. a map file contains key=value
621 template expansion works like this. a map file contains key=value
622 pairs. if value is quoted, it is treated as string. otherwise, it
622 pairs. if value is quoted, it is treated as string. otherwise, it
623 is treated as name of template file.
623 is treated as name of template file.
624
624
625 templater is asked to expand a key in map. it looks up key, and
625 templater is asked to expand a key in map. it looks up key, and
626 looks for strings like this: {foo}. it expands {foo} by looking up
626 looks for strings like this: {foo}. it expands {foo} by looking up
627 foo in map, and substituting it. expansion is recursive: it stops
627 foo in map, and substituting it. expansion is recursive: it stops
628 when there is no more {foo} to replace.
628 when there is no more {foo} to replace.
629
629
630 expansion also allows formatting and filtering.
630 expansion also allows formatting and filtering.
631
631
632 format uses key to expand each item in list. syntax is
632 format uses key to expand each item in list. syntax is
633 {key%format}.
633 {key%format}.
634
634
635 filter uses function to transform value. syntax is
635 filter uses function to transform value. syntax is
636 {key|filter1|filter2|...}.'''
636 {key|filter1|filter2|...}.'''
637
637
638 def __init__(self, loader, filters={}, defaults={}):
638 def __init__(self, loader, filters={}, defaults={}):
639 self._loader = loader
639 self._loader = loader
640 self._filters = filters
640 self._filters = filters
641 self._defaults = defaults
641 self._defaults = defaults
642 self._cache = {}
642 self._cache = {}
643
643
644 def _load(self, t):
644 def _load(self, t):
645 '''load, parse, and cache a template'''
645 '''load, parse, and cache a template'''
646 if t not in self._cache:
646 if t not in self._cache:
647 self._cache[t] = compiletemplate(self._loader(t), self)
647 self._cache[t] = compiletemplate(self._loader(t), self)
648 return self._cache[t]
648 return self._cache[t]
649
649
650 def process(self, t, mapping):
650 def process(self, t, mapping):
651 '''Perform expansion. t is name of map element to expand.
651 '''Perform expansion. t is name of map element to expand.
652 mapping contains added elements for use during expansion. Is a
652 mapping contains added elements for use during expansion. Is a
653 generator.'''
653 generator.'''
654 return _flatten(runtemplate(self, mapping, self._load(t)))
654 return _flatten(runtemplate(self, mapping, self._load(t)))
655
655
656 engines = {'default': engine}
656 engines = {'default': engine}
657
657
658 def stylelist():
658 def stylelist():
659 paths = templatepaths()
659 paths = templatepaths()
660 if not paths:
660 if not paths:
661 return _('no templates found, try `hg debuginstall` for more info')
661 return _('no templates found, try `hg debuginstall` for more info')
662 dirlist = os.listdir(paths[0])
662 dirlist = os.listdir(paths[0])
663 stylelist = []
663 stylelist = []
664 for file in dirlist:
664 for file in dirlist:
665 split = file.split(".")
665 split = file.split(".")
666 if split[0] == "map-cmdline":
666 if split[0] == "map-cmdline":
667 stylelist.append(split[1])
667 stylelist.append(split[1])
668 return ", ".join(sorted(stylelist))
668 return ", ".join(sorted(stylelist))
669
669
670 class TemplateNotFound(util.Abort):
670 class TemplateNotFound(util.Abort):
671 pass
671 pass
672
672
673 class templater(object):
673 class templater(object):
674
674
675 def __init__(self, mapfile, filters={}, defaults={}, cache={},
675 def __init__(self, mapfile, filters={}, defaults={}, cache={},
676 minchunk=1024, maxchunk=65536):
676 minchunk=1024, maxchunk=65536):
677 '''set up template engine.
677 '''set up template engine.
678 mapfile is name of file to read map definitions from.
678 mapfile is name of file to read map definitions from.
679 filters is dict of functions. each transforms a value into another.
679 filters is dict of functions. each transforms a value into another.
680 defaults is dict of default map definitions.'''
680 defaults is dict of default map definitions.'''
681 self.mapfile = mapfile or 'template'
681 self.mapfile = mapfile or 'template'
682 self.cache = cache.copy()
682 self.cache = cache.copy()
683 self.map = {}
683 self.map = {}
684 if mapfile:
684 if mapfile:
685 self.base = os.path.dirname(mapfile)
685 self.base = os.path.dirname(mapfile)
686 else:
686 else:
687 self.base = ''
687 self.base = ''
688 self.filters = templatefilters.filters.copy()
688 self.filters = templatefilters.filters.copy()
689 self.filters.update(filters)
689 self.filters.update(filters)
690 self.defaults = defaults
690 self.defaults = defaults
691 self.minchunk, self.maxchunk = minchunk, maxchunk
691 self.minchunk, self.maxchunk = minchunk, maxchunk
692 self.ecache = {}
692 self.ecache = {}
693
693
694 if not mapfile:
694 if not mapfile:
695 return
695 return
696 if not os.path.exists(mapfile):
696 if not os.path.exists(mapfile):
697 raise util.Abort(_("style '%s' not found") % mapfile,
697 raise util.Abort(_("style '%s' not found") % mapfile,
698 hint=_("available styles: %s") % stylelist())
698 hint=_("available styles: %s") % stylelist())
699
699
700 conf = config.config()
700 conf = config.config()
701 conf.read(mapfile)
701 conf.read(mapfile)
702
702
703 for key, val in conf[''].items():
703 for key, val in conf[''].items():
704 if not val:
704 if not val:
705 raise SyntaxError(_('%s: missing value') % conf.source('', key))
705 raise SyntaxError(_('%s: missing value') % conf.source('', key))
706 if val[0] in "'\"":
706 if val[0] in "'\"":
707 try:
707 try:
708 self.cache[key] = parsestring(val)
708 self.cache[key] = parsestring(val)
709 except SyntaxError, inst:
709 except SyntaxError, inst:
710 raise SyntaxError('%s: %s' %
710 raise SyntaxError('%s: %s' %
711 (conf.source('', key), inst.args[0]))
711 (conf.source('', key), inst.args[0]))
712 else:
712 else:
713 val = 'default', val
713 val = 'default', val
714 if ':' in val[1]:
714 if ':' in val[1]:
715 val = val[1].split(':', 1)
715 val = val[1].split(':', 1)
716 self.map[key] = val[0], os.path.join(self.base, val[1])
716 self.map[key] = val[0], os.path.join(self.base, val[1])
717
717
718 def __contains__(self, key):
718 def __contains__(self, key):
719 return key in self.cache or key in self.map
719 return key in self.cache or key in self.map
720
720
721 def load(self, t):
721 def load(self, t):
722 '''Get the template for the given template name. Use a local cache.'''
722 '''Get the template for the given template name. Use a local cache.'''
723 if t not in self.cache:
723 if t not in self.cache:
724 try:
724 try:
725 self.cache[t] = util.readfile(self.map[t][1])
725 self.cache[t] = util.readfile(self.map[t][1])
726 except KeyError, inst:
726 except KeyError, inst:
727 raise TemplateNotFound(_('"%s" not in template map') %
727 raise TemplateNotFound(_('"%s" not in template map') %
728 inst.args[0])
728 inst.args[0])
729 except IOError, inst:
729 except IOError, inst:
730 raise IOError(inst.args[0], _('template file %s: %s') %
730 raise IOError(inst.args[0], _('template file %s: %s') %
731 (self.map[t][1], inst.args[1]))
731 (self.map[t][1], inst.args[1]))
732 return self.cache[t]
732 return self.cache[t]
733
733
734 def __call__(self, t, **mapping):
734 def __call__(self, t, **mapping):
735 ttype = t in self.map and self.map[t][0] or 'default'
735 ttype = t in self.map and self.map[t][0] or 'default'
736 if ttype not in self.ecache:
736 if ttype not in self.ecache:
737 self.ecache[ttype] = engines[ttype](self.load,
737 self.ecache[ttype] = engines[ttype](self.load,
738 self.filters, self.defaults)
738 self.filters, self.defaults)
739 proc = self.ecache[ttype]
739 proc = self.ecache[ttype]
740
740
741 stream = proc.process(t, mapping)
741 stream = proc.process(t, mapping)
742 if self.minchunk:
742 if self.minchunk:
743 stream = util.increasingchunks(stream, min=self.minchunk,
743 stream = util.increasingchunks(stream, min=self.minchunk,
744 max=self.maxchunk)
744 max=self.maxchunk)
745 return stream
745 return stream
746
746
747 def templatepaths():
747 def templatepaths():
748 '''return locations used for template files.'''
748 '''return locations used for template files.'''
749 pathsrel = ['templates']
749 pathsrel = ['templates']
750 paths = [os.path.normpath(os.path.join(util.datapath, f))
750 paths = [os.path.normpath(os.path.join(util.datapath, f))
751 for f in pathsrel]
751 for f in pathsrel]
752 return [p for p in paths if os.path.isdir(p)]
752 return [p for p in paths if os.path.isdir(p)]
753
753
754 def templatepath(name):
754 def templatepath(name):
755 '''return location of template file. returns None if not found.'''
755 '''return location of template file. returns None if not found.'''
756 for p in templatepaths():
756 for p in templatepaths():
757 f = os.path.join(p, name)
757 f = os.path.join(p, name)
758 if os.path.exists(f):
758 if os.path.exists(f):
759 return f
759 return f
760 return None
760 return None
761
761
762 def stylemap(styles, paths=None):
762 def stylemap(styles, paths=None):
763 """Return path to mapfile for a given style.
763 """Return path to mapfile for a given style.
764
764
765 Searches mapfile in the following locations:
765 Searches mapfile in the following locations:
766 1. templatepath/style/map
766 1. templatepath/style/map
767 2. templatepath/map-style
767 2. templatepath/map-style
768 3. templatepath/map
768 3. templatepath/map
769 """
769 """
770
770
771 if paths is None:
771 if paths is None:
772 paths = templatepaths()
772 paths = templatepaths()
773 elif isinstance(paths, str):
773 elif isinstance(paths, str):
774 paths = [paths]
774 paths = [paths]
775
775
776 if isinstance(styles, str):
776 if isinstance(styles, str):
777 styles = [styles]
777 styles = [styles]
778
778
779 for style in styles:
779 for style in styles:
780 # only plain name is allowed to honor template paths
780 # only plain name is allowed to honor template paths
781 if (not style
781 if (not style
782 or style in (os.curdir, os.pardir)
782 or style in (os.curdir, os.pardir)
783 or os.sep in style
783 or os.sep in style
784 or os.altsep and os.altsep in style):
784 or os.altsep and os.altsep in style):
785 continue
785 continue
786 locations = [os.path.join(style, 'map'), 'map-' + style]
786 locations = [os.path.join(style, 'map'), 'map-' + style]
787 locations.append('map')
787 locations.append('map')
788
788
789 for path in paths:
789 for path in paths:
790 for location in locations:
790 for location in locations:
791 mapfile = os.path.join(path, location)
791 mapfile = os.path.join(path, location)
792 if os.path.isfile(mapfile):
792 if os.path.isfile(mapfile):
793 return style, mapfile
793 return style, mapfile
794
794
795 raise RuntimeError("No hgweb templates found in %r" % paths)
795 raise RuntimeError("No hgweb templates found in %r" % paths)
796
797 # tell hggettext to extract docstrings from these functions:
798 i18nfunctions = funcs.values()
General Comments 0
You need to be logged in to leave comments. Login now