##// END OF EJS Templates
templater: drop extension point of engine classes (API)...
Yuya Nishihara -
r38609:48289eaf default
parent child Browse files
Show More
@@ -1,521 +1,520 b''
1 test-abort-checkin.t
1 test-abort-checkin.t
2 test-add.t
2 test-add.t
3 test-addremove-similar.t
3 test-addremove-similar.t
4 test-addremove.t
4 test-addremove.t
5 test-amend-subrepo.t
5 test-amend-subrepo.t
6 test-amend.t
6 test-amend.t
7 test-ancestor.py
7 test-ancestor.py
8 test-annotate.py
8 test-annotate.py
9 test-annotate.t
9 test-annotate.t
10 test-archive-symlinks.t
10 test-archive-symlinks.t
11 test-atomictempfile.py
11 test-atomictempfile.py
12 test-audit-path.t
12 test-audit-path.t
13 test-audit-subrepo.t
13 test-audit-subrepo.t
14 test-automv.t
14 test-automv.t
15 test-backout.t
15 test-backout.t
16 test-backwards-remove.t
16 test-backwards-remove.t
17 test-basic.t
17 test-basic.t
18 test-bheads.t
18 test-bheads.t
19 test-bisect.t
19 test-bisect.t
20 test-bisect2.t
20 test-bisect2.t
21 test-bisect3.t
21 test-bisect3.t
22 test-blackbox.t
22 test-blackbox.t
23 test-bookmarks-current.t
23 test-bookmarks-current.t
24 test-bookmarks-merge.t
24 test-bookmarks-merge.t
25 test-bookmarks-rebase.t
25 test-bookmarks-rebase.t
26 test-bookmarks-strip.t
26 test-bookmarks-strip.t
27 test-bookmarks.t
27 test-bookmarks.t
28 test-branch-change.t
28 test-branch-change.t
29 test-branch-option.t
29 test-branch-option.t
30 test-branch-tag-confict.t
30 test-branch-tag-confict.t
31 test-branches.t
31 test-branches.t
32 test-bundle-phases.t
32 test-bundle-phases.t
33 test-bundle-type.t
33 test-bundle-type.t
34 test-bundle-vs-outgoing.t
34 test-bundle-vs-outgoing.t
35 test-bundle2-multiple-changegroups.t
35 test-bundle2-multiple-changegroups.t
36 test-cappedreader.py
36 test-cappedreader.py
37 test-casecollision.t
37 test-casecollision.t
38 test-cat.t
38 test-cat.t
39 test-cbor.py
39 test-cbor.py
40 test-censor.t
40 test-censor.t
41 test-changelog-exec.t
41 test-changelog-exec.t
42 test-check-commit.t
42 test-check-commit.t
43 test-check-execute.t
43 test-check-execute.t
44 test-check-interfaces.py
44 test-check-interfaces.py
45 test-check-module-imports.t
45 test-check-module-imports.t
46 test-check-pyflakes.t
46 test-check-pyflakes.t
47 test-check-pylint.t
47 test-check-pylint.t
48 test-check-shbang.t
48 test-check-shbang.t
49 test-children.t
49 test-children.t
50 test-clone-cgi.t
50 test-clone-cgi.t
51 test-clone-pull-corruption.t
51 test-clone-pull-corruption.t
52 test-clone-r.t
52 test-clone-r.t
53 test-clone-update-order.t
53 test-clone-update-order.t
54 test-command-template.t
54 test-command-template.t
55 test-commit-amend.t
55 test-commit-amend.t
56 test-commit-interactive.t
56 test-commit-interactive.t
57 test-commit-multiple.t
57 test-commit-multiple.t
58 test-commit-unresolved.t
58 test-commit-unresolved.t
59 test-commit.t
59 test-commit.t
60 test-committer.t
60 test-committer.t
61 test-completion.t
61 test-completion.t
62 test-config-env.py
62 test-config-env.py
63 test-config.t
63 test-config.t
64 test-conflict.t
64 test-conflict.t
65 test-confused-revert.t
65 test-confused-revert.t
66 test-context.py
66 test-context.py
67 test-contrib-check-code.t
67 test-contrib-check-code.t
68 test-contrib-check-commit.t
68 test-contrib-check-commit.t
69 test-convert-authormap.t
69 test-convert-authormap.t
70 test-convert-clonebranches.t
70 test-convert-clonebranches.t
71 test-convert-cvs-branch.t
71 test-convert-cvs-branch.t
72 test-convert-cvs-detectmerge.t
72 test-convert-cvs-detectmerge.t
73 test-convert-cvs-synthetic.t
73 test-convert-cvs-synthetic.t
74 test-convert-cvs.t
74 test-convert-cvs.t
75 test-convert-cvsnt-mergepoints.t
75 test-convert-cvsnt-mergepoints.t
76 test-convert-datesort.t
76 test-convert-datesort.t
77 test-convert-filemap.t
77 test-convert-filemap.t
78 test-convert-hg-sink.t
78 test-convert-hg-sink.t
79 test-convert-hg-source.t
79 test-convert-hg-source.t
80 test-convert-hg-startrev.t
80 test-convert-hg-startrev.t
81 test-convert-splicemap.t
81 test-convert-splicemap.t
82 test-convert-tagsbranch-topology.t
82 test-convert-tagsbranch-topology.t
83 test-copy-move-merge.t
83 test-copy-move-merge.t
84 test-copy.t
84 test-copy.t
85 test-copytrace-heuristics.t
85 test-copytrace-heuristics.t
86 test-debugbuilddag.t
86 test-debugbuilddag.t
87 test-debugbundle.t
87 test-debugbundle.t
88 test-debugextensions.t
88 test-debugextensions.t
89 test-debugindexdot.t
89 test-debugindexdot.t
90 test-debugrename.t
90 test-debugrename.t
91 test-default-push.t
91 test-default-push.t
92 test-diff-binary-file.t
92 test-diff-binary-file.t
93 test-diff-change.t
93 test-diff-change.t
94 test-diff-copy-depth.t
94 test-diff-copy-depth.t
95 test-diff-hashes.t
95 test-diff-hashes.t
96 test-diff-ignore-whitespace.t
96 test-diff-ignore-whitespace.t
97 test-diff-indent-heuristic.t
97 test-diff-indent-heuristic.t
98 test-diff-issue2761.t
98 test-diff-issue2761.t
99 test-diff-newlines.t
99 test-diff-newlines.t
100 test-diff-reverse.t
100 test-diff-reverse.t
101 test-diff-subdir.t
101 test-diff-subdir.t
102 test-diff-unified.t
102 test-diff-unified.t
103 test-diff-upgrade.t
103 test-diff-upgrade.t
104 test-diffdir.t
104 test-diffdir.t
105 test-diffstat.t
105 test-diffstat.t
106 test-directaccess.t
106 test-directaccess.t
107 test-dirstate-backup.t
107 test-dirstate-backup.t
108 test-dirstate-nonnormalset.t
108 test-dirstate-nonnormalset.t
109 test-dirstate.t
109 test-dirstate.t
110 test-dispatch.py
110 test-dispatch.py
111 test-doctest.py
111 test-doctest.py
112 test-double-merge.t
112 test-double-merge.t
113 test-drawdag.t
113 test-drawdag.t
114 test-duplicateoptions.py
114 test-duplicateoptions.py
115 test-editor-filename.t
115 test-editor-filename.t
116 test-empty-dir.t
116 test-empty-dir.t
117 test-empty-file.t
117 test-empty-file.t
118 test-empty-group.t
118 test-empty-group.t
119 test-empty.t
119 test-empty.t
120 test-encode.t
120 test-encode.t
121 test-encoding-func.py
121 test-encoding-func.py
122 test-encoding.t
122 test-encoding.t
123 test-eol-add.t
123 test-eol-add.t
124 test-eol-clone.t
124 test-eol-clone.t
125 test-eol-hook.t
125 test-eol-hook.t
126 test-eol-patch.t
126 test-eol-patch.t
127 test-eol-tag.t
127 test-eol-tag.t
128 test-eol-update.t
128 test-eol-update.t
129 test-eol.t
129 test-eol.t
130 test-eolfilename.t
130 test-eolfilename.t
131 test-excessive-merge.t
131 test-excessive-merge.t
132 test-exchange-obsmarkers-case-A1.t
132 test-exchange-obsmarkers-case-A1.t
133 test-exchange-obsmarkers-case-A2.t
133 test-exchange-obsmarkers-case-A2.t
134 test-exchange-obsmarkers-case-A3.t
134 test-exchange-obsmarkers-case-A3.t
135 test-exchange-obsmarkers-case-A4.t
135 test-exchange-obsmarkers-case-A4.t
136 test-exchange-obsmarkers-case-A5.t
136 test-exchange-obsmarkers-case-A5.t
137 test-exchange-obsmarkers-case-A6.t
137 test-exchange-obsmarkers-case-A6.t
138 test-exchange-obsmarkers-case-A7.t
138 test-exchange-obsmarkers-case-A7.t
139 test-exchange-obsmarkers-case-B1.t
139 test-exchange-obsmarkers-case-B1.t
140 test-exchange-obsmarkers-case-B2.t
140 test-exchange-obsmarkers-case-B2.t
141 test-exchange-obsmarkers-case-B3.t
141 test-exchange-obsmarkers-case-B3.t
142 test-exchange-obsmarkers-case-B4.t
142 test-exchange-obsmarkers-case-B4.t
143 test-exchange-obsmarkers-case-B5.t
143 test-exchange-obsmarkers-case-B5.t
144 test-exchange-obsmarkers-case-B6.t
144 test-exchange-obsmarkers-case-B6.t
145 test-exchange-obsmarkers-case-B7.t
145 test-exchange-obsmarkers-case-B7.t
146 test-exchange-obsmarkers-case-C1.t
146 test-exchange-obsmarkers-case-C1.t
147 test-exchange-obsmarkers-case-C2.t
147 test-exchange-obsmarkers-case-C2.t
148 test-exchange-obsmarkers-case-C3.t
148 test-exchange-obsmarkers-case-C3.t
149 test-exchange-obsmarkers-case-C4.t
149 test-exchange-obsmarkers-case-C4.t
150 test-exchange-obsmarkers-case-D1.t
150 test-exchange-obsmarkers-case-D1.t
151 test-exchange-obsmarkers-case-D2.t
151 test-exchange-obsmarkers-case-D2.t
152 test-exchange-obsmarkers-case-D3.t
152 test-exchange-obsmarkers-case-D3.t
153 test-exchange-obsmarkers-case-D4.t
153 test-exchange-obsmarkers-case-D4.t
154 test-execute-bit.t
154 test-execute-bit.t
155 test-export.t
155 test-export.t
156 test-extdata.t
156 test-extdata.t
157 test-extdiff.t
157 test-extdiff.t
158 test-extensions-afterloaded.t
158 test-extensions-afterloaded.t
159 test-extensions-wrapfunction.py
159 test-extensions-wrapfunction.py
160 test-extra-filelog-entry.t
160 test-extra-filelog-entry.t
161 test-fetch.t
161 test-fetch.t
162 test-filebranch.t
162 test-filebranch.t
163 test-filecache.py
163 test-filecache.py
164 test-filelog.py
164 test-filelog.py
165 test-fileset-generated.t
165 test-fileset-generated.t
166 test-fileset.t
166 test-fileset.t
167 test-fix-topology.t
167 test-fix-topology.t
168 test-flags.t
168 test-flags.t
169 test-generaldelta.t
169 test-generaldelta.t
170 test-getbundle.t
170 test-getbundle.t
171 test-git-export.t
171 test-git-export.t
172 test-glog-topological.t
172 test-glog-topological.t
173 test-gpg.t
173 test-gpg.t
174 test-graft.t
174 test-graft.t
175 test-hg-parseurl.py
175 test-hg-parseurl.py
176 test-hghave.t
176 test-hghave.t
177 test-hgignore.t
177 test-hgignore.t
178 test-hgk.t
178 test-hgk.t
179 test-hgrc.t
179 test-hgrc.t
180 test-hgweb-bundle.t
180 test-hgweb-bundle.t
181 test-hgweb-descend-empties.t
181 test-hgweb-descend-empties.t
182 test-hgweb-empty.t
182 test-hgweb-empty.t
183 test-hgweb-removed.t
183 test-hgweb-removed.t
184 test-hgwebdir-paths.py
184 test-hgwebdir-paths.py
185 test-hgwebdirsym.t
185 test-hgwebdirsym.t
186 test-histedit-arguments.t
186 test-histedit-arguments.t
187 test-histedit-base.t
187 test-histedit-base.t
188 test-histedit-bookmark-motion.t
188 test-histedit-bookmark-motion.t
189 test-histedit-commute.t
189 test-histedit-commute.t
190 test-histedit-drop.t
190 test-histedit-drop.t
191 test-histedit-edit.t
191 test-histedit-edit.t
192 test-histedit-fold-non-commute.t
192 test-histedit-fold-non-commute.t
193 test-histedit-fold.t
193 test-histedit-fold.t
194 test-histedit-no-change.t
194 test-histedit-no-change.t
195 test-histedit-non-commute-abort.t
195 test-histedit-non-commute-abort.t
196 test-histedit-non-commute.t
196 test-histedit-non-commute.t
197 test-histedit-obsolete.t
197 test-histedit-obsolete.t
198 test-histedit-outgoing.t
198 test-histedit-outgoing.t
199 test-histedit-templates.t
199 test-histedit-templates.t
200 test-http-branchmap.t
200 test-http-branchmap.t
201 test-http-bundle1.t
201 test-http-bundle1.t
202 test-http-clone-r.t
202 test-http-clone-r.t
203 test-http.t
203 test-http.t
204 test-hybridencode.py
204 test-hybridencode.py
205 test-identify.t
205 test-identify.t
206 test-import-bypass.t
206 test-import-bypass.t
207 test-import-eol.t
207 test-import-eol.t
208 test-import-merge.t
208 test-import-merge.t
209 test-import-unknown.t
209 test-import-unknown.t
210 test-import.t
210 test-import.t
211 test-imports-checker.t
211 test-imports-checker.t
212 test-incoming-outgoing.t
212 test-incoming-outgoing.t
213 test-inherit-mode.t
213 test-inherit-mode.t
214 test-init.t
214 test-init.t
215 test-issue1089.t
215 test-issue1089.t
216 test-issue1102.t
216 test-issue1102.t
217 test-issue1175.t
217 test-issue1175.t
218 test-issue1306.t
218 test-issue1306.t
219 test-issue1438.t
219 test-issue1438.t
220 test-issue1502.t
220 test-issue1502.t
221 test-issue1802.t
221 test-issue1802.t
222 test-issue1877.t
222 test-issue1877.t
223 test-issue1993.t
223 test-issue1993.t
224 test-issue2137.t
224 test-issue2137.t
225 test-issue3084.t
225 test-issue3084.t
226 test-issue4074.t
226 test-issue4074.t
227 test-issue522.t
227 test-issue522.t
228 test-issue586.t
228 test-issue586.t
229 test-issue612.t
229 test-issue612.t
230 test-issue619.t
230 test-issue619.t
231 test-issue660.t
231 test-issue660.t
232 test-issue672.t
232 test-issue672.t
233 test-issue842.t
233 test-issue842.t
234 test-journal-exists.t
234 test-journal-exists.t
235 test-journal-share.t
235 test-journal-share.t
236 test-journal.t
236 test-journal.t
237 test-known.t
237 test-known.t
238 test-largefiles-cache.t
238 test-largefiles-cache.t
239 test-largefiles-misc.t
239 test-largefiles-misc.t
240 test-largefiles-small-disk.t
240 test-largefiles-small-disk.t
241 test-largefiles-update.t
241 test-largefiles-update.t
242 test-largefiles.t
242 test-largefiles.t
243 test-lfs-largefiles.t
243 test-lfs-largefiles.t
244 test-lfs-pointer.py
244 test-lfs-pointer.py
245 test-linerange.py
245 test-linerange.py
246 test-locate.t
246 test-locate.t
247 test-lock-badness.t
247 test-lock-badness.t
248 test-log-linerange.t
248 test-log-linerange.t
249 test-log.t
249 test-log.t
250 test-logexchange.t
250 test-logexchange.t
251 test-lrucachedict.py
251 test-lrucachedict.py
252 test-mactext.t
252 test-mactext.t
253 test-mailmap.t
253 test-mailmap.t
254 test-manifest-merging.t
254 test-manifest-merging.t
255 test-manifest.py
255 test-manifest.py
256 test-manifest.t
256 test-manifest.t
257 test-match.py
257 test-match.py
258 test-mdiff.py
258 test-mdiff.py
259 test-merge-changedelete.t
259 test-merge-changedelete.t
260 test-merge-closedheads.t
260 test-merge-closedheads.t
261 test-merge-commit.t
261 test-merge-commit.t
262 test-merge-criss-cross.t
262 test-merge-criss-cross.t
263 test-merge-default.t
263 test-merge-default.t
264 test-merge-force.t
264 test-merge-force.t
265 test-merge-halt.t
265 test-merge-halt.t
266 test-merge-internal-tools-pattern.t
266 test-merge-internal-tools-pattern.t
267 test-merge-local.t
267 test-merge-local.t
268 test-merge-remove.t
268 test-merge-remove.t
269 test-merge-revert.t
269 test-merge-revert.t
270 test-merge-revert2.t
270 test-merge-revert2.t
271 test-merge-subrepos.t
271 test-merge-subrepos.t
272 test-merge-symlinks.t
272 test-merge-symlinks.t
273 test-merge-tools.t
273 test-merge-tools.t
274 test-merge-types.t
274 test-merge-types.t
275 test-merge1.t
275 test-merge1.t
276 test-merge10.t
276 test-merge10.t
277 test-merge2.t
277 test-merge2.t
278 test-merge4.t
278 test-merge4.t
279 test-merge5.t
279 test-merge5.t
280 test-merge6.t
280 test-merge6.t
281 test-merge7.t
281 test-merge7.t
282 test-merge8.t
282 test-merge8.t
283 test-merge9.t
283 test-merge9.t
284 test-minifileset.py
284 test-minifileset.py
285 test-minirst.py
285 test-minirst.py
286 test-mq-git.t
286 test-mq-git.t
287 test-mq-header-date.t
287 test-mq-header-date.t
288 test-mq-header-from.t
288 test-mq-header-from.t
289 test-mq-merge.t
289 test-mq-merge.t
290 test-mq-pull-from-bundle.t
290 test-mq-pull-from-bundle.t
291 test-mq-qclone-http.t
291 test-mq-qclone-http.t
292 test-mq-qdelete.t
292 test-mq-qdelete.t
293 test-mq-qdiff.t
293 test-mq-qdiff.t
294 test-mq-qfold.t
294 test-mq-qfold.t
295 test-mq-qgoto.t
295 test-mq-qgoto.t
296 test-mq-qimport-fail-cleanup.t
296 test-mq-qimport-fail-cleanup.t
297 test-mq-qnew.t
297 test-mq-qnew.t
298 test-mq-qpush-exact.t
298 test-mq-qpush-exact.t
299 test-mq-qqueue.t
299 test-mq-qqueue.t
300 test-mq-qrefresh-interactive.t
300 test-mq-qrefresh-interactive.t
301 test-mq-qrefresh-replace-log-message.t
301 test-mq-qrefresh-replace-log-message.t
302 test-mq-qrefresh.t
302 test-mq-qrefresh.t
303 test-mq-qrename.t
303 test-mq-qrename.t
304 test-mq-qsave.t
304 test-mq-qsave.t
305 test-mq-safety.t
305 test-mq-safety.t
306 test-mq-subrepo.t
306 test-mq-subrepo.t
307 test-mq-symlinks.t
307 test-mq-symlinks.t
308 test-mv-cp-st-diff.t
308 test-mv-cp-st-diff.t
309 test-narrow-archive.t
309 test-narrow-archive.t
310 test-narrow-clone-no-ellipsis.t
310 test-narrow-clone-no-ellipsis.t
311 test-narrow-clone-non-narrow-server.t
311 test-narrow-clone-non-narrow-server.t
312 test-narrow-clone-nonlinear.t
312 test-narrow-clone-nonlinear.t
313 test-narrow-clone.t
313 test-narrow-clone.t
314 test-narrow-commit.t
314 test-narrow-commit.t
315 test-narrow-copies.t
315 test-narrow-copies.t
316 test-narrow-debugcommands.t
316 test-narrow-debugcommands.t
317 test-narrow-debugrebuilddirstate.t
317 test-narrow-debugrebuilddirstate.t
318 test-narrow-exchange-merges.t
318 test-narrow-exchange-merges.t
319 test-narrow-exchange.t
319 test-narrow-exchange.t
320 test-narrow-expanddirstate.t
320 test-narrow-expanddirstate.t
321 test-narrow-merge.t
321 test-narrow-merge.t
322 test-narrow-patch.t
322 test-narrow-patch.t
323 test-narrow-patterns.t
323 test-narrow-patterns.t
324 test-narrow-pull.t
324 test-narrow-pull.t
325 test-narrow-rebase.t
325 test-narrow-rebase.t
326 test-narrow-shallow-merges.t
326 test-narrow-shallow-merges.t
327 test-narrow-shallow.t
327 test-narrow-shallow.t
328 test-narrow-strip.t
328 test-narrow-strip.t
329 test-narrow-update.t
329 test-narrow-update.t
330 test-narrow-widen.t
330 test-narrow-widen.t
331 test-narrow.t
331 test-narrow.t
332 test-nested-repo.t
332 test-nested-repo.t
333 test-newbranch.t
333 test-newbranch.t
334 test-obshistory.t
334 test-obshistory.t
335 test-obsmarker-template.t
335 test-obsmarker-template.t
336 test-obsmarkers-effectflag.t
336 test-obsmarkers-effectflag.t
337 test-obsolete-bundle-strip.t
337 test-obsolete-bundle-strip.t
338 test-obsolete-changeset-exchange.t
338 test-obsolete-changeset-exchange.t
339 test-obsolete-checkheads.t
339 test-obsolete-checkheads.t
340 test-obsolete-distributed.t
340 test-obsolete-distributed.t
341 test-obsolete-tag-cache.t
341 test-obsolete-tag-cache.t
342 test-pager.t
342 test-pager.t
343 test-parents.t
343 test-parents.t
344 test-parseindex2.py
344 test-parseindex2.py
345 test-patch.t
345 test-patch.t
346 test-pathconflicts-merge.t
346 test-pathconflicts-merge.t
347 test-pathconflicts-update.t
347 test-pathconflicts-update.t
348 test-pathencode.py
348 test-pathencode.py
349 test-pending.t
349 test-pending.t
350 test-permissions.t
350 test-permissions.t
351 test-phases.t
351 test-phases.t
352 test-pull-branch.t
352 test-pull-branch.t
353 test-pull-http.t
353 test-pull-http.t
354 test-pull-permission.t
354 test-pull-permission.t
355 test-pull-pull-corruption.t
355 test-pull-pull-corruption.t
356 test-pull-r.t
356 test-pull-r.t
357 test-pull-update.t
357 test-pull-update.t
358 test-pull.t
358 test-pull.t
359 test-purge.t
359 test-purge.t
360 test-push-checkheads-partial-C1.t
360 test-push-checkheads-partial-C1.t
361 test-push-checkheads-partial-C2.t
361 test-push-checkheads-partial-C2.t
362 test-push-checkheads-partial-C3.t
362 test-push-checkheads-partial-C3.t
363 test-push-checkheads-partial-C4.t
363 test-push-checkheads-partial-C4.t
364 test-push-checkheads-pruned-B1.t
364 test-push-checkheads-pruned-B1.t
365 test-push-checkheads-pruned-B2.t
365 test-push-checkheads-pruned-B2.t
366 test-push-checkheads-pruned-B3.t
366 test-push-checkheads-pruned-B3.t
367 test-push-checkheads-pruned-B4.t
367 test-push-checkheads-pruned-B4.t
368 test-push-checkheads-pruned-B5.t
368 test-push-checkheads-pruned-B5.t
369 test-push-checkheads-pruned-B6.t
369 test-push-checkheads-pruned-B6.t
370 test-push-checkheads-pruned-B7.t
370 test-push-checkheads-pruned-B7.t
371 test-push-checkheads-pruned-B8.t
371 test-push-checkheads-pruned-B8.t
372 test-push-checkheads-superceed-A1.t
372 test-push-checkheads-superceed-A1.t
373 test-push-checkheads-superceed-A2.t
373 test-push-checkheads-superceed-A2.t
374 test-push-checkheads-superceed-A3.t
374 test-push-checkheads-superceed-A3.t
375 test-push-checkheads-superceed-A4.t
375 test-push-checkheads-superceed-A4.t
376 test-push-checkheads-superceed-A5.t
376 test-push-checkheads-superceed-A5.t
377 test-push-checkheads-superceed-A6.t
377 test-push-checkheads-superceed-A6.t
378 test-push-checkheads-superceed-A7.t
378 test-push-checkheads-superceed-A7.t
379 test-push-checkheads-superceed-A8.t
379 test-push-checkheads-superceed-A8.t
380 test-push-checkheads-unpushed-D1.t
380 test-push-checkheads-unpushed-D1.t
381 test-push-checkheads-unpushed-D2.t
381 test-push-checkheads-unpushed-D2.t
382 test-push-checkheads-unpushed-D3.t
382 test-push-checkheads-unpushed-D3.t
383 test-push-checkheads-unpushed-D4.t
383 test-push-checkheads-unpushed-D4.t
384 test-push-checkheads-unpushed-D5.t
384 test-push-checkheads-unpushed-D5.t
385 test-push-checkheads-unpushed-D6.t
385 test-push-checkheads-unpushed-D6.t
386 test-push-checkheads-unpushed-D7.t
386 test-push-checkheads-unpushed-D7.t
387 test-push-http.t
387 test-push-http.t
388 test-push-warn.t
388 test-push-warn.t
389 test-push.t
389 test-push.t
390 test-pushvars.t
390 test-pushvars.t
391 test-rebase-abort.t
391 test-rebase-abort.t
392 test-rebase-base-flag.t
392 test-rebase-base-flag.t
393 test-rebase-bookmarks.t
393 test-rebase-bookmarks.t
394 test-rebase-brute-force.t
394 test-rebase-brute-force.t
395 test-rebase-cache.t
395 test-rebase-cache.t
396 test-rebase-check-restore.t
396 test-rebase-check-restore.t
397 test-rebase-collapse.t
397 test-rebase-collapse.t
398 test-rebase-conflicts.t
398 test-rebase-conflicts.t
399 test-rebase-dest.t
399 test-rebase-dest.t
400 test-rebase-detach.t
400 test-rebase-detach.t
401 test-rebase-emptycommit.t
401 test-rebase-emptycommit.t
402 test-rebase-inmemory.t
402 test-rebase-inmemory.t
403 test-rebase-interruptions.t
403 test-rebase-interruptions.t
404 test-rebase-issue-noparam-single-rev.t
404 test-rebase-issue-noparam-single-rev.t
405 test-rebase-legacy.t
405 test-rebase-legacy.t
406 test-rebase-mq-skip.t
406 test-rebase-mq-skip.t
407 test-rebase-mq.t
407 test-rebase-mq.t
408 test-rebase-named-branches.t
408 test-rebase-named-branches.t
409 test-rebase-newancestor.t
409 test-rebase-newancestor.t
410 test-rebase-obsolete.t
410 test-rebase-obsolete.t
411 test-rebase-parameters.t
411 test-rebase-parameters.t
412 test-rebase-partial.t
412 test-rebase-partial.t
413 test-rebase-pull.t
413 test-rebase-pull.t
414 test-rebase-rename.t
414 test-rebase-rename.t
415 test-rebase-scenario-global.t
415 test-rebase-scenario-global.t
416 test-rebase-templates.t
416 test-rebase-templates.t
417 test-rebase-transaction.t
417 test-rebase-transaction.t
418 test-rebuildstate.t
418 test-rebuildstate.t
419 test-record.t
419 test-record.t
420 test-relink.t
420 test-relink.t
421 test-remove.t
421 test-remove.t
422 test-rename-after-merge.t
422 test-rename-after-merge.t
423 test-rename-dir-merge.t
423 test-rename-dir-merge.t
424 test-rename-merge1.t
424 test-rename-merge1.t
425 test-rename.t
425 test-rename.t
426 test-repair-strip.t
426 test-repair-strip.t
427 test-repo-compengines.t
427 test-repo-compengines.t
428 test-resolve.t
428 test-resolve.t
429 test-revert-flags.t
429 test-revert-flags.t
430 test-revert-interactive.t
430 test-revert-interactive.t
431 test-revert-unknown.t
431 test-revert-unknown.t
432 test-revlog-ancestry.py
432 test-revlog-ancestry.py
433 test-revlog-group-emptyiter.t
433 test-revlog-group-emptyiter.t
434 test-revlog-mmapindex.t
434 test-revlog-mmapindex.t
435 test-revlog-packentry.t
435 test-revlog-packentry.t
436 test-revlog-raw.py
436 test-revlog-raw.py
437 test-revset-dirstate-parents.t
437 test-revset-dirstate-parents.t
438 test-revset-legacy-lookup.t
438 test-revset-legacy-lookup.t
439 test-revset-outgoing.t
439 test-revset-outgoing.t
440 test-rollback.t
440 test-rollback.t
441 test-run-tests.py
441 test-run-tests.py
442 test-run-tests.t
442 test-run-tests.t
443 test-schemes.t
443 test-schemes.t
444 test-serve.t
444 test-serve.t
445 test-setdiscovery.t
445 test-setdiscovery.t
446 test-share.t
446 test-share.t
447 test-shelve.t
447 test-shelve.t
448 test-show-stack.t
448 test-show-stack.t
449 test-show-work.t
449 test-show-work.t
450 test-show.t
450 test-show.t
451 test-simple-update.t
451 test-simple-update.t
452 test-simplekeyvaluefile.py
452 test-simplekeyvaluefile.py
453 test-simplemerge.py
453 test-simplemerge.py
454 test-single-head.t
454 test-single-head.t
455 test-sparse-clear.t
455 test-sparse-clear.t
456 test-sparse-clone.t
456 test-sparse-clone.t
457 test-sparse-import.t
457 test-sparse-import.t
458 test-sparse-merges.t
458 test-sparse-merges.t
459 test-sparse-profiles.t
459 test-sparse-profiles.t
460 test-sparse-requirement.t
460 test-sparse-requirement.t
461 test-sparse-verbose-json.t
461 test-sparse-verbose-json.t
462 test-sparse.t
462 test-sparse.t
463 test-split.t
463 test-split.t
464 test-ssh-bundle1.t
464 test-ssh-bundle1.t
465 test-ssh-clone-r.t
465 test-ssh-clone-r.t
466 test-ssh-proto-unbundle.t
466 test-ssh-proto-unbundle.t
467 test-ssh-proto.t
467 test-ssh-proto.t
468 test-ssh.t
468 test-ssh.t
469 test-sshserver.py
469 test-sshserver.py
470 test-stack.t
470 test-stack.t
471 test-status-inprocess.py
471 test-status-inprocess.py
472 test-status-rev.t
472 test-status-rev.t
473 test-status-terse.t
473 test-status-terse.t
474 test-strict.t
474 test-strict.t
475 test-strip-cross.t
475 test-strip-cross.t
476 test-strip.t
476 test-strip.t
477 test-subrepo-deep-nested-change.t
477 test-subrepo-deep-nested-change.t
478 test-subrepo-missing.t
478 test-subrepo-missing.t
479 test-subrepo-paths.t
479 test-subrepo-paths.t
480 test-subrepo-recursion.t
480 test-subrepo-recursion.t
481 test-subrepo-relative-path.t
481 test-subrepo-relative-path.t
482 test-subrepo.t
482 test-subrepo.t
483 test-symlink-os-yes-fs-no.py
483 test-symlink-os-yes-fs-no.py
484 test-symlink-placeholder.t
484 test-symlink-placeholder.t
485 test-symlinks.t
485 test-symlinks.t
486 test-tag.t
486 test-tag.t
487 test-tags.t
487 test-tags.t
488 test-template-engine.t
489 test-template-filters.t
488 test-template-filters.t
490 test-treemanifest.t
489 test-treemanifest.t
491 test-ui-color.py
490 test-ui-color.py
492 test-ui-config.py
491 test-ui-config.py
493 test-ui-verbosity.py
492 test-ui-verbosity.py
494 test-unamend.t
493 test-unamend.t
495 test-unbundlehash.t
494 test-unbundlehash.t
496 test-uncommit.t
495 test-uncommit.t
497 test-unified-test.t
496 test-unified-test.t
498 test-unionrepo.t
497 test-unionrepo.t
499 test-unrelated-pull.t
498 test-unrelated-pull.t
500 test-up-local-change.t
499 test-up-local-change.t
501 test-update-branches.t
500 test-update-branches.t
502 test-update-dest.t
501 test-update-dest.t
503 test-update-issue1456.t
502 test-update-issue1456.t
504 test-update-names.t
503 test-update-names.t
505 test-update-reverse.t
504 test-update-reverse.t
506 test-upgrade-repo.t
505 test-upgrade-repo.t
507 test-url-download.t
506 test-url-download.t
508 test-url-rev.t
507 test-url-rev.t
509 test-url.py
508 test-url.py
510 test-username-newline.t
509 test-username-newline.t
511 test-verify.t
510 test-verify.t
512 test-walk.t
511 test-walk.t
513 test-walkrepo.py
512 test-walkrepo.py
514 test-websub.t
513 test-websub.t
515 test-win32text.t
514 test-win32text.t
516 test-wireproto-clientreactor.py
515 test-wireproto-clientreactor.py
517 test-wireproto-framing.py
516 test-wireproto-framing.py
518 test-wireproto-serverreactor.py
517 test-wireproto-serverreactor.py
519 test-wireproto.py
518 test-wireproto.py
520 test-wsgirequest.py
519 test-wsgirequest.py
521 test-xdg.t
520 test-xdg.t
@@ -1,924 +1,910 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 """Slightly complicated template engine for commands and hgweb
8 """Slightly complicated template engine for commands and hgweb
9
9
10 This module provides low-level interface to the template engine. See the
10 This module provides low-level interface to the template engine. See the
11 formatter and cmdutil modules if you are looking for high-level functions
11 formatter and cmdutil modules if you are looking for high-level functions
12 such as ``cmdutil.rendertemplate(ctx, tmpl)``.
12 such as ``cmdutil.rendertemplate(ctx, tmpl)``.
13
13
14 Internal Data Types
14 Internal Data Types
15 -------------------
15 -------------------
16
16
17 Template keywords and functions take a dictionary of current symbols and
17 Template keywords and functions take a dictionary of current symbols and
18 resources (a "mapping") and return result. Inputs and outputs must be one
18 resources (a "mapping") and return result. Inputs and outputs must be one
19 of the following data types:
19 of the following data types:
20
20
21 bytes
21 bytes
22 a byte string, which is generally a human-readable text in local encoding.
22 a byte string, which is generally a human-readable text in local encoding.
23
23
24 generator
24 generator
25 a lazily-evaluated byte string, which is a possibly nested generator of
25 a lazily-evaluated byte string, which is a possibly nested generator of
26 values of any printable types, and will be folded by ``stringify()``
26 values of any printable types, and will be folded by ``stringify()``
27 or ``flatten()``.
27 or ``flatten()``.
28
28
29 None
29 None
30 sometimes represents an empty value, which can be stringified to ''.
30 sometimes represents an empty value, which can be stringified to ''.
31
31
32 True, False, int, float
32 True, False, int, float
33 can be stringified as such.
33 can be stringified as such.
34
34
35 wrappedbytes, wrappedvalue
35 wrappedbytes, wrappedvalue
36 a wrapper for the above printable types.
36 a wrapper for the above printable types.
37
37
38 date
38 date
39 represents a (unixtime, offset) tuple.
39 represents a (unixtime, offset) tuple.
40
40
41 hybrid
41 hybrid
42 represents a list/dict of printable values, which can also be converted
42 represents a list/dict of printable values, which can also be converted
43 to mappings by % operator.
43 to mappings by % operator.
44
44
45 hybriditem
45 hybriditem
46 represents a scalar printable value, also supports % operator.
46 represents a scalar printable value, also supports % operator.
47
47
48 mappinggenerator, mappinglist
48 mappinggenerator, mappinglist
49 represents mappings (i.e. a list of dicts), which may have default
49 represents mappings (i.e. a list of dicts), which may have default
50 output format.
50 output format.
51
51
52 mappedgenerator
52 mappedgenerator
53 a lazily-evaluated list of byte strings, which is e.g. a result of %
53 a lazily-evaluated list of byte strings, which is e.g. a result of %
54 operation.
54 operation.
55 """
55 """
56
56
57 from __future__ import absolute_import, print_function
57 from __future__ import absolute_import, print_function
58
58
59 import abc
59 import abc
60 import os
60 import os
61
61
62 from .i18n import _
62 from .i18n import _
63 from . import (
63 from . import (
64 config,
64 config,
65 encoding,
65 encoding,
66 error,
66 error,
67 parser,
67 parser,
68 pycompat,
68 pycompat,
69 templatefilters,
69 templatefilters,
70 templatefuncs,
70 templatefuncs,
71 templateutil,
71 templateutil,
72 util,
72 util,
73 )
73 )
74 from .utils import (
74 from .utils import (
75 stringutil,
75 stringutil,
76 )
76 )
77
77
78 # template parsing
78 # template parsing
79
79
80 elements = {
80 elements = {
81 # token-type: binding-strength, primary, prefix, infix, suffix
81 # token-type: binding-strength, primary, prefix, infix, suffix
82 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
82 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
83 ".": (18, None, None, (".", 18), None),
83 ".": (18, None, None, (".", 18), None),
84 "%": (15, None, None, ("%", 15), None),
84 "%": (15, None, None, ("%", 15), None),
85 "|": (15, None, None, ("|", 15), None),
85 "|": (15, None, None, ("|", 15), None),
86 "*": (5, None, None, ("*", 5), None),
86 "*": (5, None, None, ("*", 5), None),
87 "/": (5, None, None, ("/", 5), None),
87 "/": (5, None, None, ("/", 5), None),
88 "+": (4, None, None, ("+", 4), None),
88 "+": (4, None, None, ("+", 4), None),
89 "-": (4, None, ("negate", 19), ("-", 4), None),
89 "-": (4, None, ("negate", 19), ("-", 4), None),
90 "=": (3, None, None, ("keyvalue", 3), None),
90 "=": (3, None, None, ("keyvalue", 3), None),
91 ",": (2, None, None, ("list", 2), None),
91 ",": (2, None, None, ("list", 2), None),
92 ")": (0, None, None, None, None),
92 ")": (0, None, None, None, None),
93 "integer": (0, "integer", None, None, None),
93 "integer": (0, "integer", None, None, None),
94 "symbol": (0, "symbol", None, None, None),
94 "symbol": (0, "symbol", None, None, None),
95 "string": (0, "string", None, None, None),
95 "string": (0, "string", None, None, None),
96 "template": (0, "template", None, None, None),
96 "template": (0, "template", None, None, None),
97 "end": (0, None, None, None, None),
97 "end": (0, None, None, None, None),
98 }
98 }
99
99
100 def tokenize(program, start, end, term=None):
100 def tokenize(program, start, end, term=None):
101 """Parse a template expression into a stream of tokens, which must end
101 """Parse a template expression into a stream of tokens, which must end
102 with term if specified"""
102 with term if specified"""
103 pos = start
103 pos = start
104 program = pycompat.bytestr(program)
104 program = pycompat.bytestr(program)
105 while pos < end:
105 while pos < end:
106 c = program[pos]
106 c = program[pos]
107 if c.isspace(): # skip inter-token whitespace
107 if c.isspace(): # skip inter-token whitespace
108 pass
108 pass
109 elif c in "(=,).%|+-*/": # handle simple operators
109 elif c in "(=,).%|+-*/": # handle simple operators
110 yield (c, None, pos)
110 yield (c, None, pos)
111 elif c in '"\'': # handle quoted templates
111 elif c in '"\'': # handle quoted templates
112 s = pos + 1
112 s = pos + 1
113 data, pos = _parsetemplate(program, s, end, c)
113 data, pos = _parsetemplate(program, s, end, c)
114 yield ('template', data, s)
114 yield ('template', data, s)
115 pos -= 1
115 pos -= 1
116 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
116 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
117 # handle quoted strings
117 # handle quoted strings
118 c = program[pos + 1]
118 c = program[pos + 1]
119 s = pos = pos + 2
119 s = pos = pos + 2
120 while pos < end: # find closing quote
120 while pos < end: # find closing quote
121 d = program[pos]
121 d = program[pos]
122 if d == '\\': # skip over escaped characters
122 if d == '\\': # skip over escaped characters
123 pos += 2
123 pos += 2
124 continue
124 continue
125 if d == c:
125 if d == c:
126 yield ('string', program[s:pos], s)
126 yield ('string', program[s:pos], s)
127 break
127 break
128 pos += 1
128 pos += 1
129 else:
129 else:
130 raise error.ParseError(_("unterminated string"), s)
130 raise error.ParseError(_("unterminated string"), s)
131 elif c.isdigit():
131 elif c.isdigit():
132 s = pos
132 s = pos
133 while pos < end:
133 while pos < end:
134 d = program[pos]
134 d = program[pos]
135 if not d.isdigit():
135 if not d.isdigit():
136 break
136 break
137 pos += 1
137 pos += 1
138 yield ('integer', program[s:pos], s)
138 yield ('integer', program[s:pos], s)
139 pos -= 1
139 pos -= 1
140 elif (c == '\\' and program[pos:pos + 2] in (br"\'", br'\"')
140 elif (c == '\\' and program[pos:pos + 2] in (br"\'", br'\"')
141 or c == 'r' and program[pos:pos + 3] in (br"r\'", br'r\"')):
141 or c == 'r' and program[pos:pos + 3] in (br"r\'", br'r\"')):
142 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
142 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
143 # where some of nested templates were preprocessed as strings and
143 # where some of nested templates were preprocessed as strings and
144 # then compiled. therefore, \"...\" was allowed. (issue4733)
144 # then compiled. therefore, \"...\" was allowed. (issue4733)
145 #
145 #
146 # processing flow of _evalifliteral() at 5ab28a2e9962:
146 # processing flow of _evalifliteral() at 5ab28a2e9962:
147 # outer template string -> stringify() -> compiletemplate()
147 # outer template string -> stringify() -> compiletemplate()
148 # ------------------------ ------------ ------------------
148 # ------------------------ ------------ ------------------
149 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
149 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
150 # ~~~~~~~~
150 # ~~~~~~~~
151 # escaped quoted string
151 # escaped quoted string
152 if c == 'r':
152 if c == 'r':
153 pos += 1
153 pos += 1
154 token = 'string'
154 token = 'string'
155 else:
155 else:
156 token = 'template'
156 token = 'template'
157 quote = program[pos:pos + 2]
157 quote = program[pos:pos + 2]
158 s = pos = pos + 2
158 s = pos = pos + 2
159 while pos < end: # find closing escaped quote
159 while pos < end: # find closing escaped quote
160 if program.startswith('\\\\\\', pos, end):
160 if program.startswith('\\\\\\', pos, end):
161 pos += 4 # skip over double escaped characters
161 pos += 4 # skip over double escaped characters
162 continue
162 continue
163 if program.startswith(quote, pos, end):
163 if program.startswith(quote, pos, end):
164 # interpret as if it were a part of an outer string
164 # interpret as if it were a part of an outer string
165 data = parser.unescapestr(program[s:pos])
165 data = parser.unescapestr(program[s:pos])
166 if token == 'template':
166 if token == 'template':
167 data = _parsetemplate(data, 0, len(data))[0]
167 data = _parsetemplate(data, 0, len(data))[0]
168 yield (token, data, s)
168 yield (token, data, s)
169 pos += 1
169 pos += 1
170 break
170 break
171 pos += 1
171 pos += 1
172 else:
172 else:
173 raise error.ParseError(_("unterminated string"), s)
173 raise error.ParseError(_("unterminated string"), s)
174 elif c.isalnum() or c in '_':
174 elif c.isalnum() or c in '_':
175 s = pos
175 s = pos
176 pos += 1
176 pos += 1
177 while pos < end: # find end of symbol
177 while pos < end: # find end of symbol
178 d = program[pos]
178 d = program[pos]
179 if not (d.isalnum() or d == "_"):
179 if not (d.isalnum() or d == "_"):
180 break
180 break
181 pos += 1
181 pos += 1
182 sym = program[s:pos]
182 sym = program[s:pos]
183 yield ('symbol', sym, s)
183 yield ('symbol', sym, s)
184 pos -= 1
184 pos -= 1
185 elif c == term:
185 elif c == term:
186 yield ('end', None, pos)
186 yield ('end', None, pos)
187 return
187 return
188 else:
188 else:
189 raise error.ParseError(_("syntax error"), pos)
189 raise error.ParseError(_("syntax error"), pos)
190 pos += 1
190 pos += 1
191 if term:
191 if term:
192 raise error.ParseError(_("unterminated template expansion"), start)
192 raise error.ParseError(_("unterminated template expansion"), start)
193 yield ('end', None, pos)
193 yield ('end', None, pos)
194
194
195 def _parsetemplate(tmpl, start, stop, quote=''):
195 def _parsetemplate(tmpl, start, stop, quote=''):
196 r"""
196 r"""
197 >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
197 >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
198 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
198 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
199 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
199 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
200 ([('string', 'foo'), ('symbol', 'bar')], 9)
200 ([('string', 'foo'), ('symbol', 'bar')], 9)
201 >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
201 >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
202 ([('string', 'foo')], 4)
202 ([('string', 'foo')], 4)
203 >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
203 >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
204 ([('string', 'foo"'), ('string', 'bar')], 9)
204 ([('string', 'foo"'), ('string', 'bar')], 9)
205 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
205 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
206 ([('string', 'foo\\')], 6)
206 ([('string', 'foo\\')], 6)
207 """
207 """
208 parsed = []
208 parsed = []
209 for typ, val, pos in _scantemplate(tmpl, start, stop, quote):
209 for typ, val, pos in _scantemplate(tmpl, start, stop, quote):
210 if typ == 'string':
210 if typ == 'string':
211 parsed.append((typ, val))
211 parsed.append((typ, val))
212 elif typ == 'template':
212 elif typ == 'template':
213 parsed.append(val)
213 parsed.append(val)
214 elif typ == 'end':
214 elif typ == 'end':
215 return parsed, pos
215 return parsed, pos
216 else:
216 else:
217 raise error.ProgrammingError('unexpected type: %s' % typ)
217 raise error.ProgrammingError('unexpected type: %s' % typ)
218 raise error.ProgrammingError('unterminated scanning of template')
218 raise error.ProgrammingError('unterminated scanning of template')
219
219
220 def scantemplate(tmpl, raw=False):
220 def scantemplate(tmpl, raw=False):
221 r"""Scan (type, start, end) positions of outermost elements in template
221 r"""Scan (type, start, end) positions of outermost elements in template
222
222
223 If raw=True, a backslash is not taken as an escape character just like
223 If raw=True, a backslash is not taken as an escape character just like
224 r'' string in Python. Note that this is different from r'' literal in
224 r'' string in Python. Note that this is different from r'' literal in
225 template in that no template fragment can appear in r'', e.g. r'{foo}'
225 template in that no template fragment can appear in r'', e.g. r'{foo}'
226 is a literal '{foo}', but ('{foo}', raw=True) is a template expression
226 is a literal '{foo}', but ('{foo}', raw=True) is a template expression
227 'foo'.
227 'foo'.
228
228
229 >>> list(scantemplate(b'foo{bar}"baz'))
229 >>> list(scantemplate(b'foo{bar}"baz'))
230 [('string', 0, 3), ('template', 3, 8), ('string', 8, 12)]
230 [('string', 0, 3), ('template', 3, 8), ('string', 8, 12)]
231 >>> list(scantemplate(b'outer{"inner"}outer'))
231 >>> list(scantemplate(b'outer{"inner"}outer'))
232 [('string', 0, 5), ('template', 5, 14), ('string', 14, 19)]
232 [('string', 0, 5), ('template', 5, 14), ('string', 14, 19)]
233 >>> list(scantemplate(b'foo\\{escaped}'))
233 >>> list(scantemplate(b'foo\\{escaped}'))
234 [('string', 0, 5), ('string', 5, 13)]
234 [('string', 0, 5), ('string', 5, 13)]
235 >>> list(scantemplate(b'foo\\{escaped}', raw=True))
235 >>> list(scantemplate(b'foo\\{escaped}', raw=True))
236 [('string', 0, 4), ('template', 4, 13)]
236 [('string', 0, 4), ('template', 4, 13)]
237 """
237 """
238 last = None
238 last = None
239 for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw):
239 for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw):
240 if last:
240 if last:
241 yield last + (pos,)
241 yield last + (pos,)
242 if typ == 'end':
242 if typ == 'end':
243 return
243 return
244 else:
244 else:
245 last = (typ, pos)
245 last = (typ, pos)
246 raise error.ProgrammingError('unterminated scanning of template')
246 raise error.ProgrammingError('unterminated scanning of template')
247
247
248 def _scantemplate(tmpl, start, stop, quote='', raw=False):
248 def _scantemplate(tmpl, start, stop, quote='', raw=False):
249 """Parse template string into chunks of strings and template expressions"""
249 """Parse template string into chunks of strings and template expressions"""
250 sepchars = '{' + quote
250 sepchars = '{' + quote
251 unescape = [parser.unescapestr, pycompat.identity][raw]
251 unescape = [parser.unescapestr, pycompat.identity][raw]
252 pos = start
252 pos = start
253 p = parser.parser(elements)
253 p = parser.parser(elements)
254 try:
254 try:
255 while pos < stop:
255 while pos < stop:
256 n = min((tmpl.find(c, pos, stop)
256 n = min((tmpl.find(c, pos, stop)
257 for c in pycompat.bytestr(sepchars)),
257 for c in pycompat.bytestr(sepchars)),
258 key=lambda n: (n < 0, n))
258 key=lambda n: (n < 0, n))
259 if n < 0:
259 if n < 0:
260 yield ('string', unescape(tmpl[pos:stop]), pos)
260 yield ('string', unescape(tmpl[pos:stop]), pos)
261 pos = stop
261 pos = stop
262 break
262 break
263 c = tmpl[n:n + 1]
263 c = tmpl[n:n + 1]
264 bs = 0 # count leading backslashes
264 bs = 0 # count leading backslashes
265 if not raw:
265 if not raw:
266 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
266 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
267 if bs % 2 == 1:
267 if bs % 2 == 1:
268 # escaped (e.g. '\{', '\\\{', but not '\\{')
268 # escaped (e.g. '\{', '\\\{', but not '\\{')
269 yield ('string', unescape(tmpl[pos:n - 1]) + c, pos)
269 yield ('string', unescape(tmpl[pos:n - 1]) + c, pos)
270 pos = n + 1
270 pos = n + 1
271 continue
271 continue
272 if n > pos:
272 if n > pos:
273 yield ('string', unescape(tmpl[pos:n]), pos)
273 yield ('string', unescape(tmpl[pos:n]), pos)
274 if c == quote:
274 if c == quote:
275 yield ('end', None, n + 1)
275 yield ('end', None, n + 1)
276 return
276 return
277
277
278 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
278 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
279 if not tmpl.startswith('}', pos):
279 if not tmpl.startswith('}', pos):
280 raise error.ParseError(_("invalid token"), pos)
280 raise error.ParseError(_("invalid token"), pos)
281 yield ('template', parseres, n)
281 yield ('template', parseres, n)
282 pos += 1
282 pos += 1
283
283
284 if quote:
284 if quote:
285 raise error.ParseError(_("unterminated string"), start)
285 raise error.ParseError(_("unterminated string"), start)
286 except error.ParseError as inst:
286 except error.ParseError as inst:
287 if len(inst.args) > 1: # has location
287 if len(inst.args) > 1: # has location
288 loc = inst.args[1]
288 loc = inst.args[1]
289 # Offset the caret location by the number of newlines before the
289 # Offset the caret location by the number of newlines before the
290 # location of the error, since we will replace one-char newlines
290 # location of the error, since we will replace one-char newlines
291 # with the two-char literal r'\n'.
291 # with the two-char literal r'\n'.
292 offset = tmpl[:loc].count('\n')
292 offset = tmpl[:loc].count('\n')
293 tmpl = tmpl.replace('\n', br'\n')
293 tmpl = tmpl.replace('\n', br'\n')
294 # We want the caret to point to the place in the template that
294 # We want the caret to point to the place in the template that
295 # failed to parse, but in a hint we get a open paren at the
295 # failed to parse, but in a hint we get a open paren at the
296 # start. Therefore, we print "loc + 1" spaces (instead of "loc")
296 # start. Therefore, we print "loc + 1" spaces (instead of "loc")
297 # to line up the caret with the location of the error.
297 # to line up the caret with the location of the error.
298 inst.hint = (tmpl + '\n'
298 inst.hint = (tmpl + '\n'
299 + ' ' * (loc + 1 + offset) + '^ ' + _('here'))
299 + ' ' * (loc + 1 + offset) + '^ ' + _('here'))
300 raise
300 raise
301 yield ('end', None, pos)
301 yield ('end', None, pos)
302
302
303 def _unnesttemplatelist(tree):
303 def _unnesttemplatelist(tree):
304 """Expand list of templates to node tuple
304 """Expand list of templates to node tuple
305
305
306 >>> def f(tree):
306 >>> def f(tree):
307 ... print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
307 ... print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
308 >>> f((b'template', []))
308 >>> f((b'template', []))
309 (string '')
309 (string '')
310 >>> f((b'template', [(b'string', b'foo')]))
310 >>> f((b'template', [(b'string', b'foo')]))
311 (string 'foo')
311 (string 'foo')
312 >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
312 >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
313 (template
313 (template
314 (string 'foo')
314 (string 'foo')
315 (symbol 'rev'))
315 (symbol 'rev'))
316 >>> f((b'template', [(b'symbol', b'rev')])) # template(rev) -> str
316 >>> f((b'template', [(b'symbol', b'rev')])) # template(rev) -> str
317 (template
317 (template
318 (symbol 'rev'))
318 (symbol 'rev'))
319 >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
319 >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
320 (string 'foo')
320 (string 'foo')
321 """
321 """
322 if not isinstance(tree, tuple):
322 if not isinstance(tree, tuple):
323 return tree
323 return tree
324 op = tree[0]
324 op = tree[0]
325 if op != 'template':
325 if op != 'template':
326 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
326 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
327
327
328 assert len(tree) == 2
328 assert len(tree) == 2
329 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
329 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
330 if not xs:
330 if not xs:
331 return ('string', '') # empty template ""
331 return ('string', '') # empty template ""
332 elif len(xs) == 1 and xs[0][0] == 'string':
332 elif len(xs) == 1 and xs[0][0] == 'string':
333 return xs[0] # fast path for string with no template fragment "x"
333 return xs[0] # fast path for string with no template fragment "x"
334 else:
334 else:
335 return (op,) + xs
335 return (op,) + xs
336
336
337 def parse(tmpl):
337 def parse(tmpl):
338 """Parse template string into tree"""
338 """Parse template string into tree"""
339 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
339 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
340 assert pos == len(tmpl), 'unquoted template should be consumed'
340 assert pos == len(tmpl), 'unquoted template should be consumed'
341 return _unnesttemplatelist(('template', parsed))
341 return _unnesttemplatelist(('template', parsed))
342
342
343 def _parseexpr(expr):
343 def _parseexpr(expr):
344 """Parse a template expression into tree
344 """Parse a template expression into tree
345
345
346 >>> _parseexpr(b'"foo"')
346 >>> _parseexpr(b'"foo"')
347 ('string', 'foo')
347 ('string', 'foo')
348 >>> _parseexpr(b'foo(bar)')
348 >>> _parseexpr(b'foo(bar)')
349 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
349 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
350 >>> _parseexpr(b'foo(')
350 >>> _parseexpr(b'foo(')
351 Traceback (most recent call last):
351 Traceback (most recent call last):
352 ...
352 ...
353 ParseError: ('not a prefix: end', 4)
353 ParseError: ('not a prefix: end', 4)
354 >>> _parseexpr(b'"foo" "bar"')
354 >>> _parseexpr(b'"foo" "bar"')
355 Traceback (most recent call last):
355 Traceback (most recent call last):
356 ...
356 ...
357 ParseError: ('invalid token', 7)
357 ParseError: ('invalid token', 7)
358 """
358 """
359 p = parser.parser(elements)
359 p = parser.parser(elements)
360 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
360 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
361 if pos != len(expr):
361 if pos != len(expr):
362 raise error.ParseError(_('invalid token'), pos)
362 raise error.ParseError(_('invalid token'), pos)
363 return _unnesttemplatelist(tree)
363 return _unnesttemplatelist(tree)
364
364
365 def prettyformat(tree):
365 def prettyformat(tree):
366 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
366 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
367
367
368 def compileexp(exp, context, curmethods):
368 def compileexp(exp, context, curmethods):
369 """Compile parsed template tree to (func, data) pair"""
369 """Compile parsed template tree to (func, data) pair"""
370 if not exp:
370 if not exp:
371 raise error.ParseError(_("missing argument"))
371 raise error.ParseError(_("missing argument"))
372 t = exp[0]
372 t = exp[0]
373 if t in curmethods:
373 if t in curmethods:
374 return curmethods[t](exp, context)
374 return curmethods[t](exp, context)
375 raise error.ParseError(_("unknown method '%s'") % t)
375 raise error.ParseError(_("unknown method '%s'") % t)
376
376
377 # template evaluation
377 # template evaluation
378
378
379 def getsymbol(exp):
379 def getsymbol(exp):
380 if exp[0] == 'symbol':
380 if exp[0] == 'symbol':
381 return exp[1]
381 return exp[1]
382 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
382 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
383
383
384 def getlist(x):
384 def getlist(x):
385 if not x:
385 if not x:
386 return []
386 return []
387 if x[0] == 'list':
387 if x[0] == 'list':
388 return getlist(x[1]) + [x[2]]
388 return getlist(x[1]) + [x[2]]
389 return [x]
389 return [x]
390
390
391 def gettemplate(exp, context):
391 def gettemplate(exp, context):
392 """Compile given template tree or load named template from map file;
392 """Compile given template tree or load named template from map file;
393 returns (func, data) pair"""
393 returns (func, data) pair"""
394 if exp[0] in ('template', 'string'):
394 if exp[0] in ('template', 'string'):
395 return compileexp(exp, context, methods)
395 return compileexp(exp, context, methods)
396 if exp[0] == 'symbol':
396 if exp[0] == 'symbol':
397 # unlike runsymbol(), here 'symbol' is always taken as template name
397 # unlike runsymbol(), here 'symbol' is always taken as template name
398 # even if it exists in mapping. this allows us to override mapping
398 # even if it exists in mapping. this allows us to override mapping
399 # by web templates, e.g. 'changelogtag' is redefined in map file.
399 # by web templates, e.g. 'changelogtag' is redefined in map file.
400 return context._load(exp[1])
400 return context._load(exp[1])
401 raise error.ParseError(_("expected template specifier"))
401 raise error.ParseError(_("expected template specifier"))
402
402
403 def _runrecursivesymbol(context, mapping, key):
403 def _runrecursivesymbol(context, mapping, key):
404 raise error.Abort(_("recursive reference '%s' in template") % key)
404 raise error.Abort(_("recursive reference '%s' in template") % key)
405
405
406 def buildtemplate(exp, context):
406 def buildtemplate(exp, context):
407 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
407 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
408 return (templateutil.runtemplate, ctmpl)
408 return (templateutil.runtemplate, ctmpl)
409
409
410 def buildfilter(exp, context):
410 def buildfilter(exp, context):
411 n = getsymbol(exp[2])
411 n = getsymbol(exp[2])
412 if n in context._filters:
412 if n in context._filters:
413 filt = context._filters[n]
413 filt = context._filters[n]
414 arg = compileexp(exp[1], context, methods)
414 arg = compileexp(exp[1], context, methods)
415 return (templateutil.runfilter, (arg, filt))
415 return (templateutil.runfilter, (arg, filt))
416 if n in context._funcs:
416 if n in context._funcs:
417 f = context._funcs[n]
417 f = context._funcs[n]
418 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
418 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
419 return (f, args)
419 return (f, args)
420 raise error.ParseError(_("unknown function '%s'") % n)
420 raise error.ParseError(_("unknown function '%s'") % n)
421
421
422 def buildmap(exp, context):
422 def buildmap(exp, context):
423 darg = compileexp(exp[1], context, methods)
423 darg = compileexp(exp[1], context, methods)
424 targ = gettemplate(exp[2], context)
424 targ = gettemplate(exp[2], context)
425 return (templateutil.runmap, (darg, targ))
425 return (templateutil.runmap, (darg, targ))
426
426
427 def buildmember(exp, context):
427 def buildmember(exp, context):
428 darg = compileexp(exp[1], context, methods)
428 darg = compileexp(exp[1], context, methods)
429 memb = getsymbol(exp[2])
429 memb = getsymbol(exp[2])
430 return (templateutil.runmember, (darg, memb))
430 return (templateutil.runmember, (darg, memb))
431
431
432 def buildnegate(exp, context):
432 def buildnegate(exp, context):
433 arg = compileexp(exp[1], context, exprmethods)
433 arg = compileexp(exp[1], context, exprmethods)
434 return (templateutil.runnegate, arg)
434 return (templateutil.runnegate, arg)
435
435
436 def buildarithmetic(exp, context, func):
436 def buildarithmetic(exp, context, func):
437 left = compileexp(exp[1], context, exprmethods)
437 left = compileexp(exp[1], context, exprmethods)
438 right = compileexp(exp[2], context, exprmethods)
438 right = compileexp(exp[2], context, exprmethods)
439 return (templateutil.runarithmetic, (func, left, right))
439 return (templateutil.runarithmetic, (func, left, right))
440
440
441 def buildfunc(exp, context):
441 def buildfunc(exp, context):
442 n = getsymbol(exp[1])
442 n = getsymbol(exp[1])
443 if n in context._funcs:
443 if n in context._funcs:
444 f = context._funcs[n]
444 f = context._funcs[n]
445 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
445 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
446 return (f, args)
446 return (f, args)
447 if n in context._filters:
447 if n in context._filters:
448 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
448 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
449 if len(args) != 1:
449 if len(args) != 1:
450 raise error.ParseError(_("filter %s expects one argument") % n)
450 raise error.ParseError(_("filter %s expects one argument") % n)
451 f = context._filters[n]
451 f = context._filters[n]
452 return (templateutil.runfilter, (args[0], f))
452 return (templateutil.runfilter, (args[0], f))
453 raise error.ParseError(_("unknown function '%s'") % n)
453 raise error.ParseError(_("unknown function '%s'") % n)
454
454
455 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
455 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
456 """Compile parsed tree of function arguments into list or dict of
456 """Compile parsed tree of function arguments into list or dict of
457 (func, data) pairs
457 (func, data) pairs
458
458
459 >>> context = engine(lambda t: (templateutil.runsymbol, t))
459 >>> context = engine(lambda t: (templateutil.runsymbol, t))
460 >>> def fargs(expr, argspec):
460 >>> def fargs(expr, argspec):
461 ... x = _parseexpr(expr)
461 ... x = _parseexpr(expr)
462 ... n = getsymbol(x[1])
462 ... n = getsymbol(x[1])
463 ... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
463 ... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
464 >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
464 >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
465 ['l', 'k']
465 ['l', 'k']
466 >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
466 >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
467 >>> list(args.keys()), list(args[b'opts'].keys())
467 >>> list(args.keys()), list(args[b'opts'].keys())
468 (['opts'], ['opts', 'k'])
468 (['opts'], ['opts', 'k'])
469 """
469 """
470 def compiledict(xs):
470 def compiledict(xs):
471 return util.sortdict((k, compileexp(x, context, curmethods))
471 return util.sortdict((k, compileexp(x, context, curmethods))
472 for k, x in xs.iteritems())
472 for k, x in xs.iteritems())
473 def compilelist(xs):
473 def compilelist(xs):
474 return [compileexp(x, context, curmethods) for x in xs]
474 return [compileexp(x, context, curmethods) for x in xs]
475
475
476 if not argspec:
476 if not argspec:
477 # filter or function with no argspec: return list of positional args
477 # filter or function with no argspec: return list of positional args
478 return compilelist(getlist(exp))
478 return compilelist(getlist(exp))
479
479
480 # function with argspec: return dict of named args
480 # function with argspec: return dict of named args
481 _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
481 _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
482 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
482 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
483 keyvaluenode='keyvalue', keynode='symbol')
483 keyvaluenode='keyvalue', keynode='symbol')
484 compargs = util.sortdict()
484 compargs = util.sortdict()
485 if varkey:
485 if varkey:
486 compargs[varkey] = compilelist(treeargs.pop(varkey))
486 compargs[varkey] = compilelist(treeargs.pop(varkey))
487 if optkey:
487 if optkey:
488 compargs[optkey] = compiledict(treeargs.pop(optkey))
488 compargs[optkey] = compiledict(treeargs.pop(optkey))
489 compargs.update(compiledict(treeargs))
489 compargs.update(compiledict(treeargs))
490 return compargs
490 return compargs
491
491
492 def buildkeyvaluepair(exp, content):
492 def buildkeyvaluepair(exp, content):
493 raise error.ParseError(_("can't use a key-value pair in this context"))
493 raise error.ParseError(_("can't use a key-value pair in this context"))
494
494
495 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
495 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
496 exprmethods = {
496 exprmethods = {
497 "integer": lambda e, c: (templateutil.runinteger, e[1]),
497 "integer": lambda e, c: (templateutil.runinteger, e[1]),
498 "string": lambda e, c: (templateutil.runstring, e[1]),
498 "string": lambda e, c: (templateutil.runstring, e[1]),
499 "symbol": lambda e, c: (templateutil.runsymbol, e[1]),
499 "symbol": lambda e, c: (templateutil.runsymbol, e[1]),
500 "template": buildtemplate,
500 "template": buildtemplate,
501 "group": lambda e, c: compileexp(e[1], c, exprmethods),
501 "group": lambda e, c: compileexp(e[1], c, exprmethods),
502 ".": buildmember,
502 ".": buildmember,
503 "|": buildfilter,
503 "|": buildfilter,
504 "%": buildmap,
504 "%": buildmap,
505 "func": buildfunc,
505 "func": buildfunc,
506 "keyvalue": buildkeyvaluepair,
506 "keyvalue": buildkeyvaluepair,
507 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
507 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
508 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
508 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
509 "negate": buildnegate,
509 "negate": buildnegate,
510 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
510 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
511 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
511 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
512 }
512 }
513
513
514 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
514 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
515 methods = exprmethods.copy()
515 methods = exprmethods.copy()
516 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
516 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
517
517
518 class _aliasrules(parser.basealiasrules):
518 class _aliasrules(parser.basealiasrules):
519 """Parsing and expansion rule set of template aliases"""
519 """Parsing and expansion rule set of template aliases"""
520 _section = _('template alias')
520 _section = _('template alias')
521 _parse = staticmethod(_parseexpr)
521 _parse = staticmethod(_parseexpr)
522
522
523 @staticmethod
523 @staticmethod
524 def _trygetfunc(tree):
524 def _trygetfunc(tree):
525 """Return (name, args) if tree is func(...) or ...|filter; otherwise
525 """Return (name, args) if tree is func(...) or ...|filter; otherwise
526 None"""
526 None"""
527 if tree[0] == 'func' and tree[1][0] == 'symbol':
527 if tree[0] == 'func' and tree[1][0] == 'symbol':
528 return tree[1][1], getlist(tree[2])
528 return tree[1][1], getlist(tree[2])
529 if tree[0] == '|' and tree[2][0] == 'symbol':
529 if tree[0] == '|' and tree[2][0] == 'symbol':
530 return tree[2][1], [tree[1]]
530 return tree[2][1], [tree[1]]
531
531
532 def expandaliases(tree, aliases):
532 def expandaliases(tree, aliases):
533 """Return new tree of aliases are expanded"""
533 """Return new tree of aliases are expanded"""
534 aliasmap = _aliasrules.buildmap(aliases)
534 aliasmap = _aliasrules.buildmap(aliases)
535 return _aliasrules.expand(aliasmap, tree)
535 return _aliasrules.expand(aliasmap, tree)
536
536
537 # template engine
537 # template engine
538
538
539 def unquotestring(s):
539 def unquotestring(s):
540 '''unwrap quotes if any; otherwise returns unmodified string'''
540 '''unwrap quotes if any; otherwise returns unmodified string'''
541 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
541 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
542 return s
542 return s
543 return s[1:-1]
543 return s[1:-1]
544
544
545 class resourcemapper(object):
545 class resourcemapper(object):
546 """Mapper of internal template resources"""
546 """Mapper of internal template resources"""
547
547
548 __metaclass__ = abc.ABCMeta
548 __metaclass__ = abc.ABCMeta
549
549
550 @abc.abstractmethod
550 @abc.abstractmethod
551 def availablekeys(self, context, mapping):
551 def availablekeys(self, context, mapping):
552 """Return a set of available resource keys based on the given mapping"""
552 """Return a set of available resource keys based on the given mapping"""
553
553
554 @abc.abstractmethod
554 @abc.abstractmethod
555 def knownkeys(self):
555 def knownkeys(self):
556 """Return a set of supported resource keys"""
556 """Return a set of supported resource keys"""
557
557
558 @abc.abstractmethod
558 @abc.abstractmethod
559 def lookup(self, context, mapping, key):
559 def lookup(self, context, mapping, key):
560 """Return a resource for the key if available; otherwise None"""
560 """Return a resource for the key if available; otherwise None"""
561
561
562 @abc.abstractmethod
562 @abc.abstractmethod
563 def populatemap(self, context, origmapping, newmapping):
563 def populatemap(self, context, origmapping, newmapping):
564 """Return a dict of additional mapping items which should be paired
564 """Return a dict of additional mapping items which should be paired
565 with the given new mapping"""
565 with the given new mapping"""
566
566
567 class nullresourcemapper(resourcemapper):
567 class nullresourcemapper(resourcemapper):
568 def availablekeys(self, context, mapping):
568 def availablekeys(self, context, mapping):
569 return set()
569 return set()
570
570
571 def knownkeys(self):
571 def knownkeys(self):
572 return set()
572 return set()
573
573
574 def lookup(self, context, mapping, key):
574 def lookup(self, context, mapping, key):
575 return None
575 return None
576
576
577 def populatemap(self, context, origmapping, newmapping):
577 def populatemap(self, context, origmapping, newmapping):
578 return {}
578 return {}
579
579
580 class engine(object):
580 class engine(object):
581 '''template expansion engine.
581 '''template expansion engine.
582
582
583 template expansion works like this. a map file contains key=value
583 template expansion works like this. a map file contains key=value
584 pairs. if value is quoted, it is treated as string. otherwise, it
584 pairs. if value is quoted, it is treated as string. otherwise, it
585 is treated as name of template file.
585 is treated as name of template file.
586
586
587 templater is asked to expand a key in map. it looks up key, and
587 templater is asked to expand a key in map. it looks up key, and
588 looks for strings like this: {foo}. it expands {foo} by looking up
588 looks for strings like this: {foo}. it expands {foo} by looking up
589 foo in map, and substituting it. expansion is recursive: it stops
589 foo in map, and substituting it. expansion is recursive: it stops
590 when there is no more {foo} to replace.
590 when there is no more {foo} to replace.
591
591
592 expansion also allows formatting and filtering.
592 expansion also allows formatting and filtering.
593
593
594 format uses key to expand each item in list. syntax is
594 format uses key to expand each item in list. syntax is
595 {key%format}.
595 {key%format}.
596
596
597 filter uses function to transform value. syntax is
597 filter uses function to transform value. syntax is
598 {key|filter1|filter2|...}.'''
598 {key|filter1|filter2|...}.'''
599
599
600 def __init__(self, loader, filters=None, defaults=None, resources=None,
600 def __init__(self, loader, filters=None, defaults=None, resources=None,
601 aliases=()):
601 aliases=()):
602 self._loader = loader
602 self._loader = loader
603 if filters is None:
603 if filters is None:
604 filters = {}
604 filters = {}
605 self._filters = filters
605 self._filters = filters
606 self._funcs = templatefuncs.funcs # make this a parameter if needed
606 self._funcs = templatefuncs.funcs # make this a parameter if needed
607 if defaults is None:
607 if defaults is None:
608 defaults = {}
608 defaults = {}
609 if resources is None:
609 if resources is None:
610 resources = nullresourcemapper()
610 resources = nullresourcemapper()
611 self._defaults = defaults
611 self._defaults = defaults
612 self._resources = resources
612 self._resources = resources
613 self._aliasmap = _aliasrules.buildmap(aliases)
613 self._aliasmap = _aliasrules.buildmap(aliases)
614 self._cache = {} # key: (func, data)
614 self._cache = {} # key: (func, data)
615 self._tmplcache = {} # literal template: (func, data)
615 self._tmplcache = {} # literal template: (func, data)
616
616
617 def overlaymap(self, origmapping, newmapping):
617 def overlaymap(self, origmapping, newmapping):
618 """Create combined mapping from the original mapping and partial
618 """Create combined mapping from the original mapping and partial
619 mapping to override the original"""
619 mapping to override the original"""
620 # do not copy symbols which overrides the defaults depending on
620 # do not copy symbols which overrides the defaults depending on
621 # new resources, so the defaults will be re-evaluated (issue5612)
621 # new resources, so the defaults will be re-evaluated (issue5612)
622 knownres = self._resources.knownkeys()
622 knownres = self._resources.knownkeys()
623 newres = self._resources.availablekeys(self, newmapping)
623 newres = self._resources.availablekeys(self, newmapping)
624 mapping = {k: v for k, v in origmapping.iteritems()
624 mapping = {k: v for k, v in origmapping.iteritems()
625 if (k in knownres # not a symbol per self.symbol()
625 if (k in knownres # not a symbol per self.symbol()
626 or newres.isdisjoint(self._defaultrequires(k)))}
626 or newres.isdisjoint(self._defaultrequires(k)))}
627 mapping.update(newmapping)
627 mapping.update(newmapping)
628 mapping.update(
628 mapping.update(
629 self._resources.populatemap(self, origmapping, newmapping))
629 self._resources.populatemap(self, origmapping, newmapping))
630 return mapping
630 return mapping
631
631
632 def _defaultrequires(self, key):
632 def _defaultrequires(self, key):
633 """Resource keys required by the specified default symbol function"""
633 """Resource keys required by the specified default symbol function"""
634 v = self._defaults.get(key)
634 v = self._defaults.get(key)
635 if v is None or not callable(v):
635 if v is None or not callable(v):
636 return ()
636 return ()
637 return getattr(v, '_requires', ())
637 return getattr(v, '_requires', ())
638
638
639 def symbol(self, mapping, key):
639 def symbol(self, mapping, key):
640 """Resolve symbol to value or function; None if nothing found"""
640 """Resolve symbol to value or function; None if nothing found"""
641 v = None
641 v = None
642 if key not in self._resources.knownkeys():
642 if key not in self._resources.knownkeys():
643 v = mapping.get(key)
643 v = mapping.get(key)
644 if v is None:
644 if v is None:
645 v = self._defaults.get(key)
645 v = self._defaults.get(key)
646 return v
646 return v
647
647
648 def availableresourcekeys(self, mapping):
648 def availableresourcekeys(self, mapping):
649 """Return a set of available resource keys based on the given mapping"""
649 """Return a set of available resource keys based on the given mapping"""
650 return self._resources.availablekeys(self, mapping)
650 return self._resources.availablekeys(self, mapping)
651
651
652 def knownresourcekeys(self):
652 def knownresourcekeys(self):
653 """Return a set of supported resource keys"""
653 """Return a set of supported resource keys"""
654 return self._resources.knownkeys()
654 return self._resources.knownkeys()
655
655
656 def resource(self, mapping, key):
656 def resource(self, mapping, key):
657 """Return internal data (e.g. cache) used for keyword/function
657 """Return internal data (e.g. cache) used for keyword/function
658 evaluation"""
658 evaluation"""
659 v = self._resources.lookup(self, mapping, key)
659 v = self._resources.lookup(self, mapping, key)
660 if v is None:
660 if v is None:
661 raise templateutil.ResourceUnavailable(
661 raise templateutil.ResourceUnavailable(
662 _('template resource not available: %s') % key)
662 _('template resource not available: %s') % key)
663 return v
663 return v
664
664
665 def _load(self, t):
665 def _load(self, t):
666 '''load, parse, and cache a template'''
666 '''load, parse, and cache a template'''
667 if t not in self._cache:
667 if t not in self._cache:
668 x = parse(self._loader(t))
668 x = parse(self._loader(t))
669 if self._aliasmap:
669 if self._aliasmap:
670 x = _aliasrules.expand(self._aliasmap, x)
670 x = _aliasrules.expand(self._aliasmap, x)
671 # put poison to cut recursion while compiling 't'
671 # put poison to cut recursion while compiling 't'
672 self._cache[t] = (_runrecursivesymbol, t)
672 self._cache[t] = (_runrecursivesymbol, t)
673 try:
673 try:
674 self._cache[t] = compileexp(x, self, methods)
674 self._cache[t] = compileexp(x, self, methods)
675 except: # re-raises
675 except: # re-raises
676 del self._cache[t]
676 del self._cache[t]
677 raise
677 raise
678 return self._cache[t]
678 return self._cache[t]
679
679
680 def _parse(self, tmpl):
680 def _parse(self, tmpl):
681 """Parse and cache a literal template"""
681 """Parse and cache a literal template"""
682 if tmpl not in self._tmplcache:
682 if tmpl not in self._tmplcache:
683 x = parse(tmpl)
683 x = parse(tmpl)
684 self._tmplcache[tmpl] = compileexp(x, self, methods)
684 self._tmplcache[tmpl] = compileexp(x, self, methods)
685 return self._tmplcache[tmpl]
685 return self._tmplcache[tmpl]
686
686
687 def preload(self, t):
687 def preload(self, t):
688 """Load, parse, and cache the specified template if available"""
688 """Load, parse, and cache the specified template if available"""
689 try:
689 try:
690 self._load(t)
690 self._load(t)
691 return True
691 return True
692 except templateutil.TemplateNotFound:
692 except templateutil.TemplateNotFound:
693 return False
693 return False
694
694
695 def process(self, t, mapping):
695 def process(self, t, mapping):
696 '''Perform expansion. t is name of map element to expand.
696 '''Perform expansion. t is name of map element to expand.
697 mapping contains added elements for use during expansion. Is a
697 mapping contains added elements for use during expansion. Is a
698 generator.'''
698 generator.'''
699 func, data = self._load(t)
699 func, data = self._load(t)
700 return self._expand(func, data, mapping)
700 return self._expand(func, data, mapping)
701
701
702 def expand(self, tmpl, mapping):
702 def expand(self, tmpl, mapping):
703 """Perform expansion over a literal template
703 """Perform expansion over a literal template
704
704
705 No user aliases will be expanded since this is supposed to be called
705 No user aliases will be expanded since this is supposed to be called
706 with an internal template string.
706 with an internal template string.
707 """
707 """
708 func, data = self._parse(tmpl)
708 func, data = self._parse(tmpl)
709 return self._expand(func, data, mapping)
709 return self._expand(func, data, mapping)
710
710
711 def _expand(self, func, data, mapping):
711 def _expand(self, func, data, mapping):
712 # populate additional items only if they don't exist in the given
712 # populate additional items only if they don't exist in the given
713 # mapping. this is slightly different from overlaymap() because the
713 # mapping. this is slightly different from overlaymap() because the
714 # initial 'revcache' may contain pre-computed items.
714 # initial 'revcache' may contain pre-computed items.
715 extramapping = self._resources.populatemap(self, {}, mapping)
715 extramapping = self._resources.populatemap(self, {}, mapping)
716 if extramapping:
716 if extramapping:
717 extramapping.update(mapping)
717 extramapping.update(mapping)
718 mapping = extramapping
718 mapping = extramapping
719 return templateutil.flatten(self, mapping, func(self, mapping, data))
719 return templateutil.flatten(self, mapping, func(self, mapping, data))
720
720
721 engines = {'default': engine}
722
723 def stylelist():
721 def stylelist():
724 paths = templatepaths()
722 paths = templatepaths()
725 if not paths:
723 if not paths:
726 return _('no templates found, try `hg debuginstall` for more info')
724 return _('no templates found, try `hg debuginstall` for more info')
727 dirlist = os.listdir(paths[0])
725 dirlist = os.listdir(paths[0])
728 stylelist = []
726 stylelist = []
729 for file in dirlist:
727 for file in dirlist:
730 split = file.split(".")
728 split = file.split(".")
731 if split[-1] in ('orig', 'rej'):
729 if split[-1] in ('orig', 'rej'):
732 continue
730 continue
733 if split[0] == "map-cmdline":
731 if split[0] == "map-cmdline":
734 stylelist.append(split[1])
732 stylelist.append(split[1])
735 return ", ".join(sorted(stylelist))
733 return ", ".join(sorted(stylelist))
736
734
737 def _readmapfile(mapfile):
735 def _readmapfile(mapfile):
738 """Load template elements from the given map file"""
736 """Load template elements from the given map file"""
739 if not os.path.exists(mapfile):
737 if not os.path.exists(mapfile):
740 raise error.Abort(_("style '%s' not found") % mapfile,
738 raise error.Abort(_("style '%s' not found") % mapfile,
741 hint=_("available styles: %s") % stylelist())
739 hint=_("available styles: %s") % stylelist())
742
740
743 base = os.path.dirname(mapfile)
741 base = os.path.dirname(mapfile)
744 conf = config.config(includepaths=templatepaths())
742 conf = config.config(includepaths=templatepaths())
745 conf.read(mapfile, remap={'': 'templates'})
743 conf.read(mapfile, remap={'': 'templates'})
746
744
747 cache = {}
745 cache = {}
748 tmap = {}
746 tmap = {}
749 aliases = []
747 aliases = []
750
748
751 val = conf.get('templates', '__base__')
749 val = conf.get('templates', '__base__')
752 if val and val[0] not in "'\"":
750 if val and val[0] not in "'\"":
753 # treat as a pointer to a base class for this style
751 # treat as a pointer to a base class for this style
754 path = util.normpath(os.path.join(base, val))
752 path = util.normpath(os.path.join(base, val))
755
753
756 # fallback check in template paths
754 # fallback check in template paths
757 if not os.path.exists(path):
755 if not os.path.exists(path):
758 for p in templatepaths():
756 for p in templatepaths():
759 p2 = util.normpath(os.path.join(p, val))
757 p2 = util.normpath(os.path.join(p, val))
760 if os.path.isfile(p2):
758 if os.path.isfile(p2):
761 path = p2
759 path = p2
762 break
760 break
763 p3 = util.normpath(os.path.join(p2, "map"))
761 p3 = util.normpath(os.path.join(p2, "map"))
764 if os.path.isfile(p3):
762 if os.path.isfile(p3):
765 path = p3
763 path = p3
766 break
764 break
767
765
768 cache, tmap, aliases = _readmapfile(path)
766 cache, tmap, aliases = _readmapfile(path)
769
767
770 for key, val in conf['templates'].items():
768 for key, val in conf['templates'].items():
771 if not val:
769 if not val:
772 raise error.ParseError(_('missing value'),
770 raise error.ParseError(_('missing value'),
773 conf.source('templates', key))
771 conf.source('templates', key))
774 if val[0] in "'\"":
772 if val[0] in "'\"":
775 if val[0] != val[-1]:
773 if val[0] != val[-1]:
776 raise error.ParseError(_('unmatched quotes'),
774 raise error.ParseError(_('unmatched quotes'),
777 conf.source('templates', key))
775 conf.source('templates', key))
778 cache[key] = unquotestring(val)
776 cache[key] = unquotestring(val)
779 elif key != '__base__':
777 elif key != '__base__':
780 val = 'default', val
778 tmap[key] = os.path.join(base, val)
781 if ':' in val[1]:
782 val = val[1].split(':', 1)
783 tmap[key] = val[0], os.path.join(base, val[1])
784 aliases.extend(conf['templatealias'].items())
779 aliases.extend(conf['templatealias'].items())
785 return cache, tmap, aliases
780 return cache, tmap, aliases
786
781
787 class templater(object):
782 class templater(object):
788
783
789 def __init__(self, filters=None, defaults=None, resources=None,
784 def __init__(self, filters=None, defaults=None, resources=None,
790 cache=None, aliases=(), minchunk=1024, maxchunk=65536):
785 cache=None, aliases=(), minchunk=1024, maxchunk=65536):
791 """Create template engine optionally with preloaded template fragments
786 """Create template engine optionally with preloaded template fragments
792
787
793 - ``filters``: a dict of functions to transform a value into another.
788 - ``filters``: a dict of functions to transform a value into another.
794 - ``defaults``: a dict of symbol values/functions; may be overridden
789 - ``defaults``: a dict of symbol values/functions; may be overridden
795 by a ``mapping`` dict.
790 by a ``mapping`` dict.
796 - ``resources``: a resourcemapper object to look up internal data
791 - ``resources``: a resourcemapper object to look up internal data
797 (e.g. cache), inaccessible from user template.
792 (e.g. cache), inaccessible from user template.
798 - ``cache``: a dict of preloaded template fragments.
793 - ``cache``: a dict of preloaded template fragments.
799 - ``aliases``: a list of alias (name, replacement) pairs.
794 - ``aliases``: a list of alias (name, replacement) pairs.
800
795
801 self.cache may be updated later to register additional template
796 self.cache may be updated later to register additional template
802 fragments.
797 fragments.
803 """
798 """
804 if filters is None:
799 if filters is None:
805 filters = {}
800 filters = {}
806 if defaults is None:
801 if defaults is None:
807 defaults = {}
802 defaults = {}
808 if cache is None:
803 if cache is None:
809 cache = {}
804 cache = {}
810 self.cache = cache.copy()
805 self.cache = cache.copy()
811 self._map = {}
806 self._map = {}
812 self._filters = templatefilters.filters.copy()
807 self._filters = templatefilters.filters.copy()
813 self._filters.update(filters)
808 self._filters.update(filters)
814 self.defaults = defaults
809 self.defaults = defaults
815 self._resources = resources
810 self._resources = resources
816 self._aliases = aliases
811 self._aliases = aliases
817 self._minchunk, self._maxchunk = minchunk, maxchunk
812 self._minchunk, self._maxchunk = minchunk, maxchunk
818 self._ecache = {}
819
813
820 @classmethod
814 @classmethod
821 def frommapfile(cls, mapfile, filters=None, defaults=None, resources=None,
815 def frommapfile(cls, mapfile, filters=None, defaults=None, resources=None,
822 cache=None, minchunk=1024, maxchunk=65536):
816 cache=None, minchunk=1024, maxchunk=65536):
823 """Create templater from the specified map file"""
817 """Create templater from the specified map file"""
824 t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk)
818 t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk)
825 cache, tmap, aliases = _readmapfile(mapfile)
819 cache, tmap, aliases = _readmapfile(mapfile)
826 t.cache.update(cache)
820 t.cache.update(cache)
827 t._map = tmap
821 t._map = tmap
828 t._aliases = aliases
822 t._aliases = aliases
829 return t
823 return t
830
824
831 def __contains__(self, key):
825 def __contains__(self, key):
832 return key in self.cache or key in self._map
826 return key in self.cache or key in self._map
833
827
834 def load(self, t):
828 def load(self, t):
835 '''Get the template for the given template name. Use a local cache.'''
829 '''Get the template for the given template name. Use a local cache.'''
836 if t not in self.cache:
830 if t not in self.cache:
837 try:
831 try:
838 self.cache[t] = util.readfile(self._map[t][1])
832 self.cache[t] = util.readfile(self._map[t])
839 except KeyError as inst:
833 except KeyError as inst:
840 raise templateutil.TemplateNotFound(
834 raise templateutil.TemplateNotFound(
841 _('"%s" not in template map') % inst.args[0])
835 _('"%s" not in template map') % inst.args[0])
842 except IOError as inst:
836 except IOError as inst:
843 reason = (_('template file %s: %s')
837 reason = (_('template file %s: %s')
844 % (self._map[t][1],
838 % (self._map[t],
845 stringutil.forcebytestr(inst.args[1])))
839 stringutil.forcebytestr(inst.args[1])))
846 raise IOError(inst.args[0], encoding.strfromlocal(reason))
840 raise IOError(inst.args[0], encoding.strfromlocal(reason))
847 return self.cache[t]
841 return self.cache[t]
848
842
849 def renderdefault(self, mapping):
843 def renderdefault(self, mapping):
850 """Render the default unnamed template and return result as string"""
844 """Render the default unnamed template and return result as string"""
851 return self.render('', mapping)
845 return self.render('', mapping)
852
846
853 def render(self, t, mapping):
847 def render(self, t, mapping):
854 """Render the specified named template and return result as string"""
848 """Render the specified named template and return result as string"""
855 return b''.join(self.generate(t, mapping))
849 return b''.join(self.generate(t, mapping))
856
850
857 def generate(self, t, mapping):
851 def generate(self, t, mapping):
858 """Return a generator that renders the specified named template and
852 """Return a generator that renders the specified named template and
859 yields chunks"""
853 yields chunks"""
860 ttype = t in self._map and self._map[t][0] or 'default'
854 proc = engine(self.load, self._filters, self.defaults, self._resources,
861 if ttype not in self._ecache:
855 self._aliases)
862 try:
863 ecls = engines[ttype]
864 except KeyError:
865 raise error.Abort(_('invalid template engine: %s') % ttype)
866 self._ecache[ttype] = ecls(self.load, self._filters, self.defaults,
867 self._resources, self._aliases)
868 proc = self._ecache[ttype]
869
870 stream = proc.process(t, mapping)
856 stream = proc.process(t, mapping)
871 if self._minchunk:
857 if self._minchunk:
872 stream = util.increasingchunks(stream, min=self._minchunk,
858 stream = util.increasingchunks(stream, min=self._minchunk,
873 max=self._maxchunk)
859 max=self._maxchunk)
874 return stream
860 return stream
875
861
876 def templatepaths():
862 def templatepaths():
877 '''return locations used for template files.'''
863 '''return locations used for template files.'''
878 pathsrel = ['templates']
864 pathsrel = ['templates']
879 paths = [os.path.normpath(os.path.join(util.datapath, f))
865 paths = [os.path.normpath(os.path.join(util.datapath, f))
880 for f in pathsrel]
866 for f in pathsrel]
881 return [p for p in paths if os.path.isdir(p)]
867 return [p for p in paths if os.path.isdir(p)]
882
868
883 def templatepath(name):
869 def templatepath(name):
884 '''return location of template file. returns None if not found.'''
870 '''return location of template file. returns None if not found.'''
885 for p in templatepaths():
871 for p in templatepaths():
886 f = os.path.join(p, name)
872 f = os.path.join(p, name)
887 if os.path.exists(f):
873 if os.path.exists(f):
888 return f
874 return f
889 return None
875 return None
890
876
891 def stylemap(styles, paths=None):
877 def stylemap(styles, paths=None):
892 """Return path to mapfile for a given style.
878 """Return path to mapfile for a given style.
893
879
894 Searches mapfile in the following locations:
880 Searches mapfile in the following locations:
895 1. templatepath/style/map
881 1. templatepath/style/map
896 2. templatepath/map-style
882 2. templatepath/map-style
897 3. templatepath/map
883 3. templatepath/map
898 """
884 """
899
885
900 if paths is None:
886 if paths is None:
901 paths = templatepaths()
887 paths = templatepaths()
902 elif isinstance(paths, bytes):
888 elif isinstance(paths, bytes):
903 paths = [paths]
889 paths = [paths]
904
890
905 if isinstance(styles, bytes):
891 if isinstance(styles, bytes):
906 styles = [styles]
892 styles = [styles]
907
893
908 for style in styles:
894 for style in styles:
909 # only plain name is allowed to honor template paths
895 # only plain name is allowed to honor template paths
910 if (not style
896 if (not style
911 or style in (pycompat.oscurdir, pycompat.ospardir)
897 or style in (pycompat.oscurdir, pycompat.ospardir)
912 or pycompat.ossep in style
898 or pycompat.ossep in style
913 or pycompat.osaltsep and pycompat.osaltsep in style):
899 or pycompat.osaltsep and pycompat.osaltsep in style):
914 continue
900 continue
915 locations = [os.path.join(style, 'map'), 'map-' + style]
901 locations = [os.path.join(style, 'map'), 'map-' + style]
916 locations.append('map')
902 locations.append('map')
917
903
918 for path in paths:
904 for path in paths:
919 for location in locations:
905 for location in locations:
920 mapfile = os.path.join(path, location)
906 mapfile = os.path.join(path, location)
921 if os.path.isfile(mapfile):
907 if os.path.isfile(mapfile):
922 return style, mapfile
908 return style, mapfile
923
909
924 raise RuntimeError("No hgweb templates found in %r" % paths)
910 raise RuntimeError("No hgweb templates found in %r" % paths)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now