Alias that returns modified command (#5473)

* Command Alias

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* news

* docs

* tests

* docs

* wip

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tests

* tests

* clean

* news

* news

* bumptests

* bumptests

* new api

* tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* doooocs

* comments

* comments

* comments

* aliases.CUT_ARGS

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* aliases.CUT_ARGS

* aliases.CUT_ARGS

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* aliases.CUT_ARGS

* aliases.CUT_ARGS

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* aliases.CUT_ARGS

* comments

* bump test

* remove CUT_ARGS

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* test

* test

* wip

* revert

* wip

* wip

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip

* docs

* news

* tests

* tests

* test

* test

* test

* Update docs/tutorial.rst

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>

* Update xonsh/aliases.py

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix, thanks jaraco!

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* more comments

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* cleaning

---------

Co-authored-by: a <1@1.1>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
Andy Kipp 2024-07-12 01:35:20 +02:00 committed by GitHub
parent 24fcbb4638
commit 87032f6d30
Failed to generate hash of commit
7 changed files with 382 additions and 51 deletions

View file

@ -1275,7 +1275,7 @@ functions. If you don't know what these do, you probably don't need them.
Aliases Aliases
============================== =======
Another important xonsh built-in is the ``aliases`` mapping. This is Another important xonsh built-in is the ``aliases`` mapping. This is
like a dictionary that affects how subprocess commands are run. If you are like a dictionary that affects how subprocess commands are run. If you are
familiar with the Bash ``alias`` built-in, this is similar. Alias command familiar with the Bash ``alias`` built-in, this is similar. Alias command
@ -1305,6 +1305,44 @@ If you were to run ``gco feature-fabulous`` with the above aliases in effect,
the command would reduce to ``['git', 'checkout', 'feature-fabulous']`` before the command would reduce to ``['git', 'checkout', 'feature-fabulous']`` before
being executed. being executed.
Alias to modify command
-----------------------
The best way to modify command on the fly is to use alias that returns modified command.
One of the most interesting application is expanding an alias:
.. code-block:: xonshcon
>>> @aliases.register
... @aliases.return_command
... def _xsudo(args):
... """Sudo with expanding aliases."""
... return ['sudo', '--', *aliases.eval_alias(args)]
...
>>> aliases['install'] = "apt install cowsay"
>>> xsudo install
# Password:
# Install cowsay
Or implement logic to run the right command:
.. code-block:: xonshcon
>>> @aliases.register
... @aliases.return_command
... def _vi(args):
... """Universal vi editor."""
... if $(which vim 2>/dev/null):
... return ['vim'] + args
... else:
... return ['vi'] + args
...
>>> vi file
ExecAlias
---------
If the string is representing a block of xonsh code, the alias will be registered If the string is representing a block of xonsh code, the alias will be registered
as an ``ExecAlias``, which is a callable alias. This block of code will then be as an ``ExecAlias``, which is a callable alias. This block of code will then be
executed whenever the alias is run. The arguments are available in the list ``$args`` executed whenever the alias is run. The arguments are available in the list ``$args``

23
news/alias_return_cmd.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Added ``@aliases.return_command`` decorator to eliminate the need to wrap the logic for modifying command into callable alias wrapper (#5473).
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -183,6 +183,7 @@ def test_interrupted_process_returncode(xonsh_session, captured, interactive):
@skip_if_on_windows @skip_if_on_windows
@pytest.mark.flaky(reruns=3, reruns_delay=1)
def test_proc_raise_subproc_error(xonsh_session): def test_proc_raise_subproc_error(xonsh_session):
xonsh_session.env["RAISE_SUBPROC_ERROR"] = False xonsh_session.env["RAISE_SUBPROC_ERROR"] = False
@ -469,3 +470,110 @@ def test_partial_args_from_classmethod(xession):
xession.aliases["alias_with_partial_args"] = Class.alias xession.aliases["alias_with_partial_args"] = Class.alias
out = run_subproc([["alias_with_partial_args"]], captured="stdout") out = run_subproc([["alias_with_partial_args"]], captured="stdout")
assert out == "ok" assert out == "ok"
def test_alias_return_command_alone(xession):
@xession.aliases.register("wakka")
@xession.aliases.return_command
def _wakka(args):
return ["echo"] + args
cmds = [
["wakka"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo"]
assert spec.alias_name == "wakka"
def test_alias_return_command_alone_args(xession):
@xession.aliases.register("wakka")
@xession.aliases.return_command
def _wakka(args):
return ["echo", "e0", "e1"] + args
cmds = [
["wakka", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "e0", "e1", "0", "1"]
assert spec.alias_name == "wakka"
def test_alias_return_command_chain(xession):
xession.aliases["foreground"] = "midground f0 f1"
@xession.aliases.register("midground")
@xession.aliases.return_command
def _midground(args):
return ["ground", "m0", "m1"] + args
xession.aliases["ground"] = "background g0 g1"
xession.aliases["background"] = "echo b0 b1"
cmds = [
["foreground", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == [
"echo",
"b0",
"b1",
"g0",
"g1",
"m0",
"m1",
"f0",
"f1",
"0",
"1",
]
assert spec.alias_name == "foreground"
def test_alias_return_command_chain_spec_modifiers(xession):
xession.aliases["foreground"] = "midground f0 f1"
xession.aliases["xunthread"] = SpecAttrModifierAlias(
{"threadable": False, "force_threadable": False}
)
@xession.aliases.register("midground")
@xession.aliases.return_command
def _midground(args):
return ["ground", "m0", "m1"]
xession.aliases["ground"] = "background g0 g1"
xession.aliases["background"] = "xunthread echo b0 b1"
cmds = [
["foreground", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "b0", "b1", "g0", "g1", "m0", "m1"]
assert spec.alias_name == "foreground"
assert spec.threadable is False
def test_alias_return_command_eval_inside(xession):
xession.aliases["xthread"] = SpecAttrModifierAlias(
{"threadable": True, "force_threadable": True}
)
@xession.aliases.register("xsudo")
@xession.aliases.return_command
def _midground(args, spec_modifiers=None):
return [
"sudo",
*xession.aliases.eval_alias(args, spec_modifiers=spec_modifiers),
]
xession.aliases["cmd"] = "xthread echo 1"
cmds = [
["xsudo", "cmd"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["sudo", "echo", "1"]
assert spec.alias_name == "xsudo"
assert spec.threadable is True

View file

@ -6,7 +6,7 @@ import sys
import pytest import pytest
from xonsh.aliases import Aliases, ExecAlias from xonsh.aliases import Aliases, ExecAlias, run_alias_by_params
def cd(args, stdin=None): def cd(args, stdin=None):
@ -53,10 +53,17 @@ def test_eval_recursive(xession):
assert ales.get("color_ls") == ["ls", "- -", "--color=true"] assert ales.get("color_ls") == ["ls", "- -", "--color=true"]
def test_eval_callable(xession):
ales = make_aliases()
resolved = ales.get(["cd", "tmp"])
assert callable(resolved[0])
assert isinstance(resolved[1], str)
def test_eval_recursive_callable_partial(xonsh_execer, xession): def test_eval_recursive_callable_partial(xonsh_execer, xession):
ales = make_aliases() ales = make_aliases()
xession.env["HOME"] = os.path.expanduser("~") xession.env["HOME"] = os.path.expanduser("~")
assert ales.get("indirect_cd")(["arg2", "arg3"]) == ["..", "arg2", "arg3"] assert ales.get(["indirect_cd", "arg2", "arg3"])[1:] == ["..", "arg2", "arg3"]
def _return_to_sender_all(args, stdin, stdout, stderr, spec, stack): def _return_to_sender_all(args, stdin, stdout, stderr, spec, stack):
@ -74,9 +81,11 @@ def _return_to_sender_all(args, stdin, stdout, stderr, spec, stack):
def test_recursive_callable_partial_all(xession): def test_recursive_callable_partial_all(xession):
ales = Aliases({"rtn": _return_to_sender_all, "rtn-recurse": ["rtn", "arg1"]}) ales = Aliases({"rtn": _return_to_sender_all, "rtn-recurse": ["rtn", "arg1"]})
alias = ales.get("rtn-recurse") alias = ales.get("rtn-recurse")[0]
assert callable(alias) assert callable(alias)
args, obs = alias(["arg2"], stdin="a", stdout="b", stderr="c", spec="d", stack="e") args, obs = alias(
["arg1", "arg2"], stdin="a", stdout="b", stderr="c", spec="d", stack="e"
)
assert args == ["arg1", "arg2"] assert args == ["arg1", "arg2"]
assert len(obs) == 5 assert len(obs) == 5
exp = {"stdin": "a", "stdout": "b", "stderr": "c", "spec": "d", "stack": "e"} exp = {"stdin": "a", "stdout": "b", "stderr": "c", "spec": "d", "stack": "e"}
@ -89,9 +98,9 @@ def _return_to_sender_handles(args, stdin, stdout, stderr):
def test_recursive_callable_partial_handles(xession): def test_recursive_callable_partial_handles(xession):
ales = Aliases({"rtn": _return_to_sender_handles, "rtn-recurse": ["rtn", "arg1"]}) ales = Aliases({"rtn": _return_to_sender_handles, "rtn-recurse": ["rtn", "arg1"]})
alias = ales.get("rtn-recurse") alias = ales.get("rtn-recurse")[0]
assert callable(alias) assert callable(alias)
args, obs = alias(["arg2"], stdin="a", stdout="b", stderr="c") args, obs = alias(["arg1", "arg2"], stdin="a", stdout="b", stderr="c")
assert args == ["arg1", "arg2"] assert args == ["arg1", "arg2"]
assert len(obs) == 3 assert len(obs) == 3
exp = {"stdin": "a", "stdout": "b", "stderr": "c"} exp = {"stdin": "a", "stdout": "b", "stderr": "c"}
@ -104,7 +113,7 @@ def _return_to_sender_none():
def test_recursive_callable_partial_none(xession): def test_recursive_callable_partial_none(xession):
ales = Aliases({"rtn": _return_to_sender_none, "rtn-recurse": ["rtn"]}) ales = Aliases({"rtn": _return_to_sender_none, "rtn-recurse": ["rtn"]})
alias = ales.get("rtn-recurse") alias = ales.get("rtn-recurse")[0]
assert callable(alias) assert callable(alias)
args, obs = alias() args, obs = alias()
assert args == "wakka" assert args == "wakka"
@ -214,3 +223,26 @@ def test_register_decorator(xession):
def _private(): ... def _private(): ...
assert set(aliases) == {"debug", "name", "private"} assert set(aliases) == {"debug", "name", "private"}
def test_run_alias_by_params():
def alias_named_params(args, stdout):
return (args, stdout)
def alias_named_params_rev(stdout, args):
return (args, stdout)
def alias_list_params(a, i, o, e):
return (a, i, o, e)
assert run_alias_by_params(alias_named_params, {"args": 1, "stdout": 2}) == (1, 2)
assert run_alias_by_params(alias_named_params_rev, {"args": 1, "stdout": 2}) == (
1,
2,
)
assert run_alias_by_params(alias_list_params, {"args": 1, "stderr": 4}) == (
1,
None,
None,
4,
)

View file

@ -1,15 +1,17 @@
"""Aliases for the xonsh shell.""" """Aliases for the xonsh shell."""
import argparse import argparse
import collections.abc as cabc
import functools import functools
import inspect import inspect
import operator
import os import os
import re import re
import shutil import shutil
import sys import sys
import types import types
import typing as tp import typing as tp
from collections import abc as cabc
from typing import Literal
import xonsh.completers._aliases as xca import xonsh.completers._aliases as xca
import xonsh.history.main as xhm import xonsh.history.main as xhm
@ -42,6 +44,7 @@ from xonsh.tools import (
argvquote, argvquote,
escape_windows_cmd_string, escape_windows_cmd_string,
print_color, print_color,
print_exception,
strip_simple_quotes, strip_simple_quotes,
swap_values, swap_values,
to_repr_pretty_, to_repr_pretty_,
@ -59,8 +62,9 @@ def EXEC_ALIAS_RE():
class FuncAlias: class FuncAlias:
"""Provides a callable alias for xonsh commands.""" """Provides a callable alias for xonsh commands."""
attributes_show = ["__xonsh_threadable__", "__xonsh_capturable__"] attributes_show = ["__xonsh_threadable__", "__xonsh_capturable__", "return_what"]
attributes_inherit = attributes_show + ["__doc__"] attributes_inherit = attributes_show + ["__doc__"]
return_what: Literal["command", "result"] = "result"
def __init__(self, name, func=None): def __init__(self, name, func=None):
self.__name__ = self.name = name self.__name__ = self.name = name
@ -79,12 +83,27 @@ class FuncAlias:
return f"FuncAlias({repr(r)})" return f"FuncAlias({repr(r)})"
def __call__( def __call__(
self, args=None, stdin=None, stdout=None, stderr=None, spec=None, stack=None self,
args=None,
stdin=None,
stdout=None,
stderr=None,
spec=None,
stack=None,
spec_modifiers=None,
): ):
func_args = [args, stdin, stdout, stderr, spec, stack][ return run_alias_by_params(
: len(inspect.signature(self.func).parameters) self.func,
] {
return self.func(*func_args) "args": args,
"stdin": stdin,
"stdout": stdout,
"stderr": stderr,
"spec": spec,
"stack": stack,
"spec_modifiers": spec_modifiers,
},
)
class Aliases(cabc.MutableMapping): class Aliases(cabc.MutableMapping):
@ -132,28 +151,17 @@ class Aliases(cabc.MutableMapping):
return wrapper return wrapper
def get(self, key, default=None, spec_modifiers=None): def return_command(self, f):
"""Returns the (possibly modified) value. If the key is not present, """Decorator that switches alias from returning result to return in new command for execution."""
then `default` is returned. f.return_what = "command"
If the value is callable, it is returned without modification. If it return f
is an iterable of strings it will be evaluated recursively to expand
other aliases, resulting in a new list or a "partially applied"
callable.
"""
spec_modifiers = spec_modifiers if spec_modifiers is not None else []
val = self._raw.get(key)
if val is None:
return default
elif isinstance(val, cabc.Iterable) or callable(val):
return self.eval_alias(
val, seen_tokens={key}, spec_modifiers=spec_modifiers
)
else:
msg = "alias of {!r} has an inappropriate type: {!r}"
raise TypeError(msg.format(key, val))
def eval_alias( def eval_alias(
self, value, seen_tokens=frozenset(), acc_args=(), spec_modifiers=None self,
value,
seen_tokens=frozenset(),
acc_args=(),
spec_modifiers=None,
): ):
""" """
"Evaluates" the alias ``value``, by recursively looking up the leftmost "Evaluates" the alias ``value``, by recursively looking up the leftmost
@ -182,8 +190,18 @@ class Aliases(cabc.MutableMapping):
break break
value = value[i:] value = value[i:]
if callable(value) and getattr(value, "return_what", "result") == "command":
try:
value = value(acc_args, spec_modifiers=spec_modifiers)
acc_args = []
except Exception as e:
print_exception(f"Exception inside alias {value}: {e}")
return None
if not len(value):
raise ValueError("return_command alias: zero arguments.")
if callable(value): if callable(value):
return partial_eval_alias(value, acc_args=acc_args) return [value] + list(acc_args)
else: else:
expand_path = XSH.expand_path expand_path = XSH.expand_path
token, *rest = map(expand_path, value) token, *rest = map(expand_path, value)
@ -205,6 +223,54 @@ class Aliases(cabc.MutableMapping):
spec_modifiers=spec_modifiers, spec_modifiers=spec_modifiers,
) )
def get(
self,
key,
default=None,
spec_modifiers=None,
):
"""
Returns list that represent command with resolved aliases.
The ``key`` can be string with alias name or list for a command.
In the first position will be the resolved command name or callable alias.
If the key is not present, then `default` is returned.
``spec_modifiers`` is the list of SpecModifier objects that found during
resolving aliases (#5443).
Note! The return value is always list because during resolving
we can find return_command alias that can completely replace
command and add new arguments.
"""
spec_modifiers = spec_modifiers if spec_modifiers is not None else []
args = []
if isinstance(key, list):
args = key[1:]
key = key[0]
val = self._raw.get(key)
if callable(val) and getattr(val, "return_what", "result") == "command":
try:
val = val(args, spec_modifiers=spec_modifiers)
args = []
except Exception as e:
print_exception(f"Exception inside alias {key!r}: {e}")
return None
if not len(val):
raise ValueError("return_command alias: zero arguments.")
if val is None:
return default
elif isinstance(val, cabc.Iterable) or callable(val):
return self.eval_alias(
val,
seen_tokens={key},
spec_modifiers=spec_modifiers,
acc_args=args,
)
else:
msg = "alias of {!r} has an inappropriate type: {!r}"
raise TypeError(msg.format(key, val))
def expand_alias(self, line: str, cursor_index: int) -> str: def expand_alias(self, line: str, cursor_index: int) -> str:
"""Expands any aliases present in line if alias does not point to a """Expands any aliases present in line if alias does not point to a
builtin function and if alias is only a single command. builtin function and if alias is only a single command.
@ -408,6 +474,21 @@ class PartialEvalAlias6(PartialEvalAliasBase):
return self.f(args, stdin, stdout, stderr, spec, stack) return self.f(args, stdin, stdout, stderr, spec, stack)
class PartialEvalAlias7(PartialEvalAliasBase):
def __call__(
self,
args,
stdin=None,
stdout=None,
stderr=None,
spec=None,
stack=None,
spec_modifiers=None,
):
args = list(self.acc_args) + args
return self.f(args, stdin, stdout, stderr, spec, stack, spec_modifiers)
PARTIAL_EVAL_ALIASES = ( PARTIAL_EVAL_ALIASES = (
PartialEvalAlias0, PartialEvalAlias0,
PartialEvalAlias1, PartialEvalAlias1,
@ -416,6 +497,7 @@ PARTIAL_EVAL_ALIASES = (
PartialEvalAlias4, PartialEvalAlias4,
PartialEvalAlias5, PartialEvalAlias5,
PartialEvalAlias6, PartialEvalAlias6,
PartialEvalAlias7,
) )
@ -436,13 +518,43 @@ def partial_eval_alias(f, acc_args=()):
numargs += 1 numargs += 1
elif name in ALIAS_KWARG_NAMES and param.kind == param.KEYWORD_ONLY: elif name in ALIAS_KWARG_NAMES and param.kind == param.KEYWORD_ONLY:
numargs += 1 numargs += 1
if numargs < 7: if numargs < 8:
return PARTIAL_EVAL_ALIASES[numargs](f, acc_args=acc_args) return PARTIAL_EVAL_ALIASES[numargs](f, acc_args=acc_args)
else: else:
e = "Expected proxy with 6 or fewer arguments for {}, not {}" e = "Expected proxy with 7 or fewer arguments for {}, not {}"
raise XonshError(e.format(", ".join(ALIAS_KWARG_NAMES), numargs)) raise XonshError(e.format(", ".join(ALIAS_KWARG_NAMES), numargs))
def run_alias_by_params(func: tp.Callable, params: dict[str, tp.Any]):
"""
Run alias function based on signature and params.
If function param names are in alias signature fill them.
If function params have unknown names fill using alias signature order.
"""
alias_params = {
"args": None,
"stdin": None,
"stdout": None,
"stderr": None,
"spec": None,
"stack": None,
"spec_modifiers": None,
}
alias_params |= params
sign = inspect.signature(func)
func_params = sign.parameters.items()
kwargs = {
name: alias_params[name] for name, p in func_params if name in alias_params
}
if len(kwargs) != len(func_params):
# There is unknown param. Switch to positional mode.
kwargs = dict(
zip(map(operator.itemgetter(0), func_params), alias_params.values())
)
return func(**kwargs)
# #
# Actual aliases below # Actual aliases below
# #

View file

@ -158,7 +158,7 @@ def complete_aliases(command: CommandContext):
if cmd not in XSH.aliases: if cmd not in XSH.aliases:
# only complete aliases # only complete aliases
return return
alias = XSH.aliases.get(cmd) # type: ignore alias = XSH.aliases.get(cmd)[0] # type: ignore
completer = getattr(alias, "xonsh_complete", None) completer = getattr(alias, "xonsh_complete", None)
if not completer: if not completer:

View file

@ -705,29 +705,42 @@ class SubprocSpec:
self.cmd = new_cmd self.cmd = new_cmd
def resolve_alias(self): def resolve_alias(self):
"""Sets alias in command, if applicable.""" """Resolving alias and setting up command."""
cmd0 = self.cmd[0] cmd0 = self.cmd[0]
spec_modifiers = []
if cmd0 in self.alias_stack: if cmd0 in self.alias_stack:
# Disabling the alias resolving to prevent infinite loop in call stack # Disabling the alias resolving to prevent infinite loop in call stack
# and futher using binary_loc to resolve the alias name. # and further using binary_loc to resolve the alias name.
self.alias = None self.alias = None
return return
if callable(cmd0): if callable(cmd0):
alias = cmd0 self.alias = cmd0
else: else:
found_spec_modifiers = []
if isinstance(XSH.aliases, dict): if isinstance(XSH.aliases, dict):
# Windows tests # Windows tests
alias = XSH.aliases.get(cmd0, None) alias = XSH.aliases.get(cmd0, None)
if alias is not None:
alias = alias + self.cmd[1:]
else: else:
alias = XSH.aliases.get(cmd0, None, spec_modifiers=spec_modifiers) alias = XSH.aliases.get(
self.cmd,
None,
spec_modifiers=found_spec_modifiers,
)
if alias is not None: if alias is not None:
self.alias_name = cmd0 self.alias_name = cmd0
self.alias = alias if callable(alias[0]):
if spec_modifiers: # E.g. `alias == [FuncAlias({'name': 'cd'}), '/tmp']`
for mod in spec_modifiers: self.alias = alias[0]
self.add_spec_modifier(mod) self.cmd = [cmd0] + alias[1:]
else:
# E.g. `alias == ['ls', '-la']`
self.alias = alias
if found_spec_modifiers:
for mod in found_spec_modifiers:
self.add_spec_modifier(mod)
def resolve_binary_loc(self): def resolve_binary_loc(self):
"""Sets the binary location""" """Sets the binary location"""
@ -765,8 +778,7 @@ class SubprocSpec:
self.cmd.pop(0) self.cmd.pop(0)
return return
else: else:
self.cmd = alias + self.cmd[1:] self.cmd = alias
# resolve any redirects the aliases may have applied
self.resolve_redirects() self.resolve_redirects()
if self.binary_loc is None: if self.binary_loc is None:
return return
@ -971,7 +983,13 @@ def _trace_specs(trace_mode, specs, cmds, captured):
} }
p |= { p |= {
a: getattr(s, a, None) a: getattr(s, a, None)
for a in ["alias_name", "binary_loc", "threadable", "background"] for a in [
"alias_name",
"alias",
"binary_loc",
"threadable",
"background",
]
} }
if trace_mode == 3: if trace_mode == 3:
p |= { p |= {