fix: use templater to substitute values in command string
authorYuya Nishihara <yuya@tcha.org>
Sat, 14 Apr 2018 00:30:39 +0900
changeset 37774 d6970628b95f
parent 37773 0f084741cd66
child 37775 03d7f885d5f2
fix: use templater to substitute values in command string bytes.format() isn't supported on Python 3. Luckily, our template syntax is similar so we can reuse it. We need a hack to disable \-escapes as '\' is a directory separator on Windows.
hgext/fix.py
mercurial/cmdutil.py
--- a/hgext/fix.py	Fri Apr 13 23:07:12 2018 +0900
+++ b/hgext/fix.py	Sat Apr 14 00:30:39 2018 +0900
@@ -387,7 +387,7 @@
     for fixername, fixer in fixers.iteritems():
         if fixer.affects(opts, fixctx, path):
             ranges = lineranges(opts, path, basectxs, fixctx, newdata)
-            command = fixer.command(path, ranges)
+            command = fixer.command(ui, path, ranges)
             if command is None:
                 continue
             ui.debug('subprocess: %s\n' % (command,))
@@ -534,18 +534,20 @@
         """Should this fixer run on the file at the given path and context?"""
         return scmutil.match(fixctx, [self._fileset], opts)(path)
 
-    def command(self, path, ranges):
+    def command(self, ui, path, ranges):
         """A shell command to use to invoke this fixer on the given file/lines
 
         May return None if there is no appropriate command to run for the given
         parameters.
         """
-        parts = [self._command.format(rootpath=path,
-                                      basename=os.path.basename(path))]
+        expand = cmdutil.rendercommandtemplate
+        parts = [expand(ui, self._command,
+                        {'rootpath': path, 'basename': os.path.basename(path)})]
         if self._linerange:
             if not ranges:
                 # No line ranges to fix, so don't run the fixer.
                 return None
             for first, last in ranges:
-                parts.append(self._linerange.format(first=first, last=last))
+                parts.append(expand(ui, self._linerange,
+                                    {'first': first, 'last': last}))
         return ' '.join(parts)
--- a/mercurial/cmdutil.py	Fri Apr 13 23:07:12 2018 +0900
+++ b/mercurial/cmdutil.py	Sat Apr 14 00:30:39 2018 +0900
@@ -904,6 +904,33 @@
     else:
         return commiteditor
 
+def _escapecommandtemplate(tmpl):
+    parts = []
+    for typ, start, end in templater.scantemplate(tmpl, raw=True):
+        if typ == b'string':
+            parts.append(stringutil.escapestr(tmpl[start:end]))
+        else:
+            parts.append(tmpl[start:end])
+    return b''.join(parts)
+
+def rendercommandtemplate(ui, tmpl, props):
+    r"""Expand a literal template 'tmpl' in a way suitable for command line
+
+    '\' in outermost string is not taken as an escape character because it
+    is a directory separator on Windows.
+
+    >>> from . import ui as uimod
+    >>> ui = uimod.ui()
+    >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
+    'c:\\foo'
+    >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
+    'c:{path}'
+    """
+    if not tmpl:
+        return tmpl
+    t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
+    return t.renderdefault(props)
+
 def rendertemplate(ctx, tmpl, props=None):
     """Expand a literal template 'tmpl' byte-string against one changeset