mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 16:34:47 +01:00
Merge branch 'master' into nochild
This commit is contained in:
commit
d460542b7f
20 changed files with 864 additions and 129 deletions
|
@ -1,5 +1,52 @@
|
|||
Core Events
|
||||
===========
|
||||
The following events are defined by xonsh itself. For more information about events, see `the events tutorial <tutorial_events.html>`_.
|
||||
The following events are defined by xonsh itself. For more information about events,
|
||||
see `the events tutorial <tutorial_events.html>`_.
|
||||
|
||||
.. include:: eventsbody
|
||||
|
||||
|
||||
Event Categories
|
||||
----------------
|
||||
Additionally, there are a few categories of events whose names are part of
|
||||
the specification of the event. These events are fired if they exist, and
|
||||
are ignored otherwise. Here are their specifications.
|
||||
|
||||
-------
|
||||
|
||||
``on_pre_spec_run_<cmd-name>(spec: SubprocSpec) -> None``
|
||||
.........................................................
|
||||
This event fires whenever a command with a give name (``<cmd-name>``)
|
||||
has its ``SubprocSpec.run()`` method called. This is fired
|
||||
prior to the run call executing anything at all. This recieves the
|
||||
``SubprocSpec`` object as ``spec`` that triggered the event, allowing
|
||||
the handler to modify the spec if needed. For example, if we wanted to
|
||||
intercept an ``ls`` spec, we could write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@events.on_pre_spec_run_ls
|
||||
def print_when_ls(spec=None, **kwargs):
|
||||
print("Look at me list stuff!")
|
||||
|
||||
|
||||
``on_post_spec_run_<cmd-name>(spec: SubprocSpec) -> None``
|
||||
..........................................................
|
||||
This event fires whenever a command with a give name (``<cmd-name>``)
|
||||
has its ``SubprocSpec.run()`` method called. This is fired
|
||||
after to the run call has executed everything except returning. This recieves the
|
||||
``SubprocSpec`` object as ``spec`` that triggered the event, allowing
|
||||
the handler to modify the spec if needed. Note that because of the
|
||||
way process pipelines and specs work in xonsh, the command will have
|
||||
started running, but won't necessarily have completed. This is because
|
||||
``SubprocSpec.run()`` does not block.
|
||||
For example, if we wanted to get an ``ls`` spec after ls has started running,
|
||||
we could write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@events.on_post_spec_run_ls
|
||||
def print_while_ls(spec=None, **kwargs):
|
||||
print("Mom! I'm listing!")
|
||||
|
||||
|
||||
|
|
74
news/lscolors.rst
Normal file
74
news/lscolors.rst
Normal file
|
@ -0,0 +1,74 @@
|
|||
**Added:**
|
||||
|
||||
* New ``xonsh.color_tools.short_to_ints()`` function for directly
|
||||
converting a short (0 - 256) color into a 3-tuple of ints
|
||||
represeting its RGB value.
|
||||
* New ``xonsh.ansi_colors.ansi_reverse_style()`` function for
|
||||
converting a mapping of color names to ANSI escape codes into
|
||||
a mapping from escape codes into color names. This is not a
|
||||
round-trippable operation.
|
||||
* New ``xonsh.ansi_colors.ansi_color_escape_code_to_name()`` function
|
||||
for converting an ANSI color escape code into the closest xonsh
|
||||
color name for a given style.
|
||||
* New ``xonsh.events.EventManager.exists()`` method enables the checking
|
||||
of whether events actually exist with out making the event if it
|
||||
doesn't exist.
|
||||
* New command-specific event categories called ``on_pre_spec_run_<cmd-name>``
|
||||
and ``on_post_spec_run_<cmd-name>`` will be fired before and after
|
||||
``SubpocSpec.run()`` is called. This allows for command specific
|
||||
events to be executed. For example, ``on_pre_spec_run_ls`` would
|
||||
be run prior to an invocation of ``ls``.
|
||||
* New ``xonsh.environ.LsColors`` class for managing the ``$LS_COLORS``
|
||||
environment variable. This ensures that the ``ls`` command respects the
|
||||
``$XONSH_COLOR_STYLE`` setting. An instance of this class is added to the
|
||||
environment when either the ``$LS_COLORS`` class is first accessed or
|
||||
the ``ls`` command is executed.
|
||||
* The ``on_pre_spec_run_ls`` event is initialized with a default handler
|
||||
that ensures that ``$LS_COLORS`` is set in the actual environment prior
|
||||
to running an ``ls`` command.
|
||||
* New ``xonsh.tools.detype()`` function that simply calls an objects own
|
||||
``detype()`` method in order to detype it.
|
||||
* New ``xonsh.tools.always_none()`` function that simply returns None.
|
||||
* New ``Env.set_ensurer()`` method for setting an ensurer on an environment.
|
||||
|
||||
**Changed:**
|
||||
|
||||
* The black and white style ``bw`` now uses actual black and white
|
||||
ANSI colore codes for its colors, rather than just empty color
|
||||
sequences.
|
||||
* An environment variable ``detype`` operation no longer needs to be
|
||||
function, but may also be ``None``. If ``None``, this variable is
|
||||
considered not detypeable, and will not be exported to subprocess
|
||||
environments via the ``Env.detype()`` function.
|
||||
* An environment variable ``detype`` function no longer needs to return
|
||||
a string, but may also return ``None``. If ``None`` is returned, this
|
||||
variable is considered not detypeable, and will not be exported to
|
||||
subprocess environments via the ``Env.detype()`` function.
|
||||
* The ``Env.detype()`` method has been updated to respect the new
|
||||
``None`` types when detyping.
|
||||
* The ``xonsh.tools.expandvars()`` function has been updated to respect
|
||||
the new ``None`` types when detyping.
|
||||
* The ``xonsh.xonfig.make_xonfig_wizard()`` function has been updated to respect
|
||||
the new ``None`` types when detyping.
|
||||
* Event handlers may now be added and discarded during event firing for
|
||||
normal events. Such modifications will not be applied to until the
|
||||
current firing operation is concluded. Thus you won't see newly added
|
||||
events fired.
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* The ``xonsh.color_tools.make_pallete()`` function is no
|
||||
longer deprecated, as it is actually needed in other parts of
|
||||
xonsh still, such as ``pyghooks``.
|
||||
|
||||
**Removed:**
|
||||
|
||||
* All code references to ``$FORMATTER_DICT`` have been removed.
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* Minor fixes to ``xonsh.events.debug_level()``.
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
42
tests/test_ansi_colors.py
Normal file
42
tests/test_ansi_colors.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
"""Tests ANSI color tools."""
|
||||
import pytest
|
||||
|
||||
from xonsh.ansi_colors import ansi_color_escape_code_to_name, ansi_reverse_style
|
||||
|
||||
RS = ansi_reverse_style(style="default")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key, value", [("", "NO_COLOR"), ("31", "RED")])
|
||||
def test_ansi_reverse_style(key, value):
|
||||
assert key in RS
|
||||
assert RS[key] == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inp, exp",
|
||||
[
|
||||
("0", ("NO_COLOR",)),
|
||||
("\0010\002", ("NO_COLOR",)),
|
||||
("\033[0m", ("NO_COLOR",)),
|
||||
("\001\033[0m\002", ("NO_COLOR",)),
|
||||
("00;36", ("CYAN",)),
|
||||
("01;31", ("BOLD_RED",)),
|
||||
("04;31", ("UNDERLINE_RED",)),
|
||||
("1;4;31", ("BOLD_UNDERLINE_RED",)),
|
||||
("4;1;31", ("BOLD_UNDERLINE_RED",)),
|
||||
("31;42", ("RED", "BACKGROUND_GREEN")),
|
||||
("42;31", ("BACKGROUND_GREEN", "RED")),
|
||||
("40", ("BACKGROUND_BLACK",)),
|
||||
("38;5;89", ("PURPLE",)),
|
||||
("48;5;89", ("BACKGROUND_PURPLE",)),
|
||||
("38;2;170;0;0", ("RED",)),
|
||||
("48;2;170;0;0", ("BACKGROUND_RED",)),
|
||||
("1;38;5;124", ("BOLD_RED",)),
|
||||
("4;1;38;2;170;0;0", ("BOLD_UNDERLINE_RED",)),
|
||||
("1;38;5;40", ("BOLD_GREEN",)),
|
||||
("48;5;16;38;5;184", ("BACKGROUND_BLACK", "INTENSE_YELLOW")),
|
||||
],
|
||||
)
|
||||
def test_ansi_color_escape_code_to_name(inp, exp):
|
||||
obs = ansi_color_escape_code_to_name(inp, "default", reversed_style=RS)
|
||||
assert obs == exp
|
|
@ -6,13 +6,14 @@ import tempfile
|
|||
import builtins
|
||||
import itertools
|
||||
from tempfile import TemporaryDirectory
|
||||
from xonsh.tools import ON_WINDOWS
|
||||
from xonsh.tools import ON_WINDOWS, always_true
|
||||
|
||||
import pytest
|
||||
|
||||
from xonsh.commands_cache import CommandsCache
|
||||
from xonsh.environ import (
|
||||
Env,
|
||||
Ensurer,
|
||||
locate_binary,
|
||||
DEFAULT_ENSURERS,
|
||||
DEFAULT_VALUES,
|
||||
|
@ -67,6 +68,7 @@ def test_env_detype_mutable_access_clear(path1, path2):
|
|||
|
||||
def test_env_detype_no_dict():
|
||||
env = Env(YO={"hey": 42})
|
||||
env.set_ensurer('YO', Ensurer(always_true, None, None))
|
||||
det = env.detype()
|
||||
assert "YO" not in det
|
||||
|
||||
|
|
|
@ -161,3 +161,9 @@ def test_typos(xonsh_builtins):
|
|||
if "pytest" in name:
|
||||
continue
|
||||
assert inspect.getdoc(ev)
|
||||
|
||||
|
||||
def test_exists(events):
|
||||
events.doc("on_test", "Test event")
|
||||
assert events.exists("on_test")
|
||||
assert not events.exists("on_best")
|
||||
|
|
|
@ -64,7 +64,7 @@ def test_parse_aliases():
|
|||
"__XONSH_ALIAS_END__\n"
|
||||
"more filth"
|
||||
)
|
||||
obs = parse_aliases(s, 'bash')
|
||||
obs = parse_aliases(s, "bash")
|
||||
assert exp == obs
|
||||
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ def test_premain_D(shell):
|
|||
|
||||
def test_premain_custom_rc(shell, tmpdir, monkeypatch):
|
||||
monkeypatch.setattr(sys.stdin, "isatty", lambda: True)
|
||||
monkeypatch.setitem(os.environ, "XONSH_CACHE_SCRIPTS", 'False')
|
||||
monkeypatch.setitem(os.environ, "XONSH_CACHE_SCRIPTS", "False")
|
||||
f = tmpdir.join("wakkawakka")
|
||||
f.write("print('hi')")
|
||||
args = xonsh.main.premain(["--rc", f.strpath])
|
||||
|
@ -78,7 +78,7 @@ def test_force_interactive_custom_rc_with_script(shell, tmpdir, monkeypatch):
|
|||
"""Calling a custom RC file on a script-call with the interactive flag
|
||||
should run interactively
|
||||
"""
|
||||
monkeypatch.setitem(os.environ, "XONSH_CACHE_SCRIPTS", 'False')
|
||||
monkeypatch.setitem(os.environ, "XONSH_CACHE_SCRIPTS", "False")
|
||||
f = tmpdir.join("wakkawakka")
|
||||
f.write("print('hi')")
|
||||
args = xonsh.main.premain(["-i", "--rc", f.strpath, "tests/sample.xsh"])
|
||||
|
@ -120,8 +120,10 @@ def test_premain_invalid_arguments(shell, case, capsys):
|
|||
xonsh.main.premain([case])
|
||||
assert "unrecognized argument" in capsys.readouterr()[1]
|
||||
|
||||
|
||||
def test_premain_timings_arg(shell):
|
||||
xonsh.main.premain(['--timings'])
|
||||
xonsh.main.premain(["--timings"])
|
||||
|
||||
|
||||
def test_xonsh_failback(shell, monkeypatch, monkeypatch_stderr):
|
||||
failback_checker = []
|
||||
|
|
|
@ -106,74 +106,65 @@ def test_format_prompt_with_no_env(formatter, xonsh_builtins, live_fields):
|
|||
xonsh_builtins.__xonsh__.shell.prompt_formatter = formatter
|
||||
|
||||
env = Env()
|
||||
env.pop('VIRTUAL_ENV', None) # For virtualenv
|
||||
env.pop('CONDA_DEFAULT_ENV', None) # For conda/CircleCI
|
||||
env.pop("VIRTUAL_ENV", None) # For virtualenv
|
||||
env.pop("CONDA_DEFAULT_ENV", None) # For conda/CircleCI
|
||||
xonsh_builtins.__xonsh__.env = env
|
||||
|
||||
assert formatter('{env_name}', fields=live_fields) == ''
|
||||
assert formatter("{env_name}", fields=live_fields) == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize('envname', ['env', 'foo', 'bar'])
|
||||
def test_format_prompt_with_various_envs(formatter, xonsh_builtins, live_fields, envname):
|
||||
@pytest.mark.parametrize("envname", ["env", "foo", "bar"])
|
||||
def test_format_prompt_with_various_envs(
|
||||
formatter, xonsh_builtins, live_fields, envname
|
||||
):
|
||||
xonsh_builtins.__xonsh__.shell.prompt_formatter = formatter
|
||||
|
||||
env = Env(VIRTUAL_ENV=envname)
|
||||
xonsh_builtins.__xonsh__.env = env
|
||||
|
||||
exp = live_fields['env_prefix'] + envname + live_fields['env_postfix']
|
||||
assert formatter('{env_name}', fields=live_fields) == exp
|
||||
exp = live_fields["env_prefix"] + envname + live_fields["env_postfix"]
|
||||
assert formatter("{env_name}", fields=live_fields) == exp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pre', ['(', '[[', '', ' '])
|
||||
@pytest.mark.parametrize('post', [')', ']]', '', ' '])
|
||||
@pytest.mark.parametrize("pre", ["(", "[[", "", " "])
|
||||
@pytest.mark.parametrize("post", [")", "]]", "", " "])
|
||||
def test_format_prompt_with_various_prepost(
|
||||
formatter,
|
||||
xonsh_builtins,
|
||||
live_fields,
|
||||
pre,
|
||||
post,
|
||||
formatter, xonsh_builtins, live_fields, pre, post
|
||||
):
|
||||
xonsh_builtins.__xonsh__.shell.prompt_formatter = formatter
|
||||
|
||||
env = Env(VIRTUAL_ENV='env')
|
||||
env = Env(VIRTUAL_ENV="env")
|
||||
xonsh_builtins.__xonsh__.env = env
|
||||
|
||||
live_fields.update({'env_prefix': pre, 'env_postfix': post})
|
||||
live_fields.update({"env_prefix": pre, "env_postfix": post})
|
||||
|
||||
exp = pre + 'env' + post
|
||||
assert formatter('{env_name}', fields=live_fields) == exp
|
||||
exp = pre + "env" + post
|
||||
assert formatter("{env_name}", fields=live_fields) == exp
|
||||
|
||||
|
||||
def test_noenv_with_disable_set(formatter, xonsh_builtins, live_fields):
|
||||
xonsh_builtins.__xonsh__.shell.prompt_formatter = formatter
|
||||
|
||||
env = Env(VIRTUAL_ENV='env', VIRTUAL_ENV_DISABLE_PROMPT=1)
|
||||
env = Env(VIRTUAL_ENV="env", VIRTUAL_ENV_DISABLE_PROMPT=1)
|
||||
xonsh_builtins.__xonsh__.env = env
|
||||
|
||||
exp = ''
|
||||
assert formatter('{env_name}', fields=live_fields) == exp
|
||||
exp = ""
|
||||
assert formatter("{env_name}", fields=live_fields) == exp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('disable', [0, 1])
|
||||
def test_custom_env_overrides_default(
|
||||
formatter,
|
||||
xonsh_builtins,
|
||||
live_fields,
|
||||
disable,
|
||||
):
|
||||
@pytest.mark.parametrize("disable", [0, 1])
|
||||
def test_custom_env_overrides_default(formatter, xonsh_builtins, live_fields, disable):
|
||||
xonsh_builtins.__xonsh__.shell.prompt_formatter = formatter
|
||||
|
||||
prompt = '!venv active! '
|
||||
prompt = "!venv active! "
|
||||
|
||||
env = Env(
|
||||
VIRTUAL_ENV='env',
|
||||
VIRTUAL_ENV_PROMPT=prompt,
|
||||
VIRTUAL_ENV_DISABLE_PROMPT=disable,
|
||||
VIRTUAL_ENV="env", VIRTUAL_ENV_PROMPT=prompt, VIRTUAL_ENV_DISABLE_PROMPT=disable
|
||||
)
|
||||
xonsh_builtins.__xonsh__.env = env
|
||||
|
||||
exp = '' if disable else prompt
|
||||
assert formatter('{env_name}', fields=live_fields) == exp
|
||||
exp = "" if disable else prompt
|
||||
assert formatter("{env_name}", fields=live_fields) == exp
|
||||
|
||||
|
||||
def test_promptformatter_cache(formatter):
|
||||
|
|
|
@ -375,22 +375,25 @@ def test_subproc_toks_pyeval_nested():
|
|||
assert exp == obs
|
||||
|
||||
|
||||
@pytest.mark.parametrize('phrase', [
|
||||
'xandy',
|
||||
'xory',
|
||||
'xand',
|
||||
'andy',
|
||||
'xor',
|
||||
'ory',
|
||||
'x-and',
|
||||
'x-or',
|
||||
'and-y',
|
||||
'or-y',
|
||||
'x-and-y',
|
||||
'x-or-y',
|
||||
'in/and/path',
|
||||
'in/or/path',
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"phrase",
|
||||
[
|
||||
"xandy",
|
||||
"xory",
|
||||
"xand",
|
||||
"andy",
|
||||
"xor",
|
||||
"ory",
|
||||
"x-and",
|
||||
"x-or",
|
||||
"and-y",
|
||||
"or-y",
|
||||
"x-and-y",
|
||||
"x-or-y",
|
||||
"in/and/path",
|
||||
"in/or/path",
|
||||
],
|
||||
)
|
||||
def test_subproc_toks_and_or(phrase):
|
||||
s = "echo " + phrase
|
||||
exp = "![{0}]".format(s)
|
||||
|
|
|
@ -51,7 +51,9 @@ skip_if_on_msys = pytest.mark.skipif(
|
|||
|
||||
skip_if_on_windows = pytest.mark.skipif(ON_WINDOWS, reason="Unix stuff")
|
||||
|
||||
skip_if_on_azure_pipelines = pytest.mark.skipif(ON_AZURE_PIPELINES, reason="not suitable for azure")
|
||||
skip_if_on_azure_pipelines = pytest.mark.skipif(
|
||||
ON_AZURE_PIPELINES, reason="not suitable for azure"
|
||||
)
|
||||
|
||||
skip_if_on_unix = pytest.mark.skipif(not ON_WINDOWS, reason="Windows stuff")
|
||||
|
||||
|
@ -201,9 +203,11 @@ def nodes_equal(x, y):
|
|||
type(y),
|
||||
)
|
||||
if isinstance(x, (ast.Expr, ast.FunctionDef, ast.ClassDef)):
|
||||
assert x.lineno == y.lineno, (
|
||||
"Ast nodes do not have the same line number : %s != %s"
|
||||
% (x.lineno, y.lineno)
|
||||
assert (
|
||||
x.lineno == y.lineno
|
||||
), "Ast nodes do not have the same line number : %s != %s" % (
|
||||
x.lineno,
|
||||
y.lineno,
|
||||
)
|
||||
assert x.col_offset == y.col_offset, (
|
||||
"Ast nodes do not have the same column offset number : %s != %s"
|
||||
|
|
|
@ -22,6 +22,8 @@ else:
|
|||
_sys.modules["xonsh.lazyasd"] = __amalgam__
|
||||
lazyjson = __amalgam__
|
||||
_sys.modules["xonsh.lazyjson"] = __amalgam__
|
||||
color_tools = __amalgam__
|
||||
_sys.modules["xonsh.color_tools"] = __amalgam__
|
||||
platform = __amalgam__
|
||||
_sys.modules["xonsh.platform"] = __amalgam__
|
||||
pretty = __amalgam__
|
||||
|
@ -36,10 +38,10 @@ else:
|
|||
_sys.modules["xonsh.tokenize"] = __amalgam__
|
||||
tools = __amalgam__
|
||||
_sys.modules["xonsh.tools"] = __amalgam__
|
||||
ansi_colors = __amalgam__
|
||||
_sys.modules["xonsh.ansi_colors"] = __amalgam__
|
||||
ast = __amalgam__
|
||||
_sys.modules["xonsh.ast"] = __amalgam__
|
||||
color_tools = __amalgam__
|
||||
_sys.modules["xonsh.color_tools"] = __amalgam__
|
||||
commands_cache = __amalgam__
|
||||
_sys.modules["xonsh.commands_cache"] = __amalgam__
|
||||
diff_history = __amalgam__
|
||||
|
@ -56,18 +58,18 @@ else:
|
|||
_sys.modules["xonsh.lexer"] = __amalgam__
|
||||
openpy = __amalgam__
|
||||
_sys.modules["xonsh.openpy"] = __amalgam__
|
||||
style_tools = __amalgam__
|
||||
_sys.modules["xonsh.style_tools"] = __amalgam__
|
||||
xontribs = __amalgam__
|
||||
_sys.modules["xonsh.xontribs"] = __amalgam__
|
||||
ansi_colors = __amalgam__
|
||||
_sys.modules["xonsh.ansi_colors"] = __amalgam__
|
||||
dirstack = __amalgam__
|
||||
_sys.modules["xonsh.dirstack"] = __amalgam__
|
||||
inspectors = __amalgam__
|
||||
_sys.modules["xonsh.inspectors"] = __amalgam__
|
||||
proc = __amalgam__
|
||||
_sys.modules["xonsh.proc"] = __amalgam__
|
||||
shell = __amalgam__
|
||||
_sys.modules["xonsh.shell"] = __amalgam__
|
||||
style_tools = __amalgam__
|
||||
_sys.modules["xonsh.style_tools"] = __amalgam__
|
||||
timings = __amalgam__
|
||||
_sys.modules["xonsh.timings"] = __amalgam__
|
||||
xonfig = __amalgam__
|
||||
|
@ -76,14 +78,12 @@ else:
|
|||
_sys.modules["xonsh.base_shell"] = __amalgam__
|
||||
environ = __amalgam__
|
||||
_sys.modules["xonsh.environ"] = __amalgam__
|
||||
inspectors = __amalgam__
|
||||
_sys.modules["xonsh.inspectors"] = __amalgam__
|
||||
tracer = __amalgam__
|
||||
_sys.modules["xonsh.tracer"] = __amalgam__
|
||||
readline_shell = __amalgam__
|
||||
_sys.modules["xonsh.readline_shell"] = __amalgam__
|
||||
replay = __amalgam__
|
||||
_sys.modules["xonsh.replay"] = __amalgam__
|
||||
tracer = __amalgam__
|
||||
_sys.modules["xonsh.tracer"] = __amalgam__
|
||||
aliases = __amalgam__
|
||||
_sys.modules["xonsh.aliases"] = __amalgam__
|
||||
dumb_shell = __amalgam__
|
||||
|
|
|
@ -208,6 +208,7 @@ class PartialEvalAliasBase:
|
|||
"""
|
||||
self.f = f
|
||||
self.acc_args = acc_args
|
||||
self.__name__ = getattr(f, "__name__", self.__class__.__name__)
|
||||
|
||||
def __call__(
|
||||
self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
"""Tools for helping with ANSI color codes."""
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
import builtins
|
||||
|
||||
from xonsh.platform import HAS_PYGMENTS
|
||||
from xonsh.lazyasd import LazyDict
|
||||
from xonsh.lazyasd import LazyDict, lazyobject
|
||||
from xonsh.color_tools import (
|
||||
RE_BACKGROUND,
|
||||
BASE_XONSH_COLORS,
|
||||
|
@ -12,6 +13,7 @@ from xonsh.color_tools import (
|
|||
find_closest_color,
|
||||
rgb2short,
|
||||
rgb_to_256,
|
||||
short_to_ints,
|
||||
)
|
||||
from xonsh.tools import FORMATTER
|
||||
|
||||
|
@ -114,6 +116,166 @@ def ansi_color_style(style="default"):
|
|||
return cmap
|
||||
|
||||
|
||||
def ansi_reverse_style(style="default", return_style=False):
|
||||
"""Reverses an ANSI color style mapping so that escape codes map to
|
||||
colors. Style may either be string or mapping. May also return
|
||||
the style it looked up.
|
||||
"""
|
||||
style = ansi_style_by_name(style) if isinstance(style, str) else style
|
||||
reversed_style = {v: k for k, v in style.items()}
|
||||
# add keys to make this more useful
|
||||
updates = {
|
||||
"1": "BOLD_",
|
||||
"2": "FAINT_",
|
||||
"4": "UNDERLINE_",
|
||||
"5": "SLOWBLINK_",
|
||||
"1;4": "BOLD_UNDERLINE_",
|
||||
"4;1": "BOLD_UNDERLINE_",
|
||||
"38": "SET_FOREGROUND_",
|
||||
"48": "SET_BACKGROUND_",
|
||||
"38;2": "SET_FOREGROUND_3INTS_",
|
||||
"48;2": "SET_BACKGROUND_3INTS_",
|
||||
"38;5": "SET_FOREGROUND_SHORT_",
|
||||
"48;5": "SET_BACKGROUND_SHORT_",
|
||||
}
|
||||
for ec, name in reversed_style.items():
|
||||
no_left_zero = ec.lstrip("0")
|
||||
if no_left_zero.startswith(";"):
|
||||
updates[no_left_zero[1:]] = name
|
||||
elif no_left_zero != ec:
|
||||
updates[no_left_zero] = name
|
||||
reversed_style.update(updates)
|
||||
# return results
|
||||
if return_style:
|
||||
return style, reversed_style
|
||||
else:
|
||||
return reversed_style
|
||||
|
||||
|
||||
@lazyobject
|
||||
def ANSI_ESCAPE_CODE_RE():
|
||||
return re.compile(r"\001?(\033\[)?([0-9;]+)m?\002?")
|
||||
|
||||
|
||||
@lazyobject
|
||||
def ANSI_REVERSE_COLOR_NAME_TRANSLATIONS():
|
||||
base = {
|
||||
"SET_FOREGROUND_FAINT_": "SET_FOREGROUND_3INTS_",
|
||||
"SET_BACKGROUND_FAINT_": "SET_BACKGROUND_3INTS_",
|
||||
"SET_FOREGROUND_SLOWBLINK_": "SET_FOREGROUND_SHORT_",
|
||||
"SET_BACKGROUND_SLOWBLINK_": "SET_BACKGROUND_SHORT_",
|
||||
}
|
||||
data = {"UNDERLINE_BOLD_": "BOLD_UNDERLINE_"}
|
||||
data.update(base)
|
||||
data.update({"BOLD_" + k: "BOLD_" + v for k, v in base.items()})
|
||||
data.update({"UNDERLINE_" + k: "UNDERLINE_" + v for k, v in base.items()})
|
||||
data.update({"BOLD_UNDERLINE_" + k: "BOLD_UNDERLINE_" + v for k, v in base.items()})
|
||||
data.update({"UNDERLINE_BOLD_" + k: "BOLD_UNDERLINE_" + v for k, v in base.items()})
|
||||
return data
|
||||
|
||||
|
||||
@lazyobject
|
||||
def ANSI_COLOR_NAME_SET_3INTS_RE():
|
||||
return re.compile(r"(\w+_)?SET_(FORE|BACK)GROUND_3INTS_(\d+)_(\d+)_(\d+)")
|
||||
|
||||
|
||||
@lazyobject
|
||||
def ANSI_COLOR_NAME_SET_SHORT_RE():
|
||||
return re.compile(r"(\w+_)?SET_(FORE|BACK)GROUND_SHORT_(\d+)")
|
||||
|
||||
|
||||
def _color_name_from_ints(ints, background=False, prefix=None):
|
||||
name = find_closest_color(ints, BASE_XONSH_COLORS)
|
||||
if background:
|
||||
name = "BACKGROUND_" + name
|
||||
name = name if prefix is None else prefix + name
|
||||
return name
|
||||
|
||||
|
||||
_ANSI_COLOR_ESCAPE_CODE_TO_NAME_CACHE = {}
|
||||
|
||||
|
||||
def ansi_color_escape_code_to_name(escape_code, style, reversed_style=None):
|
||||
"""Converts an ASNI color code escape sequence to a tuple of color names
|
||||
in the provided style ('default' should almost be the style). For example,
|
||||
'0' becomes ('NO_COLOR',) and '32;41' becomes ('GREEN', 'BACKGROUND_RED').
|
||||
The style keyword may either be a string, in which the style is looked up,
|
||||
or an actual style dict. You can also provide a reversed style mapping,
|
||||
too, which is just the keys/values of the style dict swapped. If reversed
|
||||
style is not provided, it is computed.
|
||||
"""
|
||||
key = (escape_code, style)
|
||||
if key in _ANSI_COLOR_ESCAPE_CODE_TO_NAME_CACHE:
|
||||
return _ANSI_COLOR_ESCAPE_CODE_TO_NAME_CACHE[key]
|
||||
if reversed_style is None:
|
||||
style, reversed_style = ansi_reverse_style(style, return_style=True)
|
||||
# strip some actual escape codes, if needed.
|
||||
ec = ANSI_ESCAPE_CODE_RE.match(escape_code).group(2)
|
||||
names = []
|
||||
n_ints = 0
|
||||
seen_set_foreback = False
|
||||
for e in ec.split(";"):
|
||||
no_left_zero = e.lstrip("0") if len(e) > 1 else e
|
||||
if seen_set_foreback and n_ints > 0:
|
||||
names.append(e)
|
||||
n_ints -= 1
|
||||
if n_ints == 0:
|
||||
seen_set_foreback = False
|
||||
continue
|
||||
else:
|
||||
names.append(reversed_style.get(no_left_zero, no_left_zero))
|
||||
# set the flags for next time
|
||||
if "38" == e or "48" == e:
|
||||
seen_set_foreback = True
|
||||
elif "2" == e:
|
||||
n_ints = 3
|
||||
elif "5" == e:
|
||||
n_ints = 1
|
||||
# normalize names
|
||||
n = ""
|
||||
norm_names = []
|
||||
colors = set(reversed_style.values())
|
||||
for name in names:
|
||||
if name == "NO_COLOR":
|
||||
# skip most '0' entries
|
||||
continue
|
||||
n = n + name if n else name
|
||||
n = ANSI_REVERSE_COLOR_NAME_TRANSLATIONS.get(n, n)
|
||||
if n.endswith("_"):
|
||||
continue
|
||||
elif ANSI_COLOR_NAME_SET_SHORT_RE.match(n) is not None:
|
||||
pre, fore_back, short = ANSI_COLOR_NAME_SET_SHORT_RE.match(n).groups()
|
||||
n = _color_name_from_ints(
|
||||
short_to_ints(short), background=(fore_back == "BACK"), prefix=pre
|
||||
)
|
||||
elif ANSI_COLOR_NAME_SET_3INTS_RE.match(n) is not None:
|
||||
pre, fore_back, r, g, b = ANSI_COLOR_NAME_SET_3INTS_RE.match(n).groups()
|
||||
n = _color_name_from_ints(
|
||||
(int(r), int(g), int(b)), background=(fore_back == "BACK"), prefix=pre
|
||||
)
|
||||
elif "GROUND_3INTS_" in n:
|
||||
# have 1 or 2, but not 3 ints
|
||||
n += "_"
|
||||
continue
|
||||
# error check
|
||||
if n not in colors:
|
||||
msg = (
|
||||
"Could not translate ANSI color code {escape_code!r} "
|
||||
"into a known color in the palette. Specifically, the {n!r} "
|
||||
"portion of {name!r} in {names!r} seems to missing."
|
||||
)
|
||||
raise ValueError(
|
||||
msg.format(escape_code=escape_code, names=names, name=name, n=n)
|
||||
)
|
||||
norm_names.append(n)
|
||||
n = ""
|
||||
# return
|
||||
if len(norm_names) == 0:
|
||||
return ("NO_COLOR",)
|
||||
else:
|
||||
return tuple(norm_names)
|
||||
|
||||
|
||||
def _ansi_expand_style(cmap):
|
||||
"""Expands a style in order to more quickly make color map changes."""
|
||||
for key, val in list(cmap.items()):
|
||||
|
@ -133,23 +295,23 @@ def _ansi_expand_style(cmap):
|
|||
|
||||
def _bw_style():
|
||||
style = {
|
||||
"BLACK": "",
|
||||
"BLUE": "",
|
||||
"CYAN": "",
|
||||
"GREEN": "",
|
||||
"INTENSE_BLACK": "",
|
||||
"INTENSE_BLUE": "",
|
||||
"INTENSE_CYAN": "",
|
||||
"INTENSE_GREEN": "",
|
||||
"INTENSE_PURPLE": "",
|
||||
"INTENSE_RED": "",
|
||||
"INTENSE_WHITE": "",
|
||||
"INTENSE_YELLOW": "",
|
||||
"BLACK": "0;30",
|
||||
"BLUE": "0;37",
|
||||
"CYAN": "0;37",
|
||||
"GREEN": "0;37",
|
||||
"INTENSE_BLACK": "0;90",
|
||||
"INTENSE_BLUE": "0;97",
|
||||
"INTENSE_CYAN": "0;97",
|
||||
"INTENSE_GREEN": "0;97",
|
||||
"INTENSE_PURPLE": "0;97",
|
||||
"INTENSE_RED": "0;97",
|
||||
"INTENSE_WHITE": "0;97",
|
||||
"INTENSE_YELLOW": "0;97",
|
||||
"NO_COLOR": "0",
|
||||
"PURPLE": "",
|
||||
"RED": "",
|
||||
"WHITE": "",
|
||||
"YELLOW": "",
|
||||
"PURPLE": "0;37",
|
||||
"RED": "0;37",
|
||||
"WHITE": "0;37",
|
||||
"YELLOW": "0;37",
|
||||
}
|
||||
_ansi_expand_style(style)
|
||||
return style
|
||||
|
|
|
@ -535,6 +535,8 @@ class SubprocSpec:
|
|||
|
||||
def run(self, *, pipeline_group=None):
|
||||
"""Launches the subprocess and returns the object."""
|
||||
event_name = self._cmd_event_name()
|
||||
self._pre_run_event_fire(event_name)
|
||||
kwargs = {n: getattr(self, n) for n in self.kwnames}
|
||||
self.prep_env(kwargs)
|
||||
self.prep_preexec_fn(kwargs, pipeline_group=pipeline_group)
|
||||
|
@ -549,6 +551,7 @@ class SubprocSpec:
|
|||
p.last_in_pipeline = self.last_in_pipeline
|
||||
p.captured_stdout = self.captured_stdout
|
||||
p.captured_stderr = self.captured_stderr
|
||||
self._post_run_event_fire(event_name, p)
|
||||
return p
|
||||
|
||||
def _run_binary(self, kwargs):
|
||||
|
@ -604,6 +607,26 @@ class SubprocSpec:
|
|||
for i in range(len(cmd)):
|
||||
cmd[i] = cmd[i].replace("\0", "\\0")
|
||||
|
||||
def _cmd_event_name(self):
|
||||
if callable(self.alias):
|
||||
return self.alias.__name__
|
||||
elif self.binary_loc is None:
|
||||
return "<not-found>"
|
||||
else:
|
||||
return os.path.basename(self.binary_loc)
|
||||
|
||||
def _pre_run_event_fire(self, name):
|
||||
event_name = "on_pre_spec_run_" + name
|
||||
if events.exists(event_name):
|
||||
event = getattr(events, event_name)
|
||||
event.fire(spec=self)
|
||||
|
||||
def _post_run_event_fire(self, name, proc):
|
||||
event_name = "on_post_spec_run_" + name
|
||||
if events.exists(event_name):
|
||||
event = getattr(events, event_name)
|
||||
event.fire(spec=self, proc=proc)
|
||||
|
||||
#
|
||||
# Building methods
|
||||
#
|
||||
|
|
|
@ -9,7 +9,6 @@ import re
|
|||
import math
|
||||
|
||||
from xonsh.lazyasd import lazyobject, LazyObject
|
||||
from xonsh.tools import deprecated
|
||||
|
||||
|
||||
RE_BACKGROUND = LazyObject(
|
||||
|
@ -396,6 +395,11 @@ def rgb_to_ints(rgb):
|
|||
return tuple([int(h * 2, 16) for h in RE_RGB3.split(rgb)[1:4]])
|
||||
|
||||
|
||||
def short_to_ints(short):
|
||||
"""Coverts a short (256) color to a 3-tuple of ints."""
|
||||
return rgb_to_ints(short2rgb(short))
|
||||
|
||||
|
||||
def color_dist(x, y):
|
||||
return math.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2 + (x[2] - y[2]) ** 2)
|
||||
|
||||
|
@ -413,8 +417,3 @@ def make_palette(strings):
|
|||
t, _, s = t.partition(" ")
|
||||
palette[t] = rgb_to_ints(t)
|
||||
return palette
|
||||
|
||||
|
||||
@deprecated(deprecated_in="0.5.10", removed_in="0.6.0")
|
||||
def make_pallete(*args, **kwargs):
|
||||
make_palette(*args, **kwargs)
|
||||
|
|
395
xonsh/environ.py
395
xonsh/environ.py
|
@ -11,6 +11,7 @@ import warnings
|
|||
import contextlib
|
||||
import collections
|
||||
import collections.abc as cabc
|
||||
import subprocess
|
||||
|
||||
from xonsh import __version__ as XONSH_VERSION
|
||||
from xonsh.lazyasd import LazyObject, lazyobject
|
||||
|
@ -31,6 +32,7 @@ from xonsh.style_tools import PTK2_STYLE
|
|||
from xonsh.tools import (
|
||||
always_true,
|
||||
always_false,
|
||||
detype,
|
||||
ensure_string,
|
||||
is_env_path,
|
||||
str_to_env_path,
|
||||
|
@ -78,6 +80,11 @@ from xonsh.tools import (
|
|||
to_str_str_dict,
|
||||
dict_to_str,
|
||||
)
|
||||
from xonsh.ansi_colors import (
|
||||
ansi_color_escape_code_to_name,
|
||||
ansi_reverse_style,
|
||||
ansi_style_by_name,
|
||||
)
|
||||
import xonsh.prompt.base as prompt
|
||||
|
||||
|
||||
|
@ -105,6 +112,17 @@ cause a recursion until the limit.
|
|||
)
|
||||
|
||||
|
||||
events.doc(
|
||||
"on_pre_spec_run_ls",
|
||||
"""
|
||||
on_pre_spec_run_ls(spec: xonsh.built_ins.SubprocSpec) -> None
|
||||
|
||||
Fires right before a SubprocSpec.run() is called for the ls
|
||||
command.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
@lazyobject
|
||||
def HELP_TEMPLATE():
|
||||
return (
|
||||
|
@ -158,6 +176,301 @@ def to_debug(x):
|
|||
return val
|
||||
|
||||
|
||||
#
|
||||
# $LS_COLORS tools
|
||||
#
|
||||
|
||||
|
||||
class LsColors(cabc.MutableMapping):
|
||||
"""Helps convert to/from $LS_COLORS format, respecting the xonsh color style.
|
||||
This accepts the same inputs as dict().
|
||||
"""
|
||||
|
||||
default_settings = {
|
||||
"*.7z": ("BOLD_RED",),
|
||||
"*.Z": ("BOLD_RED",),
|
||||
"*.aac": ("CYAN",),
|
||||
"*.ace": ("BOLD_RED",),
|
||||
"*.alz": ("BOLD_RED",),
|
||||
"*.arc": ("BOLD_RED",),
|
||||
"*.arj": ("BOLD_RED",),
|
||||
"*.asf": ("BOLD_PURPLE",),
|
||||
"*.au": ("CYAN",),
|
||||
"*.avi": ("BOLD_PURPLE",),
|
||||
"*.bmp": ("BOLD_PURPLE",),
|
||||
"*.bz": ("BOLD_RED",),
|
||||
"*.bz2": ("BOLD_RED",),
|
||||
"*.cab": ("BOLD_RED",),
|
||||
"*.cgm": ("BOLD_PURPLE",),
|
||||
"*.cpio": ("BOLD_RED",),
|
||||
"*.deb": ("BOLD_RED",),
|
||||
"*.dl": ("BOLD_PURPLE",),
|
||||
"*.dwm": ("BOLD_RED",),
|
||||
"*.dz": ("BOLD_RED",),
|
||||
"*.ear": ("BOLD_RED",),
|
||||
"*.emf": ("BOLD_PURPLE",),
|
||||
"*.esd": ("BOLD_RED",),
|
||||
"*.flac": ("CYAN",),
|
||||
"*.flc": ("BOLD_PURPLE",),
|
||||
"*.fli": ("BOLD_PURPLE",),
|
||||
"*.flv": ("BOLD_PURPLE",),
|
||||
"*.gif": ("BOLD_PURPLE",),
|
||||
"*.gl": ("BOLD_PURPLE",),
|
||||
"*.gz": ("BOLD_RED",),
|
||||
"*.jar": ("BOLD_RED",),
|
||||
"*.jpeg": ("BOLD_PURPLE",),
|
||||
"*.jpg": ("BOLD_PURPLE",),
|
||||
"*.lha": ("BOLD_RED",),
|
||||
"*.lrz": ("BOLD_RED",),
|
||||
"*.lz": ("BOLD_RED",),
|
||||
"*.lz4": ("BOLD_RED",),
|
||||
"*.lzh": ("BOLD_RED",),
|
||||
"*.lzma": ("BOLD_RED",),
|
||||
"*.lzo": ("BOLD_RED",),
|
||||
"*.m2v": ("BOLD_PURPLE",),
|
||||
"*.m4a": ("CYAN",),
|
||||
"*.m4v": ("BOLD_PURPLE",),
|
||||
"*.mid": ("CYAN",),
|
||||
"*.midi": ("CYAN",),
|
||||
"*.mjpeg": ("BOLD_PURPLE",),
|
||||
"*.mjpg": ("BOLD_PURPLE",),
|
||||
"*.mka": ("CYAN",),
|
||||
"*.mkv": ("BOLD_PURPLE",),
|
||||
"*.mng": ("BOLD_PURPLE",),
|
||||
"*.mov": ("BOLD_PURPLE",),
|
||||
"*.mp3": ("CYAN",),
|
||||
"*.mp4": ("BOLD_PURPLE",),
|
||||
"*.mp4v": ("BOLD_PURPLE",),
|
||||
"*.mpc": ("CYAN",),
|
||||
"*.mpeg": ("BOLD_PURPLE",),
|
||||
"*.mpg": ("BOLD_PURPLE",),
|
||||
"*.nuv": ("BOLD_PURPLE",),
|
||||
"*.oga": ("CYAN",),
|
||||
"*.ogg": ("CYAN",),
|
||||
"*.ogm": ("BOLD_PURPLE",),
|
||||
"*.ogv": ("BOLD_PURPLE",),
|
||||
"*.ogx": ("BOLD_PURPLE",),
|
||||
"*.opus": ("CYAN",),
|
||||
"*.pbm": ("BOLD_PURPLE",),
|
||||
"*.pcx": ("BOLD_PURPLE",),
|
||||
"*.pgm": ("BOLD_PURPLE",),
|
||||
"*.png": ("BOLD_PURPLE",),
|
||||
"*.ppm": ("BOLD_PURPLE",),
|
||||
"*.qt": ("BOLD_PURPLE",),
|
||||
"*.ra": ("CYAN",),
|
||||
"*.rar": ("BOLD_RED",),
|
||||
"*.rm": ("BOLD_PURPLE",),
|
||||
"*.rmvb": ("BOLD_PURPLE",),
|
||||
"*.rpm": ("BOLD_RED",),
|
||||
"*.rz": ("BOLD_RED",),
|
||||
"*.sar": ("BOLD_RED",),
|
||||
"*.spx": ("CYAN",),
|
||||
"*.svg": ("BOLD_PURPLE",),
|
||||
"*.svgz": ("BOLD_PURPLE",),
|
||||
"*.swm": ("BOLD_RED",),
|
||||
"*.t7z": ("BOLD_RED",),
|
||||
"*.tar": ("BOLD_RED",),
|
||||
"*.taz": ("BOLD_RED",),
|
||||
"*.tbz": ("BOLD_RED",),
|
||||
"*.tbz2": ("BOLD_RED",),
|
||||
"*.tga": ("BOLD_PURPLE",),
|
||||
"*.tgz": ("BOLD_RED",),
|
||||
"*.tif": ("BOLD_PURPLE",),
|
||||
"*.tiff": ("BOLD_PURPLE",),
|
||||
"*.tlz": ("BOLD_RED",),
|
||||
"*.txz": ("BOLD_RED",),
|
||||
"*.tz": ("BOLD_RED",),
|
||||
"*.tzo": ("BOLD_RED",),
|
||||
"*.tzst": ("BOLD_RED",),
|
||||
"*.vob": ("BOLD_PURPLE",),
|
||||
"*.war": ("BOLD_RED",),
|
||||
"*.wav": ("CYAN",),
|
||||
"*.webm": ("BOLD_PURPLE",),
|
||||
"*.wim": ("BOLD_RED",),
|
||||
"*.wmv": ("BOLD_PURPLE",),
|
||||
"*.xbm": ("BOLD_PURPLE",),
|
||||
"*.xcf": ("BOLD_PURPLE",),
|
||||
"*.xpm": ("BOLD_PURPLE",),
|
||||
"*.xspf": ("CYAN",),
|
||||
"*.xwd": ("BOLD_PURPLE",),
|
||||
"*.xz": ("BOLD_RED",),
|
||||
"*.yuv": ("BOLD_PURPLE",),
|
||||
"*.z": ("BOLD_RED",),
|
||||
"*.zip": ("BOLD_RED",),
|
||||
"*.zoo": ("BOLD_RED",),
|
||||
"*.zst": ("BOLD_RED",),
|
||||
"bd": ("BACKGROUND_BLACK", "YELLOW"),
|
||||
"ca": ("BLACK", "BACKGROUND_RED"),
|
||||
"cd": ("BACKGROUND_BLACK", "YELLOW"),
|
||||
"di": ("BOLD_BLUE",),
|
||||
"do": ("BOLD_PURPLE",),
|
||||
"ex": ("BOLD_GREEN",),
|
||||
"ln": ("BOLD_CYAN",),
|
||||
"mh": ("NO_COLOR",),
|
||||
"mi": ("NO_COLOR",),
|
||||
"or": ("BACKGROUND_BLACK", "RED"),
|
||||
"ow": ("BLUE", "BACKGROUND_GREEN"),
|
||||
"pi": ("BACKGROUND_BLACK", "YELLOW"),
|
||||
"rs": ("NO_COLOR",),
|
||||
"sg": ("BLACK", "BACKGROUND_YELLOW"),
|
||||
"so": ("BOLD_PURPLE",),
|
||||
"st": ("WHITE", "BACKGROUND_BLUE"),
|
||||
"su": ("WHITE", "BACKGROUND_RED"),
|
||||
"tw": ("BLACK", "BACKGROUND_GREEN"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._d = dict(*args, **kwargs)
|
||||
self._style = self._style_name = None
|
||||
self._detyped = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._d[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._detyped = None
|
||||
self._d[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._detyped = None
|
||||
del self._d[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._d)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._d
|
||||
|
||||
def __str__(self):
|
||||
return str(self._d)
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}.{1}(...)".format(
|
||||
self.__class__.__module__, self.__class__.__name__, self._d
|
||||
)
|
||||
|
||||
def _repr_pretty_(self, p, cycle):
|
||||
name = "{0}.{1}".format(self.__class__.__module__, self.__class__.__name__)
|
||||
with p.group(0, name + "(", ")"):
|
||||
if cycle:
|
||||
p.text("...")
|
||||
elif len(self):
|
||||
p.break_()
|
||||
p.pretty(dict(self))
|
||||
|
||||
def detype(self):
|
||||
"""De-types the instance, allowing it to be exported to the environment."""
|
||||
style = self.style
|
||||
if self._detyped is None:
|
||||
self._detyped = ":".join(
|
||||
[
|
||||
key + "=" + ";".join([style[v] or "0" for v in val])
|
||||
for key, val in sorted(self._d.items())
|
||||
]
|
||||
)
|
||||
return self._detyped
|
||||
|
||||
@property
|
||||
def style_name(self):
|
||||
"""Current XONSH_COLOR_STYLE value"""
|
||||
env = builtins.__xonsh__.env
|
||||
env_style_name = env.get("XONSH_COLOR_STYLE")
|
||||
if self._style_name is None or self._style_name != env_style_name:
|
||||
self._style_name = env_style_name
|
||||
self._style = self._dtyped = None
|
||||
return self._style_name
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
"""The ANSI color style for the current XONSH_COLOR_STYLE"""
|
||||
style_name = self.style_name
|
||||
if self._style is None:
|
||||
self._style = ansi_style_by_name(style_name)
|
||||
self._detyped = None
|
||||
return self._style
|
||||
|
||||
@classmethod
|
||||
def fromstring(cls, s):
|
||||
"""Creates a new instance of the LsColors class from a colon-separated
|
||||
string of dircolor-valid keys to ANSI color escape sequences.
|
||||
"""
|
||||
obj = cls()
|
||||
# string inputs always use default codes, so translating into
|
||||
# xonsh names should be done from defaults
|
||||
reversed_default = ansi_reverse_style(style="default")
|
||||
data = {}
|
||||
for item in s.split(":"):
|
||||
key, eq, esc = item.partition("=")
|
||||
if not eq:
|
||||
# not a valid item
|
||||
continue
|
||||
data[key] = ansi_color_escape_code_to_name(
|
||||
esc, "default", reversed_style=reversed_default
|
||||
)
|
||||
obj._d = data
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def fromdircolors(cls, filename=None):
|
||||
"""Constructs an LsColors instance by running dircolors.
|
||||
If a filename is provided, it is passed down to the dircolors command.
|
||||
"""
|
||||
# assemble command
|
||||
cmd = ["dircolors", "-b"]
|
||||
if filename is not None:
|
||||
cmd.append(filename)
|
||||
# get env
|
||||
if hasattr(builtins, "__xonsh__") and hasattr(builtins.__xonsh__, "env"):
|
||||
denv = builtins.__xonsh__.env.detype()
|
||||
else:
|
||||
denv = None
|
||||
# run dircolors
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
cmd, env=denv, universal_newlines=True, stderr=subprocess.DEVNULL
|
||||
)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return cls(cls.default_settings)
|
||||
s = out.splitlines()[0]
|
||||
_, _, s = s.partition("'")
|
||||
s, _, _ = s.rpartition("'")
|
||||
return cls.fromstring(s)
|
||||
|
||||
@classmethod
|
||||
def convert(cls, x):
|
||||
"""Converts an object to LsColors, if needed."""
|
||||
if isinstance(x, cls):
|
||||
return x
|
||||
elif isinstance(x, str):
|
||||
return cls.fromstring(x)
|
||||
elif isinstance(x, bytes):
|
||||
return cls.fromstring(x.decode())
|
||||
else:
|
||||
return cls(x)
|
||||
|
||||
|
||||
def is_lscolors(x):
|
||||
"""Checks if an object is an instance of LsColors"""
|
||||
return isinstance(x, LsColors)
|
||||
|
||||
|
||||
@events.on_pre_spec_run_ls
|
||||
def ensure_ls_colors_in_env(spec=None, **kwargs):
|
||||
"""This ensures that the $LS_COLORS environment variable is in the
|
||||
environment. This fires exactly once upon the first time the
|
||||
ls command is called.
|
||||
"""
|
||||
env = builtins.__xonsh__.env
|
||||
if "LS_COLORS" not in env._d:
|
||||
# this adds it to the env too
|
||||
default_lscolors(env)
|
||||
events.on_pre_spec_run_ls.discard(ensure_ls_colors_in_env)
|
||||
|
||||
|
||||
#
|
||||
# Ensurerers
|
||||
#
|
||||
|
||||
Ensurer = collections.namedtuple("Ensurer", ["validate", "convert", "detype"])
|
||||
Ensurer.__doc__ = """Named tuples whose elements are functions that
|
||||
represent environment variable validation, conversion, detyping.
|
||||
|
@ -213,6 +526,7 @@ def DEFAULT_ENSURERS():
|
|||
"LC_MONETARY": (always_false, locale_convert("LC_MONETARY"), ensure_string),
|
||||
"LC_NUMERIC": (always_false, locale_convert("LC_NUMERIC"), ensure_string),
|
||||
"LC_TIME": (always_false, locale_convert("LC_TIME"), ensure_string),
|
||||
"LS_COLORS": (is_lscolors, LsColors.convert, detype),
|
||||
"LOADED_RC_FILES": (is_bool_seq, csv_to_bool_seq, bool_seq_to_csv),
|
||||
"MOUSE_SUPPORT": (is_bool, to_bool, bool_to_str),
|
||||
"MULTILINE_PROMPT": (is_string_or_callable, ensure_string, ensure_string),
|
||||
|
@ -224,6 +538,7 @@ def DEFAULT_ENSURERS():
|
|||
),
|
||||
"PRETTY_PRINT_RESULTS": (is_bool, to_bool, bool_to_str),
|
||||
"PROMPT": (is_string_or_callable, ensure_string, ensure_string),
|
||||
"PROMPT_FIELDS": (always_true, None, None),
|
||||
"PROMPT_TOOLKIT_COLOR_DEPTH": (
|
||||
always_false,
|
||||
ptk2_color_depth_setter,
|
||||
|
@ -347,6 +662,19 @@ def xonsh_append_newline(env):
|
|||
return env.get("XONSH_INTERACTIVE", False)
|
||||
|
||||
|
||||
@default_value
|
||||
def default_lscolors(env):
|
||||
"""Gets a default instanse of LsColors"""
|
||||
inherited_lscolors = os_environ.get("LS_COLORS", None)
|
||||
if inherited_lscolors is None:
|
||||
lsc = LsColors.fromdircolors()
|
||||
else:
|
||||
lsc = LsColors.fromstring(inherited_lscolors)
|
||||
# have to place this in the env, so it is applied
|
||||
env["LS_COLORS"] = lsc
|
||||
return lsc
|
||||
|
||||
|
||||
# Default values should generally be immutable, that way if a user wants
|
||||
# to set them they have to do a copy and write them to the environment.
|
||||
# try to keep this sorted.
|
||||
|
@ -388,6 +716,7 @@ def DEFAULT_VALUES():
|
|||
"LC_TIME": locale.setlocale(locale.LC_TIME),
|
||||
"LC_MONETARY": locale.setlocale(locale.LC_MONETARY),
|
||||
"LC_NUMERIC": locale.setlocale(locale.LC_NUMERIC),
|
||||
"LS_COLORS": default_lscolors,
|
||||
"LOADED_RC_FILES": (),
|
||||
"MOUSE_SUPPORT": False,
|
||||
"MULTILINE_PROMPT": ".",
|
||||
|
@ -641,6 +970,7 @@ def DEFAULT_DOCS():
|
|||
configurable=ON_WINDOWS,
|
||||
),
|
||||
"LANG": VarDocs("Fallback locale setting for systems where it matters"),
|
||||
"LS_COLORS": VarDocs("Color settings for ``ls`` command line utility"),
|
||||
"LOADED_RC_FILES": VarDocs(
|
||||
"Whether or not any of the xonsh run control files were loaded at "
|
||||
"startup. This is a sequence of bools in Python that is converted "
|
||||
|
@ -1015,22 +1345,22 @@ class Env(cabc.MutableMapping):
|
|||
self._d["PATH"] = list(PATH_DEFAULT)
|
||||
self._detyped = None
|
||||
|
||||
@staticmethod
|
||||
def detypeable(val):
|
||||
return not (callable(val) or isinstance(val, cabc.MutableMapping))
|
||||
|
||||
def detype(self):
|
||||
if self._detyped is not None:
|
||||
return self._detyped
|
||||
ctx = {}
|
||||
for key, val in self._d.items():
|
||||
if not self.detypeable(val):
|
||||
continue
|
||||
if not isinstance(key, str):
|
||||
key = str(key)
|
||||
ensurer = self.get_ensurer(key)
|
||||
val = ensurer.detype(val)
|
||||
ctx[key] = val
|
||||
if ensurer.detype is None:
|
||||
# cannot be detyped
|
||||
continue
|
||||
deval = ensurer.detype(val)
|
||||
if deval is None:
|
||||
# cannot be detyped
|
||||
continue
|
||||
ctx[key] = deval
|
||||
self._detyped = ctx
|
||||
return ctx
|
||||
|
||||
|
@ -1052,7 +1382,14 @@ class Env(cabc.MutableMapping):
|
|||
os_environ.update(self._orig_env)
|
||||
self._orig_env = None
|
||||
|
||||
def get_ensurer(self, key, default=Ensurer(always_true, None, ensure_string)):
|
||||
def _get_default_ensurer(self, default=None):
|
||||
if default is not None:
|
||||
return default
|
||||
else:
|
||||
default = Ensurer(always_true, None, ensure_string)
|
||||
return default
|
||||
|
||||
def get_ensurer(self, key, default=None):
|
||||
"""Gets an ensurer for the given key."""
|
||||
if key in self._ensurers:
|
||||
return self._ensurers[key]
|
||||
|
@ -1062,10 +1399,15 @@ class Env(cabc.MutableMapping):
|
|||
if k.match(key) is not None:
|
||||
break
|
||||
else:
|
||||
ensurer = default
|
||||
ensurer = self._get_default_ensurer(default=default)
|
||||
self._ensurers[key] = ensurer
|
||||
return ensurer
|
||||
|
||||
def set_ensurer(self, key, value):
|
||||
"""Sets an ensurer."""
|
||||
self._detyped = None
|
||||
self._ensurers[key] = value
|
||||
|
||||
def get_docs(self, key, default=VarDocs("<no documentation>")):
|
||||
"""Gets the documentation for the environment variable."""
|
||||
vd = self._docs.get(key, None)
|
||||
|
@ -1134,13 +1476,6 @@ class Env(cabc.MutableMapping):
|
|||
|
||||
def __getitem__(self, key):
|
||||
# remove this block on next release
|
||||
if key == "FORMATTER_DICT":
|
||||
print(
|
||||
"PendingDeprecationWarning: FORMATTER_DICT is an alias of "
|
||||
"PROMPT_FIELDS and will be removed in the next release",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return self["PROMPT_FIELDS"]
|
||||
if key is Ellipsis:
|
||||
return self
|
||||
elif key in self._d:
|
||||
|
@ -1165,24 +1500,26 @@ class Env(cabc.MutableMapping):
|
|||
# existing envvars can have any value including None
|
||||
old_value = self._d[key] if key in self._d else self._no_value
|
||||
self._d[key] = val
|
||||
if self.detypeable(val):
|
||||
self._detyped = None
|
||||
if self.get("UPDATE_OS_ENVIRON"):
|
||||
if self._orig_env is None:
|
||||
self.replace_env()
|
||||
else:
|
||||
os_environ[key] = ensurer.detype(val)
|
||||
self._detyped = None
|
||||
if self.get("UPDATE_OS_ENVIRON"):
|
||||
if self._orig_env is None:
|
||||
self.replace_env()
|
||||
elif ensurer.detype is None:
|
||||
pass
|
||||
else:
|
||||
deval = ensurer.detype(val)
|
||||
if deval is not None:
|
||||
os_environ[key] = deval
|
||||
if old_value is self._no_value:
|
||||
events.on_envvar_new.fire(name=key, value=val)
|
||||
elif old_value != val:
|
||||
events.on_envvar_change.fire(name=key, oldvalue=old_value, newvalue=val)
|
||||
|
||||
def __delitem__(self, key):
|
||||
val = self._d.pop(key)
|
||||
if self.detypeable(val):
|
||||
self._detyped = None
|
||||
if self.get("UPDATE_OS_ENVIRON") and key in os_environ:
|
||||
del os_environ[key]
|
||||
del self._d[key]
|
||||
self._detyped = None
|
||||
if self.get("UPDATE_OS_ENVIRON") and key in os_environ:
|
||||
del os_environ[key]
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""The environment will look up default values from its own defaults if a
|
||||
|
|
|
@ -22,7 +22,7 @@ def has_kwargs(func):
|
|||
|
||||
|
||||
def debug_level():
|
||||
if hasattr(builtins.__xonsh__, "env"):
|
||||
if hasattr(builtins, "__xonsh__") and hasattr(builtins.__xonsh__, "env"):
|
||||
return builtins.__xonsh__.env.get("XONSH_DEBUG")
|
||||
# FIXME: Under py.test, return 1(?)
|
||||
else:
|
||||
|
@ -118,6 +118,9 @@ class Event(AbstractEvent):
|
|||
# Wish I could just pull from set...
|
||||
def __init__(self):
|
||||
self._handlers = set()
|
||||
self._firing = False
|
||||
self._delayed_adds = None
|
||||
self._delayed_discards = None
|
||||
|
||||
def __len__(self):
|
||||
return len(self._handlers)
|
||||
|
@ -134,7 +137,12 @@ class Event(AbstractEvent):
|
|||
|
||||
This has no effect if the element is already present.
|
||||
"""
|
||||
self._handlers.add(item)
|
||||
if self._firing:
|
||||
if self._delayed_adds is None:
|
||||
self._delayed_adds = set()
|
||||
self._delayed_adds.add(item)
|
||||
else:
|
||||
self._handlers.add(item)
|
||||
|
||||
def discard(self, item):
|
||||
"""
|
||||
|
@ -142,7 +150,12 @@ class Event(AbstractEvent):
|
|||
|
||||
If the element is not a member, do nothing.
|
||||
"""
|
||||
self._handlers.discard(item)
|
||||
if self._firing:
|
||||
if self._delayed_discards is None:
|
||||
self._delayed_discards = set()
|
||||
self._delayed_discards.add(item)
|
||||
else:
|
||||
self._handlers.discard(item)
|
||||
|
||||
def fire(self, **kwargs):
|
||||
"""
|
||||
|
@ -163,6 +176,7 @@ class Event(AbstractEvent):
|
|||
appear multiple times.
|
||||
"""
|
||||
vals = []
|
||||
self._firing = True
|
||||
for handler in self._filterhandlers(self._handlers, **kwargs):
|
||||
try:
|
||||
rv = handler(**kwargs)
|
||||
|
@ -170,6 +184,14 @@ class Event(AbstractEvent):
|
|||
print_exception("Exception raised in event handler; ignored.")
|
||||
else:
|
||||
vals.append(rv)
|
||||
# clean up
|
||||
self._firing = False
|
||||
if self._delayed_adds is not None:
|
||||
self._handlers.update(self._delayed_adds)
|
||||
self._delayed_adds = None
|
||||
if self._delayed_discards is not None:
|
||||
self._handlers.difference_update(self._delayed_discards)
|
||||
self._delayed_discards = None
|
||||
return vals
|
||||
|
||||
|
||||
|
@ -300,6 +322,13 @@ class EventManager:
|
|||
for handler in oldevent:
|
||||
newevent.add(handler)
|
||||
|
||||
def exists(self, name):
|
||||
"""Checks if an event with a given name exist. If it does not exist, it
|
||||
will not be created. That is what makes this different than
|
||||
``hasattr(events, name)``, which will create the event.
|
||||
"""
|
||||
return name in self.__dict__
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an event, if it doesn't already exist."""
|
||||
if name.startswith("_"):
|
||||
|
|
|
@ -60,7 +60,7 @@ Parameters:
|
|||
events.doc(
|
||||
"on_pre_prompt",
|
||||
"""
|
||||
on_first_prompt() -> None
|
||||
on_pre_prompt() -> None
|
||||
|
||||
Fires just before the prompt is shown
|
||||
""",
|
||||
|
@ -69,7 +69,7 @@ Fires just before the prompt is shown
|
|||
events.doc(
|
||||
"on_post_prompt",
|
||||
"""
|
||||
on_first_prompt() -> None
|
||||
on_post_prompt() -> None
|
||||
|
||||
Fires just after the prompt returns
|
||||
""",
|
||||
|
|
|
@ -1089,6 +1089,11 @@ def swap_values(d, updates, default=_DEFAULT_SENTINEL):
|
|||
#
|
||||
|
||||
|
||||
def detype(x):
|
||||
"""This assumes that the object has a detype method, and calls that."""
|
||||
return x.detype()
|
||||
|
||||
|
||||
def is_int(x):
|
||||
"""Tests if something is an integer"""
|
||||
return isinstance(x, int)
|
||||
|
@ -1134,6 +1139,11 @@ def always_false(x):
|
|||
return False
|
||||
|
||||
|
||||
def always_none(x):
|
||||
"""Returns None"""
|
||||
return None
|
||||
|
||||
|
||||
def ensure_string(x):
|
||||
"""Returns a string if x is not a string, and x if it already is."""
|
||||
return str(x)
|
||||
|
@ -2115,7 +2125,9 @@ def expandvars(path):
|
|||
name = match.group("envvar")
|
||||
if name in env:
|
||||
ensurer = env.get_ensurer(name)
|
||||
value = ensurer.detype(env[name])
|
||||
val = env[name]
|
||||
value = str(val) if ensurer.detype is None else ensurer.detype(val)
|
||||
value = str(val) if value is None else value
|
||||
path = POSIX_ENVVAR_REGEX.sub(value, path, count=1)
|
||||
return path
|
||||
|
||||
|
|
|
@ -184,7 +184,8 @@ def _dump_xonfig_foreign_shell(path, value):
|
|||
def _dump_xonfig_env(path, value):
|
||||
name = os.path.basename(path.rstrip("/"))
|
||||
ensurer = builtins.__xonsh__.env.get_ensurer(name)
|
||||
dval = ensurer.detype(value)
|
||||
dval = str(value) if ensurer.detype is None else ensurer.detype(value)
|
||||
dval = str(value) if dval is None else dval
|
||||
return "${name} = {val!r}".format(name=name, val=dval)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue