This commit is contained in:
Anthony Scopatz 2016-04-09 00:14:03 -04:00
commit 230174235d
19 changed files with 332 additions and 70 deletions

3
.gitignore vendored
View file

@ -33,3 +33,6 @@ include/
# Mac
.DS_Store
# Editor project files
*.komodo*

View file

@ -3,6 +3,6 @@ python:
- 3.4
- 3.5
install:
- pip install ply nose pygments prompt_toolkit
- pip install -r requirements-tests.txt
script:
- nosetests -q

View file

@ -15,7 +15,9 @@ Current Developments
on Windows. This functionality can be enabled/disabled with the
$INTENSIFY_COLORS_ON_WIN environment variable.
* Added ``Ellipsis`` lookup to ``__xonsh_env__`` to allow environment variable checks, e.g. ``'HOME' in ${...}``
* Added an option to update ``os.environ`` every time the xonsh environment changes.
This disabled by default, but can be enabled by setting ``$UPDATE_OS_ENVIRON`` to
True.
**Changed:**
@ -25,6 +27,9 @@ Current Developments
loading bash completions.
* rc files are now compiled and cached, to avoid re-parsing when they haven't
changed.
* Left and Right arrows in the ``prompt_toolkit`` shell now wrap in multiline
environments
* Regexpath matching with backticks, now returns an empty list in python mode.
**Deprecated:** None
@ -33,9 +38,11 @@ Current Developments
**Fixed:**
* Fixed bug with loading prompt-toolkit shell < v0.57.
* Fixed bug with prompt-toolkit completion when the cursor is not at the end of the line
**Security:** None
v0.2.7
====================
**Added:**
@ -82,7 +89,6 @@ v0.2.7
argument to be deleted.
**Removed:**
* The ``xonsh.tools.TERM_COLORS`` mapping has been axed, along with all

24
docs/add_to_shell.rst Normal file
View file

@ -0,0 +1,24 @@
Additional Setup
================
If you want to use xonsh as your default shell, you will first have to add xonsh to `/etc/shells`.
First ensure that xonsh is on your ``$PATH``
.. code-block:: bash
$ which xonsh
Then, as root, add xonsh to the shell list
.. code-block:: bash
# echo $(which xonsh) >> /etc/shells
To change shells, run
.. code-block:: bash
$ chsh -s $(which xonsh)
You will have to log out and log back in before the changes take effect.

View file

@ -59,4 +59,5 @@ For those of you who want the gritty details.
jupyter_kernel
wizard
xonfig
vox

17
docs/dependencies.rst Normal file
View file

@ -0,0 +1,17 @@
Dependencies
============
Xonsh currently has the following external dependencies,
*Run Time:*
#. Python v3.4+
#. PLY
#. prompt-toolkit (optional)
#. Jupyter (optional)
#. setproctitle (optional)
*Documentation:*
#. `Sphinx <http://sphinx-doc.org/>`_ (which uses `reStructuredText <http://sphinx-doc.org/rest.html>`_)
#. Numpydoc
#. Cloud Sphinx Theme

View file

@ -74,10 +74,34 @@ If you want to lint the entire code base run::
How to Test
================
First, install nose: http://nose.readthedocs.org/en/latest/. Second, ensure
your cwd is the root directory of the project (i.e., the one containing the
Ensure your cwd is the root directory of the project (i.e., the one containing the
.git directory).
----------------------------------
Dependencies
----------------------------------
Prep your environment for running the tests::
$ pip install requirements-tests.txt
----------------------------------
Running the Tests - Basic
----------------------------------
Run all the tests using Nose::
$ nosetests -q
Use "-q" to keep nose from outputing a dot for every test. There are A LOT of tests
and you will waste time waiting for all the dots to get pushed through stdout.
----------------------------------
Running the Tests - Advanced
----------------------------------
To perform all unit tests::
$ scripts/run_tests.xsh all

View file

@ -93,6 +93,6 @@ manually use the ``$[]`` or ``$()`` operators on your code.
5. Context-sensitive parsing is gross
--------------------------------------
Yes, context-sensitive parsing is gross. But the point of xonsh is that it
Yes, context-sensitive parsing is gross. But the point of xonsh is that it uses xontext-sensitive parsing and
is ultimately a lot less gross than other shell languages, such as BASH.
Furthermore, its use is heavily limited here.

View file

@ -72,6 +72,16 @@ alike.
=========
Contents
=========
**Installation:**
.. toctree::
:titlesonly:
:maxdepth: 1
linux
osx
windows
**Guides:**
.. toctree::
@ -106,52 +116,6 @@ Contents
faq
todo
============
Installation
============
You can install xonsh using conda, pip, or from source.
**conda:**
.. code-block:: bash
$ conda install -c xonsh xonsh
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
**pip:**
.. code-block:: bash
$ pip install xonsh
**source:** Download the source `from github <https://github.com/scopatz/xonsh>`_
(`zip file <https://github.com/scopatz/xonsh/archive/master.zip>`_), then run
the following from the source directory,
.. code-block:: bash
$ python setup.py install
Arch Linux users can install xonsh from the Arch User Repository with e.g.
yaourt or aura:
**yaourt:**
.. code-block:: bash
$ yaourt -Sa xonsh # yaourt will call sudo when needed
**aura:**
.. code-block:: bash
$ sudo aura -A xonsh
If you run into any problems, please let us know!
==========
Comparison
==========
@ -264,8 +228,7 @@ Xonsh currently has the following external dependencies,
*Documentation:*
#. `Sphinx <http://sphinx-doc.org/>` (which uses
`reStructuredText <http://sphinx-doc.org/rest.html>`)
#. `Sphinx <http://sphinx-doc.org/>`_ (which uses `reStructuredText <http://sphinx-doc.org/rest.html>`_)
#. Numpydoc
#. Cloud Sphinx Theme

60
docs/linux.rst Normal file
View file

@ -0,0 +1,60 @@
==========================
Linux Guide
==========================
Installation
============
You can install xonsh using ``conda``, ``pip``, or from source.
**conda:**
.. code-block:: bash
$ conda install -c xonsh xonsh
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
**pip:**
.. code-block:: bash
$ pip install xonsh
**source:** Download the source `from github <https://github.com/scopatz/xonsh>`_
(`zip file <https://github.com/scopatz/xonsh/archive/master.zip>`_), then run
the following from the source directory,
.. code-block:: bash
$ python setup.py install
Arch Linux users can install xonsh from the Arch User Repository with e.g.
``yaourt``, ``aura``, ``pacaur``, ``PKGBUILD``, etc...:
**yaourt:**
.. code-block:: bash
$ yaourt -Sa xonsh # yaourt will call sudo when needed
**aura:**
.. code-block:: bash
$ sudo aura -A xonsh
**pacaur:**
.. code-block:: bash
$ pacaur -S xonsh
If you run into any problems, please let us know!
.. include:: add_to_shell.rst
.. include:: dependencies.rst

37
docs/osx.rst Normal file
View file

@ -0,0 +1,37 @@
==========================
OSX Guide
==========================
Installation
============
You can install xonsh using conda, pip, or from source.
**conda:**
.. code-block:: bash
$ conda install -c xonsh xonsh
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
**pip:**
.. code-block:: bash
$ pip install xonsh
**source:** Download the source `from github <https://github.com/scopatz/xonsh>`_
(`zip file <https://github.com/scopatz/xonsh/archive/master.zip>`_), then run
the following from the source directory,
.. code-block:: bash
$ python setup.py install
.. include:: add_to_shell.rst
.. include:: dependencies.rst

View file

@ -134,9 +134,31 @@ variable in Python. The same is true for deleting them too.
Become the Lord of the Files
>>> del $GOAL
Very nice. All environment variables live in the built-in
``__xonsh_env__`` mapping. You can access this mapping directly, but in most
situations, you shouldn't need to.
Very nice.
__xonsh_env__
--------------
All environment variables live in the built-in ``__xonsh_env__`` mapping. You can access this
mapping directly, but in most situations, you shouldn't need to.
One helpful method on the __xonsh_env__ is :func:`~xonsh.environ.Env.swap`. It can be used to temporarily set an
environment variable:
.. code-block:: xonshcon
>>> with __xonsh_env__.swap(SOMEVAR='foo'):
... echo $SOMEVAR
...
...
foo
>>> echo $SOMEVAR
>>>
Environment Types
-----------------
Like other variables in Python, environment variables have a type. Sometimes
this type is imposed based on the variable name. The current rules are pretty

4
requirements-tests.txt Normal file
View file

@ -0,0 +1,4 @@
ply
nose
prompt-toolkit
pygments

View file

@ -72,5 +72,35 @@ def test_HISTCONTROL():
assert_true('ignoreerr' in env['HISTCONTROL'])
assert_true('ignoredups' in env['HISTCONTROL'])
def test_swap():
env = Env(VAR='wakka')
assert_equal(env['VAR'], 'wakka')
# positional arg
with env.swap({'VAR': 'foo'}):
assert_equal(env['VAR'], 'foo')
# make sure the environment goes back outside the context manager
assert_equal(env['VAR'], 'wakka')
# kwargs only
with env.swap(VAR1='foo', VAR2='bar'):
assert_equal(env['VAR1'], 'foo')
assert_equal(env['VAR2'], 'bar')
# positional and kwargs
with env.swap({'VAR3': 'baz'}, VAR1='foo', VAR2='bar'):
assert_equal(env['VAR1'], 'foo')
assert_equal(env['VAR2'], 'bar')
assert_equal(env['VAR3'], 'baz')
# make sure the environment goes back outside the context manager
assert_equal(env['VAR'], 'wakka')
assert 'VAR1' not in env
assert 'VAR2' not in env
assert 'VAR3' not in env
if __name__ == '__main__':
nose.runmodule()

View file

@ -266,13 +266,14 @@ def reglob(path, parts=None, i=None):
return paths
def regexpath(s):
def regexpath(s, pymode=False):
"""Takes a regular expression string and returns a list of file
paths that match the regex.
"""
s = expand_path(s)
o = reglob(s)
return o if len(o) != 0 else [s]
no_match = [] if pymode else [s]
return o if len(o) != 0 else no_match
def globpath(s, ignore_case=False):

View file

