889 elif editform: |
890 elif editform: |
890 return lambda r, c, s: commiteditor(r, c, s, editform=editform) |
891 return lambda r, c, s: commiteditor(r, c, s, editform=editform) |
891 else: |
892 else: |
892 return commiteditor |
893 return commiteditor |
893 |
894 |
894 def makefilename(ctx, pat, |
895 def rendertemplate(ctx, tmpl, props=None): |
895 total=None, seqno=None, revwidth=None, pathname=None): |
896 """Expand a literal template 'tmpl' byte-string against one changeset |
|
897 |
|
898 Each props item must be a stringify-able value or a callable returning |
|
899 such value, i.e. no bare list nor dict should be passed. |
|
900 """ |
|
901 repo = ctx.repo() |
|
902 tres = formatter.templateresources(repo.ui, repo) |
|
903 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords, |
|
904 resources=tres) |
|
905 mapping = {'ctx': ctx, 'revcache': {}} |
|
906 if props: |
|
907 mapping.update(props) |
|
908 return t.render(mapping) |
|
909 |
|
910 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): |
|
911 r"""Convert old-style filename format string to template string |
|
912 |
|
913 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) |
|
914 'foo-{reporoot|basename}-{seqno}.patch' |
|
915 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H') |
|
916 '{rev}{tags % "{tag}"}{node}' |
|
917 |
|
918 '\' in outermost strings has to be escaped because it is a directory |
|
919 separator on Windows: |
|
920 |
|
921 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0) |
|
922 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch' |
|
923 >>> _buildfntemplate(b'\\\\foo\\bar.patch') |
|
924 '\\\\\\\\foo\\\\bar.patch' |
|
925 >>> _buildfntemplate(b'\\{tags % "{tag}"}') |
|
926 '\\\\{tags % "{tag}"}' |
|
927 |
|
928 but inner strings follow the template rules (i.e. '\' is taken as an |
|
929 escape character): |
|
930 |
|
931 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0) |
|
932 '{"c:\\tmp"}' |
|
933 """ |
896 expander = { |
934 expander = { |
897 'H': lambda: ctx.hex(), |
935 b'H': b'{node}', |
898 'R': lambda: '%d' % ctx.rev(), |
936 b'R': b'{rev}', |
899 'h': lambda: short(ctx.node()), |
937 b'h': b'{node|short}', |
900 'm': lambda: re.sub('[^\w]', '_', |
938 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}', |
901 ctx.description().strip().splitlines()[0]), |
939 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}', |
902 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0), |
940 b'%': b'%', |
903 '%': lambda: '%', |
941 b'b': b'{reporoot|basename}', |
904 'b': lambda: os.path.basename(ctx.repo().root), |
942 } |
905 } |
|
906 if total is not None: |
943 if total is not None: |
907 expander['N'] = lambda: '%d' % total |
944 expander[b'N'] = b'{total}' |
908 if seqno is not None: |
945 if seqno is not None: |
909 expander['n'] = lambda: '%d' % seqno |
946 expander[b'n'] = b'{seqno}' |
910 if total is not None and seqno is not None: |
947 if total is not None and seqno is not None: |
911 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total))) |
948 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}' |
912 if pathname is not None: |
949 if pathname is not None: |
913 expander['s'] = lambda: os.path.basename(pathname) |
950 expander[b's'] = b'{pathname|basename}' |
914 expander['d'] = lambda: os.path.dirname(pathname) or '.' |
951 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}' |
915 expander['p'] = lambda: pathname |
952 expander[b'p'] = b'{pathname}' |
916 |
953 |
917 newname = [] |
954 newname = [] |
918 patlen = len(pat) |
955 for typ, start, end in templater.scantemplate(pat, raw=True): |
919 i = 0 |
956 if typ != b'string': |
920 while i < patlen: |
957 newname.append(pat[start:end]) |
921 c = pat[i:i + 1] |
958 continue |
922 if c == '%': |
959 i = start |
923 i += 1 |
960 while i < end: |
924 c = pat[i:i + 1] |
961 n = pat.find(b'%', i, end) |
|
962 if n < 0: |
|
963 newname.append(util.escapestr(pat[i:end])) |
|
964 break |
|
965 newname.append(util.escapestr(pat[i:n])) |
|
966 if n + 2 > end: |
|
967 raise error.Abort(_("incomplete format spec in output " |
|
968 "filename")) |
|
969 c = pat[n + 1:n + 2] |
|
970 i = n + 2 |
925 try: |
971 try: |
926 c = expander[c]() |
972 newname.append(expander[c]) |
927 except KeyError: |
973 except KeyError: |
928 raise error.Abort(_("invalid format spec '%%%s' in output " |
974 raise error.Abort(_("invalid format spec '%%%s' in output " |
929 "filename") % c) |
975 "filename") % c) |
930 newname.append(c) |
|
931 i += 1 |
|
932 return ''.join(newname) |
976 return ''.join(newname) |
|
977 |
|
978 def makefilename(ctx, pat, **props): |
|
979 if not pat: |
|
980 return pat |
|
981 tmpl = _buildfntemplate(pat, **props) |
|
982 # BUG: alias expansion shouldn't be made against template fragments |
|
983 # rewritten from %-format strings, but we have no easy way to partially |
|
984 # disable the expansion. |
|
985 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) |
933 |
986 |
934 def isstdiofilename(pat): |
987 def isstdiofilename(pat): |
935 """True if the given pat looks like a filename denoting stdin/stdout""" |
988 """True if the given pat looks like a filename denoting stdin/stdout""" |
936 return not pat or pat == '-' |
989 return not pat or pat == '-' |
937 |
990 |