Skip to content

Commit

Permalink
Allow specifying of shell-quoted arguments to SubprocessCommand, e.g.…
Browse files Browse the repository at this point in the history
… to pass a command to ssh to run remotely.
  • Loading branch information
kentonv committed Aug 24, 2009
1 parent 59234b8 commit 307bc83
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 1 deletion.
23 changes: 22 additions & 1 deletion command.py
Expand Up @@ -35,6 +35,7 @@

import cStringIO
import os
import pipes
import shutil
import subprocess

Expand Down Expand Up @@ -369,10 +370,23 @@ class SubprocessCommand(Command):
"""Command which launches a separate process."""

class DirectoryToken(object):
"""Can be used in an argument list to indicate that the on-disk location
of the given virtual directory should be used as the argument."""

def __init__(self, dirname):
typecheck(dirname, basestring)
self.dirname = dirname

class Quoted(object):
"""Can be used in an argument list to indicate that the given sublist of
args should be interpreted like a top-level arg list, then combined into
one string using shell quoting rules such that passing said string to
sh -c would execute the command represented by the arg list. This can
be used e.g. to safely pass a command to ssh to be run remotely."""

def __init__(self, args):
self.args = args

def __init__(self, action, args, implicit = [],
capture_stdout=None, capture_stderr=None,
capture_exit_status=None, working_dir=None):
Expand Down Expand Up @@ -537,7 +551,8 @@ def __verify_args(self, args):
elif not isinstance(arg, basestring) and \
not isinstance(arg, Artifact) and \
not isinstance(arg, ContentToken) and \
not isinstance(arg, SubprocessCommand.DirectoryToken):
not isinstance(arg, SubprocessCommand.DirectoryToken) and \
not isinstance(arg, SubprocessCommand.Quoted):
raise TypeError("Invalid argument: %s" % arg)

def __format_args(self, args, context, split_content=True):
Expand All @@ -555,6 +570,9 @@ def __format_args(self, args, context, split_content=True):
yield content
elif isinstance(arg, SubprocessCommand.DirectoryToken):
yield context.get_disk_directory_path(arg.dirname)
elif isinstance(arg, SubprocessCommand.Quoted):
sub_formatted = self.__format_args(arg.args, context)
yield " ".join([pipes.quote(part) for part in sub_formatted])
elif isinstance(arg, list):
yield "".join(self.__format_args(
arg, context, split_content = False))
Expand Down Expand Up @@ -610,6 +628,9 @@ def __hash_args(self, args, hasher):
elif isinstance(arg, SubprocessCommand.DirectoryToken):
hasher.update("d")
_hash_string_and_length(arg.dirname, hasher)
elif isinstance(arg, SubprocessCommand.Quoted):
hasher.update("q")
self.__hash_args(arg.args, hasher)
elif isinstance(arg, list):
hasher.update("l")
self.__hash_args(arg, hasher)
Expand Down
9 changes: 9 additions & 0 deletions command_test.py
Expand Up @@ -476,6 +476,15 @@ def testFormatArgs(self):
["(content with\nspaces)"],
"($(filename))")

self.assertFormattedAs(["(", SubprocessCommand.Quoted(["foo bar", "baz"]),
")"],
["(", "'foo bar' baz", ")"])
self.assertFormattedAs([SubprocessCommand.Quoted(["'hello'"])],
["\"'hello'\""])
self.assertFormattedAs([SubprocessCommand.Quoted(["(", artifact, ")"])],
["'(' disk/filename ')'"],
"'(' filename ')'")

def assertFormattedAs(self, args, result, printed = None):
context = MockCommandContext(self.__dir, diskpath_prefix = "disk/")
command = SubprocessCommand(self.__action, list(args))
Expand Down

0 comments on commit 307bc83

Please sign in to comment.