@ -85,6 +85,7 @@ DEFAULT_ENSURERS = {
'RAISE_SUBPROC_ERROR': (is_bool, to_bool, bool_to_str),
'RIGHT_PROMPT': (is_string, ensure_string, ensure_string),
'TEEPTY_PIPE_DELAY': (is_float, float, str),
'UPDATE_OS_ENVIRON': (is_bool, to_bool, bool_to_str),
'XONSHRC': (is_env_path, str_to_env_path, env_path_to_str),
'XONSH_COLOR_STYLE': (is_string, ensure_string, ensure_string),
'XONSH_ENCODING': (is_string, ensure_string, ensure_string),
@ -194,6 +195,7 @@ DEFAULT_VALUES = {
'SUGGEST_THRESHOLD': 3,
'TEEPTY_PIPE_DELAY': 0.01,
'TITLE': DEFAULT_TITLE,
'UPDATE_OS_ENVIRON': False,
'VI_MODE': False,
'WIN_UNICODE_CONSOLE': True,
'XDG_CONFIG_HOME': os.path.expanduser(os.path.join('~', '.config')),
@ -394,6 +396,9 @@ DEFAULT_DOCS = {
"in the same manner as $PROMPT, see 'Customizing the Prompt' "
'http://xon.sh/tutorial.html#customizing-the-prompt.',
default='xonsh.environ.DEFAULT_TITLE'),
'UPDATE_OS_ENVIRON': VarDocs("If True os.environ will always be updated "
"when the xonsh environment changes. The environment can be reset to "
"the default value by calling '__xonsh_env__.undo_replace_env()'"),
'VI_MODE': VarDocs(
"Flag to enable 'vi_mode' in the 'prompt_toolkit' shell."),
'VIRTUAL_ENV': VarDocs(
@ -491,6 +496,7 @@ class Env(MutableMapping):
def __init__(self, *args, **kwargs):
"""If no initial environment is given, os.environ is used."""
self._d = {}
self._orig_env = None
self.ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
self.defaults = DEFAULT_VALUES
self.docs = DEFAULT_DOCS
@ -499,14 +505,17 @@ class Env(MutableMapping):
for key, val in dict(*args, **kwargs).items():
self[key] = val
self._detyped = None
self._orig_env = None
@staticmethod
def detypeable(val):
return not (callable(val) or isinstance(val, MutableMapping))
def detype(self):
if self._detyped is not None:
return self._detyped
ctx = {}
for key, val in self._d.items():
if callable(val) or isinstance(val, MutableMapping):
if not self.detypeable(val):
continue
if not isinstance(key, string_types):
key = str(key)
@ -563,16 +572,27 @@ class Env(MutableMapping):
return vd
@contextmanager
def swap(self, other):
def swap(self, other=None, **kwargs):
"""Provides a context manager for temporarily swapping out certain
environment variables with other values. On exit from the context
manager, the original values are restored.
"""
old = {}
for k, v in other.items():
# single positional argument should be a dict-like object
if other is not None:
for k, v in other.items():
old[k] = self.get(k, NotImplemented)
self[k] = v
# kwargs could also have been sent in
for k, v in kwargs.items():
old[k] = self.get(k, NotImplemented)
self[k] = v
yield self
# restore the values
for k, v in old.items():
if v is NotImplemented:
del self[k]
@ -613,12 +633,23 @@ class Env(MutableMapping):
if not ensurer.validate(val):
val = ensurer.convert(val)
self._d[key] = val
self._detyped = None
if self.detypeable(val):
self._detyped = None
if self.get('UPDATE_OS_ENVIRON'):
if self._orig_env is None:
self.replace_env()
else:
dval = ensurer.detype(val)
os.environ[key] = dval
def __delitem__(self, key):
del self._d[key]
self._detyped = None
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]
def get(self, key, default=None):
"""The environment will look up default values from its own defaults if a
default is not given here.

View file

@ -25,7 +25,7 @@ class PromptToolkitCompleter(Completer):
if complete_event.completion_requested:
line = document.current_line.lstrip()
endidx = document.cursor_position_col
begidx = line.rfind(' ') + 1 if line.rfind(' ') >= 0 else 0
begidx = line[:endidx].rfind(' ') + 1 if line[:endidx].rfind(' ') >= 0 else 0
prefix = line[begidx:endidx]
completions, l = self.completer.complete(prefix,
line,

View file

@ -70,6 +70,28 @@ class TabShouldInsertIndentFilter(Filter):
return bool(before_cursor.isspace())
class BeginningOfLine(Filter):
"""
Check if cursor is at beginning of a line other than the first line
in a multiline document
"""
def __call__(self, cli):
before_cursor = cli.current_buffer.document.current_line_before_cursor
return bool(len(before_cursor) == 0
and not cli.current_buffer.document.on_first_line)
class EndOfLine(Filter):
"""
Check if cursor is at the end of a line other than the last line
in a multiline document
"""
def __call__(self, cli):
d = cli.current_buffer.document
at_end = d.is_cursor_at_the_end_of_line
last_line = d.is_cursor_at_the_end
return bool(at_end and not last_line)
def can_compile(src):
"""Returns whether the code can be compiled, i.e. it is valid xonsh."""
@ -109,6 +131,21 @@ def load_xonsh_bindings(key_bindings_manager):
b = event.cli.current_buffer
carriage_return(b, event.cli)
@handle(Keys.Left, filter=BeginningOfLine())
def wrap_cursor_back(event):
"""Move cursor to end of previous line unless at beginning of document"""
b = event.cli.current_buffer
b.cursor_up(count=1)
relative_end_index = b.document.get_end_of_line_position()
b.cursor_right(count=relative_end_index)
@handle(Keys.Right, filter=EndOfLine())
def wrap_cursor_forward(event):
"""Move cursor to beginning of next line unless at end of document"""
b = event.cli.current_buffer
relative_begin_index = b.document.get_start_of_line_position()
b.cursor_left(count=abs(relative_begin_index))
b.cursor_down(count=1)
def _is_blank(l):
return len(l.strip()) == 0

View file

@ -993,6 +993,8 @@ def expandvars(path):
var = eval(var, builtins.__xonsh_ctx__)
if _is_in_env(var):
value = _get_env_string(var)
elif var is Ellipsis:
value = dollar + brace + '...' + rbrace
else:
value = dollar + brace + var + rbrace
except: