##// END OF EJS Templates
cmdutil: expand filename format string by templater (BC)...
Yuya Nishihara -
r36528:aa329402 default
parent child Browse files
Show More
@@ -42,6 +42,7 b' from . import ('
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 subrepoutil,
44 subrepoutil,
45 templatekw,
45 templater,
46 templater,
46 util,
47 util,
47 vfs as vfsmod,
48 vfs as vfsmod,
@@ -891,46 +892,98 b' def getcommiteditor(edit=False, finishde'
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)
933
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))
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 == '-'
@@ -1285,7 +1285,9 b' def cat(ui, repo, file1, *pats, **opts):'
1285 no revision is given, the parent of the working directory is used.
1285 no revision is given, the parent of the working directory is used.
1286
1286
1287 Output may be to a file, in which case the name of the file is
1287 Output may be to a file, in which case the name of the file is
1288 given using a format string. The formatting rules as follows:
1288 given using a template string. See :hg:`help templates`. In addition
1289 to the common template keywords, the following formatting rules are
1290 supported:
1289
1291
1290 :``%%``: literal "%" character
1292 :``%%``: literal "%" character
1291 :``%s``: basename of file being printed
1293 :``%s``: basename of file being printed
@@ -1296,6 +1298,7 b' def cat(ui, repo, file1, *pats, **opts):'
1296 :``%h``: short-form changeset hash (12 hexadecimal digits)
1298 :``%h``: short-form changeset hash (12 hexadecimal digits)
1297 :``%r``: zero-padded changeset revision number
1299 :``%r``: zero-padded changeset revision number
1298 :``%b``: basename of the exporting repository
1300 :``%b``: basename of the exporting repository
1301 :``\\``: literal "\\" character
1299
1302
1300 Returns 0 on success.
1303 Returns 0 on success.
1301 """
1304 """
@@ -1901,7 +1904,9 b' def export(ui, repo, *changesets, **opts'
1901 first parent only.
1904 first parent only.
1902
1905
1903 Output may be to a file, in which case the name of the file is
1906 Output may be to a file, in which case the name of the file is
1904 given using a format string. The formatting rules are as follows:
1907 given using a template string. See :hg:`help templates`. In addition
1908 to the common template keywords, the following formatting rules are
1909 supported:
1905
1910
1906 :``%%``: literal "%" character
1911 :``%%``: literal "%" character
1907 :``%H``: changeset hash (40 hexadecimal digits)
1912 :``%H``: changeset hash (40 hexadecimal digits)
@@ -1912,6 +1917,7 b' def export(ui, repo, *changesets, **opts'
1912 :``%m``: first line of the commit message (only alphanumeric characters)
1917 :``%m``: first line of the commit message (only alphanumeric characters)
1913 :``%n``: zero-padded sequence number, starting at 1
1918 :``%n``: zero-padded sequence number, starting at 1
1914 :``%r``: zero-padded changeset revision number
1919 :``%r``: zero-padded changeset revision number
1920 :``\\``: literal "\\" character
1915
1921
1916 Without the -a/--text option, export will avoid generating diffs
1922 Without the -a/--text option, export will avoid generating diffs
1917 of files it detects as binary. With -a, export will generate a
1923 of files it detects as binary. With -a, export will generate a
@@ -42,6 +42,7 b' def testmod(name, optionflags=0, testtar'
42
42
43 testmod('mercurial.changegroup')
43 testmod('mercurial.changegroup')
44 testmod('mercurial.changelog')
44 testmod('mercurial.changelog')
45 testmod('mercurial.cmdutil')
45 testmod('mercurial.color')
46 testmod('mercurial.color')
46 testmod('mercurial.config')
47 testmod('mercurial.config')
47 testmod('mercurial.context')
48 testmod('mercurial.context')
@@ -186,11 +186,45 b' Checking if only alphanumeric characters'
186 exporting patch:
186 exporting patch:
187 ___________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch
187 ___________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch
188
188
189 Template fragments in file name:
190
191 $ hg export -v -o '{node|shortest}.patch' tip
192 exporting patch:
193 197e.patch
194
195 Backslash should be preserved because it is a directory separator on Windows:
196
197 $ mkdir out
198 $ hg export -v -o 'out\{node|shortest}.patch' tip
199 exporting patch:
200 out\197e.patch
201
202 Still backslash is taken as an escape character in inner template strings:
203
204 $ hg export -v -o '{"out\{foo}.patch"}' tip
205 exporting patch:
206 out{foo}.patch
207
189 Invalid pattern in file name:
208 Invalid pattern in file name:
190
209
191 $ hg export -o '%x.patch' tip
210 $ hg export -o '%x.patch' tip
192 abort: invalid format spec '%x' in output filename
211 abort: invalid format spec '%x' in output filename
193 [255]
212 [255]
213 $ hg export -o '%' tip
214 abort: incomplete format spec in output filename
215 [255]
216 $ hg export -o '%{"foo"}' tip
217 abort: incomplete format spec in output filename
218 [255]
219 $ hg export -o '%m{' tip
220 hg: parse error at 3: unterminated template expansion
221 [255]
222 $ hg export -o '%\' tip
223 abort: invalid format spec '%\' in output filename
224 [255]
225 $ hg export -o '\%' tip
226 abort: incomplete format spec in output filename
227 [255]
194
228
195 Catch exporting unknown revisions (especially empty revsets, see issue3353)
229 Catch exporting unknown revisions (especially empty revsets, see issue3353)
196
230
General Comments 0
You need to be logged in to leave comments. Login now