diff --git a/plumbum/cli.py b/plumbum/cli.py index d288f3f20..5112c7613 100644 --- a/plumbum/cli.py +++ b/plumbum/cli.py @@ -41,17 +41,17 @@ def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) -def switch(names, argtype = None, argname = None, list = False, mandatory = False, requires = (), +def switch(names, argtype = None, argname = None, list = False, mandatory = False, requires = (), excludes = (), help = None, overridable = False, group = "Switches"): """ A decorator that exposes functions as command-line switches. Usage:: - + class MyApp(Application): @switch(["-l", "--log-to-file"], argtype = str) def log_to_file(self, filename): handler = logging.FileHandler(filename) logger.addHandler(handler) - + @switch(["--verbose"], excludes=["--terse"], requires=["--log-to-file"]) def set_debug(self): logger.setLevel(logging.DEBUG) @@ -59,79 +59,79 @@ def set_debug(self): @switch(["--terse"], excludes=["--verbose"], requires=["--log-to-file"]) def set_terse(self): logger.setLevel(logging.WARNING) - + :param names: The name(s) under which the function is reachable; it can be a string or a list of string, but at least one name is required. There's no need to prefix the name with ``-`` or ``--`` (this is added automatically), but it can be used for clarity. Single-letter names are prefixed by ``-``, while longer names are prefixed by ``--`` - + :param argtype: If this function takes an argument, you need to specify its type. The default is ``None``, which means the function takes no argument. The type is more of a "validator" than a real type; it can be any callable object that raises a ``TypeError`` if the argument is invalid, or returns an - appropriate value on success. If the user provides an invalid value, + appropriate value on success. If the user provides an invalid value, :func:`plumbum.cli.WrongArgumentType` - + :param argname: The name of the argument; if ``None``, the name will be inferred from the function's signature - + :param list: Whether or not this switch can be repeated (e.g. ``gcc -I/lib -I/usr/lib``). If ``False``, only a single occurrence of the switch is allowed; if ``True``, it may be repeated indefinitely. The occurrences are collected into a list, so the function is only called once with the collections. For instance, - for ``gcc -I/lib -I/usr/lib``, the function will be called with + for ``gcc -I/lib -I/usr/lib``, the function will be called with ``["/lib", "/usr/lib"]``. - + :param mandatory: Whether or not this switch is mandatory; if a mandatory switch is not given, :class:`MissingMandatorySwitch ` is raised. The default is ``False``. - - :param requires: A list of switches that this switch depends on ("requires"). This means that - it's invalid to invoke this switch without also invoking the required ones. - In the example above, it's illegal to pass ``--verbose`` or ``--terse`` - without also passing ``--log-to-file``. By default, this list is empty, + + :param requires: A list of switches that this switch depends on ("requires"). This means that + it's invalid to invoke this switch without also invoking the required ones. + In the example above, it's illegal to pass ``--verbose`` or ``--terse`` + without also passing ``--log-to-file``. By default, this list is empty, which means the switch has no prerequisites. If an invalid combination is given, :class:`SwitchCombinationError ` is raised. - - Note that this list is made of the switch *names*; if a switch has more + + Note that this list is made of the switch *names*; if a switch has more than a single name, any of its names will do. - + .. note:: - There is no guarantee on the (topological) order in which the actual + There is no guarantee on the (topological) order in which the actual switch functions will be invoked, as the dependency graph might contain cycles. - - :param excludes: A list of switches that this switch forbids ("excludes"). This means that - it's invalid to invoke this switch if any of the excluded ones are given. - In the example above, it's illegal to pass ``--verbose`` along with - ``--terse``, as it will result in a contradiction. By default, this list - is empty, which means the switch has no prerequisites. If an invalid - combination is given, :class:`SwitchCombinationError + + :param excludes: A list of switches that this switch forbids ("excludes"). This means that + it's invalid to invoke this switch if any of the excluded ones are given. + In the example above, it's illegal to pass ``--verbose`` along with + ``--terse``, as it will result in a contradiction. By default, this list + is empty, which means the switch has no prerequisites. If an invalid + combination is given, :class:`SwitchCombinationError ` is raised. - - Note that this list is made of the switch *names*; if a switch has more + + Note that this list is made of the switch *names*; if a switch has more than a single name, any of its names will do. - + :param help: The help message (description) for this switch; this description is used when ``--help`` is given. If ``None``, the function's docstring will be used. - + :param overridable: Whether or not the names of this switch are overridable by other switches. - If ``False`` (the default), having another switch function with the same + If ``False`` (the default), having another switch function with the same name(s) will cause an exception. If ``True``, this is silently ignored. - + :param group: The switch's *group*; this is a string that is used to group related switches together when ``--help`` is given. The default group is ``Switches``. - + :returns: The decorated function (with a ``_switch_info`` attribute) """ - if isinstance(names, str): + if isinstance(names, basestring): names = [names] names = [n.lstrip("-") for n in names] requires = [n.lstrip("-") for n in requires] excludes = [n.lstrip("-") for n in excludes] - + def deco(func): if argname is None: argspec = inspect.getargspec(func)[0] @@ -144,7 +144,7 @@ def deco(func): help2 = inspect.getdoc(func) if help is None else help if not help2: help2 = str(func) - func._switch_info = SwitchInfo(names = names, argtype = argtype, list = list, func = func, + func._switch_info = SwitchInfo(names = names, argtype = argtype, list = list, func = func, mandatory = mandatory, overridable = overridable, group = group, requires = requires, excludes = excludes, argname = argname2, help = help2) return func @@ -164,14 +164,14 @@ def deco(func): class SwitchAttr(object): """ A switch that stores its result in an attribute (descriptor). Usage:: - + class MyApp(Application): logfile = SwitchAttr(["-f", "--log-file"], str) - + def main(self): if self.logfile: open(self.logfile, "w") - + :param names: The switch names :param argtype: The switch argument's (and attribute's) type :param default: The attribute's default value (``None``) @@ -203,7 +203,7 @@ class Flag(SwitchAttr): """A specialized :class:`SwitchAttr ` for boolean flags. If the flag is not given, the value of this attribute is the ``default``; if it is given, the value changes to ``not default``. Usage:: - + class MyApp(Application): verbose = Flag(["-v", "--verbose"], help = "If given, I'll be very talkative") @@ -218,14 +218,14 @@ def __call__(self, _): self._value = not self._value class CountOf(SwitchAttr): - """A specialized :class:`SwitchAttr ` that counts the number of + """A specialized :class:`SwitchAttr ` that counts the number of occurrences of the switch in the command line. Usage:: class MyApp(Application): verbosity = CountOf(["-v", "--verbose"], help = "The more, the merrier") - + If ``-v -v -vv`` is given in the command-line, it will result in ``verbosity = 4``. - + :param names: The switch names :param default: The default value (0) :param kwargs: Any of the keyword arguments accepted by :func:`switch `, @@ -241,12 +241,12 @@ def __call__(self, _, v): #=================================================================================================== class Range(object): """ - A switch-type validator that checks for the inclusion of a value in a certain range. + A switch-type validator that checks for the inclusion of a value in a certain range. Usage:: - + class MyApp(Application): age = SwitchAttr(["--age"], Range(18, 120)) - + :param start: The minimal value :param end: The maximal value """ @@ -263,12 +263,12 @@ def __call__(self, obj): class Set(object): """ - A switch-type validator that checks that the value is contained in a defined + A switch-type validator that checks that the value is contained in a defined set of values. Usage:: - + class MyApp(Application): mode = SwitchAttr(["--mode"], Set("TCP", "UDP", case_insensitive = False)) - + :param values: The set of values (strings) :param case_insensitive: A keyword argument that indicates whether to use case-sensitive comparison or not. The default is ``True`` @@ -333,50 +333,50 @@ def __init__(self, swname, val, index): class Application(object): """ The base class for CLI applications; your "entry point" class should derive from it, - define the relevant switch functions and attributes, and the ``main()`` function. - The class defines two overridable "meta switches" for version (``-v``, ``--version``) - and help (``-h``, ``--help``). - - The signature of the main function matters: any positional arguments (e.g., non-switch + define the relevant switch functions and attributes, and the ``main()`` function. + The class defines two overridable "meta switches" for version (``-v``, ``--version``) + and help (``-h``, ``--help``). + + The signature of the main function matters: any positional arguments (e.g., non-switch arguments) given on the command line are passed to the ``main()`` function; if you wish to allow unlimited number of positional arguments, use varargs (``*args``). The names of the arguments will be shown in the help message. - + The classmethod ``run`` serves as the entry point of the class. It parses the command-line - arguments, invokes switch functions and enter ``main``. You should **not override** this + arguments, invokes switch functions and enter ``main``. You should **not override** this method. - + Usage:: - + class FileCopier(Application): stat = Flag("p", "copy stat info as well") - + def main(self, src, dst): if self.stat: shutil.copy2(src, dst) else: shutil.copy(src, dst) - + if __name__ == "__main__": FileCopier.run() - + There are several class-level attributes you may set: - + * ``PROGNAME`` - the name of the program; if ``None`` (the default), it is set to the name of the executable (``argv[0]``) * ``VERSION`` - the program's version (defaults to ``1.0``) - + * ``DESCRIPTION`` - a short description of your program (shown in help) - + * ``USAGE`` - the usage line (shown in help) """ - + PROGNAME = None DESCRIPTION = None VERSION = "1.0" USAGE = "Usage: %(executable)s [SWITCHES] %(tailargs)s" - + def __init__(self, executable): if self.PROGNAME is None: self.PROGNAME = os.path.basename(executable) @@ -393,7 +393,7 @@ def __init__(self, executable): raise SwitchError("Switch %r already defined and is not overridable" % (name,)) self._switches_by_name[name] = swinfo self._switches_by_func[swinfo.func] = swinfo - + def _parse_args(self, argv): tailargs = [] swfuncs = {} @@ -405,9 +405,9 @@ def _parse_args(self, argv): # end of options, treat the rest as tailargs tailargs.extend(argv) break - + elif a.startswith("--") and len(a) >= 3: - # [--name], [--name=XXX], [--name, XXX], [--name, ==, XXX], + # [--name], [--name=XXX], [--name, XXX], [--name, ==, XXX], # [--name=, XXX], [--name, =XXX] eqsign = a.find("=") if eqsign >= 0: @@ -432,7 +432,7 @@ def _parse_args(self, argv): val = argv.pop(0) else: val = a - + elif a.startswith("-") and len(a) >= 2: # [-a], [-a, XXX], [-aXXX], [-abc] name = a[1] @@ -449,7 +449,7 @@ def _parse_args(self, argv): val = argv.pop(0) elif len(a) >= 3: argv.insert(0, "-" + a[2:]) - + else: if a.startswith("-"): raise UnknownSwitch("Unknown switch %s" % (a,)) @@ -466,7 +466,7 @@ def _parse_args(self, argv): swname, swinfo.argtype, val, ex)) else: val = NotImplemented - + if swinfo.func in swfuncs: if swinfo.list: swfuncs[swinfo.func].val[0].append(val) @@ -483,62 +483,62 @@ def _parse_args(self, argv): swfuncs[swinfo.func] = SwitchParseInfo(swname, (), index) else: swfuncs[swinfo.func] = SwitchParseInfo(swname, (val,), index) - + return swfuncs, tailargs - + def _validate_args(self, swfuncs, tailargs): if six.get_method_function(self.help) in swfuncs: raise ShowHelp() if six.get_method_function(self.version) in swfuncs: raise ShowVersion() - + requirements = {} exclusions = {} for swinfo in self._switches_by_func.values(): if swinfo.mandatory and not swinfo.func in swfuncs: - raise MissingMandatorySwitch("Switch %s is mandatory" % + raise MissingMandatorySwitch("Switch %s is mandatory" % ("/".join(("-" if len(n) == 1 else "--") + n for n in swinfo.names),)) requirements[swinfo.func] = set(self._switches_by_name[req] for req in swinfo.requires) exclusions[swinfo.func] = set(self._switches_by_name[exc] for exc in swinfo.excludes) - + # TODO: compute topological order - + gotten = set(swfuncs.keys()) for func in gotten: missing = set(f.func for f in requirements[func]) - gotten if missing: - raise SwitchCombinationError("Given %s, the following are missing %r" % + raise SwitchCombinationError("Given %s, the following are missing %r" % (swfuncs[func].swname, [self._switches_by_func[f].names[0] for f in missing])) invalid = set(f.func for f in exclusions[func]) & gotten if invalid: - raise SwitchCombinationError("Given %s, the following are invalid %r" % + raise SwitchCombinationError("Given %s, the following are invalid %r" % (swfuncs[func].swname, [swfuncs[f].swname for f in invalid])) - + m_args, m_varargs, _, m_defaults = inspect.getargspec(self.main) max_args = six.MAXSIZE if m_varargs else len(m_args) - 1 min_args = len(m_args) - 1 - (len(m_defaults) if m_defaults else 0) if len(tailargs) < min_args: - raise PositionalArgumentsError("Expected at least %d positional arguments, got %r" % + raise PositionalArgumentsError("Expected at least %d positional arguments, got %r" % (min_args, tailargs)) elif len(tailargs) > max_args: - raise PositionalArgumentsError("Expected at most %d positional arguments, got %r" % + raise PositionalArgumentsError("Expected at most %d positional arguments, got %r" % (max_args, tailargs)) - - ordered = [(f, a) for _, f, a in + + ordered = [(f, a) for _, f, a in sorted([(sf.index, f, sf.val) for f, sf in swfuncs.items()])] return ordered, tailargs - + @classmethod def run(cls, argv = sys.argv, exit = True): #@ReservedAssignment """ - Runs the application, taking the arguments from ``sys.argv`` by default. If ``exit`` is - ``True`` (the default), the function will exit with the appropriate return code; - otherwise it will return a tuple of ``(inst, retcode)``, where ``inst`` is the - application instance created internally by this function and ``retcode`` is the - exit code of the application. - + Runs the application, taking the arguments from ``sys.argv`` by default. If ``exit`` is + ``True`` (the default), the function will exit with the appropriate return code; + otherwise it will return a tuple of ``(inst, retcode)``, where ``inst`` is the + application instance created internally by this function and ``retcode`` is the + exit code of the application. + .. note:: - Setting ``exit`` to ``False`` is intendend for testing/debugging purposes only -- do + Setting ``exit`` to ``False`` is intendend for testing/debugging purposes only -- do not override it other situations. """ argv = list(argv) @@ -570,11 +570,11 @@ def run(cls, argv = sys.argv, exit = True): #@ReservedAssignment sys.exit(retcode) else: return inst, retcode - + def main(self): """Override me""" pass - + @switch(["-h", "--help"], overridable = True, group = "Meta-switches") def help(self): #@ReservedAssignment """Prints this help message and quits""" @@ -590,22 +590,22 @@ def help(self): #@ReservedAssignment if m_varargs: tailargs.append("%s..." % (m_varargs,)) tailargs = " ".join(tailargs) - + print("") - print(self.USAGE % {"executable" : self.executable, "progname" : self.PROGNAME, - "tailargs" : tailargs}) - + print(self.USAGE % {"executable" : self.executable, "progname" : self.PROGNAME, + "tailargs" : tailargs}) + by_groups = {} for si in self._switches_by_func.values(): if si.group not in by_groups: by_groups[si.group] = [] by_groups[si.group].append(si) - + for grp, swinfos in sorted(by_groups.items(), key = lambda item: item[0]): print("%s:" % (grp,)) - + for si in sorted(swinfos, key = lambda si: si.names): - swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names + swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names if self._switches_by_name[n] == si) if si.argtype: if isinstance(si.argtype, type): @@ -625,12 +625,12 @@ def help(self): #@ReservedAssignment if si.excludes: help += "; excludes %s" % (", ".join(si.excludes)) prefix = swnames + argtype - wrapper = TextWrapper(width = int(local.env.get("COLUMNS", 80)), + wrapper = TextWrapper(width = int(local.env.get("COLUMNS", 80)), initial_indent = " " * min(max(31, len(prefix)), 50), subsequent_indent = " " * 31) help = wrapper.fill(" ".join(l.strip() for l in help.splitlines())) #@ReservedAssignment print(" %-25s %s" % (prefix, help.strip())) print ("") - + @switch(["-v", "--version"], overridable = True, group = "Meta-switches") def version(self): """Prints the program's version and quits""" diff --git a/plumbum/commands.py b/plumbum/commands.py index a1fbd8358..5a5ca3905 100644 --- a/plumbum/commands.py +++ b/plumbum/commands.py @@ -27,8 +27,8 @@ def _Popen_send_signal(self, sig): #=================================================================================================== class ProcessExecutionError(Exception): """Represents the failure of a process. When the exit code of a terminated process does not - match the expected result, this exception is raised by :func:`run_proc - `. It contains the process' return code, stdout, and stderr, as + match the expected result, this exception is raised by :func:`run_proc + `. It contains the process' return code, stdout, and stderr, as well as the command line used to create the process (``argv``) """ def __init__(self, argv, retcode, stdout, stderr): @@ -52,15 +52,15 @@ def __str__(self): return "\n".join(lines) class ProcessTimedOut(Exception): - """Raises by :func:`run_proc ` when a ``timeout`` has been + """Raises by :func:`run_proc ` when a ``timeout`` has been specified and it has elapsed before the process terminated""" def __init__(self, msg, argv): Exception.__init__(self, msg, argv) self.argv = argv class CommandNotFound(Exception): - """Raised by :func:`local.which ` and - :func:`RemoteMachine.which ` when a + """Raised by :func:`local.which ` and + :func:`RemoteMachine.which ` when a command was not found in the system's ``PATH``""" def __init__(self, program, path): Exception.__init__(self, program, path) @@ -69,7 +69,7 @@ def __init__(self, program, path): class RedirectionError(Exception): """Raised when an attempt is made to redirect an process' standard handle, - which was already redirected to/from a file""" + which was already redirected to/from a file""" #=================================================================================================== @@ -131,19 +131,19 @@ def _timeout_thread(): def run_proc(proc, retcode, timeout = None): """Waits for the given process to terminate, with the expected exit code - + :param proc: a running Popen-like object - - :param retcode: the expected return (exit) code of the process. It defaults to 0 (the + + :param retcode: the expected return (exit) code of the process. It defaults to 0 (the convention for success). If ``None``, the return code is ignored. - It may also be a tuple (or any object that supports ``__contains__``) - of expected return codes. + It may also be a tuple (or any object that supports ``__contains__``) + of expected return codes. - :param timeout: the number of seconds (a ``float``) to allow the process to run, before + :param timeout: the number of seconds (a ``float``) to allow the process to run, before forcefully terminating it. If ``None``, not timeout is imposed; otherwise the process is expected to terminate within that timeout value, or it will - be killed and :class:`ProcessTimedOut ` - will be raised + be killed and :class:`ProcessTimedOut ` + will be raised :returns: A tuple of (return code, stdout, stderr) """ @@ -158,18 +158,18 @@ def run_proc(proc, retcode, timeout = None): if getattr(proc, "encoding", None): stdout = stdout.decode(proc.encoding, "ignore") stderr = stderr.decode(proc.encoding, "ignore") - + if getattr(proc, "_timed_out", False): - raise ProcessTimedOut("Process did not terminate within %s seconds" % (timeout,), + raise ProcessTimedOut("Process did not terminate within %s seconds" % (timeout,), getattr(proc, "argv", None)) - + if retcode is not None: if hasattr(retcode, "__contains__"): if proc.returncode not in retcode: - raise ProcessExecutionError(getattr(proc, "argv", None), proc.returncode, + raise ProcessExecutionError(getattr(proc, "argv", None), proc.returncode, stdout, stderr) elif proc.returncode != retcode: - raise ProcessExecutionError(getattr(proc, "argv", None), proc.returncode, + raise ProcessExecutionError(getattr(proc, "argv", None), proc.returncode, stdout, stderr) return proc.returncode, stdout, stderr @@ -178,31 +178,31 @@ def run_proc(proc, retcode, timeout = None): #=================================================================================================== class BaseCommand(object): """Base of all command objects""" - + __slots__ = ["cwd", "env", "encoding"] - + def __str__(self): return " ".join(self.formulate()) - + def __or__(self, other): """Creates a pipe with the other command""" return Pipeline(self, other) - + def __gt__(self, file): """Redirects the process' stdout to the given file""" return StdoutRedirection(self, file) - + def __ge__(self, file): """Redirects the process' stderr to the given file""" return StderrRedirection(self, file) - + def __lt__(self, file): """Redirects the given file into the process' stdin""" return StdinRedirection(self, file) def __lshift__(self, data): """Redirects the given data into the process' stdin""" return StdinDataRedirection(self, data) - + def __getitem__(self, args): """Creates a bound-command with the given arguments""" if not isinstance(args, (tuple, list)): @@ -213,7 +213,7 @@ def __getitem__(self, args): return BoundCommand(self.cmd, self.args + tuple(args)) else: return BoundCommand(self, args) - + def __call__(self, *args, **kwargs): """A shortcut for `run(args)`, returning only the process' stdout""" return self.run(args, **kwargs)[1] @@ -223,49 +223,49 @@ def _get_encoding(self): def formulate(self, level = 0, args = ()): """Formulates the command into a command-line, i.e., a list of shell-quoted strings - that can be executed by ``Popen`` or shells. - + that can be executed by ``Popen`` or shells. + :param level: The nesting level of the formulation; it dictates how much shell-quoting (if any) should be performed - + :param args: The arguments passed to this command (a tuple) - + :returns: A list of strings """ raise NotImplementedError() def popen(self, args = (), **kwargs): """Spawns the given command, returning a ``Popen``-like object. - + :param args: Any arguments to be passed to the process (a tuple) - + :param kwargs: Any keyword-arguments to be passed to the ``Popen`` constructor - + :returns: A ``Popen``-like object """ raise NotImplementedError() - + def run(self, args = (), **kwargs): - """Runs the given command (equivalent to popen() followed by + """Runs the given command (equivalent to popen() followed by :func:`run_proc `). If the exit code of the process does - not match the expected one, :class:`ProcessExecutionError + not match the expected one, :class:`ProcessExecutionError ` is raised. - + :param args: Any arguments to be passed to the process (a tuple) - + :param retcode: The expected return code of this process (defaults to 0). In order to disable exit-code validation, pass ``None``. It may also be a tuple (or any iterable) of expected exit codes. - + .. note:: this argument must be passed as a keyword argument. - + :param timeout: The maximal amount of time (in seconds) to allow the process to run. ``None`` means no timeout is imposed; otherwise, if the process hasn't - terminated after that many seconds, the process will be forcefully + terminated after that many seconds, the process will be forcefully terminated an exception will be raised - + :param kwargs: Any keyword-arguments to be passed to the ``Popen`` constructor - + :returns: A tuple of (return code, stdout, stderr) """ retcode = kwargs.pop("retcode", 0) @@ -292,7 +292,7 @@ def _get_encoding(self): def formulate(self, level = 0, args = ()): return self.cmd.formulate(level + 1, self.args + tuple(args)) def popen(self, args = (), **kwargs): - if isinstance(args, str): + if isinstance(args, basestring): args = (args,) return self.cmd.popen(self.args + tuple(args), **kwargs) @@ -312,7 +312,7 @@ def popen(self, args = (), **kwargs): src_kwargs = kwargs.copy() src_kwargs["stdout"] = PIPE src_kwargs["stderr"] = PIPE - + srcproc = self.srccmd.popen(args, **src_kwargs) kwargs["stdin"] = srcproc.stdout dstproc = self.dstcmd.popen(**kwargs) @@ -329,7 +329,7 @@ class BaseRedirection(BaseCommand): SYM = None KWARG = None MODE = None - + def __init__(self, cmd, file): self.cmd = cmd self.file = file @@ -342,10 +342,10 @@ def formulate(self, level = 0, args = ()): def popen(self, args = (), **kwargs): from plumbum.local_machine import LocalPath from plumbum.remote_machine import RemotePath - + if self.KWARG in kwargs and kwargs[self.KWARG] not in (PIPE, None): raise RedirectionError("%s is already redirected" % (self.KWARG,)) - if isinstance(self.file, (str, LocalPath)): + if isinstance(self.file, (basestring, LocalPath)): f = kwargs[self.KWARG] = open(str(self.file), self.MODE) elif isinstance(self.file, RemotePath): raise TypeError("Cannot redirect to/from remote paths") @@ -386,13 +386,13 @@ def __str__(self): class StdinDataRedirection(BaseCommand): __slots__ = ["cmd", "data"] CHUNK_SIZE = 16000 - + def __init__(self, cmd, data): self.cmd = cmd self.data = data def _get_encoding(self): return self.cmd._get_encoding() - + def formulate(self, level = 0, args = ()): return ["echo %s" % (shquote(self.data),), "|", self.cmd.formulate(level + 1, args)] def popen(self, args = (), **kwargs): @@ -473,7 +473,7 @@ def __repr__(self): return "" % (self.proc.argv, self._returncode if self.ready() else "running",) def poll(self): """Polls the underlying process for termination; returns ``None`` if still running, - or the process' returncode if terminated""" + or the process' returncode if terminated""" if self.proc.poll() is not None: self.wait() return self._returncode is not None @@ -483,7 +483,7 @@ def wait(self): :class:`plumbum.commands.ProcessExecutionError` in case of failure""" if self._returncode is not None: return - self._returncode, self._stdout, self._stderr = run_proc(self.proc, + self._returncode, self._stdout, self._stderr = run_proc(self.proc, self._expected_retcode, self._timeout) @property def stdout(self): @@ -503,12 +503,12 @@ def returncode(self): class BG(ExecutionModifier): """ - An execution modifier that runs the given command in the background, returning a + An execution modifier that runs the given command in the background, returning a :class:`Future ` object. In order to mimic shell syntax, it applies when you right-and it with a command. If you wish to expect a different return code (other than the normal success indicate by 0), use ``BG(retcode)``. Example:: - - future = sleep[5] & BG # a future expecting an exit code of 0 + + future = sleep[5] & BG # a future expecting an exit code of 0 future = sleep[5] & BG(7) # a future expecting an exit code of 7 """ __slots__ = [] @@ -517,12 +517,12 @@ def __rand__(self, cmd): BG = BG() """ -An execution modifier that runs the given command in the background, returning a +An execution modifier that runs the given command in the background, returning a :class:`Future ` object. In order to mimic shell syntax, it applies when you right-and it with a command. If you wish to expect a different return code (other than the normal success indicate by 0), use ``BG(retcode)``. Example:: - - future = sleep[5] & BG # a future expecting an exit code of 0 + + future = sleep[5] & BG # a future expecting an exit code of 0 future = sleep[5] & BG(7) # a future expecting an exit code of 7 """ @@ -531,12 +531,12 @@ class FG(ExecutionModifier): An execution modifier that runs the given command in the foreground, passing it the current process' stdin, stdout and stderr. Useful for interactive programs that require a TTY. There is no return value. - - In order to mimic shell syntax, it applies when you right-and it with a command. - If you wish to expect a different return code (other than the normal success indicate by 0), + + In order to mimic shell syntax, it applies when you right-and it with a command. + If you wish to expect a different return code (other than the normal success indicate by 0), use ``BG(retcode)``. Example:: - - vim & FG # run vim in the foreground, expecting an exit code of 0 + + vim & FG # run vim in the foreground, expecting an exit code of 0 vim & FG(7) # run vim in the foreground, expecting an exit code of 7 """ __slots__ = [] @@ -549,11 +549,11 @@ def __rand__(self, cmd): current process' stdin, stdout and stderr. Useful for interactive programs that require a TTY. There is no return value. -In order to mimic shell syntax, it applies when you right-and it with a command. -If you wish to expect a different return code (other than the normal success indicate by 0), +In order to mimic shell syntax, it applies when you right-and it with a command. +If you wish to expect a different return code (other than the normal success indicate by 0), use ``BG(retcode)``. Example:: - - vim & FG # run vim in the foreground, expecting an exit code of 0 + + vim & FG # run vim in the foreground, expecting an exit code of 0 vim & FG(7) # run vim in the foreground, expecting an exit code of 7 """ diff --git a/plumbum/local_machine.py b/plumbum/local_machine.py index 99de7a502..a1487a1c6 100644 --- a/plumbum/local_machine.py +++ b/plumbum/local_machine.py @@ -421,7 +421,7 @@ def __repr__(self): return "LocalCommand(%r)" % (self.executable,) def popen(self, args = (), cwd = None, env = None, **kwargs): - if isinstance(args, str): + if isinstance(args, basestring): args = (args,) return local._popen(self.executable, self.formulate(0, args), cwd = self.cwd if cwd is None else cwd, env = self.env if env is None else env, @@ -498,9 +498,9 @@ def which(cls, progname): raise CommandNotFound(progname, list(cls.env.path)) def path(self, *parts): - """A factory for :class:`LocalPaths `. + """A factory for :class:`LocalPaths `. Usage :: - + p = local.path("/usr", "lib", "python2.7") """ parts2 = [str(self.cwd)] @@ -520,7 +520,7 @@ def __getitem__(self, cmd): """ if isinstance(cmd, LocalPath): return LocalCommand(cmd) - elif isinstance(cmd, str): + elif not isinstance(cmd, RemotePath): if "/" in cmd or "\\" in cmd: # assume path return LocalCommand(local.path(cmd)) @@ -528,7 +528,7 @@ def __getitem__(self, cmd): # search for command return LocalCommand(self.which(cmd)) else: - raise TypeError("cmd must be a LocalPath or a string: %r" % (cmd,)) + raise TypeError("cmd must not be a RemotePath: %r" % (cmd,)) def _popen(self, executable, argv, stdin = PIPE, stdout = PIPE, stderr = PIPE, cwd = None, env = None, **kwargs): diff --git a/plumbum/remote_machine.py b/plumbum/remote_machine.py index 35d102d32..36d45e52e 100644 --- a/plumbum/remote_machine.py +++ b/plumbum/remote_machine.py @@ -68,7 +68,7 @@ def update(self, *args, **kwargs): BaseEnv.update(self, *args, **kwargs) self.remote._session.run("export " + " ".join("%s=%s" % (k, shquote(v)) for k, v in self.getdict().items())) - + def expand(self, expr): """Expands any environment variables and home shortcuts found in ``expr`` (like ``os.path.expanduser`` combined with ``os.path.expandvars``) @@ -78,7 +78,7 @@ def expand(self, expr): :returns: The expanded string""" return self.remote._session.run("echo %s" % (expr,)) - + def expanduser(self, expr): """Expand home shortcuts (e.g., ``~/foo/bar`` or ``~john/foo/bar``) @@ -180,9 +180,9 @@ def close(self): self._session = ClosedRemote(self) def path(self, *parts): - """A factory for :class:`RemotePaths `. + """A factory for :class:`RemotePaths `. Usage :: - + p = rem.path("/usr", "lib", "python2.7") """ parts2 = [str(self.cwd)] @@ -228,13 +228,13 @@ def __getitem__(self, cmd): return RemoteCommand(self, cmd) else: raise TypeError("Given path does not belong to this remote machine: %r" % (cmd,)) - elif isinstance(cmd, str): + elif not isinstance(cmd, LocalPath): if "/" in cmd or "\\" in cmd: return RemoteCommand(self, self.path(cmd)) else: return RemoteCommand(self, self.which(cmd)) else: - raise TypeError("cmd must be a path or a string: %r" % (cmd,)) + raise TypeError("cmd must not be a LocalPath: %r" % (cmd,)) @property def python(self): diff --git a/plumbum/remote_path.py b/plumbum/remote_path.py index 1368d23d3..2ca1a2e55 100644 --- a/plumbum/remote_path.py +++ b/plumbum/remote_path.py @@ -138,7 +138,7 @@ def delete(self): def move(self, dst): if isinstance(dst, RemotePath) and dst.remote is not self.remote: raise TypeError("dst points to a different remote machine") - elif not isinstance(dst, str): + elif not isinstance(dst, basestring): raise TypeError("dst must be a string or a RemotePath (to the same remote machine)") self.remote._session.run("mv %s %s" % (shquote(self), shquote(dst))) @@ -147,10 +147,10 @@ def copy(self, dst, override = False): if isinstance(dst, RemotePath): if dst.remote is not self.remote: raise TypeError("dst points to a different remote machine") - elif not isinstance(dst, str): + elif not isinstance(dst, basestring): raise TypeError("dst must be a string or a RemotePath (to the same remote machine)", repr(dst)) if override: - if isinstance(dst, str): + if isinstance(dst, basestring): dst = RemotePath(self.remote, dst) dst.remove() self.remote._session.run("cp -r %s %s" % (shquote(self), shquote(dst))) diff --git a/plumbum/utils.py b/plumbum/utils.py index fceea1a81..4b7d47cba 100644 --- a/plumbum/utils.py +++ b/plumbum/utils.py @@ -3,15 +3,15 @@ from plumbum.local_machine import local, LocalPath def delete(*paths): - """Deletes the given paths. The arguments can be either strings, - :class:`local paths `, + """Deletes the given paths. The arguments can be either strings, + :class:`local paths `, :class:`remote paths `, or iterables of such. No error is raised if any of the paths does not exist (it is silently ignored) """ for p in paths: if isinstance(p, Path): p.delete() - elif isinstance(p, str): + elif isinstance(p, basestring): local.path(p).delete() elif hasattr(p, "__iter__"): delete(*p) @@ -25,15 +25,15 @@ def _move(src, dst): def move(src, dst): """Moves the source path onto the destination path; ``src`` and ``dst`` can be either - strings, :class:`LocalPaths ` or - :class:`RemotePath `; any combination of the three will + strings, :class:`LocalPaths ` or + :class:`RemotePath `; any combination of the three will work. """ if not isinstance(src, Path): src = local.path(src) if not isinstance(dst, Path): dst = local.path(dst) - + if isinstance(src, LocalPath): if isinstance(dst, LocalPath): return src.move(dst) @@ -49,16 +49,16 @@ def move(src, dst): def copy(src, dst): """ - Copy (recursively) the source path onto the destination path; ``src`` and ``dst`` can be - either strings, :class:`LocalPaths ` or - :class:`RemotePath `; any combination of the three will - work. + Copy (recursively) the source path onto the destination path; ``src`` and ``dst`` can be + either strings, :class:`LocalPaths ` or + :class:`RemotePath `; any combination of the three will + work. """ if not isinstance(src, Path): src = local.path(src) if not isinstance(dst, Path): dst = local.path(dst) - + if isinstance(src, LocalPath): if isinstance(dst, LocalPath): return src.copy(dst)