diff --git a/docs/api/index.rst b/docs/api/index.rst index 3762c5c9d..3b5db6285 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -49,6 +49,7 @@ For those of you who want the gritty details. :maxdepth: 1 tools + platform lazyjson teepty openpy diff --git a/docs/api/platform.rst b/docs/api/platform.rst new file mode 100644 index 000000000..34f96c125 --- /dev/null +++ b/docs/api/platform.rst @@ -0,0 +1,11 @@ +.. _xonsh_platform: + +Platform-specific constants and implementations (``xonsh.platform``) +==================================================================== + + +.. automodule:: xonsh.platform + :members: + :undoc-members: + :inherited-members: + diff --git a/docs/dependencies.rst b/docs/dependencies.rst index 42ffe7fee..826e1556d 100644 --- a/docs/dependencies.rst +++ b/docs/dependencies.rst @@ -9,6 +9,7 @@ Xonsh currently has the following external dependencies, #. prompt-toolkit (optional) #. Jupyter (optional) #. setproctitle (optional) + #. distro (optional) *Documentation:* diff --git a/docs/xonshconfig.rst b/docs/xonshconfig.rst index b092741a7..584febf76 100644 --- a/docs/xonshconfig.rst +++ b/docs/xonshconfig.rst @@ -50,7 +50,7 @@ dictionaries have the following structure: ``default=false`` :envcmd: *str, optional* - The command to generate environment output with. ``default="env"`` -:aliascmd: *str, optional* - The command to generate alais output with. +:aliascmd: *str, optional* - The command to generate alias output with. ``default="alias"`` :extra_args: *list of str, optional* - Addtional command line options to pass into the shell. ``default=[]`` diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2cf812938..000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -numpydoc==0.5 \ No newline at end of file diff --git a/setup.py b/setup.py index 6a64575a3..66578319c 100755 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ if HAVE_SETUPTOOLS: def main(): """The main entry point.""" - if sys.version_info[0] < 3: + if sys.version_info[:2] < (3, 4): sys.exit('xonsh currently requires Python 3.4+') try: if '--name' not in sys.argv: @@ -168,8 +168,7 @@ def main(): if HAVE_SETUPTOOLS: skw['entry_points'] = { 'pygments.lexers': ['xonsh = xonsh.pyghooks:XonshLexer', - 'xonshcon = xonsh.pyghooks:XonshConsoleLexer', - ], + 'xonshcon = xonsh.pyghooks:XonshConsoleLexer'], 'console_scripts': ['xonsh = xonsh.main:main'], } skw['cmdclass']['develop'] = xdevelop diff --git a/xonsh/aliases.py b/xonsh/aliases.py index 11dccb8ad..a303bff2d 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -1,28 +1,25 @@ # -*- coding: utf-8 -*- """Aliases for the xonsh shell.""" +from argparse import ArgumentParser, Action +import builtins +from collections.abc import MutableMapping, Iterable, Sequence import os import shlex -import builtins -import sys -import subprocess -from functools import lru_cache -from argparse import ArgumentParser, Action -from collections.abc import MutableMapping, Iterable, Sequence from xonsh.dirstack import cd, pushd, popd, dirs, _get_cwd -from xonsh.jobs import jobs, fg, bg, kill_all_jobs -from xonsh.proc import foreground -from xonsh.timings import timeit_alias -from xonsh.tools import (ON_MAC, ON_WINDOWS, ON_ANACONDA, - XonshError, to_bool, string_types) -from xonsh.history import main as history_alias -from xonsh.replay import main as replay_main -from xonsh.xontribs import main as xontribs_main from xonsh.environ import locate_binary from xonsh.foreign_shells import foreign_shell_data +from xonsh.jobs import jobs, fg, bg, kill_all_jobs +from xonsh.history import main as history_alias +from xonsh.platform import ON_ANACONDA, ON_DARWIN, ON_WINDOWS +from xonsh.proc import foreground +from xonsh.replay import main as replay_main +from xonsh.timings import timeit_alias +from xonsh.tools import (XonshError, argvquote, escape_windows_cmd_string, + to_bool) from xonsh.vox import Vox -from xonsh.tools import argvquote, escape_windows_cmd_string +from xonsh.xontribs import main as xontribs_main from xonsh.xoreutils import _which @@ -106,7 +103,7 @@ class Aliases(MutableMapping): return self._raw[key] def __setitem__(self, key, val): - if isinstance(val, string_types): + if isinstance(val, str): self._raw[key] = shlex.split(val) else: self._raw[key] = val @@ -330,7 +327,6 @@ def bang_bang(args, stdin=None): class AWitchAWitch(Action): SUPPRESS = '==SUPPRESS==' - def __init__(self, option_strings, version=None, dest=SUPPRESS, default=SUPPRESS, **kwargs): super().__init__(option_strings=option_strings, dest=dest, @@ -384,10 +380,10 @@ def which(args, stdin=None, stdout=None, stderr=None): parser.print_usage(file=stderr) return -1 pargs = parser.parse_args(args) - + if pargs.all: pargs.verbose = True - + if ON_WINDOWS: if pargs.exts: exts = pargs.exts @@ -411,9 +407,9 @@ def which(args, stdin=None, stdout=None, stderr=None): nmatches += 1 if not pargs.all: continue - macthes = _which.whichgen(arg, exts=exts, verbose=pargs.verbose, + matches = _which.whichgen(arg, exts=exts, verbose=pargs.verbose, path=builtins.__xonsh_env__['PATH']) - for abs_name, from_where in macthes: + for abs_name, from_where in matches: if ON_WINDOWS: # Use list dir to get correct case for the filename # i.e. windows is case insesitive but case preserving @@ -548,10 +544,9 @@ def make_default_aliases(): print(msg.format(cmd)) default_aliases['sudo'] = sudo - elif ON_MAC: + elif ON_DARWIN: default_aliases['ls'] = ['ls', '-G'] else: default_aliases['grep'] = ['grep', '--color=auto'] default_aliases['ls'] = ['ls', '--color=auto', '-v'] return default_aliases - diff --git a/xonsh/ast.py b/xonsh/ast.py index 5c9f9e988..ea617ee7f 100644 --- a/xonsh/ast.py +++ b/xonsh/ast.py @@ -18,9 +18,10 @@ from ast import Ellipsis # pylint: disable=redefined-builtin import textwrap from itertools import repeat -from xonsh.tools import subproc_toks, VER_3_5, VER_MAJOR_MINOR +from xonsh.tools import subproc_toks +from xonsh.platform import PYTHON_VERSION_INFO -if VER_3_5 <= VER_MAJOR_MINOR: +if PYTHON_VERSION_INFO >= (3, 5, 0): # pylint: disable=unused-import # pylint: disable=no-name-in-module from ast import MatMult, AsyncFunctionDef, AsyncWith, AsyncFor, Await diff --git a/xonsh/base_shell.py b/xonsh/base_shell.py index 4848e534c..bb7525efc 100644 --- a/xonsh/base_shell.py +++ b/xonsh/base_shell.py @@ -6,14 +6,14 @@ import sys import time import builtins -from xonsh.tools import XonshError, escape_windows_cmd_string, ON_WINDOWS, \ - print_exception, HAVE_PYGMENTS +from xonsh.tools import XonshError, escape_windows_cmd_string, print_exception +from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS from xonsh.codecache import (should_use_cache, code_cache_name, code_cache_check, get_cache_filename, update_cache, run_compiled_code) from xonsh.completer import Completer from xonsh.environ import multiline_prompt, format_prompt, partial_format_prompt -if HAVE_PYGMENTS: +if HAS_PYGMENTS: from xonsh.pyghooks import XonshStyle @@ -119,7 +119,7 @@ class BaseShell(object): self.buffer = [] self.need_more_lines = False self.mlprompt = None - if HAVE_PYGMENTS: + if HAS_PYGMENTS: env = builtins.__xonsh_env__ self.styler = XonshStyle(env.get('XONSH_COLOR_STYLE')) else: diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py index c4a0396b9..745556ba7 100644 --- a/xonsh/built_ins.py +++ b/xonsh/built_ins.py @@ -4,34 +4,35 @@ Note that this module is named 'built_ins' so as not to be confused with the special Python builtins module. """ +import atexit +import builtins +from collections import Sequence +from contextlib import contextmanager +import inspect +from glob import iglob import os import re -import sys -import time import shlex -import atexit import signal -import inspect -import builtins -import tempfile -from glob import glob, iglob from subprocess import Popen, PIPE, STDOUT, CalledProcessError -from contextlib import contextmanager -from collections import Sequence, Iterable +import sys +import tempfile +import time -from xonsh.tools import ( - suggest_commands, XonshError, ON_POSIX, ON_WINDOWS, string_types, - expandvars, CommandsCache -) -from xonsh.inspectors import Inspector +from xonsh.aliases import Aliases, make_default_aliases from xonsh.environ import Env, default_env, locate_binary +from xonsh.foreign_shells import load_foreign_aliases +from xonsh.history import History +from xonsh.inspectors import Inspector from xonsh.jobs import add_job, wait_for_active_job +from xonsh.platform import ON_POSIX, ON_WINDOWS from xonsh.proc import (ProcProxy, SimpleProcProxy, ForegroundProcProxy, SimpleForegroundProcProxy, TeePTYProc, CompletedCommand, HiddenCompletedCommand) -from xonsh.aliases import Aliases, make_default_aliases -from xonsh.history import History -from xonsh.foreign_shells import load_foreign_aliases +from xonsh.tools import ( + suggest_commands, XonshError, expandvars, CommandsCache +) + ENV = None BUILTINS_LOADED = False @@ -401,7 +402,7 @@ def run_subproc(cmds, captured=False): procinfo['args'] = list(cmd) stdin = None stderr = None - if isinstance(cmd, string_types): + if isinstance(cmd, str): continue streams = {} while True: @@ -653,10 +654,10 @@ def subproc_uncaptured(*cmds): def ensure_list_of_strs(x): """Ensures that x is a list of strings.""" - if isinstance(x, string_types): + if isinstance(x, str): rtn = [x] elif isinstance(x, Sequence): - rtn = [i if isinstance(i, string_types) else str(i) for i in x] + rtn = [i if isinstance(i, str) else str(i) for i in x] else: rtn = [str(x)] return rtn diff --git a/xonsh/completer.py b/xonsh/completer.py index 7a1529c4d..93cb65c19 100644 --- a/xonsh/completer.py +++ b/xonsh/completer.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- """A (tab-)completer for xonsh.""" -import os -import re import ast -import sys -import shlex +import builtins +import inspect +import importlib +import os from pathlib import Path import pickle -import inspect -import builtins -import importlib +import re +import shlex import subprocess +import sys from xonsh.built_ins import iglobpath, expand_path +from xonsh.platform import ON_WINDOWS, scandir from xonsh.tools import (subexpr_from_unbalanced, get_sep, check_for_partial_string, RE_STRING_START) -from xonsh.tools import ON_WINDOWS RE_DASHF = re.compile(r'-F\s+(\w+)') @@ -445,13 +445,13 @@ class Completer(object): def _collect_completions_sources(): sources = [] paths = (Path(x) for x in - builtins.__xonsh_env__.get('BASH_COMPLETIONS')) + builtins.__xonsh_env__.get('BASH_COMPLETIONS', ())) for path in paths: if path.is_file(): - sources.append('source ' + str(path)) + sources.append('source ' + path.path) elif path.is_dir(): - for _file in (x for x in path.glob('*') if x.is_file()): - sources.append('source ' + str(_file)) + for _file in (x for x in scandir(str(path)) if x.is_file()): + sources.append('source ' + _file.path) return sources def _load_bash_complete_funcs(self): diff --git a/xonsh/environ.py b/xonsh/environ.py index 3c8c7bd00..02e9de717 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -1,36 +1,37 @@ # -*- coding: utf-8 -*- """Environment for the xonsh shell.""" -import os -import re -import sys +import builtins +from collections import Mapping, MutableMapping, MutableSequence, MutableSet, namedtuple +from contextlib import contextmanager +from functools import wraps +from itertools import chain import json +import locale +import os +from pprint import pformat +import re import socket import string -import locale -import builtins import subprocess -from itertools import chain +import sys from warnings import warn -from pprint import pformat -from functools import wraps -from contextlib import contextmanager -from collections import (Mapping, MutableMapping, MutableSequence, - MutableSet, namedtuple) from xonsh import __version__ as XONSH_VERSION -from xonsh.tools import ( - ON_WINDOWS, ON_MAC, ON_LINUX, ON_ARCH, IS_ROOT, ON_ANACONDA, - always_true, always_false, ensure_string, is_env_path, str_to_env_path, - env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple, - history_tuple_to_str, is_float, string_types, is_string, DEFAULT_ENCODING, - is_completions_display_value, to_completions_display_value, is_string_set, - csv_to_set, set_to_csv, get_sep, is_int, is_bool_seq, csv_to_bool_seq, - bool_seq_to_csv, DefaultNotGiven, setup_win_unicode_console, - intensify_colors_on_win_setter, print_exception -) from xonsh.codecache import run_script_with_cache from xonsh.dirstack import _get_cwd from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs +from xonsh.platform import (BASH_COMPLETIONS_DEFAULT, ON_ANACONDA, ON_LINUX, + ON_WINDOWS, DEFAULT_ENCODING) +from xonsh.tools import ( + IS_SUPERUSER, always_true, always_false, ensure_string, is_env_path, + str_to_env_path, env_path_to_str, is_bool, to_bool, bool_to_str, + is_history_tuple, to_history_tuple, history_tuple_to_str, is_float, + is_string, is_completions_display_value, to_completions_display_value, + is_string_set, csv_to_set, set_to_csv, get_sep, is_int, is_bool_seq, + csv_to_bool_seq, bool_seq_to_csv, DefaultNotGiven, print_exception, + setup_win_unicode_console, intensify_colors_on_win_setter +) + LOCALE_CATS = { 'LC_CTYPE': locale.LC_CTYPE, @@ -157,14 +158,7 @@ DEFAULT_VALUES = { 'AUTO_CD': False, 'AUTO_PUSHD': False, 'AUTO_SUGGEST': True, - 'BASH_COMPLETIONS': (('/usr/local/etc/bash_completion', - '/opt/local/etc/profile.d/bash_completion.sh') - if ON_MAC else - ('/usr/share/bash-completion/bash_completion', - '/usr/share/bash-completion/completions/git') - if ON_ARCH else - ('/etc/bash_completion', - '/usr/share/bash-completion/completions/git')), + 'BASH_COMPLETIONS': BASH_COMPLETIONS_DEFAULT, 'CASE_SENSITIVE_COMPLETIONS': ON_LINUX, 'CDPATH': (), 'COMPLETIONS_DISPLAY': 'multi', @@ -320,7 +314,8 @@ DEFAULT_DOCS = { 'INTENSIFY_COLORS_ON_WIN': VarDocs('Enhance style colors for readability ' 'when using the default terminal (cmd.exe) on winodws. Blue colors, ' 'which are hard to read, are replaced with cyan. Other colors are ' - 'generally replaced by their bright counter parts.'), + 'generally replaced by their bright counter parts.', + configurable=ON_WINDOWS), 'LOADED_CONFIG': VarDocs('Whether or not the xonsh config file was loaded', configurable=False), 'LOADED_RC_FILES': VarDocs( @@ -391,7 +386,8 @@ DEFAULT_DOCS = { 'used. This can be used to fix situations where a spawned process, ' 'such as piping into \'grep\', exits too quickly for the piping ' 'operation itself. TeePTY (and thus this variable) are currently ' - 'only used when $XONSH_STORE_STDOUT is True.', configurable=ON_LINUX), + 'only used when $XONSH_STORE_STDOUT is True.', + configurable=ON_LINUX), 'TERM': VarDocs( 'TERM is sometimes set by the terminal emulator. This is used (when ' "valid) to determine whether or not to set the title. Users shouldn't " @@ -538,7 +534,7 @@ class Env(MutableMapping): for key, val in self._d.items(): if not self.detypeable(val): continue - if not isinstance(key, string_types): + if not isinstance(key, str): key = str(key) ensurer = self.get_ensurer(key) val = ensurer.detype(val) @@ -570,16 +566,14 @@ class Env(MutableMapping): if key in self.ensurers: return self.ensurers[key] for k, ensurer in self.ensurers.items(): - if isinstance(k, string_types): + if isinstance(k, str): continue - m = k.match(key) - if m is not None: - ens = ensurer + if k.match(key) is not None: break else: - ens = default - self.ensurers[key] = ens - return ens + ensurer = default + self.ensurers[key] = ensurer + return ensurer def get_docs(self, key, default=VarDocs('')): """Gets the documentation for the environment variable.""" @@ -660,16 +654,14 @@ class Env(MutableMapping): if self._orig_env is None: self.replace_env() else: - dval = ensurer.detype(val) - os.environ[key] = dval + os.environ[key] = ensurer.detype(val) def __delitem__(self, key): val = self._d.pop(key) if self.detypeable(val): self._detyped = None - if self.get('UPDATE_OS_ENVIRON'): - if key in os.environ: - del os.environ[key] + 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 @@ -756,7 +748,7 @@ def yield_executables_posix(directory, name): names = os.listdir(directory) except PermissionError: return - if name in os.listdir(directory): + if name in names: path = os.path.join(directory, name) if _is_executable_file(path): yield path @@ -1052,7 +1044,7 @@ else: FORMATTER_DICT = dict( user=os.environ.get(USER, ''), - prompt_end='#' if IS_ROOT else '$', + prompt_end='#' if IS_SUPERUSER else '$', hostname=socket.gethostname().split('.', 1)[0], cwd=_replace_home_cwd, cwd_dir=lambda: os.path.dirname(_replace_home_cwd()), @@ -1127,7 +1119,7 @@ def partial_format_prompt(template=DEFAULT_PROMPT, formatter_dict=None): if field is None: continue elif field.startswith('$'): - v = builtins.__xonsh_env__[name[1:]] + v = builtins.__xonsh_env__[name[1:]] # FIXME `name` is an unresolved ref v = _FORMATTER.convert_field(v, conv) v = _FORMATTER.format_field(v, spec) toks.append(v) @@ -1226,7 +1218,7 @@ def load_static_config(ctx, config=None): def xonshrc_context(rcfiles=None, execer=None): """Attempts to read in xonshrc file, and return the contents.""" loaded = builtins.__xonsh_env__['LOADED_RC_FILES'] = [] - if (rcfiles is None or execer is None): + if rcfiles is None or execer is None: return {} env = {} for rcfile in rcfiles: diff --git a/xonsh/imphooks.py b/xonsh/imphooks.py index 523ef2712..5d2dc5c46 100644 --- a/xonsh/imphooks.py +++ b/xonsh/imphooks.py @@ -3,13 +3,12 @@ This module registers the hooks it defines when it is imported. """ +import builtins +from importlib.abc import MetaPathFinder, SourceLoader +from importlib.machinery import ModuleSpec import os import sys -import builtins -from importlib.machinery import ModuleSpec -from importlib.abc import MetaPathFinder, SourceLoader -from xonsh.tools import string_types from xonsh.execer import Execer @@ -46,7 +45,7 @@ class XonshImportHook(MetaPathFinder, SourceLoader): name = fullname.rsplit(dot, 1)[-1] fname = name + '.xsh' for p in path: - if not isinstance(p, string_types): + if not isinstance(p, str): continue if not os.path.isdir(p): continue diff --git a/xonsh/inspectors.py b/xonsh/inspectors.py index 8f9dec392..0a9c082b2 100644 --- a/xonsh/inspectors.py +++ b/xonsh/inspectors.py @@ -8,29 +8,24 @@ This file was forked from the IPython project: * Copyright (c) 2001, Janko Hauser * Copyright (c) 2001, Nathaniel Gray """ +from collections import namedtuple +import inspect +import io as stdlib_io +from itertools import zip_longest +import linecache import os import sys import types -import inspect -import linecache -import io as stdlib_io -from collections import namedtuple from xonsh import openpy -from xonsh.tools import (cast_unicode, safe_hasattr, string_types, indent, - VER_MAJOR_MINOR, VER_3_4, print_color, format_color, HAVE_PYGMENTS) +from xonsh.tools import (cast_unicode, safe_hasattr, indent, + print_color, format_color) +from xonsh.platform import HAS_PYGMENTS, PYTHON_VERSION_INFO -if HAVE_PYGMENTS: +if HAS_PYGMENTS: import pygments from xonsh import pyghooks -if sys.version_info[0] > 2: - ISPY3K = True - from itertools import zip_longest -else: - ISPY3K = False - from itertools import izip_longest as zip_longest - # builtin docstrings to ignore _func_call_docstring = types.FunctionType.__call__.__doc__ @@ -100,7 +95,7 @@ def getdoc(obj): pass else: # if we get extra info, we add it to the normal docstring. - if isinstance(ds, string_types): + if isinstance(ds, str): return inspect.cleandoc(ds) try: @@ -162,7 +157,7 @@ def getargspec(obj): if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): obj = obj.__call__ - return inspect.getfullargspec(obj) if ISPY3K else inspect.getargspec(obj) + return inspect.getfullargspec(obj) def format_argspec(argspec): @@ -300,7 +295,7 @@ def find_source_lines(obj): return lineno -if VER_MAJOR_MINOR <= VER_3_4: +if PYTHON_VERSION_INFO < (3, 5, 0): FrameInfo = namedtuple('FrameInfo', ['frame', 'filename', 'lineno', 'function', 'code_context', 'index']) def getouterframes(frame, context=1): @@ -351,8 +346,6 @@ class Inspector(object): if inspect.isclass(obj): header = self.__head('Class constructor information:\n') obj = obj.__init__ - elif (not ISPY3K) and type(obj) is types.InstanceType: - obj = obj.__call__ output = self._getdef(obj, oname) if output is None: @@ -488,7 +481,7 @@ class Inspector(object): title_width : int How many characters to pad titles to. Default to longest title. """ - if HAVE_PYGMENTS: + if HAS_PYGMENTS: rtn = self._format_fields_tokens(fields, title_width=title_width) else: rtn = self._format_fields_str(fields, title_width=title_width) @@ -535,14 +528,6 @@ class Inspector(object): displayfields.append((title, field.rstrip())) add_fields(self.pinfo_fields1) - - # Base class for old-style instances - if ((not ISPY3K) and - isinstance(obj, types.InstanceType) and - info['base_class']): - o = ("Base Class", info['base_class'].rstrip()) - displayfields.append(o) - add_fields(self.pinfo_fields2) # Namespace @@ -600,7 +585,7 @@ class Inspector(object): # Get docstring, special-casing aliases: if isalias: if not callable(obj): - if len(obj) >= 2 and isinstance(obj[1], string_types): + if len(obj) >= 2 and isinstance(obj[1], str): ds = "Alias to the system command:\n {0}".format(obj[1]) else: # pylint:disable=bare-except ds = "Alias: " + str(obj) @@ -689,7 +674,7 @@ class Inspector(object): source = getsource(obj.__class__, binary_file) if source is not None: source = source.rstrip() - if HAVE_PYGMENTS: + if HAS_PYGMENTS: lexer = pyghooks.XonshLexer() source = list(pygments.lex(source, lexer=lexer)) out['source'] = source diff --git a/xonsh/lazyjson.py b/xonsh/lazyjson.py index dc87f2793..15a018cd7 100644 --- a/xonsh/lazyjson.py +++ b/xonsh/lazyjson.py @@ -1,20 +1,15 @@ # -*- coding: utf-8 -*- """Implements a lazy JSON file class that wraps around json data.""" import io -import weakref -from contextlib import contextmanager +import json from collections import Mapping, Sequence +from contextlib import contextmanager +import weakref -try: - import simplejson as json -except ImportError: - import json - -from xonsh.tools import string_types def _to_json_with_size(obj, offset=0, sort_keys=False): - if isinstance(obj, string_types): + if isinstance(obj, str): s = json.dumps(obj) o = offset n = size = len(s.encode()) # size in bytes @@ -207,7 +202,7 @@ class LazyJSON(Node): """ self._f = f self.reopen = reopen - if not reopen and isinstance(f, string_types): + if not reopen and isinstance(f, str): self._f = open(f, 'r', newline='\n') self._load_index() self.root = weakref.proxy(self) @@ -224,7 +219,7 @@ class LazyJSON(Node): @contextmanager def _open(self, *args, **kwargs): - if self.reopen and isinstance(self._f, string_types): + if self.reopen and isinstance(self._f, str): f = open(self._f, *args, **kwargs) yield f f.close() diff --git a/xonsh/lexer.py b/xonsh/lexer.py index b9a191fe7..f0b301cd6 100644 --- a/xonsh/lexer.py +++ b/xonsh/lexer.py @@ -3,7 +3,6 @@ Written using a hybrid of ``tokenize`` and PLY. """ -import xonsh.tokenize as tokenize from io import BytesIO from keyword import kwlist @@ -13,7 +12,8 @@ try: except ImportError: from xonsh.ply.lex import LexToken -from xonsh.tools import VER_3_5, VER_MAJOR_MINOR +from xonsh.platform import PYTHON_VERSION_INFO +import xonsh.tokenize as tokenize token_map = {} """ @@ -51,7 +51,7 @@ token_map[tokenize.REGEXPATH] = 'REGEXPATH' token_map[tokenize.NEWLINE] = 'NEWLINE' token_map[tokenize.INDENT] = 'INDENT' token_map[tokenize.DEDENT] = 'DEDENT' -if VER_3_5 <= VER_MAJOR_MINOR: +if PYTHON_VERSION_INFO >= (3, 5, 0): token_map[tokenize.ASYNC] = 'ASYNC' token_map[tokenize.AWAIT] = 'AWAIT' diff --git a/xonsh/main.py b/xonsh/main.py index 7ebefe172..80b0bde95 100644 --- a/xonsh/main.py +++ b/xonsh/main.py @@ -13,15 +13,16 @@ except ImportError: setproctitle = None from xonsh import __version__ +from xonsh.environ import DEFAULT_VALUES from xonsh.shell import Shell from xonsh.pretty import pprint, pretty from xonsh.proc import HiddenCompletedCommand from xonsh.jobs import ignore_sigtstp -from xonsh.tools import (HAVE_PYGMENTS, setup_win_unicode_console, print_color, - ON_WINDOWS) -from xonsh.codecache import (run_script_with_cache, run_code_with_cache) +from xonsh.tools import setup_win_unicode_console, print_color +from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS +from xonsh.codecache import run_script_with_cache, run_code_with_cache -if HAVE_PYGMENTS: +if HAS_PYGMENTS: import pygments from xonsh import pyghooks @@ -134,6 +135,7 @@ def arg_undoers(): return au + def undo_args(args): """Undoes missaligned args.""" au = arg_undoers() @@ -145,11 +147,12 @@ def undo_args(args): if a.startswith(k): au[k](args) + def _pprint_displayhook(value): if value is None or isinstance(value, HiddenCompletedCommand): return builtins._ = None # Set '_' to None to avoid recursion - if HAVE_PYGMENTS: + if HAS_PYGMENTS: s = pretty(value) # color case lexer = pyghooks.XonshLexer() tokens = list(pygments.lex(s, lexer=lexer)) @@ -158,12 +161,14 @@ def _pprint_displayhook(value): pprint(value) # black & white case builtins._ = value + class XonshMode(enum.Enum): single_command = 0 script_from_file = 1 script_from_stdin = 2 interactive = 3 + def premain(argv=None): """Setup for main xonsh entry point, returns parsed arguments.""" if setproctitle is not None: @@ -181,7 +186,8 @@ def premain(argv=None): version = '/'.join(('xonsh', __version__)), print(version) exit() - shell_kwargs = {'shell_type': args.shell_type, + shell_kwargs = {'shell_type': args.shell_type or + DEFAULT_VALUES.get('SHELL_TYPE'), 'completer': False, 'login': False, 'scriptcache': args.scriptcache, @@ -268,6 +274,5 @@ def main_context(argv=None): postmain(args) - if __name__ == '__main__': main() diff --git a/xonsh/openpy.py b/xonsh/openpy.py index 8041c7689..24a7ae39e 100644 --- a/xonsh/openpy.py +++ b/xonsh/openpy.py @@ -13,11 +13,9 @@ This file was forked from the IPython project: * Copyright (c) 2001, Nathaniel Gray """ import io -import re import os.path -from io import TextIOWrapper, BytesIO +import re -from xonsh.tools import unicode_type cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE) cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE) @@ -127,7 +125,7 @@ except ImportError: buf = io.open(filename, 'rb') # Tweaked to use io.open for Python 2 encoding, lines = detect_encoding(buf.readline) buf.seek(0) - text = TextIOWrapper(buf, encoding, line_buffering=True) + text = io.TextIOWrapper(buf, encoding, line_buffering=True) text.mode = 'r' return text @@ -140,10 +138,10 @@ def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True): txt can be either a bytes buffer or a string containing the source code. """ - if isinstance(txt, unicode_type): + if isinstance(txt, str): return txt if isinstance(txt, bytes): - buf = BytesIO(txt) + buf = io.BytesIO(txt) else: buf = txt try: @@ -151,7 +149,7 @@ def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True): except SyntaxError: encoding = "ascii" buf.seek(0) - text = TextIOWrapper(buf, encoding, errors=errors, line_buffering=True) + text = io.TextIOWrapper(buf, encoding, errors=errors, line_buffering=True) text.mode = 'r' if skip_encoding_cookie: return u"".join(strip_encoding_cookie(text)) diff --git a/xonsh/parser.py b/xonsh/parser.py index 770b89b4f..21017d7e2 100644 --- a/xonsh/parser.py +++ b/xonsh/parser.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """Implements the xonsh parser.""" -from xonsh.tools import (VER_3_4, VER_3_5, VER_MAJOR_MINOR) +from xonsh.platform import PYTHON_VERSION_INFO -if VER_MAJOR_MINOR <= VER_3_4: +if PYTHON_VERSION_INFO < (3, 5, 0): from xonsh.parsers.v34 import Parser else: from xonsh.parsers.v35 import Parser diff --git a/xonsh/parsers/base.py b/xonsh/parsers/base.py index 917bfe758..e93060bb9 100644 --- a/xonsh/parsers/base.py +++ b/xonsh/parsers/base.py @@ -9,7 +9,7 @@ except ImportError: from xonsh import ast from xonsh.lexer import Lexer, LexToken -from xonsh.tools import VER_3_5_1, VER_FULL +from xonsh.platform import PYTHON_VERSION_INFO class Location(object): @@ -610,7 +610,7 @@ class BaseParser(object): """tfpdef : name_tok colon_test_opt""" p1 = p[1] kwargs = {'arg': p1.value, 'annotation': p[2]} - if VER_FULL >= VER_3_5_1: + if PYTHON_VERSION_INFO >= (3, 5, 1): kwargs.update({ 'lineno': p1.lineno, 'col_offset': p1.lexpos, @@ -736,7 +736,7 @@ class BaseParser(object): """vfpdef : name_tok""" p1 = p[1] kwargs = {'arg': p1.value, 'annotation': None} - if VER_FULL >= VER_3_5_1: + if PYTHON_VERSION_INFO >= (3, 5, 1): kwargs.update({ 'lineno': p1.lineno, 'col_offset': p1.lexpos, diff --git a/xonsh/platform.py b/xonsh/platform.py new file mode 100644 index 000000000..3415385d4 --- /dev/null +++ b/xonsh/platform.py @@ -0,0 +1,170 @@ +""" Module for platform-specific constants and implementations, as well as + compatibility layers to make use of the 'best' implementation available + on a platform. """ + +from functools import lru_cache +import os +import platform +import sys + +try: + import distro +except ImportError: + distro = None +except: + raise + + +# do not import any xonsh-modules here to avoid circular dependencies + + +# +# OS +# + +ON_DARWIN = platform.system() == 'Darwin' +ON_LINUX = platform.system() == 'Linux' +ON_WINDOWS = platform.system() == 'Windows' + +ON_POSIX = (os.name == 'posix') + + + +# +# Python & packages +# + +PYTHON_VERSION_INFO = sys.version_info[:3] + +if PYTHON_VERSION_INFO < (3, 5, 0): + from pathlib import Path + + def _path(self): + return str(self) + Path.path = property(_path) + + def scandir(path): + """ Compatibility wrapper for os.scandir from Python 3.5+ """ + return (Path(path) / x for x in os.listdir(path)) +else: + from os import scandir + + +ON_ANACONDA = any(s in sys.version for s in {'Anaconda', 'Continuum'}) + +try: + import pygments +except ImportError: + HAS_PYGMENTS, PYGMENTS_VERSION = False, None +except: + raise +else: + HAS_PYGMENTS, PYGMENTS_VERSION = True, pygments.__version__ + + +@lru_cache(1) +def has_prompt_toolkit(): + try: + import prompt_toolkit + except ImportError: + return False + except: + raise + else: + return True + + +@lru_cache(1) +def ptk_version(): + if has_prompt_toolkit(): + import prompt_toolkit + return getattr(prompt_toolkit, '__version__', '<0.57') + else: + return None + + +@lru_cache(1) +def ptk_version_info(): + if has_prompt_toolkit(): + return tuple(int(x) for x in ptk_version().strip('<>+-=.').split('.')) + else: + return None + + +if ON_WINDOWS or has_prompt_toolkit(): + BEST_SHELL_TYPE = 'prompt_toolkit' +else: + BEST_SHELL_TYPE = 'readline' + + +@lru_cache(1) +def is_readline_available(): + """Checks if readline is available to import.""" + try: + import readline + except: # pyreadline will sometimes fail in strange ways + return False + else: + return True +# +# Encoding +# + +DEFAULT_ENCODING = sys.getdefaultencoding() + + +# +# Linux distro +# + +if ON_LINUX: + if distro: + LINUX_DISTRO = distro.id() + elif PYTHON_VERSION_INFO < (3, 7, 0): + LINUX_DISTRO = platform.linux_distribution()[0] or 'unknown' + elif '-ARCH-' in platform.platform(): + LINUX_DISTRO = 'arch' # that's the only one we need to know for now + else: + LINUX_DISTRO = 'unknown' +else: + LINUX_DISTRO = None + + +# +# Windows +# + +if ON_WINDOWS: + try: + import win_unicode_console + except ImportError: + win_unicode_console = None +else: + win_unicode_console = None + + +# +# Bash completions defaults +# + +if LINUX_DISTRO == 'arch': + BASH_COMPLETIONS_DEFAULT = ( + '/etc/bash_completion', + '/usr/share/bash-completion/completions') +elif ON_LINUX: + BASH_COMPLETIONS_DEFAULT = ( + '/usr/share/bash-completion', + '/usr/share/bash-completion/completions') +elif ON_DARWIN: + BASH_COMPLETIONS_DEFAULT = ( + '/usr/local/etc/bash_completion', + '/opt/local/etc/profile.d/bash_completion.sh') +else: + BASH_COMPLETIONS_DEFAULT = () + +# +# All constants as a dict +# + +PLATFORM_INFO = {name: obj for name, obj in globals().items() + if name.isupper()} diff --git a/xonsh/ply/yacc.py b/xonsh/ply/yacc.py index e7f36aaba..3597c0082 100644 --- a/xonsh/ply/yacc.py +++ b/xonsh/ply/yacc.py @@ -92,12 +92,6 @@ resultlimit = 40 # Size limit of results when running in debug mod pickle_protocol = 0 # Protocol to use when writing pickle files -# String type-checking compatibility -if sys.version_info[0] < 3: - string_types = basestring -else: - string_types = str - MAXINT = sys.maxsize # This object is a stand-in for a logging object created by the @@ -1982,7 +1976,7 @@ class LRTable(object): import cPickle as pickle except ImportError: import pickle - + if not os.path.exists(filename): raise ImportError @@ -3002,7 +2996,7 @@ class ParserReflect(object): # Validate the start symbol def validate_start(self): if self.start is not None: - if not isinstance(self.start, string_types): + if not isinstance(self.start, str): self.log.error("'start' must be a string") # Look for error handler @@ -3088,12 +3082,12 @@ class ParserReflect(object): self.error = True return assoc = p[0] - if not isinstance(assoc, string_types): + if not isinstance(assoc, str): self.log.error('precedence associativity must be a string') self.error = True return for term in p[1:]: - if not isinstance(term, string_types): + if not isinstance(term, str): self.log.error('precedence items must be strings') self.error = True return diff --git a/xonsh/pretty.py b/xonsh/pretty.py index 7b048fcab..99c75b40b 100644 --- a/xonsh/pretty.py +++ b/xonsh/pretty.py @@ -73,12 +73,11 @@ without open / close parameters. You can also use this code:: with p.indent(2): ... - + :copyright: 2007 by Armin Ronacher. Portions (c) 2009 by Robert Kern. :license: BSD License. """ -#from __future__ import print_function from contextlib import contextmanager import sys import types @@ -88,7 +87,6 @@ from collections import deque #from IPython.utils.py3compat import PY3, cast_unicode, string_types #from IPython.utils.encoding import get_stream_enc -string_types = (str,) from io import StringIO @@ -102,7 +100,7 @@ _re_pattern_type = type(re.compile('')) def _safe_getattr(obj, attr, default=None): """Safe version of getattr. - + Same as getattr, but will return ``default`` on any Exception, rather than raising. """ @@ -231,7 +229,7 @@ class PrettyPrinter(_PrettyPrinterBase): self.buffer.append(Breakable(sep, width, self)) self.buffer_width += width self._break_outer_groups() - + def break_(self): """ Explicitly insert a newline into the output, maintaining correct indentation. @@ -241,7 +239,7 @@ class PrettyPrinter(_PrettyPrinterBase): self.output.write(' ' * self.indentation) self.output_width = self.indentation self.buffer_width = 0 - + def begin_group(self, indent=0, open=''): """ @@ -267,7 +265,7 @@ class PrettyPrinter(_PrettyPrinterBase): self.group_stack.append(group) self.group_queue.enq(group) self.indentation += indent - + def _enumerate(self, seq): """like enumerate, but with an upper limit on the number of items""" for idx, x in enumerate(seq): @@ -277,7 +275,7 @@ class PrettyPrinter(_PrettyPrinterBase): self.text('...') return yield idx, x - + def end_group(self, dedent=0, close=''): """End a group. See `begin_group` for more details.""" self.indentation -= dedent @@ -675,13 +673,13 @@ def _type_pprint(obj, p, cycle): mod = _safe_getattr(obj, '__module__', None) try: name = obj.__qualname__ - if not isinstance(name, string_types): + if not isinstance(name, str): # This can happen if the type implements __qualname__ as a property # or other descriptor in Python 2. raise Exception("Try __name__") except Exception: name = obj.__name__ - if not isinstance(name, string_types): + if not isinstance(name, str): name = '' if mod in (None, '__builtin__', 'builtins', 'exceptions'): @@ -739,7 +737,7 @@ _type_pprinters = { tuple: _seq_pprinter_factory('(', ')', tuple), list: _seq_pprinter_factory('[', ']', list), dict: _dict_pprinter_factory('{', '}', dict), - + set: _set_pprinter_factory('{', '}', set), frozenset: _set_pprinter_factory('frozenset({', '})', frozenset), super: _super_pprint, @@ -748,7 +746,7 @@ _type_pprinters = { types.FunctionType: _function_pprint, types.BuiltinFunctionType: _function_pprint, types.MethodType: _repr_pprint, - + datetime.datetime: _repr_pprint, datetime.timedelta: _repr_pprint, _exception_base: _exception_pprint @@ -760,7 +758,7 @@ try: _type_pprinters[types.SliceType] = _repr_pprint except AttributeError: # Python 3 _type_pprinters[slice] = _repr_pprint - + try: _type_pprinters[xrange] = _repr_pprint _type_pprinters[long] = _repr_pprint diff --git a/xonsh/proc.py b/xonsh/proc.py index 26488ee1e..e6bf976cb 100644 --- a/xonsh/proc.py +++ b/xonsh/proc.py @@ -437,6 +437,7 @@ def foreground(f): # Pseudo-terminal Proxies # + @fallback(ON_LINUX, Popen) class TeePTYProc(object): diff --git a/xonsh/ptk/completer.py b/xonsh/ptk/completer.py index 427702260..b2c268562 100644 --- a/xonsh/ptk/completer.py +++ b/xonsh/ptk/completer.py @@ -2,11 +2,12 @@ """Completer implementation to use with prompt_toolkit.""" import os import builtins -import xonsh.shell from prompt_toolkit.layout.dimension import LayoutDimension from prompt_toolkit.completion import Completer, Completion +from xonsh.platform import ptk_version + class PromptToolkitCompleter(Completer): """Simple prompt_toolkit Completer object. @@ -42,7 +43,7 @@ class PromptToolkitCompleter(Completer): def reserve_space(self): cli = builtins.__xonsh_shell__.shell.prompter.cli - if xonsh.shell.prompt_toolkit_version().startswith("1.0"): + if ptk_version().startswith("1.0"): # This is the layout for ptk 1.0 window = cli.application.layout.children[0].content.children[1] else: diff --git a/xonsh/ptk/shell.py b/xonsh/ptk/shell.py index b2d603aa7..942d94cd1 100644 --- a/xonsh/ptk/shell.py +++ b/xonsh/ptk/shell.py @@ -7,22 +7,20 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.layout.lexers import PygmentsLexer from prompt_toolkit.filters import Condition from prompt_toolkit.styles import PygmentsStyle -from pygments.style import Style from pygments.styles import get_all_styles -from pygments.styles.default import DefaultStyle -from pygments.token import (Keyword, Name, Comment, String, Error, Number, - Operator, Generic, Whitespace, Token) +from pygments.token import Token from xonsh.base_shell import BaseShell from xonsh.tools import print_exception from xonsh.environ import partial_format_prompt -from xonsh.pyghooks import XonshLexer, XonshStyle, partial_color_tokenize, \ - xonsh_style_proxy +from xonsh.platform import ptk_version, ptk_version_info +from xonsh.pyghooks import (XonshLexer, partial_color_tokenize, + xonsh_style_proxy) from xonsh.ptk.completer import PromptToolkitCompleter from xonsh.ptk.history import PromptToolkitHistory from xonsh.ptk.key_bindings import load_xonsh_bindings from xonsh.ptk.shortcuts import Prompter, print_tokens -from xonsh.shell import prompt_toolkit_version + class PromptToolkitShell(BaseShell): """The xonsh shell.""" @@ -39,10 +37,10 @@ class PromptToolkitShell(BaseShell): 'enable_abort_and_exit_bindings': True, 'enable_open_in_editor': True } - vptk = prompt_toolkit_version() - major, minor = [int(x) for x in vptk.split('.')[:2]] - self.new_vi_mode_flag = ((major, minor) >= (1, 0)) and (vptk != '<0.57') - if not(self.new_vi_mode_flag): + major, minor = ptk_version_info()[:2] + self.new_vi_mode_flag = (major, minor) >= (1, 0) \ + and ptk_version() != '<0.57' + if not self.new_vi_mode_flag: # enable_vi_mode is deprecated acoording to prompt_toolset 1.0 document. key_bindings_manager_args['enable_vi_mode'] = Condition(lambda cli: builtins.__xonsh_env__.get('VI_MODE')) diff --git a/xonsh/ptk/shortcuts.py b/xonsh/ptk/shortcuts.py index 5b42cafab..c164f3bea 100644 --- a/xonsh/ptk/shortcuts.py +++ b/xonsh/ptk/shortcuts.py @@ -1,12 +1,14 @@ """A prompt-toolkit inspired shortcut collection.""" +import builtins +import textwrap + from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.utils import DummyContext from prompt_toolkit.shortcuts import (create_prompt_application, create_eventloop, create_asyncio_eventloop, create_output) -from xonsh.shell import prompt_toolkit_version_info +from xonsh.platform import ptk_version_info -import builtins class Prompter(object): @@ -22,7 +24,7 @@ class Prompter(object): will be created when the prompt() method is called. """ self.cli = cli - self.major_minor = prompt_toolkit_version_info()[:2] + self.major_minor = ptk_version_info()[:2] def __enter__(self): self.reset() @@ -95,7 +97,7 @@ class Prompter(object): if return_asyncio_coroutine: # Create an asyncio coroutine and call it. exec_context = {'patch_context': patch_context, 'cli': cli} - exec_(textwrap.dedent(''' + exec(textwrap.dedent(''' import asyncio @asyncio.coroutine def prompt_coro(): diff --git a/xonsh/readline_shell.py b/xonsh/readline_shell.py index 435884a55..148aa2fa1 100644 --- a/xonsh/readline_shell.py +++ b/xonsh/readline_shell.py @@ -12,9 +12,10 @@ from xonsh import lazyjson from xonsh.base_shell import BaseShell from xonsh.ansi_colors import partial_color_format, color_style_names, color_style from xonsh.environ import partial_format_prompt, multiline_prompt -from xonsh.tools import ON_WINDOWS, print_exception, HAVE_PYGMENTS +from xonsh.tools import print_exception +from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS -if HAVE_PYGMENTS: +if HAS_PYGMENTS: from xonsh import pyghooks import pygments from pygments.formatters.terminal256 import Terminal256Formatter diff --git a/xonsh/shell.py b/xonsh/shell.py index 2bafd57ea..624f34e44 100644 --- a/xonsh/shell.py +++ b/xonsh/shell.py @@ -1,52 +1,15 @@ # -*- coding: utf-8 -*- """The xonsh shell""" -import random import builtins +import random from warnings import warn from xonsh import xontribs -from xonsh.execer import Execer from xonsh.environ import xonshrc_context -from xonsh.tools import XonshError, ON_WINDOWS - - -def is_readline_available(): - """Checks if readline is available to import.""" - try: - import readline - return True - except Exception: # pyreadline will sometimes fail in strange ways - return False - - -def is_prompt_toolkit_available(): - """Checks if prompt_toolkit is available to import.""" - try: - import prompt_toolkit - return True - except ImportError: - return False - - -def prompt_toolkit_version(): - """Gets the prompt toolkit version.""" - import prompt_toolkit - return getattr(prompt_toolkit, '__version__', '<0.57') - - -def prompt_toolkit_version_info(): - """Gets the prompt toolkit version info tuple.""" - v = prompt_toolkit_version().strip('<>+-=.') - return tuple(map(int, v.split('.'))) - - -def best_shell_type(): - """Gets the best shell type that is available""" - if ON_WINDOWS or is_prompt_toolkit_available(): - shell_type = 'prompt_toolkit' - else: - shell_type = 'readline' - return shell_type +from xonsh.execer import Execer +from xonsh.platform import (BEST_SHELL_TYPE, has_prompt_toolkit, ptk_version, + ptk_version_info) +from xonsh.tools import XonshError class Shell(object): @@ -85,20 +48,19 @@ class Shell(object): env['SHELL_TYPE'] = shell_type shell_type = env.get('SHELL_TYPE') if shell_type == 'best': - shell_type = best_shell_type() + shell_type = BEST_SHELL_TYPE elif shell_type == 'random': shell_type = random.choice(('readline', 'prompt_toolkit')) if shell_type == 'prompt_toolkit': - if not is_prompt_toolkit_available(): + if not has_prompt_toolkit(): warn('prompt_toolkit is not available, using readline instead.') shell_type = env['SHELL_TYPE'] = 'readline' # actually make the shell if shell_type == 'none': from xonsh.base_shell import BaseShell as shell_class elif shell_type == 'prompt_toolkit': - vptk = prompt_toolkit_version() - major,minor = [int(x) for x in vptk.split('.')[:2]] - if (major,minor) < (0, 57) or vptk == '<0.57': # TODO: remove in future + if ptk_version_info()[:2] < (0, 57) or \ + ptk_version() == '<0.57': # TODO: remove in future msg = ('prompt-toolkit version < v0.57 and may not work as ' 'expected. Please update.') warn(msg, RuntimeWarning) diff --git a/xonsh/tokenize/__init__.py b/xonsh/tokenize/__init__.py index 34e22fd02..2c5a30940 100644 --- a/xonsh/tokenize/__init__.py +++ b/xonsh/tokenize/__init__.py @@ -1,6 +1,6 @@ -from xonsh.tools import VER_3_5, VER_FULL +from xonsh.platform import PYTHON_VERSION_INFO -if VER_FULL >= VER_3_5: +if PYTHON_VERSION_INFO >= (3, 5, 0): from xonsh.tokenize.tokenize_35 import * else: from xonsh.tokenize.tokenize_34 import * diff --git a/xonsh/tools.py b/xonsh/tools.py index e1ad91fe9..5b51363b6 100644 --- a/xonsh/tools.py +++ b/xonsh/tools.py @@ -17,73 +17,31 @@ Implementations: * indent() """ +import builtins +from collections import OrderedDict, Sequence, Set +from contextlib import contextmanager +import ctypes import os import re -import sys -import ctypes -import builtins -import platform -import traceback -import threading +import string import subprocess -from contextlib import contextmanager -from collections import OrderedDict, Sequence, Set +import sys +import threading +import traceback from warnings import warn -# -# Check pygments -# +# adding further imports from xonsh modules is discouraged to avoid cirular +# dependencies +from xonsh.platform import (has_prompt_toolkit, scandir, win_unicode_console, + DEFAULT_ENCODING, ON_LINUX, ON_WINDOWS) -def pygments_version(): - """Returns the Pygments version or False.""" - try: - import pygments - v = pygments.__version__ - except ImportError: - v = False - return v - -if sys.version_info[0] >= 3: - string_types = (str, bytes) - unicode_type = str +if has_prompt_toolkit(): + import prompt_toolkit else: - string_types = (str, unicode) - unicode_type = unicode - -try: - import win_unicode_console -except ImportError: - win_unicode_console = None + prompt_toolkit = None -DEFAULT_ENCODING = sys.getdefaultencoding() - -ON_ANACONDA = any(s in sys.version for s in ['Anaconda','Continuum']) -ON_WINDOWS = (platform.system() == 'Windows') -ON_MAC = (platform.system() == 'Darwin') -ON_LINUX = (platform.system() == 'Linux') -ON_ARCH = (platform.linux_distribution()[0] == 'arch') -ON_POSIX = (os.name == 'posix') -IS_ROOT = ctypes.windll.shell32.IsUserAnAdmin() != 0 if ON_WINDOWS else os.getuid() == 0 -HAVE_PYGMENTS = bool(pygments_version()) - -VER_3_4 = (3, 4) -VER_3_5 = (3, 5) -VER_3_5_1 = (3, 5, 1) -VER_FULL = sys.version_info[:3] -VER_MAJOR_MINOR = sys.version_info[:2] -V_MAJOR_MINOR = 'v{0}{1}'.format(*sys.version_info[:2]) - - -def docstring_by_version(**kwargs): - """Sets a docstring by the python version.""" - doc = kwargs.get(V_MAJOR_MINOR, None) - if V_MAJOR_MINOR is None: - raise RuntimeError('unrecognized version ' + V_MAJOR_MINOR) - def dec(f): - f.__doc__ = doc - return f - return dec +IS_SUPERUSER = ctypes.windll.shell32.IsUserAnAdmin() != 0 if ON_WINDOWS else os.getuid() == 0 class XonshError(Exception): @@ -168,7 +126,7 @@ def subproc_toks(line, mincol=-1, maxcol=None, lexer=None, returnline=False): return # handle comment lines tok = toks[-1] pos = tok.lexpos - if isinstance(tok.value, string_types): + if isinstance(tok.value, str): end_offset = len(tok.value.rstrip()) else: el = line[pos:].split('#')[0].rstrip() @@ -361,7 +319,7 @@ def suggest_commands(cmd, env, aliases): suggested[a] = 'Alias' for d in filter(os.path.isdir, env.get('PATH')): - for f in os.listdir(d): + for f in (x.name for x in scandir(d)): if f not in suggested: if levenshtein(f.lower(), cmd, thresh) < thresh: fname = os.path.join(d, f) @@ -507,7 +465,7 @@ def is_float(x): def is_string(x): """Tests if something is a string""" - return isinstance(x, string_types) + return isinstance(x, str) def always_true(x): @@ -522,19 +480,16 @@ def always_false(x): def ensure_string(x): """Returns a string if x is not a string, and x if it already is.""" - if isinstance(x, string_types): - return x - else: - return str(x) + return str(x) def is_env_path(x): """This tests if something is an environment path, ie a list of strings.""" - if isinstance(x, string_types): + if isinstance(x, str): return False else: return (isinstance(x, Sequence) and - all([isinstance(a, string_types) for a in x])) + all(isinstance(a, str) for a in x)) def str_to_env_path(x): @@ -560,7 +515,7 @@ def to_bool(x): """"Converts to a boolean in a semantically meaningful way.""" if isinstance(x, bool): return x - elif isinstance(x, string_types): + elif isinstance(x, str): return False if x.lower() in _FALSES else True else: return bool(x) @@ -573,8 +528,9 @@ def bool_to_str(x): _BREAKS = frozenset(['b', 'break', 's', 'skip', 'q', 'quit']) + def to_bool_or_break(x): - if isinstance(x, string_types) and x.lower() in _BREAKS: + if isinstance(x, str) and x.lower() in _BREAKS: return 'break' else: return to_bool(x) @@ -596,11 +552,8 @@ def ensure_int_or_slice(x): def is_string_set(x): """Tests if something is a set""" - if isinstance(x, string_types): - return False - else: - return (isinstance(x, Set) and - all([isinstance(a, string_types) for a in x])) + return (isinstance(x, Set) and + all(isinstance(a, str) for a in x)) def csv_to_set(x): @@ -618,14 +571,12 @@ def set_to_csv(x): def is_bool_seq(x): """Tests if an object is a sequence of bools.""" - return isinstance(x, Sequence) and all(map(isinstance, x, [bool]*len(x))) + return isinstance(x, Sequence) and all(isinstance(y, bool) for y in x) def csv_to_bool_seq(x): """Takes a comma-separated string and converts it into a list of bools.""" - if len(x) == 0: - return [] - return list(map(to_bool, x.split(','))) + return [to_bool(y) for y in csv_to_set(x)] def bool_seq_to_csv(x): @@ -643,8 +594,8 @@ def to_completions_display_value(x): x = 'none' elif x in {'multi', 'true'}: x = 'multi' - elif x in {'single'}: - x = 'single' + elif x == 'single': + pass else: warn('"{}" is not a valid value for $COMPLETIONS_DISPLAY. '.format(x) + 'Using "multi".', RuntimeWarning) @@ -784,12 +735,6 @@ def color_style(): return builtins.__xonsh_shell__.shell.color_style() -try: - import prompt_toolkit -except ImportError: - prompt_toolkit = None - - def _get_color_indexes(style_map): """ Generates the color and windows color index for a style """ table = prompt_toolkit.terminal.win32_output.ColorLookupTable() @@ -980,9 +925,8 @@ def expandvars(path): if isinstance(path, bytes): path = path.decode(encoding=ENV.get('XONSH_ENCODING'), errors=ENV.get('XONSH_ENCODING_ERRORS')) - if '$' not in path and ((not ON_WINDOWS) or ('%' not in path)): + if '$' not in path and (not ON_WINDOWS or '%' not in path): return path - import string varchars = string.ascii_letters + string.digits + '_-' quote = '\'' percent = '%' @@ -1108,28 +1052,30 @@ class CommandsCache(Set): @property def all_commands(self): - path = builtins.__xonsh_env__.get('PATH', []) + paths = builtins.__xonsh_env__.get('PATH', []) + paths = frozenset(x for x in paths if os.path.isdir(x)) # did PATH change? - path_hash = hash(frozenset(path)) + path_hash = hash(paths) cache_valid = path_hash == self._path_checksum self._path_checksum = path_hash # did aliases change? - al_hash = hash(frozenset(builtins.aliases.keys())) + al_hash = hash(frozenset(builtins.aliases)) cache_valid = cache_valid and al_hash == self._alias_checksum self._alias_checksum = al_hash - pm = self._path_mtime # did the contents of any directory in PATH change? - for d in filter(os.path.isdir, path): - m = os.stat(d).st_mtime - if m > pm: - pm = m - cache_valid = False - self._path_mtime = pm + max_mtime = 0 + for path in paths: + mtime = os.stat(path).st_mtime + if mtime > max_mtime: + max_mtime = mtime + cache_valid = cache_valid and max_mtime > self._path_mtime + self._path_mtime = max_mtime if cache_valid: return self._cmds_cache allcmds = set() - for d in filter(os.path.isdir, path): - allcmds |= set(os.listdir(d)) - allcmds |= set(builtins.aliases.keys()) + for path in paths: + allcmds |= set(x.name for x in scandir(path) + if x.is_file() and os.access(x.path, os.X_OK)) + allcmds |= set(builtins.aliases) self._cmds_cache = frozenset(allcmds) return self._cmds_cache diff --git a/xonsh/tracer.py b/xonsh/tracer.py index c1c8abd63..b17283594 100644 --- a/xonsh/tracer.py +++ b/xonsh/tracer.py @@ -7,27 +7,27 @@ import linecache from functools import lru_cache from argparse import ArgumentParser -from xonsh.tools import (DefaultNotGiven, print_color, pygments_version, - format_color, normabspath, to_bool, HAVE_PYGMENTS) +from xonsh.tools import DefaultNotGiven, print_color, normabspath, to_bool +from xonsh.platform import HAS_PYGMENTS from xonsh import inspectors from xonsh.environ import _replace_home as replace_home -if HAVE_PYGMENTS: + +if HAS_PYGMENTS: from xonsh import pyghooks import pygments import pygments.formatters.terminal + class TracerType(object): """Represents a xonsh tracer object, which keeps track of all tracing state. This is a singleton. """ - _inst = None valid_events = frozenset(['line', 'call']) def __new__(cls, *args, **kwargs): if cls._inst is None: - cls._inst = super(TracerType, cls).__new__(cls, *args, - **kwargs) + cls._inst = super(TracerType, cls).__new__(cls, *args, **kwargs) return cls._inst def __init__(self): @@ -97,7 +97,7 @@ def format_line(fname, lineno, line, color=True, lexer=None, formatter=None): if not color: return COLORLESS_LINE.format(fname=fname, lineno=lineno, line=line) cline = COLOR_LINE.format(fname=fname, lineno=lineno) - if not HAVE_PYGMENTS: + if not HAS_PYGMENTS: return cline + line # OK, so we have pygments tokens = pyghooks.partial_color_tokenize(cline) @@ -109,11 +109,12 @@ def format_line(fname, lineno, line, color=True, lexer=None, formatter=None): # # Command line interface # + def _find_caller(args): """Somewhat hacky method of finding the __file__ based on the line executed.""" re_line = re.compile(r'[^;\s|&<>]+\s+' + r'\s+'.join(args)) curr = inspect.currentframe() - for _, fname, lineno, _, lines, _ in inspectors.getouterframes(curr, context=1)[3:]: + for _, fname, lineno, _, lines, _ in inspectors.getouterframes(curr, context=1)[3:]: if lines is not None and re_line.search(lines[0]) is not None: return fname elif lineno == 1 and re_line.search(linecache.getline(fname, lineno)) is not None: @@ -186,6 +187,7 @@ _MAIN_ACTIONS = { 'color': _color, } + def main(args=None): """Main function for tracer command-line interface.""" parser = _create_parser() @@ -195,4 +197,3 @@ def main(args=None): if __name__ == '__main__': main() - diff --git a/xonsh/vox.py b/xonsh/vox.py index e1e94b7fe..e7a034f20 100644 --- a/xonsh/vox.py +++ b/xonsh/vox.py @@ -1,10 +1,9 @@ +import builtins from os.path import join, basename, exists, expanduser -from os import listdir from shutil import rmtree import venv -import builtins - +from xonsh.platform import scandir, ON_POSIX, ON_WINDOWS import xonsh.tools @@ -82,10 +81,10 @@ class Vox: print('This environment doesn\'t exist. Create it with "vox new %s".\n' % name) return None - if xonsh.tools.ON_WINDOWS: + if ON_WINDOWS: bin_dir = 'Scripts' - elif xonsh.tools.ON_POSIX: + elif ON_POSIX: bin_dir = 'bin' else: @@ -117,7 +116,7 @@ class Vox: if xonsh.tools.ON_WINDOWS: bin_dir = 'Scripts' - elif xonsh.tools.ON_POSIX: + elif ON_POSIX: bin_dir = 'bin' else: @@ -137,19 +136,20 @@ class Vox: def list_envs(): """List available virtual environments.""" - env_names = listdir(builtins.__xonsh_env__['VIRTUALENV_HOME']) + venv_home = builtins.__xonsh_env__['VIRTUALENV_HOME'] + try: + env_dirs = (x.name for x in scandir(venv_home) if x.is_dir()) + except PermissionError: + print('No permissions on {}'.format(venv_home)) + return None - if not env_names: - print('No environments evailable. Create one with "vox new".\n') + if not env_dirs: + print('No environments available. Create one with "vox new".\n') return None print('Available environments:') + print('\n'.join(env_dirs)) - for env_name in env_names: - print(' %s' % env_name) - - else: - print() @staticmethod def remove_env(name): diff --git a/xonsh/xonfig.py b/xonsh/xonfig.py index c82c21647..c45daf018 100644 --- a/xonsh/xonfig.py +++ b/xonsh/xonfig.py @@ -1,5 +1,4 @@ """The xonsh configuration (xonfig) utility.""" -import os import ast import json import shutil @@ -17,10 +16,10 @@ except ImportError: from xonsh import ply from xonsh import __version__ as XONSH_VERSION -from xonsh import tools from xonsh.environ import is_template_string -from xonsh.shell import (is_readline_available, is_prompt_toolkit_available, - prompt_toolkit_version) +from xonsh import platform +from xonsh.platform import is_readline_available, ptk_version +from xonsh import tools from xonsh.wizard import (Wizard, Pass, Message, Save, Load, YesNo, Input, PromptVisitor, While, StoreNonEmpty, create_truefalse_cond, YN, Unstorable, Question) @@ -332,20 +331,21 @@ def _format_json(data): def _info(ns): data = [ ('xonsh', XONSH_VERSION), - ('Python', '.'.join(map(str, tools.VER_FULL))), + ('Python', '{}.{}.{}'.format(*platform.PYTHON_VERSION_INFO)), ('PLY', ply.__version__), ('have readline', is_readline_available()), - ('prompt toolkit', prompt_toolkit_version() if \ - is_prompt_toolkit_available() else False), - ('pygments', tools.pygments_version()), - ('on posix', tools.ON_POSIX), - ('on linux', tools.ON_LINUX), - ('on arch', tools.ON_ARCH), - ('on windows', tools.ON_WINDOWS), - ('on mac', tools.ON_MAC), - ('are root user', tools.IS_ROOT), - ('default encoding', tools.DEFAULT_ENCODING), - ] + ('prompt toolkit', ptk_version() or None), + ('pygments', platform.PYGMENTS_VERSION), + ('on posix', platform.ON_POSIX), + ('on linux', platform.ON_LINUX)] + if platform.ON_LINUX: + data.append(('distro', platform.LINUX_DISTRO)) + data.extend([ + ('on darwin', platform.ON_DARWIN), + ('on windows', platform.ON_WINDOWS), + ('is superuser', tools.IS_SUPERUSER), + ('default encoding', platform.DEFAULT_ENCODING), + ]) formatter = _format_json if ns.json else _format_human s = formatter(data) return s