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