Merge branch 'master' into nochild

This commit is contained in:
Anthony Scopatz 2019-02-26 12:02:27 -05:00
commit d460542b7f
20 changed files with 864 additions and 129 deletions

View file

@ -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
View 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
View 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

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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 = []

View file

@ -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):

View file

@ -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)

View file

@ -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"

View file

@ -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__

View file

@ -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

View file

@ -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

View file

@ -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
#

View file

@ -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)

View file

@ -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

View file

@ -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("_"):

View file

@ -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
""",

View file

@ -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

View file

@ -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)