mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
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:
parent
24fcbb4638
commit
87032f6d30
7 changed files with 382 additions and 51 deletions
|
@ -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
23
news/alias_return_cmd.rst
Normal 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>
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
172
xonsh/aliases.py
172
xonsh/aliases.py
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -705,28 +705,41 @@ 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
|
||||||
|
if callable(alias[0]):
|
||||||
|
# E.g. `alias == [FuncAlias({'name': 'cd'}), '/tmp']`
|
||||||
|
self.alias = alias[0]
|
||||||
|
self.cmd = [cmd0] + alias[1:]
|
||||||
|
else:
|
||||||
|
# E.g. `alias == ['ls', '-la']`
|
||||||
self.alias = alias
|
self.alias = alias
|
||||||
if spec_modifiers:
|
|
||||||
for mod in spec_modifiers:
|
if found_spec_modifiers:
|
||||||
|
for mod in found_spec_modifiers:
|
||||||
self.add_spec_modifier(mod)
|
self.add_spec_modifier(mod)
|
||||||
|
|
||||||
def resolve_binary_loc(self):
|
def resolve_binary_loc(self):
|
||||||
|
@ -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 |= {
|
||||||
|
|
Loading…
Add table
Reference in a new issue