Mercurial > hg
comparison mercurial/templater.py @ 28696:efa192203623
templater: use templatefunc to mark a function as template function
Using decorator can localize changes for adding (or removing) a
template function in source code.
This patch also removes leading ":FUNC(ARG...):" part in help document
of each function, because using templatefunc makes it useless.
This patch uses not 'func' but 'templatefunc' as a decorator name,
because the former is too generic one, even though the latter is a
little redundant in 'templater.py'.
author | FUJIWARA Katsunori <foozy@lares.dti.ne.jp> |
---|---|
date | Wed, 30 Mar 2016 02:10:44 +0900 |
parents | cc103bd0dbf9 |
children | 6b86ce3e3576 |
comparison
equal
deleted
inserted
replaced
28695:cc103bd0dbf9 | 28696:efa192203623 |
---|---|
15 from . import ( | 15 from . import ( |
16 config, | 16 config, |
17 error, | 17 error, |
18 minirst, | 18 minirst, |
19 parser, | 19 parser, |
20 registrar, | |
20 revset as revsetmod, | 21 revset as revsetmod, |
21 templatefilters, | 22 templatefilters, |
22 templatekw, | 23 templatekw, |
23 util, | 24 util, |
24 ) | 25 ) |
391 raise error.ParseError(_("filter %s expects one argument") % n) | 392 raise error.ParseError(_("filter %s expects one argument") % n) |
392 f = context._filters[n] | 393 f = context._filters[n] |
393 return (runfilter, (args[0], f)) | 394 return (runfilter, (args[0], f)) |
394 raise error.ParseError(_("unknown function '%s'") % n) | 395 raise error.ParseError(_("unknown function '%s'") % n) |
395 | 396 |
397 # dict of template built-in functions | |
398 funcs = {} | |
399 | |
400 templatefunc = registrar.templatefunc(funcs) | |
401 | |
402 @templatefunc('date(date[, fmt])') | |
396 def date(context, mapping, args): | 403 def date(context, mapping, args): |
397 """:date(date[, fmt]): Format a date. See :hg:`help dates` for formatting | 404 """Format a date. See :hg:`help dates` for formatting |
398 strings. The default is a Unix date format, including the timezone: | 405 strings. The default is a Unix date format, including the timezone: |
399 "Mon Sep 04 15:13:13 2006 0700".""" | 406 "Mon Sep 04 15:13:13 2006 0700".""" |
400 if not (1 <= len(args) <= 2): | 407 if not (1 <= len(args) <= 2): |
401 # i18n: "date" is a keyword | 408 # i18n: "date" is a keyword |
402 raise error.ParseError(_("date expects one or two arguments")) | 409 raise error.ParseError(_("date expects one or two arguments")) |
412 return util.datestr(date, fmt) | 419 return util.datestr(date, fmt) |
413 except (TypeError, ValueError): | 420 except (TypeError, ValueError): |
414 # i18n: "date" is a keyword | 421 # i18n: "date" is a keyword |
415 raise error.ParseError(_("date expects a date information")) | 422 raise error.ParseError(_("date expects a date information")) |
416 | 423 |
424 @templatefunc('diff([includepattern [, excludepattern]])') | |
417 def diff(context, mapping, args): | 425 def diff(context, mapping, args): |
418 """:diff([includepattern [, excludepattern]]): Show a diff, optionally | 426 """Show a diff, optionally |
419 specifying files to include or exclude.""" | 427 specifying files to include or exclude.""" |
420 if len(args) > 2: | 428 if len(args) > 2: |
421 # i18n: "diff" is a keyword | 429 # i18n: "diff" is a keyword |
422 raise error.ParseError(_("diff expects zero, one, or two arguments")) | 430 raise error.ParseError(_("diff expects zero, one, or two arguments")) |
423 | 431 |
431 ctx = mapping['ctx'] | 439 ctx = mapping['ctx'] |
432 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1))) | 440 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1))) |
433 | 441 |
434 return ''.join(chunks) | 442 return ''.join(chunks) |
435 | 443 |
444 @templatefunc('fill(text[, width[, initialident[, hangindent]]])') | |
436 def fill(context, mapping, args): | 445 def fill(context, mapping, args): |
437 """:fill(text[, width[, initialident[, hangindent]]]): Fill many | 446 """Fill many |
438 paragraphs with optional indentation. See the "fill" filter.""" | 447 paragraphs with optional indentation. See the "fill" filter.""" |
439 if not (1 <= len(args) <= 4): | 448 if not (1 <= len(args) <= 4): |
440 # i18n: "fill" is a keyword | 449 # i18n: "fill" is a keyword |
441 raise error.ParseError(_("fill expects one to four arguments")) | 450 raise error.ParseError(_("fill expects one to four arguments")) |
442 | 451 |
454 except IndexError: | 463 except IndexError: |
455 pass | 464 pass |
456 | 465 |
457 return templatefilters.fill(text, width, initindent, hangindent) | 466 return templatefilters.fill(text, width, initindent, hangindent) |
458 | 467 |
468 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])') | |
459 def pad(context, mapping, args): | 469 def pad(context, mapping, args): |
460 """:pad(text, width[, fillchar=' '[, right=False]]): Pad text with a | 470 """Pad text with a |
461 fill character.""" | 471 fill character.""" |
462 if not (2 <= len(args) <= 4): | 472 if not (2 <= len(args) <= 4): |
463 # i18n: "pad" is a keyword | 473 # i18n: "pad" is a keyword |
464 raise error.ParseError(_("pad() expects two to four arguments")) | 474 raise error.ParseError(_("pad() expects two to four arguments")) |
465 | 475 |
479 if right: | 489 if right: |
480 return text.rjust(width, fillchar) | 490 return text.rjust(width, fillchar) |
481 else: | 491 else: |
482 return text.ljust(width, fillchar) | 492 return text.ljust(width, fillchar) |
483 | 493 |
494 @templatefunc('indent(text, indentchars[, firstline])') | |
484 def indent(context, mapping, args): | 495 def indent(context, mapping, args): |
485 """:indent(text, indentchars[, firstline]): Indents all non-empty lines | 496 """Indents all non-empty lines |
486 with the characters given in the indentchars string. An optional | 497 with the characters given in the indentchars string. An optional |
487 third parameter will override the indent for the first line only | 498 third parameter will override the indent for the first line only |
488 if present.""" | 499 if present.""" |
489 if not (2 <= len(args) <= 3): | 500 if not (2 <= len(args) <= 3): |
490 # i18n: "indent" is a keyword | 501 # i18n: "indent" is a keyword |
499 firstline = indent | 510 firstline = indent |
500 | 511 |
501 # the indent function doesn't indent the first line, so we do it here | 512 # the indent function doesn't indent the first line, so we do it here |
502 return templatefilters.indent(firstline + text, indent) | 513 return templatefilters.indent(firstline + text, indent) |
503 | 514 |
515 @templatefunc('get(dict, key)') | |
504 def get(context, mapping, args): | 516 def get(context, mapping, args): |
505 """:get(dict, key): Get an attribute/key from an object. Some keywords | 517 """Get an attribute/key from an object. Some keywords |
506 are complex types. This function allows you to obtain the value of an | 518 are complex types. This function allows you to obtain the value of an |
507 attribute on these types.""" | 519 attribute on these types.""" |
508 if len(args) != 2: | 520 if len(args) != 2: |
509 # i18n: "get" is a keyword | 521 # i18n: "get" is a keyword |
510 raise error.ParseError(_("get() expects two arguments")) | 522 raise error.ParseError(_("get() expects two arguments")) |
515 raise error.ParseError(_("get() expects a dict as first argument")) | 527 raise error.ParseError(_("get() expects a dict as first argument")) |
516 | 528 |
517 key = evalfuncarg(context, mapping, args[1]) | 529 key = evalfuncarg(context, mapping, args[1]) |
518 return dictarg.get(key) | 530 return dictarg.get(key) |
519 | 531 |
532 @templatefunc('if(expr, then[, else])') | |
520 def if_(context, mapping, args): | 533 def if_(context, mapping, args): |
521 """:if(expr, then[, else]): Conditionally execute based on the result of | 534 """Conditionally execute based on the result of |
522 an expression.""" | 535 an expression.""" |
523 if not (2 <= len(args) <= 3): | 536 if not (2 <= len(args) <= 3): |
524 # i18n: "if" is a keyword | 537 # i18n: "if" is a keyword |
525 raise error.ParseError(_("if expects two or three arguments")) | 538 raise error.ParseError(_("if expects two or three arguments")) |
526 | 539 |
528 if test: | 541 if test: |
529 yield args[1][0](context, mapping, args[1][1]) | 542 yield args[1][0](context, mapping, args[1][1]) |
530 elif len(args) == 3: | 543 elif len(args) == 3: |
531 yield args[2][0](context, mapping, args[2][1]) | 544 yield args[2][0](context, mapping, args[2][1]) |
532 | 545 |
546 @templatefunc('ifcontains(search, thing, then[, else])') | |
533 def ifcontains(context, mapping, args): | 547 def ifcontains(context, mapping, args): |
534 """:ifcontains(search, thing, then[, else]): Conditionally execute based | 548 """Conditionally execute based |
535 on whether the item "search" is in "thing".""" | 549 on whether the item "search" is in "thing".""" |
536 if not (3 <= len(args) <= 4): | 550 if not (3 <= len(args) <= 4): |
537 # i18n: "ifcontains" is a keyword | 551 # i18n: "ifcontains" is a keyword |
538 raise error.ParseError(_("ifcontains expects three or four arguments")) | 552 raise error.ParseError(_("ifcontains expects three or four arguments")) |
539 | 553 |
543 if item in items: | 557 if item in items: |
544 yield args[2][0](context, mapping, args[2][1]) | 558 yield args[2][0](context, mapping, args[2][1]) |
545 elif len(args) == 4: | 559 elif len(args) == 4: |
546 yield args[3][0](context, mapping, args[3][1]) | 560 yield args[3][0](context, mapping, args[3][1]) |
547 | 561 |
562 @templatefunc('ifeq(expr1, expr2, then[, else])') | |
548 def ifeq(context, mapping, args): | 563 def ifeq(context, mapping, args): |
549 """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on | 564 """Conditionally execute based on |
550 whether 2 items are equivalent.""" | 565 whether 2 items are equivalent.""" |
551 if not (3 <= len(args) <= 4): | 566 if not (3 <= len(args) <= 4): |
552 # i18n: "ifeq" is a keyword | 567 # i18n: "ifeq" is a keyword |
553 raise error.ParseError(_("ifeq expects three or four arguments")) | 568 raise error.ParseError(_("ifeq expects three or four arguments")) |
554 | 569 |
557 if test == match: | 572 if test == match: |
558 yield args[2][0](context, mapping, args[2][1]) | 573 yield args[2][0](context, mapping, args[2][1]) |
559 elif len(args) == 4: | 574 elif len(args) == 4: |
560 yield args[3][0](context, mapping, args[3][1]) | 575 yield args[3][0](context, mapping, args[3][1]) |
561 | 576 |
577 @templatefunc('join(list, sep)') | |
562 def join(context, mapping, args): | 578 def join(context, mapping, args): |
563 """:join(list, sep): Join items in a list with a delimiter.""" | 579 """Join items in a list with a delimiter.""" |
564 if not (1 <= len(args) <= 2): | 580 if not (1 <= len(args) <= 2): |
565 # i18n: "join" is a keyword | 581 # i18n: "join" is a keyword |
566 raise error.ParseError(_("join expects one or two arguments")) | 582 raise error.ParseError(_("join expects one or two arguments")) |
567 | 583 |
568 joinset = args[0][0](context, mapping, args[0][1]) | 584 joinset = args[0][0](context, mapping, args[0][1]) |
580 first = False | 596 first = False |
581 else: | 597 else: |
582 yield joiner | 598 yield joiner |
583 yield x | 599 yield x |
584 | 600 |
601 @templatefunc('label(label, expr)') | |
585 def label(context, mapping, args): | 602 def label(context, mapping, args): |
586 """:label(label, expr): Apply a label to generated content. Content with | 603 """Apply a label to generated content. Content with |
587 a label applied can result in additional post-processing, such as | 604 a label applied can result in additional post-processing, such as |
588 automatic colorization.""" | 605 automatic colorization.""" |
589 if len(args) != 2: | 606 if len(args) != 2: |
590 # i18n: "label" is a keyword | 607 # i18n: "label" is a keyword |
591 raise error.ParseError(_("label expects two arguments")) | 608 raise error.ParseError(_("label expects two arguments")) |
596 # etc. don't need to be quoted | 613 # etc. don't need to be quoted |
597 label = evalstringliteral(context, mapping, args[0]) | 614 label = evalstringliteral(context, mapping, args[0]) |
598 | 615 |
599 return ui.label(thing, label) | 616 return ui.label(thing, label) |
600 | 617 |
618 @templatefunc('latesttag([pattern])') | |
601 def latesttag(context, mapping, args): | 619 def latesttag(context, mapping, args): |
602 """:latesttag([pattern]): The global tags matching the given pattern on the | 620 """The global tags matching the given pattern on the |
603 most recent globally tagged ancestor of this changeset.""" | 621 most recent globally tagged ancestor of this changeset.""" |
604 if len(args) > 1: | 622 if len(args) > 1: |
605 # i18n: "latesttag" is a keyword | 623 # i18n: "latesttag" is a keyword |
606 raise error.ParseError(_("latesttag expects at most one argument")) | 624 raise error.ParseError(_("latesttag expects at most one argument")) |
607 | 625 |
609 if len(args) == 1: | 627 if len(args) == 1: |
610 pattern = evalstring(context, mapping, args[0]) | 628 pattern = evalstring(context, mapping, args[0]) |
611 | 629 |
612 return templatekw.showlatesttags(pattern, **mapping) | 630 return templatekw.showlatesttags(pattern, **mapping) |
613 | 631 |
632 @templatefunc('localdate(date[, tz])') | |
614 def localdate(context, mapping, args): | 633 def localdate(context, mapping, args): |
615 """:localdate(date[, tz]): Converts a date to the specified timezone. | 634 """Converts a date to the specified timezone. |
616 The default is local date.""" | 635 The default is local date.""" |
617 if not (1 <= len(args) <= 2): | 636 if not (1 <= len(args) <= 2): |
618 # i18n: "localdate" is a keyword | 637 # i18n: "localdate" is a keyword |
619 raise error.ParseError(_("localdate expects one or two arguments")) | 638 raise error.ParseError(_("localdate expects one or two arguments")) |
620 | 639 |
637 raise error.ParseError(_("localdate expects a timezone")) | 656 raise error.ParseError(_("localdate expects a timezone")) |
638 else: | 657 else: |
639 tzoffset = util.makedate()[1] | 658 tzoffset = util.makedate()[1] |
640 return (date[0], tzoffset) | 659 return (date[0], tzoffset) |
641 | 660 |
661 @templatefunc('revset(query[, formatargs...])') | |
642 def revset(context, mapping, args): | 662 def revset(context, mapping, args): |
643 """:revset(query[, formatargs...]): Execute a revision set query. See | 663 """Execute a revision set query. See |
644 :hg:`help revset`.""" | 664 :hg:`help revset`.""" |
645 if not len(args) > 0: | 665 if not len(args) > 0: |
646 # i18n: "revset" is a keyword | 666 # i18n: "revset" is a keyword |
647 raise error.ParseError(_("revset expects one or more arguments")) | 667 raise error.ParseError(_("revset expects one or more arguments")) |
648 | 668 |
667 revs = list(revs) | 687 revs = list(revs) |
668 revsetcache[raw] = revs | 688 revsetcache[raw] = revs |
669 | 689 |
670 return templatekw.showrevslist("revision", revs, **mapping) | 690 return templatekw.showrevslist("revision", revs, **mapping) |
671 | 691 |
692 @templatefunc('rstdoc(text, style)') | |
672 def rstdoc(context, mapping, args): | 693 def rstdoc(context, mapping, args): |
673 """:rstdoc(text, style): Format ReStructuredText.""" | 694 """Format ReStructuredText.""" |
674 if len(args) != 2: | 695 if len(args) != 2: |
675 # i18n: "rstdoc" is a keyword | 696 # i18n: "rstdoc" is a keyword |
676 raise error.ParseError(_("rstdoc expects two arguments")) | 697 raise error.ParseError(_("rstdoc expects two arguments")) |
677 | 698 |
678 text = evalstring(context, mapping, args[0]) | 699 text = evalstring(context, mapping, args[0]) |
679 style = evalstring(context, mapping, args[1]) | 700 style = evalstring(context, mapping, args[1]) |
680 | 701 |
681 return minirst.format(text, style=style, keep=['verbose']) | 702 return minirst.format(text, style=style, keep=['verbose']) |
682 | 703 |
704 @templatefunc('shortest(node, minlength=4)') | |
683 def shortest(context, mapping, args): | 705 def shortest(context, mapping, args): |
684 """:shortest(node, minlength=4): Obtain the shortest representation of | 706 """Obtain the shortest representation of |
685 a node.""" | 707 a node.""" |
686 if not (1 <= len(args) <= 2): | 708 if not (1 <= len(args) <= 2): |
687 # i18n: "shortest" is a keyword | 709 # i18n: "shortest" is a keyword |
688 raise error.ParseError(_("shortest() expects one or two arguments")) | 710 raise error.ParseError(_("shortest() expects one or two arguments")) |
689 | 711 |
732 else: | 754 else: |
733 length += 1 | 755 length += 1 |
734 if len(shortest) <= length: | 756 if len(shortest) <= length: |
735 return shortest | 757 return shortest |
736 | 758 |
759 @templatefunc('strip(text[, chars])') | |
737 def strip(context, mapping, args): | 760 def strip(context, mapping, args): |
738 """:strip(text[, chars]): Strip characters from a string. By default, | 761 """Strip characters from a string. By default, |
739 strips all leading and trailing whitespace.""" | 762 strips all leading and trailing whitespace.""" |
740 if not (1 <= len(args) <= 2): | 763 if not (1 <= len(args) <= 2): |
741 # i18n: "strip" is a keyword | 764 # i18n: "strip" is a keyword |
742 raise error.ParseError(_("strip expects one or two arguments")) | 765 raise error.ParseError(_("strip expects one or two arguments")) |
743 | 766 |
745 if len(args) == 2: | 768 if len(args) == 2: |
746 chars = evalstring(context, mapping, args[1]) | 769 chars = evalstring(context, mapping, args[1]) |
747 return text.strip(chars) | 770 return text.strip(chars) |
748 return text.strip() | 771 return text.strip() |
749 | 772 |
773 @templatefunc('sub(pattern, replacement, expression)') | |
750 def sub(context, mapping, args): | 774 def sub(context, mapping, args): |
751 """:sub(pattern, replacement, expression): Perform text substitution | 775 """Perform text substitution |
752 using regular expressions.""" | 776 using regular expressions.""" |
753 if len(args) != 3: | 777 if len(args) != 3: |
754 # i18n: "sub" is a keyword | 778 # i18n: "sub" is a keyword |
755 raise error.ParseError(_("sub expects three arguments")) | 779 raise error.ParseError(_("sub expects three arguments")) |
756 | 780 |
766 yield patre.sub(rpl, src) | 790 yield patre.sub(rpl, src) |
767 except re.error: | 791 except re.error: |
768 # i18n: "sub" is a keyword | 792 # i18n: "sub" is a keyword |
769 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl) | 793 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl) |
770 | 794 |
795 @templatefunc('startswith(pattern, text)') | |
771 def startswith(context, mapping, args): | 796 def startswith(context, mapping, args): |
772 """:startswith(pattern, text): Returns the value from the "text" argument | 797 """Returns the value from the "text" argument |
773 if it begins with the content from the "pattern" argument.""" | 798 if it begins with the content from the "pattern" argument.""" |
774 if len(args) != 2: | 799 if len(args) != 2: |
775 # i18n: "startswith" is a keyword | 800 # i18n: "startswith" is a keyword |
776 raise error.ParseError(_("startswith expects two arguments")) | 801 raise error.ParseError(_("startswith expects two arguments")) |
777 | 802 |
779 text = evalstring(context, mapping, args[1]) | 804 text = evalstring(context, mapping, args[1]) |
780 if text.startswith(patn): | 805 if text.startswith(patn): |
781 return text | 806 return text |
782 return '' | 807 return '' |
783 | 808 |
784 | 809 @templatefunc('word(number, text[, separator])') |
785 def word(context, mapping, args): | 810 def word(context, mapping, args): |
786 """:word(number, text[, separator]): Return the nth word from a string.""" | 811 """Return the nth word from a string.""" |
787 if not (2 <= len(args) <= 3): | 812 if not (2 <= len(args) <= 3): |
788 # i18n: "word" is a keyword | 813 # i18n: "word" is a keyword |
789 raise error.ParseError(_("word expects two or three arguments, got %d") | 814 raise error.ParseError(_("word expects two or three arguments, got %d") |
790 % len(args)) | 815 % len(args)) |
791 | 816 |
818 } | 843 } |
819 | 844 |
820 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) | 845 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) |
821 methods = exprmethods.copy() | 846 methods = exprmethods.copy() |
822 methods["integer"] = exprmethods["symbol"] # '{1}' as variable | 847 methods["integer"] = exprmethods["symbol"] # '{1}' as variable |
823 | |
824 funcs = { | |
825 "date": date, | |
826 "diff": diff, | |
827 "fill": fill, | |
828 "get": get, | |
829 "if": if_, | |
830 "ifcontains": ifcontains, | |
831 "ifeq": ifeq, | |
832 "indent": indent, | |
833 "join": join, | |
834 "label": label, | |
835 "latesttag": latesttag, | |
836 "localdate": localdate, | |
837 "pad": pad, | |
838 "revset": revset, | |
839 "rstdoc": rstdoc, | |
840 "shortest": shortest, | |
841 "startswith": startswith, | |
842 "strip": strip, | |
843 "sub": sub, | |
844 "word": word, | |
845 } | |
846 | 848 |
847 # template engine | 849 # template engine |
848 | 850 |
849 stringify = templatefilters.stringify | 851 stringify = templatefilters.stringify |
850 | 852 |