diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py index a845fd358..8bbd73133 100644 --- a/xonsh/built_ins.py +++ b/xonsh/built_ins.py @@ -367,7 +367,7 @@ def no_pg_xonsh_preexec_fn(): signal.signal(signal.SIGTSTP, default_signal_pauser) -class SubprocCmd: +class SubprocSpec: """A container for specifiying how a subprocess command should be executed. """ @@ -560,16 +560,16 @@ class SubprocCmd: was recieved. """ # modifications that do not alter cmds may come before creating instance - p = kls(cmd, cls=cls, **kwargs) + spec = kls(cmd, cls=cls, **kwargs) # modifications that alter cmds must come after creating instance - self.redirect_leading() - self.redirect_trailing() - self.resolve_alias() - self.resolve_binary_loc() - self.resolve_auto_cd() - self.resolve_executable_commands() - self.resolve_alias_cls() - return p + spec.redirect_leading() + spec.redirect_trailing() + spec.resolve_alias() + spec.resolve_binary_loc() + sped.resolve_auto_cd() + spec.resolve_executable_commands() + spec.resolve_alias_cls() + return spec def redirect_leading(self): """Manage leading redirects such as with '< input.txt COMMAND'. """ @@ -664,7 +664,7 @@ def stdout_capture_kinds(): return frozenset(['stdout', 'object']) -def _update_last_subproc(last, captured=False) +def _update_last_spec(last, captured=False) env = builtins.__xonsh_env__ # set standard in if (last.stdin is not None and captured == 'object' and @@ -704,32 +704,32 @@ def _update_last_subproc(last, captured=False) last.captured_stderr = last.stderr -def cmds_to_subprocs(cmds, captured=False): - """Converts a list of cmds to a list of SubprocCmd objects that are +def cmds_to_specs(cmds, captured=False): + """Converts a list of cmds to a list of SubprocSpec objects that are ready to be executed. """ # first build the subprocs independently and separate from the redirects - subprocs = [] + specs = [] redirects = [] for cmd in cmds: if isinstance(cmd, str): redirects.append(cmd) else: - subproc = SubprocCmd.build(cmd) - subprocs.append(subproc) + spec = SubprocSpec.build(cmd) + spes.append(spec) # now modify the subprocs based on the redirects. for i, redirect in enumerate(redirects): if redirect == '|': r, w = os.pipe() - subprocs[i].stdout = w - subprocs[i + 1].stdin = r + specs[i].stdout = w + specs[i + 1].stdin = r elif redirect == '&' and i == len(redirects) - 1: - subprocs[-1].background = True + specs[-1].background = True else: raise XonshError('unrecognized redirect {0!r}'.format(redirect)) # Apply boundry conditions - _update_last_subproc(subproc[-1], captured=captured) - return subprocs + _update_last_spec(spec[-1], captured=captured) + return specs def _should_set_title(captured=False): @@ -754,22 +754,22 @@ def run_subproc(cmds, captured=False): Lastly, the captured argument affects only the last real command. """ env = builtins.__xonsh_env__ - subprocs = cmds_to_subprocs(cmds, captured=captured) + specs = cmds_to_specs(cmds, captured=captured) procs = [] proc = pipeline_group = None - for subproc in subprocs: + for spec in specs: starttime = time.time() - proc = subproc.run(pipeline_group=pipeline_group) + proc = spec.run(pipeline_group=pipeline_group) procs.append(proc) if ON_POSIX and pipeline_group is None and \ - subproc.cls is subprocess.Popen: - pipeline_group = prev_proc.pid + spec.cls is subprocess.Popen: + pipeline_group = proc.pid if not subproc.is_proxy: add_job({ 'cmds': cmds, 'pids': [i.pid for i in procs], 'obj': proc, - 'bg': subproc.background + 'bg': spec.background }) procinfo = {} if _should_set_title(captured=captured): diff --git a/xonsh/proc.py b/xonsh/proc.py index 8a2ebce5f..92b4910f0 100644 --- a/xonsh/proc.py +++ b/xonsh/proc.py @@ -552,31 +552,41 @@ def _wcode_to_popen(code): raise ValueError("Invalid os.wait code: {}".format(code)) -_CCTuple = collections.namedtuple("_CCTuple", ["stdin", "stdout", "stderr", - "pid", "returncode", "args", "alias", "stdin_redirect", - "stdout_redirect", "stderr_redirect", "timestamp", - "executed_cmd"]) +class Command: + """Represents a subprocess-mode command.""" + attrnames = ("stdin", "stdout", "stderr", "pid", "returncode", "args", + "alias", "stdin_redirect", "stdout_redirect", + "stderr_redirect", "timestamp", "executed_cmd") -class CompletedCommand(_CCTuple): - """Represents a completed subprocess-mode command.""" + def __init__(self, spec, proc, timestamp): + """ + Parameters + ---------- + spec : SubprocSpec + Process sepcification + proc : Popen-like + Process object. + """ + self.proc = proc + self.spec = spec + self.timestamp = timestamp def __bool__(self): return self.returncode == 0 def __iter__(self): - if not self.stdout: + proc = self.proc + stdout = proc.stdout + if not stdout: + if not proc.poll(): + proc.wait() + self.endtime() raise StopIteration() - - pre = self.stdout - post = None - - while post != '': - pre, sep, post = pre.partition('\n') - # this line may be optional since we use universal newlines. - pre = pre[:-1] if pre and pre[-1] == '\r' else pre - yield pre - pre = post + while not proc.poll(): + yield from stdout.readlines(1024) + self.endtime() + yield from stdout.readlines() def itercheck(self): yield from self @@ -586,31 +596,106 @@ class CompletedCommand(_CCTuple): raise XonshCalledProcessError(self.returncode, self.executed_cmd, self.stdout, self.stderr, self) + def endtime(self): + """Sets the closing timestamp if it hasn't been already.""" + if self.timestamp[1] is None: + self.timestamp[1] = time.time() + + # + # Properties + # + + @propertybh + def stdin(self): + """Process stdin.""" + return self.proc.stdin + @property def inp(self): """Creates normalized input string from args.""" return ' '.join(self.args) @property - def out(self): - """Alias to stdout.""" - return self.stdout + def stdout(self): + """Process stdout.""" + return self.proc.stdout + + out = stdout @property - def err(self): - """Alias to stderr.""" - return self.stderr + def stderr(self): + """Process stderr.""" + return self.proc.stderr + + err = stdout + + @property + def pid(self): + """Process identifier.""" + return self.proc.pid + + @property + def returncode(self): + """Process return code, waits until command is completed.""" + proc = self.proc + if proc.returncode is None: + proc.wait() + self.endtime() + return proc.returncode + + rtn = returncode @property def rtn(self): """Alias to return code.""" return self.returncode + @property + def args(self): + """Arguments to the process.""" + return self.spec.args -CompletedCommand.__new__.__defaults__ = (None,) * len(CompletedCommand._fields) + @property + def rtn(self): + """Alias to return code.""" + return self.returncode + + @property + def alias(self): + """Alias the process used.""" + return self.spec.alias + + @property + def stdin_redirect(self): + """Redirection used for stdin.""" + stdin = self.spec.stdin + name = getattr(stdin, 'name', '') + mode = getattr(stdin, 'mode', 'r') + return [name, mode] + + @property + def sdtout_redirect(self): + """Redirection used for stdout.""" + stdout = self.spec.stdout + name = getattr(stdout, 'name', '') + mode = getattr(stdout, 'mode', 'a') + return [name, mode] + + @property + def stderr_redirect(self): + """Redirection used for stderr.""" + stderr = self.spec.stderr + name = getattr(stderr, 'name', '') + mode = getattr(stderr, 'mode', 'r') + return [name, mode] + + @propery + def executed_cmd(self): + """The resolve and executed command.""" + return self.spec.cmd -class HiddenCompletedCommand(CompletedCommand): +class HiddenCommand(Command): def __repr__(self): return ''