453 return compargs |
436 return compargs |
454 |
437 |
455 def buildkeyvaluepair(exp, content): |
438 def buildkeyvaluepair(exp, content): |
456 raise error.ParseError(_("can't use a key-value pair in this context")) |
439 raise error.ParseError(_("can't use a key-value pair in this context")) |
457 |
440 |
458 # dict of template built-in functions |
|
459 funcs = {} |
|
460 |
|
461 templatefunc = registrar.templatefunc(funcs) |
|
462 |
|
463 @templatefunc('date(date[, fmt])') |
|
464 def date(context, mapping, args): |
|
465 """Format a date. See :hg:`help dates` for formatting |
|
466 strings. The default is a Unix date format, including the timezone: |
|
467 "Mon Sep 04 15:13:13 2006 0700".""" |
|
468 if not (1 <= len(args) <= 2): |
|
469 # i18n: "date" is a keyword |
|
470 raise error.ParseError(_("date expects one or two arguments")) |
|
471 |
|
472 date = evalfuncarg(context, mapping, args[0]) |
|
473 fmt = None |
|
474 if len(args) == 2: |
|
475 fmt = evalstring(context, mapping, args[1]) |
|
476 try: |
|
477 if fmt is None: |
|
478 return dateutil.datestr(date) |
|
479 else: |
|
480 return dateutil.datestr(date, fmt) |
|
481 except (TypeError, ValueError): |
|
482 # i18n: "date" is a keyword |
|
483 raise error.ParseError(_("date expects a date information")) |
|
484 |
|
485 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs') |
|
486 def dict_(context, mapping, args): |
|
487 """Construct a dict from key-value pairs. A key may be omitted if |
|
488 a value expression can provide an unambiguous name.""" |
|
489 data = util.sortdict() |
|
490 |
|
491 for v in args['args']: |
|
492 k = templateutil.findsymbolicname(v) |
|
493 if not k: |
|
494 raise error.ParseError(_('dict key cannot be inferred')) |
|
495 if k in data or k in args['kwargs']: |
|
496 raise error.ParseError(_("duplicated dict key '%s' inferred") % k) |
|
497 data[k] = evalfuncarg(context, mapping, v) |
|
498 |
|
499 data.update((k, evalfuncarg(context, mapping, v)) |
|
500 for k, v in args['kwargs'].iteritems()) |
|
501 return templateutil.hybriddict(data) |
|
502 |
|
503 @templatefunc('diff([includepattern [, excludepattern]])') |
|
504 def diff(context, mapping, args): |
|
505 """Show a diff, optionally |
|
506 specifying files to include or exclude.""" |
|
507 if len(args) > 2: |
|
508 # i18n: "diff" is a keyword |
|
509 raise error.ParseError(_("diff expects zero, one, or two arguments")) |
|
510 |
|
511 def getpatterns(i): |
|
512 if i < len(args): |
|
513 s = evalstring(context, mapping, args[i]).strip() |
|
514 if s: |
|
515 return [s] |
|
516 return [] |
|
517 |
|
518 ctx = context.resource(mapping, 'ctx') |
|
519 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1))) |
|
520 |
|
521 return ''.join(chunks) |
|
522 |
|
523 @templatefunc('extdata(source)', argspec='source') |
|
524 def extdata(context, mapping, args): |
|
525 """Show a text read from the specified extdata source. (EXPERIMENTAL)""" |
|
526 if 'source' not in args: |
|
527 # i18n: "extdata" is a keyword |
|
528 raise error.ParseError(_('extdata expects one argument')) |
|
529 |
|
530 source = evalstring(context, mapping, args['source']) |
|
531 cache = context.resource(mapping, 'cache').setdefault('extdata', {}) |
|
532 ctx = context.resource(mapping, 'ctx') |
|
533 if source in cache: |
|
534 data = cache[source] |
|
535 else: |
|
536 data = cache[source] = scmutil.extdatasource(ctx.repo(), source) |
|
537 return data.get(ctx.rev(), '') |
|
538 |
|
539 @templatefunc('files(pattern)') |
|
540 def files(context, mapping, args): |
|
541 """All files of the current changeset matching the pattern. See |
|
542 :hg:`help patterns`.""" |
|
543 if not len(args) == 1: |
|
544 # i18n: "files" is a keyword |
|
545 raise error.ParseError(_("files expects one argument")) |
|
546 |
|
547 raw = evalstring(context, mapping, args[0]) |
|
548 ctx = context.resource(mapping, 'ctx') |
|
549 m = ctx.match([raw]) |
|
550 files = list(ctx.matches(m)) |
|
551 return templateutil.compatlist(context, mapping, "file", files) |
|
552 |
|
553 @templatefunc('fill(text[, width[, initialident[, hangindent]]])') |
|
554 def fill(context, mapping, args): |
|
555 """Fill many |
|
556 paragraphs with optional indentation. See the "fill" filter.""" |
|
557 if not (1 <= len(args) <= 4): |
|
558 # i18n: "fill" is a keyword |
|
559 raise error.ParseError(_("fill expects one to four arguments")) |
|
560 |
|
561 text = evalstring(context, mapping, args[0]) |
|
562 width = 76 |
|
563 initindent = '' |
|
564 hangindent = '' |
|
565 if 2 <= len(args) <= 4: |
|
566 width = evalinteger(context, mapping, args[1], |
|
567 # i18n: "fill" is a keyword |
|
568 _("fill expects an integer width")) |
|
569 try: |
|
570 initindent = evalstring(context, mapping, args[2]) |
|
571 hangindent = evalstring(context, mapping, args[3]) |
|
572 except IndexError: |
|
573 pass |
|
574 |
|
575 return templatefilters.fill(text, width, initindent, hangindent) |
|
576 |
|
577 @templatefunc('formatnode(node)') |
|
578 def formatnode(context, mapping, args): |
|
579 """Obtain the preferred form of a changeset hash. (DEPRECATED)""" |
|
580 if len(args) != 1: |
|
581 # i18n: "formatnode" is a keyword |
|
582 raise error.ParseError(_("formatnode expects one argument")) |
|
583 |
|
584 ui = context.resource(mapping, 'ui') |
|
585 node = evalstring(context, mapping, args[0]) |
|
586 if ui.debugflag: |
|
587 return node |
|
588 return templatefilters.short(node) |
|
589 |
|
590 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])', |
|
591 argspec='text width fillchar left') |
|
592 def pad(context, mapping, args): |
|
593 """Pad text with a |
|
594 fill character.""" |
|
595 if 'text' not in args or 'width' not in args: |
|
596 # i18n: "pad" is a keyword |
|
597 raise error.ParseError(_("pad() expects two to four arguments")) |
|
598 |
|
599 width = evalinteger(context, mapping, args['width'], |
|
600 # i18n: "pad" is a keyword |
|
601 _("pad() expects an integer width")) |
|
602 |
|
603 text = evalstring(context, mapping, args['text']) |
|
604 |
|
605 left = False |
|
606 fillchar = ' ' |
|
607 if 'fillchar' in args: |
|
608 fillchar = evalstring(context, mapping, args['fillchar']) |
|
609 if len(color.stripeffects(fillchar)) != 1: |
|
610 # i18n: "pad" is a keyword |
|
611 raise error.ParseError(_("pad() expects a single fill character")) |
|
612 if 'left' in args: |
|
613 left = evalboolean(context, mapping, args['left']) |
|
614 |
|
615 fillwidth = width - encoding.colwidth(color.stripeffects(text)) |
|
616 if fillwidth <= 0: |
|
617 return text |
|
618 if left: |
|
619 return fillchar * fillwidth + text |
|
620 else: |
|
621 return text + fillchar * fillwidth |
|
622 |
|
623 @templatefunc('indent(text, indentchars[, firstline])') |
|
624 def indent(context, mapping, args): |
|
625 """Indents all non-empty lines |
|
626 with the characters given in the indentchars string. An optional |
|
627 third parameter will override the indent for the first line only |
|
628 if present.""" |
|
629 if not (2 <= len(args) <= 3): |
|
630 # i18n: "indent" is a keyword |
|
631 raise error.ParseError(_("indent() expects two or three arguments")) |
|
632 |
|
633 text = evalstring(context, mapping, args[0]) |
|
634 indent = evalstring(context, mapping, args[1]) |
|
635 |
|
636 if len(args) == 3: |
|
637 firstline = evalstring(context, mapping, args[2]) |
|
638 else: |
|
639 firstline = indent |
|
640 |
|
641 # the indent function doesn't indent the first line, so we do it here |
|
642 return templatefilters.indent(firstline + text, indent) |
|
643 |
|
644 @templatefunc('get(dict, key)') |
|
645 def get(context, mapping, args): |
|
646 """Get an attribute/key from an object. Some keywords |
|
647 are complex types. This function allows you to obtain the value of an |
|
648 attribute on these types.""" |
|
649 if len(args) != 2: |
|
650 # i18n: "get" is a keyword |
|
651 raise error.ParseError(_("get() expects two arguments")) |
|
652 |
|
653 dictarg = evalfuncarg(context, mapping, args[0]) |
|
654 if not util.safehasattr(dictarg, 'get'): |
|
655 # i18n: "get" is a keyword |
|
656 raise error.ParseError(_("get() expects a dict as first argument")) |
|
657 |
|
658 key = evalfuncarg(context, mapping, args[1]) |
|
659 return templateutil.getdictitem(dictarg, key) |
|
660 |
|
661 @templatefunc('if(expr, then[, else])') |
|
662 def if_(context, mapping, args): |
|
663 """Conditionally execute based on the result of |
|
664 an expression.""" |
|
665 if not (2 <= len(args) <= 3): |
|
666 # i18n: "if" is a keyword |
|
667 raise error.ParseError(_("if expects two or three arguments")) |
|
668 |
|
669 test = evalboolean(context, mapping, args[0]) |
|
670 if test: |
|
671 yield evalrawexp(context, mapping, args[1]) |
|
672 elif len(args) == 3: |
|
673 yield evalrawexp(context, mapping, args[2]) |
|
674 |
|
675 @templatefunc('ifcontains(needle, haystack, then[, else])') |
|
676 def ifcontains(context, mapping, args): |
|
677 """Conditionally execute based |
|
678 on whether the item "needle" is in "haystack".""" |
|
679 if not (3 <= len(args) <= 4): |
|
680 # i18n: "ifcontains" is a keyword |
|
681 raise error.ParseError(_("ifcontains expects three or four arguments")) |
|
682 |
|
683 haystack = evalfuncarg(context, mapping, args[1]) |
|
684 try: |
|
685 needle = evalastype(context, mapping, args[0], |
|
686 getattr(haystack, 'keytype', None) or bytes) |
|
687 found = (needle in haystack) |
|
688 except error.ParseError: |
|
689 found = False |
|
690 |
|
691 if found: |
|
692 yield evalrawexp(context, mapping, args[2]) |
|
693 elif len(args) == 4: |
|
694 yield evalrawexp(context, mapping, args[3]) |
|
695 |
|
696 @templatefunc('ifeq(expr1, expr2, then[, else])') |
|
697 def ifeq(context, mapping, args): |
|
698 """Conditionally execute based on |
|
699 whether 2 items are equivalent.""" |
|
700 if not (3 <= len(args) <= 4): |
|
701 # i18n: "ifeq" is a keyword |
|
702 raise error.ParseError(_("ifeq expects three or four arguments")) |
|
703 |
|
704 test = evalstring(context, mapping, args[0]) |
|
705 match = evalstring(context, mapping, args[1]) |
|
706 if test == match: |
|
707 yield evalrawexp(context, mapping, args[2]) |
|
708 elif len(args) == 4: |
|
709 yield evalrawexp(context, mapping, args[3]) |
|
710 |
|
711 @templatefunc('join(list, sep)') |
|
712 def join(context, mapping, args): |
|
713 """Join items in a list with a delimiter.""" |
|
714 if not (1 <= len(args) <= 2): |
|
715 # i18n: "join" is a keyword |
|
716 raise error.ParseError(_("join expects one or two arguments")) |
|
717 |
|
718 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb |
|
719 # abuses generator as a keyword that returns a list of dicts. |
|
720 joinset = evalrawexp(context, mapping, args[0]) |
|
721 joinset = templateutil.unwrapvalue(joinset) |
|
722 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity) |
|
723 joiner = " " |
|
724 if len(args) > 1: |
|
725 joiner = evalstring(context, mapping, args[1]) |
|
726 |
|
727 first = True |
|
728 for x in pycompat.maybebytestr(joinset): |
|
729 if first: |
|
730 first = False |
|
731 else: |
|
732 yield joiner |
|
733 yield joinfmt(x) |
|
734 |
|
735 @templatefunc('label(label, expr)') |
|
736 def label(context, mapping, args): |
|
737 """Apply a label to generated content. Content with |
|
738 a label applied can result in additional post-processing, such as |
|
739 automatic colorization.""" |
|
740 if len(args) != 2: |
|
741 # i18n: "label" is a keyword |
|
742 raise error.ParseError(_("label expects two arguments")) |
|
743 |
|
744 ui = context.resource(mapping, 'ui') |
|
745 thing = evalstring(context, mapping, args[1]) |
|
746 # preserve unknown symbol as literal so effects like 'red', 'bold', |
|
747 # etc. don't need to be quoted |
|
748 label = evalstringliteral(context, mapping, args[0]) |
|
749 |
|
750 return ui.label(thing, label) |
|
751 |
|
752 @templatefunc('latesttag([pattern])') |
|
753 def latesttag(context, mapping, args): |
|
754 """The global tags matching the given pattern on the |
|
755 most recent globally tagged ancestor of this changeset. |
|
756 If no such tags exist, the "{tag}" template resolves to |
|
757 the string "null".""" |
|
758 if len(args) > 1: |
|
759 # i18n: "latesttag" is a keyword |
|
760 raise error.ParseError(_("latesttag expects at most one argument")) |
|
761 |
|
762 pattern = None |
|
763 if len(args) == 1: |
|
764 pattern = evalstring(context, mapping, args[0]) |
|
765 return templatekw.showlatesttags(context, mapping, pattern) |
|
766 |
|
767 @templatefunc('localdate(date[, tz])') |
|
768 def localdate(context, mapping, args): |
|
769 """Converts a date to the specified timezone. |
|
770 The default is local date.""" |
|
771 if not (1 <= len(args) <= 2): |
|
772 # i18n: "localdate" is a keyword |
|
773 raise error.ParseError(_("localdate expects one or two arguments")) |
|
774 |
|
775 date = evalfuncarg(context, mapping, args[0]) |
|
776 try: |
|
777 date = dateutil.parsedate(date) |
|
778 except AttributeError: # not str nor date tuple |
|
779 # i18n: "localdate" is a keyword |
|
780 raise error.ParseError(_("localdate expects a date information")) |
|
781 if len(args) >= 2: |
|
782 tzoffset = None |
|
783 tz = evalfuncarg(context, mapping, args[1]) |
|
784 if isinstance(tz, bytes): |
|
785 tzoffset, remainder = dateutil.parsetimezone(tz) |
|
786 if remainder: |
|
787 tzoffset = None |
|
788 if tzoffset is None: |
|
789 try: |
|
790 tzoffset = int(tz) |
|
791 except (TypeError, ValueError): |
|
792 # i18n: "localdate" is a keyword |
|
793 raise error.ParseError(_("localdate expects a timezone")) |
|
794 else: |
|
795 tzoffset = dateutil.makedate()[1] |
|
796 return (date[0], tzoffset) |
|
797 |
|
798 @templatefunc('max(iterable)') |
|
799 def max_(context, mapping, args, **kwargs): |
|
800 """Return the max of an iterable""" |
|
801 if len(args) != 1: |
|
802 # i18n: "max" is a keyword |
|
803 raise error.ParseError(_("max expects one argument")) |
|
804 |
|
805 iterable = evalfuncarg(context, mapping, args[0]) |
|
806 try: |
|
807 x = max(pycompat.maybebytestr(iterable)) |
|
808 except (TypeError, ValueError): |
|
809 # i18n: "max" is a keyword |
|
810 raise error.ParseError(_("max first argument should be an iterable")) |
|
811 return templateutil.wraphybridvalue(iterable, x, x) |
|
812 |
|
813 @templatefunc('min(iterable)') |
|
814 def min_(context, mapping, args, **kwargs): |
|
815 """Return the min of an iterable""" |
|
816 if len(args) != 1: |
|
817 # i18n: "min" is a keyword |
|
818 raise error.ParseError(_("min expects one argument")) |
|
819 |
|
820 iterable = evalfuncarg(context, mapping, args[0]) |
|
821 try: |
|
822 x = min(pycompat.maybebytestr(iterable)) |
|
823 except (TypeError, ValueError): |
|
824 # i18n: "min" is a keyword |
|
825 raise error.ParseError(_("min first argument should be an iterable")) |
|
826 return templateutil.wraphybridvalue(iterable, x, x) |
|
827 |
|
828 @templatefunc('mod(a, b)') |
|
829 def mod(context, mapping, args): |
|
830 """Calculate a mod b such that a / b + a mod b == a""" |
|
831 if not len(args) == 2: |
|
832 # i18n: "mod" is a keyword |
|
833 raise error.ParseError(_("mod expects two arguments")) |
|
834 |
|
835 func = lambda a, b: a % b |
|
836 return templateutil.runarithmetic(context, mapping, |
|
837 (func, args[0], args[1])) |
|
838 |
|
839 @templatefunc('obsfateoperations(markers)') |
|
840 def obsfateoperations(context, mapping, args): |
|
841 """Compute obsfate related information based on markers (EXPERIMENTAL)""" |
|
842 if len(args) != 1: |
|
843 # i18n: "obsfateoperations" is a keyword |
|
844 raise error.ParseError(_("obsfateoperations expects one argument")) |
|
845 |
|
846 markers = evalfuncarg(context, mapping, args[0]) |
|
847 |
|
848 try: |
|
849 data = obsutil.markersoperations(markers) |
|
850 return templateutil.hybridlist(data, name='operation') |
|
851 except (TypeError, KeyError): |
|
852 # i18n: "obsfateoperations" is a keyword |
|
853 errmsg = _("obsfateoperations first argument should be an iterable") |
|
854 raise error.ParseError(errmsg) |
|
855 |
|
856 @templatefunc('obsfatedate(markers)') |
|
857 def obsfatedate(context, mapping, args): |
|
858 """Compute obsfate related information based on markers (EXPERIMENTAL)""" |
|
859 if len(args) != 1: |
|
860 # i18n: "obsfatedate" is a keyword |
|
861 raise error.ParseError(_("obsfatedate expects one argument")) |
|
862 |
|
863 markers = evalfuncarg(context, mapping, args[0]) |
|
864 |
|
865 try: |
|
866 data = obsutil.markersdates(markers) |
|
867 return templateutil.hybridlist(data, name='date', fmt='%d %d') |
|
868 except (TypeError, KeyError): |
|
869 # i18n: "obsfatedate" is a keyword |
|
870 errmsg = _("obsfatedate first argument should be an iterable") |
|
871 raise error.ParseError(errmsg) |
|
872 |
|
873 @templatefunc('obsfateusers(markers)') |
|
874 def obsfateusers(context, mapping, args): |
|
875 """Compute obsfate related information based on markers (EXPERIMENTAL)""" |
|
876 if len(args) != 1: |
|
877 # i18n: "obsfateusers" is a keyword |
|
878 raise error.ParseError(_("obsfateusers expects one argument")) |
|
879 |
|
880 markers = evalfuncarg(context, mapping, args[0]) |
|
881 |
|
882 try: |
|
883 data = obsutil.markersusers(markers) |
|
884 return templateutil.hybridlist(data, name='user') |
|
885 except (TypeError, KeyError, ValueError): |
|
886 # i18n: "obsfateusers" is a keyword |
|
887 msg = _("obsfateusers first argument should be an iterable of " |
|
888 "obsmakers") |
|
889 raise error.ParseError(msg) |
|
890 |
|
891 @templatefunc('obsfateverb(successors, markers)') |
|
892 def obsfateverb(context, mapping, args): |
|
893 """Compute obsfate related information based on successors (EXPERIMENTAL)""" |
|
894 if len(args) != 2: |
|
895 # i18n: "obsfateverb" is a keyword |
|
896 raise error.ParseError(_("obsfateverb expects two arguments")) |
|
897 |
|
898 successors = evalfuncarg(context, mapping, args[0]) |
|
899 markers = evalfuncarg(context, mapping, args[1]) |
|
900 |
|
901 try: |
|
902 return obsutil.obsfateverb(successors, markers) |
|
903 except TypeError: |
|
904 # i18n: "obsfateverb" is a keyword |
|
905 errmsg = _("obsfateverb first argument should be countable") |
|
906 raise error.ParseError(errmsg) |
|
907 |
|
908 @templatefunc('relpath(path)') |
|
909 def relpath(context, mapping, args): |
|
910 """Convert a repository-absolute path into a filesystem path relative to |
|
911 the current working directory.""" |
|
912 if len(args) != 1: |
|
913 # i18n: "relpath" is a keyword |
|
914 raise error.ParseError(_("relpath expects one argument")) |
|
915 |
|
916 repo = context.resource(mapping, 'ctx').repo() |
|
917 path = evalstring(context, mapping, args[0]) |
|
918 return repo.pathto(path) |
|
919 |
|
920 @templatefunc('revset(query[, formatargs...])') |
|
921 def revset(context, mapping, args): |
|
922 """Execute a revision set query. See |
|
923 :hg:`help revset`.""" |
|
924 if not len(args) > 0: |
|
925 # i18n: "revset" is a keyword |
|
926 raise error.ParseError(_("revset expects one or more arguments")) |
|
927 |
|
928 raw = evalstring(context, mapping, args[0]) |
|
929 ctx = context.resource(mapping, 'ctx') |
|
930 repo = ctx.repo() |
|
931 |
|
932 def query(expr): |
|
933 m = revsetmod.match(repo.ui, expr, repo=repo) |
|
934 return m(repo) |
|
935 |
|
936 if len(args) > 1: |
|
937 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]] |
|
938 revs = query(revsetlang.formatspec(raw, *formatargs)) |
|
939 revs = list(revs) |
|
940 else: |
|
941 cache = context.resource(mapping, 'cache') |
|
942 revsetcache = cache.setdefault("revsetcache", {}) |
|
943 if raw in revsetcache: |
|
944 revs = revsetcache[raw] |
|
945 else: |
|
946 revs = query(raw) |
|
947 revs = list(revs) |
|
948 revsetcache[raw] = revs |
|
949 return templatekw.showrevslist(context, mapping, "revision", revs) |
|
950 |
|
951 @templatefunc('rstdoc(text, style)') |
|
952 def rstdoc(context, mapping, args): |
|
953 """Format reStructuredText.""" |
|
954 if len(args) != 2: |
|
955 # i18n: "rstdoc" is a keyword |
|
956 raise error.ParseError(_("rstdoc expects two arguments")) |
|
957 |
|
958 text = evalstring(context, mapping, args[0]) |
|
959 style = evalstring(context, mapping, args[1]) |
|
960 |
|
961 return minirst.format(text, style=style, keep=['verbose']) |
|
962 |
|
963 @templatefunc('separate(sep, args)', argspec='sep *args') |
|
964 def separate(context, mapping, args): |
|
965 """Add a separator between non-empty arguments.""" |
|
966 if 'sep' not in args: |
|
967 # i18n: "separate" is a keyword |
|
968 raise error.ParseError(_("separate expects at least one argument")) |
|
969 |
|
970 sep = evalstring(context, mapping, args['sep']) |
|
971 first = True |
|
972 for arg in args['args']: |
|
973 argstr = evalstring(context, mapping, arg) |
|
974 if not argstr: |
|
975 continue |
|
976 if first: |
|
977 first = False |
|
978 else: |
|
979 yield sep |
|
980 yield argstr |
|
981 |
|
982 @templatefunc('shortest(node, minlength=4)') |
|
983 def shortest(context, mapping, args): |
|
984 """Obtain the shortest representation of |
|
985 a node.""" |
|
986 if not (1 <= len(args) <= 2): |
|
987 # i18n: "shortest" is a keyword |
|
988 raise error.ParseError(_("shortest() expects one or two arguments")) |
|
989 |
|
990 node = evalstring(context, mapping, args[0]) |
|
991 |
|
992 minlength = 4 |
|
993 if len(args) > 1: |
|
994 minlength = evalinteger(context, mapping, args[1], |
|
995 # i18n: "shortest" is a keyword |
|
996 _("shortest() expects an integer minlength")) |
|
997 |
|
998 # _partialmatch() of filtered changelog could take O(len(repo)) time, |
|
999 # which would be unacceptably slow. so we look for hash collision in |
|
1000 # unfiltered space, which means some hashes may be slightly longer. |
|
1001 cl = context.resource(mapping, 'ctx')._repo.unfiltered().changelog |
|
1002 return cl.shortest(node, minlength) |
|
1003 |
|
1004 @templatefunc('strip(text[, chars])') |
|
1005 def strip(context, mapping, args): |
|
1006 """Strip characters from a string. By default, |
|
1007 strips all leading and trailing whitespace.""" |
|
1008 if not (1 <= len(args) <= 2): |
|
1009 # i18n: "strip" is a keyword |
|
1010 raise error.ParseError(_("strip expects one or two arguments")) |
|
1011 |
|
1012 text = evalstring(context, mapping, args[0]) |
|
1013 if len(args) == 2: |
|
1014 chars = evalstring(context, mapping, args[1]) |
|
1015 return text.strip(chars) |
|
1016 return text.strip() |
|
1017 |
|
1018 @templatefunc('sub(pattern, replacement, expression)') |
|
1019 def sub(context, mapping, args): |
|
1020 """Perform text substitution |
|
1021 using regular expressions.""" |
|
1022 if len(args) != 3: |
|
1023 # i18n: "sub" is a keyword |
|
1024 raise error.ParseError(_("sub expects three arguments")) |
|
1025 |
|
1026 pat = evalstring(context, mapping, args[0]) |
|
1027 rpl = evalstring(context, mapping, args[1]) |
|
1028 src = evalstring(context, mapping, args[2]) |
|
1029 try: |
|
1030 patre = re.compile(pat) |
|
1031 except re.error: |
|
1032 # i18n: "sub" is a keyword |
|
1033 raise error.ParseError(_("sub got an invalid pattern: %s") % pat) |
|
1034 try: |
|
1035 yield patre.sub(rpl, src) |
|
1036 except re.error: |
|
1037 # i18n: "sub" is a keyword |
|
1038 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl) |
|
1039 |
|
1040 @templatefunc('startswith(pattern, text)') |
|
1041 def startswith(context, mapping, args): |
|
1042 """Returns the value from the "text" argument |
|
1043 if it begins with the content from the "pattern" argument.""" |
|
1044 if len(args) != 2: |
|
1045 # i18n: "startswith" is a keyword |
|
1046 raise error.ParseError(_("startswith expects two arguments")) |
|
1047 |
|
1048 patn = evalstring(context, mapping, args[0]) |
|
1049 text = evalstring(context, mapping, args[1]) |
|
1050 if text.startswith(patn): |
|
1051 return text |
|
1052 return '' |
|
1053 |
|
1054 @templatefunc('word(number, text[, separator])') |
|
1055 def word(context, mapping, args): |
|
1056 """Return the nth word from a string.""" |
|
1057 if not (2 <= len(args) <= 3): |
|
1058 # i18n: "word" is a keyword |
|
1059 raise error.ParseError(_("word expects two or three arguments, got %d") |
|
1060 % len(args)) |
|
1061 |
|
1062 num = evalinteger(context, mapping, args[0], |
|
1063 # i18n: "word" is a keyword |
|
1064 _("word expects an integer index")) |
|
1065 text = evalstring(context, mapping, args[1]) |
|
1066 if len(args) == 3: |
|
1067 splitter = evalstring(context, mapping, args[2]) |
|
1068 else: |
|
1069 splitter = None |
|
1070 |
|
1071 tokens = text.split(splitter) |
|
1072 if num >= len(tokens) or num < -len(tokens): |
|
1073 return '' |
|
1074 else: |
|
1075 return tokens[num] |
|
1076 |
|
1077 # methods to interpret function arguments or inner expressions (e.g. {_(x)}) |
441 # methods to interpret function arguments or inner expressions (e.g. {_(x)}) |
1078 exprmethods = { |
442 exprmethods = { |
1079 "integer": lambda e, c: (templateutil.runinteger, e[1]), |
443 "integer": lambda e, c: (templateutil.runinteger, e[1]), |
1080 "string": lambda e, c: (templateutil.runstring, e[1]), |
444 "string": lambda e, c: (templateutil.runstring, e[1]), |
1081 "symbol": lambda e, c: (templateutil.runsymbol, e[1]), |
445 "symbol": lambda e, c: (templateutil.runsymbol, e[1]), |