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