Merge branch 'master' of github.com:mitnk/xonsh into history-backends

This commit is contained in:
Hugo Wang 2016-11-27 16:40:55 +08:00
commit fd37e37527
11 changed files with 204 additions and 37 deletions

13
news/conda-forge.rst Normal file
View file

@ -0,0 +1,13 @@
**Added:** None
**Changed:** None
**Deprecated:** None
**Removed:** None
**Fixed:**
* Fix failure to detect an Anaconda python distribution if the python was install from the conda-forge channel.
**Security:** None

20
news/stderr.rst Normal file
View file

@ -0,0 +1,20 @@
**Added:**
* New ``$XONSH_STDERR_PREFIX`` and ``$XONSH_STDERR_POSTFIX`` environment
variables allow the user to print a prompt-like string before and after
all stderr that is seen. For example, say that you would like stderr
to appear on a red background, you might set
``$XONSH_STDERR_PREFIX = "{BACKGROUND_RED}"`` and
``$XONSH_STDERR_PREFIX = "{NO_COLOR}"``.
* New ``xonsh.pyghooks.XonshTerminal256Formatter`` class patches
the pygments formatter to understand xonsh color token semantics.
**Changed:** None
**Deprecated:** None
**Removed:** None
**Fixed:** None
**Security:** None

View file

@ -7,7 +7,7 @@ import time
import builtins
from xonsh.tools import (XonshError, print_exception, DefaultNotGiven,
check_for_partial_string)
check_for_partial_string, format_std_prepost)
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS
from xonsh.codecache import (should_use_cache, code_cache_name,
code_cache_check, get_cache_filename,
@ -28,7 +28,8 @@ class _TeeStdBuf(io.RawIOBase):
in memory buffer.
"""
def __init__(self, stdbuf, membuf, encoding=None, errors=None):
def __init__(self, stdbuf, membuf, encoding=None, errors=None, prestd=b'',
poststd=b''):
"""
Parameters
----------
@ -42,11 +43,17 @@ class _TeeStdBuf(io.RawIOBase):
errors : str or None, optional
The error form for the encoding of the stream. Only used if stdbuf
is a text stream, rather than a binary one.
prestd : bytes, optional
The prefix to prepend to the standard buffer.
poststd : bytes, optional
The postfix to append to the standard buffer.
"""
self.stdbuf = stdbuf
self.membuf = membuf
self.encoding = encoding
self.errors = errors
self.prestd = prestd
self.poststd = poststd
self._std_is_binary = not hasattr(stdbuf, 'encoding')
def fileno(self):
@ -71,18 +78,24 @@ class _TeeStdBuf(io.RawIOBase):
def write(self, b):
"""Write bytes into both buffers."""
std_b = b
if self.prestd:
std_b = self.prestd + b
if self.poststd:
std_b += self.poststd
# write to stdbuf
if self._std_is_binary:
self.stdbuf.write(b)
self.stdbuf.write(std_b)
else:
self.stdbuf.write(b.decode(encoding=self.encoding,
errors=self.errors))
self.stdbuf.write(std_b.decode(encoding=self.encoding,
errors=self.errors))
return self.membuf.write(b)
class _TeeStd(io.TextIOBase):
"""Tees a std stream into an in-memory container and the original stream."""
def __init__(self, name, mem):
def __init__(self, name, mem, prestd='', poststd=''):
"""
Parameters
----------
@ -90,17 +103,26 @@ class _TeeStd(io.TextIOBase):
The name of the buffer in the sys module, e.g. 'stdout'.
mem : io.TextIOBase-like
The in-memory text-based representation.
prestd : str, optional
The prefix to prepend to the standard stream.
poststd : str, optional
The postfix to append to the standard stream.
"""
self._name = name
self.std = std = getattr(sys, name)
self.mem = mem
self.prestd = prestd
self.poststd = poststd
preb = prestd.encode(encoding=mem.encoding, errors=mem.errors)
postb = poststd.encode(encoding=mem.encoding, errors=mem.errors)
if hasattr(std, 'buffer'):
buffer = _TeeStdBuf(std.buffer, mem.buffer)
buffer = _TeeStdBuf(std.buffer, mem.buffer,
prestd=preb, poststd=postb)
else:
# TextIO does not have buffer as part of the API, so std streams
# may not either.
buffer = _TeeStdBuf(std, mem.buffer, encoding=mem.encoding,
errors=mem.errors)
errors=mem.errors, prestd=preb, poststd=postb)
self.buffer = buffer
setattr(sys, name, self)
@ -135,7 +157,12 @@ class _TeeStd(io.TextIOBase):
def write(self, s):
"""Writes data to the original std stream and the in-memory object."""
self.std.write(s)
std_s = s
if self.prestd:
std_s = self.prestd + std_s
if self.poststd:
std_s += self.poststd
self.std.write(std_s)
self.mem.write(s)
def flush(self):
@ -198,7 +225,11 @@ class Tee:
line_buffering=line_buffering,
write_through=write_through)
self.stdout = _TeeStd('stdout', self.memory)
self.stderr = _TeeStd('stderr', self.memory)
env = builtins.__xonsh_env__
prestderr = format_std_prepost(env.get('XONSH_STDERR_PREFIX'))
poststderr = format_std_prepost(env.get('XONSH_STDERR_POSTFIX'))
self.stderr = _TeeStd('stderr', self.memory, prestd=prestderr,
poststd=poststderr)
@property
def line_buffering(self):
@ -280,10 +311,9 @@ class BaseShell(object):
env = builtins.__xonsh_env__
hist = builtins.__xonsh_history__ # pylint: disable=no-member
ts1 = None
store_stdout = env.get('XONSH_STORE_STDOUT') # pylint: disable=no-member
enc = env.get('XONSH_ENCODING')
err = env.get('XONSH_ENCODING_ERRORS')
tee = Tee(encoding=enc, errors=err) if store_stdout else io.StringIO()
tee = Tee(encoding=enc, errors=err)
try:
ts0 = time.time()
run_compiled_code(code, self.ctx, None, 'single')
@ -307,10 +337,10 @@ class BaseShell(object):
return True
def _append_history(self, tee_out=None, **info):
"""
Append information about the command to the history.
"""Append information about the command to the history.
(Also handles on_postcommand because this is the place where all the information is available)
This also handles on_postcommand because this is the place where all the
information is available.
"""
hist = builtins.__xonsh_history__ # pylint: disable=no-member
if hist is None:
@ -339,11 +369,11 @@ class BaseShell(object):
"""Check if the cwd changed out from under us"""
cwd = os.getcwd()
if cwd != builtins.__xonsh_env__.get('PWD'):
old = builtins.__xonsh_env__.get('PWD') # working directory changed without updating $PWD
builtins.__xonsh_env__['PWD'] = cwd # track it now
old = builtins.__xonsh_env__.get('PWD') # working directory changed without updating $PWD
builtins.__xonsh_env__['PWD'] = cwd # track it now
if old is not None:
builtins.__xonsh_env__['OLDPWD'] = old # and update $OLDPWD like dirstack.
events.on_chdir.fire(old, cwd) # fire event after cwd actually changed.
builtins.__xonsh_env__['OLDPWD'] = old # and update $OLDPWD like dirstack.
events.on_chdir.fire(old, cwd) # fire event after cwd actually changed.
def push(self, line):
"""Pushes a line onto the buffer and compiles the code in a way that

View file

@ -159,6 +159,8 @@ def DEFAULT_ENSURERS():
'XONSH_LOGIN': (is_bool, to_bool, bool_to_str),
'XONSH_PROC_FREQUENCY': (is_float, float, str),
'XONSH_SHOW_TRACEBACK': (is_bool, to_bool, bool_to_str),
'XONSH_STDERR_PREFIX': (is_string, ensure_string, ensure_string),
'XONSH_STDERR_POSTFIX': (is_string, ensure_string, ensure_string),
'XONSH_STORE_STDOUT': (is_bool, to_bool, bool_to_str),
'XONSH_STORE_STDIN': (is_bool, to_bool, bool_to_str),
'XONSH_TRACEBACK_LOGFILE': (is_logfile_opt, to_logfile_opt, logfile_opt_to_str),
@ -302,6 +304,8 @@ def DEFAULT_VALUES():
'XONSH_LOGIN': False,
'XONSH_PROC_FREQUENCY': 1e-4,
'XONSH_SHOW_TRACEBACK': False,
'XONSH_STDERR_PREFIX': '',
'XONSH_STDERR_POSTFIX': '',
'XONSH_STORE_STDIN': False,
'XONSH_STORE_STDOUT': False,
'XONSH_TRACEBACK_LOGFILE': None,
@ -670,6 +674,18 @@ def DEFAULT_DOCS():
"When running a xonsh script, this variable contains the absolute path "
"to the currently executing script's file.",
configurable=False),
'XONSH_STDERR_PREFIX': VarDocs(
'A format string, using the same keys and colors as ``$PROMPT``, that '
'is prepended whenever stderr is displayed. This may be used in '
'conjunction with ``$XONSH_STDERR_POSTFIX`` to close out the block.'
'For example, to have stderr appear on a red background, the '
'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".'),
'XONSH_STDERR_POSTFIX': VarDocs(
'A format string, using the same keys and colors as ``$PROMPT``, that '
'is appended whenever stderr is displayed. This may be used in '
'conjunction with ``$XONSH_STDERR_PREFIX`` to start the block.'
'For example, to have stderr appear on a red background, the '
'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".'),
'XONSH_STORE_STDIN': VarDocs(
'Whether or not to store the stdin that is supplied to the '
'``!()`` and ``![]`` operators.'),

View file

@ -67,3 +67,8 @@ def winutils():
else:
m = None
return m
@lazyobject
def terminal256():
return importlib.import_module('pygments.formatters.terminal256')

View file

@ -62,7 +62,7 @@ ON_BSD = LazyBool(lambda: ON_FREEBSD or ON_NETBSD,
PYTHON_VERSION_INFO = sys.version_info[:3]
""" Version of Python interpreter as three-value tuple. """
ON_ANACONDA = LazyBool(
lambda: any(s in sys.version for s in {'Anaconda', 'Continuum'}),
lambda: any(s in sys.version for s in {'Anaconda', 'Continuum', 'conda-forge'}),
globals(), 'ON_ANACONDA')
""" ``True`` if executed in an Anaconda instance, else ``False``. """
CAN_RESIZE_WINDOW = LazyBool(lambda: hasattr(signal, 'SIGWINCH'),

View file

@ -26,7 +26,7 @@ import collections.abc as cabc
from xonsh.platform import ON_WINDOWS, ON_POSIX, CAN_RESIZE_WINDOW
from xonsh.tools import (redirect_stdout, redirect_stderr, print_exception,
XonshCalledProcessError, findfirst, on_main_thread,
XonshError)
XonshError, format_std_prepost)
from xonsh.lazyasd import lazyobject, LazyObject
from xonsh.jobs import wait_for_active_job
from xonsh.lazyimps import fcntl, termios, _winapi, msvcrt, winutils
@ -1681,6 +1681,7 @@ class CommandPipeline:
self.input = self._output = self.errors = self.endtime = None
self._closed_handle_cache = {}
self.lines = []
self._stderr_prefix = self._stderr_postfix = None
def __repr__(self):
s = self.__class__.__name__ + '('
@ -1851,6 +1852,10 @@ class CommandPipeline:
enc = env.get('XONSH_ENCODING')
err = env.get('XONSH_ENCODING_ERRORS')
b = b''.join(lines)
if self.stderr_prefix:
b = self.stderr_prefix + b
if self.stderr_postfix:
b += self.stderr_postfix
stderr_has_buffer = hasattr(sys.stderr, 'buffer')
# write bytes to std stream
if stderr_has_buffer:
@ -2102,6 +2107,32 @@ class CommandPipeline:
"""The resolve and executed command."""
return self.spec.cmd
@property
def stderr_prefix(self):
"""Prefix to print in front of stderr, as bytes."""
p = self._stderr_prefix
if p is None:
env = builtins.__xonsh_env__
t = env.get('XONSH_STDERR_PREFIX')
s = format_std_prepost(t, env=env)
p = s.encode(encoding=env.get('XONSH_ENCODING'),
errors=env.get('XONSH_ENCODING_ERRORS'))
self._stderr_prefix = p
return p
@property
def stderr_postfix(self):
"""Postfix to print after stderr, as bytes."""
p = self._stderr_postfix
if p is None:
env = builtins.__xonsh_env__
t = env.get('XONSH_STDERR_POSTFIX')
s = format_std_prepost(t, env=env)
p = s.encode(encoding=env.get('XONSH_ENCODING'),
errors=env.get('XONSH_ENCODING_ERRORS'))
self._stderr_postfix = p
return p
class HiddenCommandPipeline(CommandPipeline):
def __repr__(self):

View file

@ -8,13 +8,14 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.layout.lexers import PygmentsLexer
from prompt_toolkit.shortcuts import print_tokens
from prompt_toolkit.styles import PygmentsStyle
import pygments
from pygments.styles import get_all_styles
from pygments.token import Token
from xonsh.base_shell import BaseShell
from xonsh.tools import print_exception
from xonsh.pyghooks import (XonshLexer, partial_color_tokenize,
xonsh_style_proxy)
xonsh_style_proxy, XonshTerminal256Formatter)
from xonsh.ptk.completer import PromptToolkitCompleter
from xonsh.ptk.history import PromptToolkitHistory
from xonsh.ptk.key_bindings import load_xonsh_bindings
@ -205,11 +206,21 @@ class PromptToolkitShell(BaseShell):
toks.append((Token, ' ')) # final space
return toks
def format_color(self, string, **kwargs):
def format_color(self, string, hide=False, force_string=False, **kwargs):
"""Formats a color string using Pygments. This, therefore, returns
a list of (Token, str) tuples.
a list of (Token, str) tuples. If force_string is set to true, though,
this will return a color fomtatted string.
"""
return partial_color_tokenize(string)
tokens = partial_color_tokenize(string)
if force_string:
env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
proxy_style = xonsh_style_proxy(self.styler)
formatter = XonshTerminal256Formatter(style=proxy_style)
s = pygments.format(tokens, formatter)
return s
else:
return tokens
def print_color(self, string, end='\n', **kwargs):
"""Prints a color string using prompt-toolkit color management."""

View file

@ -21,9 +21,10 @@ from pygments.styles import get_style_by_name
import pygments.util
from xonsh.commands_cache import CommandsCache
from xonsh.lazyasd import LazyObject, LazyDict
from xonsh.lazyasd import LazyObject, LazyDict, lazyobject
from xonsh.tools import (ON_WINDOWS, intensify_colors_for_cmd_exe,
expand_gray_colors_for_cmd_exe)
from xonsh.lazyimps import terminal256
load_module_in_background('pkg_resources', debug='XONSH_DEBUG',
replacements={'pygments.plugin': 'pkg_resources'})
@ -1389,3 +1390,27 @@ del (_algol_style, _algol_nu_style, _autumn_style, _borland_style, _bw_style,
_murphy_style, _native_style, _paraiso_dark_style, _paraiso_light_style,
_pastie_style, _perldoc_style, _rrt_style, _tango_style, _trac_style,
_vim_style, _vs_style, _xcode_style)
#
# Formatter
#
@lazyobject
def XonshTerminal256Formatter():
class XonshTerminal256FormatterProxy(terminal256.Terminal256Formatter):
"""Proxy class for xonsh terminal256 formatting that understands.
xonsh color tokens.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# just keep the opening token for colors.
color_names = set(map(str, Color.subtypes))
for name, (opener, closer) in self.style_string.items():
if name in color_names:
self.style_string[name] = (opener, '')
# special case NO_COLOR, because it is special.
self.style_string['Token.Color.NO_COLOR'] = ('\x1b[39m', '')
return XonshTerminal256FormatterProxy

View file

@ -29,10 +29,6 @@ from xonsh.tools import print_exception, check_for_partial_string
from xonsh.platform import ON_WINDOWS, ON_CYGWIN, ON_DARWIN
from xonsh.lazyimps import pygments, pyghooks
terminal256 = LazyObject(
lambda: importlib.import_module('pygments.formatters.terminal256'),
globals(), 'terminal')
readline = None
RL_COMPLETION_SUPPRESS_APPEND = RL_LIB = RL_STATE = None
RL_CAN_RESIZE = False
@ -432,13 +428,13 @@ class ReadlineShell(BaseShell, cmd.Cmd):
self.settitle()
return p
def format_color(self, string, hide=False, **kwargs):
"""Readline implementation of color formatting. This usesg ANSI color
def format_color(self, string, hide=False, force_string=False, **kwargs):
"""Readline implementation of color formatting. This usess ANSI color
codes.
"""
hide = hide if self._force_hide is None else self._force_hide
return ansi_partial_color_format(string, hide=hide,
style=builtins.__xonsh_env__.get('XONSH_COLOR_STYLE'))
style = builtins.__xonsh_env__.get('XONSH_COLOR_STYLE')
return ansi_partial_color_format(string, hide=hide, style=style)
def print_color(self, string, hide=False, **kwargs):
if isinstance(string, str):
@ -448,7 +444,7 @@ class ReadlineShell(BaseShell, cmd.Cmd):
env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
style_proxy = pyghooks.xonsh_style_proxy(self.styler)
formatter = terminal256.Terminal256Formatter(style=style_proxy)
formatter = pyghooks.XonshTerminal256Formatter(style=style_proxy)
s = pygments.format(string, formatter).rstrip()
print(s, **kwargs)

View file

@ -1413,6 +1413,27 @@ def intensify_colors_on_win_setter(enable):
return enable
def format_std_prepost(template, env=None):
"""Formats a template prefix/postfix string for a standard buffer.
Returns a string suitable for prepending or appending.
"""
if not template:
return ''
env = builtins.__xonsh_env__ if env is None else env
shell = builtins.__xonsh_shell__.shell
try:
s = shell.prompt_formatter(template)
except Exception:
print_exception()
# \001\002 is there to fool pygments into not returning an empty string
# for potentially empty input. This happend when the template is just a
# color code with no visible text.
invis = '\001\002'
s = shell.format_color(invis + s + invis, force_string=True)
s = s.replace(invis, '')
return s
_RE_STRING_START = "[bBrRuU]*"
_RE_STRING_TRIPLE_DOUBLE = '"""'
_RE_STRING_TRIPLE_SINGLE = "'''"
@ -1446,8 +1467,7 @@ terminating quotes)"""
def check_for_partial_string(x):
"""
Returns the starting index (inclusive), ending index (exclusive), and
"""Returns the starting index (inclusive), ending index (exclusive), and
starting quote string of the most recent Python string found in the input.
check_for_partial_string(x) -> (startix, endix, quote)