mercurial/cmdutil.py
changeset 36511 aa3294027936
parent 36508 d7a23d6184a2
child 36607 c6061cadb400
equal deleted inserted replaced
36509:638c012a87ef 36511:aa3294027936
    40     revlog,
    40     revlog,
    41     rewriteutil,
    41     rewriteutil,
    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,
    48 )
    49 )
    49 stringio = util.stringio
    50 stringio = util.stringio
   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