diff --git a/.authors.yml b/.authors.yml index cedc5b0d3..e55d8b990 100644 --- a/.authors.yml +++ b/.authors.yml @@ -64,7 +64,7 @@ - Gilbert.Forsyth@capitalone.com - gforsyth@gwu.edu - gil@forsyth.dev - num_commits: 665 + num_commits: 680 first_commit: 2015-10-19 16:04:32 github: gforsyth - name: Morten Enemark Lund @@ -1136,7 +1136,7 @@ github: marciomazza - name: Noortheen Raja email: jnoortheen@gmail.com - num_commits: 237 + num_commits: 239 first_commit: 2020-03-15 10:13:56 github: jnoortheen - name: Samuel Lotz @@ -1283,7 +1283,7 @@ first_commit: 2021-02-08 10:50:51 - name: Evgeny email: eugenesvk@users.noreply.github.com - num_commits: 11 + num_commits: 12 first_commit: 2021-02-22 09:32:34 - name: Adam Schwalm email: adamschwalm@gmail.com @@ -1301,7 +1301,7 @@ email: 46109467+yaxollum@users.noreply.github.com alternate_emails: - yaxollum@gmail.com - num_commits: 34 + num_commits: 36 first_commit: 2021-06-11 14:11:19 - name: Walter A. Boring IV email: waboring@hemna.com @@ -1431,7 +1431,7 @@ first_commit: 2021-11-18 18:42:29 - name: doronz88 email: doron88@gmail.com - num_commits: 6 + num_commits: 7 first_commit: 2022-04-27 12:41:13 - name: Stefano Rivera email: github@rivera.za.net @@ -1465,7 +1465,7 @@ first_commit: 2022-06-27 22:21:34 - name: pre-commit-ci[bot] email: 66853113+pre-commit-ci[bot]@users.noreply.github.com - num_commits: 66 + num_commits: 78 first_commit: 2022-07-11 14:26:34 - name: jgart email: 47760695+jgarte@users.noreply.github.com @@ -1571,7 +1571,7 @@ first_commit: 2024-01-15 05:25:23 - name: JamesParrott email: 80779630+JamesParrott@users.noreply.github.com - num_commits: 1 + num_commits: 2 first_commit: 2024-01-16 08:56:07 - name: Airat Makhmutov email: 108220148+rautyrauty@users.noreply.github.com @@ -1613,3 +1613,31 @@ email: kulkarniniraj14@gmail.com num_commits: 1 first_commit: 2024-07-04 04:37:55 +- name: Aidan Courtney + email: aidanfc97@gmail.com + num_commits: 1 + first_commit: 2024-07-27 11:33:35 +- name: Max Nordlund + email: max.nordlund@gmail.com + num_commits: 1 + first_commit: 2024-10-30 09:11:04 +- name: Shawn Wallace + email: yungwallace@live.com + num_commits: 1 + first_commit: 2024-10-31 05:33:45 +- name: Faidon Liambotis + email: paravoid@debian.org + num_commits: 1 + first_commit: 2024-09-17 06:47:59 +- name: Jueun Lee + email: seirios0107@gmail.com + num_commits: 1 + first_commit: 2024-11-18 18:04:24 +- name: Simon Billinge + email: sbillinge@users.noreply.github.com + num_commits: 1 + first_commit: 2024-12-08 03:28:31 +- name: Bala + email: kumaran.4353@gmail.com + num_commits: 1 + first_commit: 2024-12-02 07:01:09 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6a5be5ab3..90c5a7036 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,6 +22,7 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" steps: - uses: actions/checkout@v4 - name: setup-python diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7256af3e0..93e751550 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: - "3.10" - "3.11" - "3.12" -# - "3.13-dev" + - "3.13" name: Test Python ${{ matrix.python-version }} ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -76,11 +76,4 @@ jobs: run: | python -m pip install -e . --no-deps python -m xonsh run-tests.xsh test --report-coverage -- --timeout=240 - - name: Upload coverage to Codecov - if: ${{ startsWith(matrix.python-version, env.DEFAULT_PYTHON_VERSION) }} - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - verbose: true - flags: ${{ matrix.os }} + diff --git a/.mailmap b/.mailmap index 756fd84f2..57f16adbc 100644 --- a/.mailmap +++ b/.mailmap @@ -36,8 +36,8 @@ anki-code a anki-code a anki-code a <1@1.1> BlahGeek -Daniel Shimon pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> +Daniel Shimon Gyuri Horak Jean-Benoist Leger Jean-Benoist Leger Jean-Benoist Leger Jean-Benoist Leger @@ -47,9 +47,9 @@ Leonardo Santagada Burak Yiğit Kaya Burak Yigit Kaya Matthias Bussonnier Aaron Griffin +Peter Ye <46109467+yaxollum@users.noreply.github.com> Peter Ye Rob Brewer Robert W. Brewer virus -Peter Ye <46109467+yaxollum@users.noreply.github.com> Peter Ye Sagar Tewari Sagar Tewari Gordon Ball Eadaen1 @@ -95,6 +95,7 @@ Alessio Bogon Alessio Bogon Yohei Tamura Maximilian Köhl Alexander Sosedkin +doronz88 Cody Scott Jake Hedman traverseda @@ -107,7 +108,6 @@ Jared Crawford Mike Crowe drmikecrowe Vasilis Gerakaris Vasilis Gerakaris Angus Hollands -doronz88 JuanPablo Ollie Terrance Marcel Bollmann @@ -214,6 +214,7 @@ cmidkiff87 <39914727+cmidkiff87@users.noreply.github.com> jbw3 Naveen <172697+naveensrinivasan@users.noreply.github.com> Blake Ramsdell +JamesParrott <80779630+JamesParrott@users.noreply.github.com> jyn Dan Allan Ned Letcher @@ -345,7 +346,6 @@ Wilfried Pollan Jacqueline Leykam Joshix-1 <57299889+Joshix-1@users.noreply.github.com> Nathan Monfils -JamesParrott <80779630+JamesParrott@users.noreply.github.com> Airat Makhmutov <108220148+rautyrauty@users.noreply.github.com> Matthieu LAURENT Daniel Saunders @@ -355,5 +355,12 @@ amacfie-tc <91503417+amacfie-tc@users.noreply.github.com> lunrenyi <87307989+lunrenyi@users.noreply.github.com> Spencer Bliven Niraj Kulkarni +Aidan Courtney +Max Nordlund +Shawn Wallace +Faidon Liambotis +Jueun Lee +Simon Billinge +Bala goodboy Atsushi Morimoto diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 732b23289..6d4dbd395 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ default_language_version: # force all unspecified python hooks to run python3 - python: python3.10 + python: python3.13 #ci: # autofix_prs: false @@ -8,7 +8,7 @@ default_language_version: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.5.6' + rev: 'v0.8.2' hooks: - id: ruff args: [., --fix, --exit-non-zero-on-fix] @@ -18,14 +18,14 @@ repos: pass_filenames: false - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.1' # Use the sha / tag you want to point at + rev: 'v1.13.0' # Use the sha / tag you want to point at hooks: - id: mypy pass_filenames: false additional_dependencies: - types-ujson - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace exclude: | diff --git a/AUTHORS.rst b/AUTHORS.rst index 61b5be57b..c7210905b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -14,8 +14,8 @@ Authors are sorted by number of commits. * Bob Hyman * anki-code * BlahGeek -* Daniel Shimon * pre-commit-ci[bot] +* Daniel Shimon * Gyuri Horak * Jean-Benoist Leger * christopher @@ -24,9 +24,9 @@ Authors are sorted by number of commits. * Burak Yiğit Kaya * Matthias Bussonnier * Aaron Griffin +* Peter Ye * Rob Brewer * virus -* Peter Ye * Sagar Tewari * Gordon Ball * Eadaen1 @@ -70,6 +70,7 @@ Authors are sorted by number of commits. * Yohei Tamura * Maximilian Köhl * Alexander Sosedkin +* doronz88 * Cody Scott * Jake Hedman * traverseda @@ -82,7 +83,6 @@ Authors are sorted by number of commits. * Mike Crowe * Vasilis Gerakaris * Angus Hollands -* doronz88 * JuanPablo * Ollie Terrance * Marcel Bollmann @@ -189,6 +189,7 @@ Authors are sorted by number of commits. * jbw3 * Naveen * Blake Ramsdell +* JamesParrott * jyn * Dan Allan * Ned Letcher @@ -320,7 +321,6 @@ Authors are sorted by number of commits. * Jacqueline Leykam * Joshix-1 * Nathan Monfils -* JamesParrott * Airat Makhmutov * Matthieu LAURENT * Daniel Saunders @@ -330,5 +330,12 @@ Authors are sorted by number of commits. * lunrenyi * Spencer Bliven * Niraj Kulkarni +* Aidan Courtney +* Max Nordlund +* Shawn Wallace +* Faidon Liambotis +* Jueun Lee +* Simon Billinge +* Bala * goodboy * Atsushi Morimoto diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1a2716af9..34d97a36a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,88 @@ Xonsh Change Log .. current developments +v0.19.0 +==================== + +**Added:** + +* env: Added ``$XONSH_SUPPRESS_WELCOME`` variable to suppress the welcome message. + +**Changed:** + +* replaced `case_insensitive_dictionary` dependency with local + `CaseInsensitiveDict` class + +**Fixed:** + +* parsers: fix deprecation warnings triggered on python3.13 +* Fix DeprecationWarning while initializing Expression + +**Authors:** + +* Gil Forsyth +* pre-commit-ci[bot] +* Evgeny +* doronz88 +* JamesParrott +* Jueun Lee +* Simon Billinge +* Bala + + + +v0.18.4 +==================== + +**Changed:** + +* Now SystemExit that invoked from callable alias follows to exiting from callable alias instead of exiting the entire shell. + +**Fixed:** + +* Built-in commands such as `xonfig -h` and `xontrib -h` no longer cause the shell to exit. +* Fixed incorrect quoting behaviour in `activate.xsh` for virtualenv version 20.26.6. + +**Authors:** + +* anki-code +* pre-commit-ci[bot] +* Peter Ye +* Max Nordlund +* Shawn Wallace +* Faidon Liambotis + + + +v0.18.3 +==================== + +**Added:** + +* executables: Added ``locate_relative_path`` functionality to ``locate_file``. + +**Changed:** + +* ``$MULTILINE_PROMPT`` changed from ``'.'`` to ``' '`` (space) + to have an ability to copy the multiline functions from shell history without deleting the dot manually (#5624 #5634). + +**Fixed:** + +* Fixed ``$AUTO_CD`` regress after previous refactoring. +* Partial fix for "Bad file descriptor" in case of callable alias with execx invocation inside e.g. ExecAlias (#5645). +* completer: Fixed exception when in python-only completion context (#5632). +* Fixed exception "object has no attribute readlines" in case of redirect callable alias output. + +**Authors:** + +* Gil Forsyth +* Noortheen Raja +* anki-code +* pre-commit-ci[bot] +* Aidan Courtney + + + v0.18.2 ==================== @@ -2692,7 +2774,7 @@ v0.8.6 * Exits after concatenating normal files which have a finite size * Continues to run for special files which do not have a size, such as ``/dev/random`` - * Is interruptable in all cases with Crtl-C. + * Is interruptable in all cases with Ctrl-C. * Callable aliases were not properly raising a ``CalledProcessError`` when they returned a non-zero exist status when ``$RAISE_SUBPROC_ERROR = True``. This has been fixed. @@ -4434,7 +4516,7 @@ v0.5.0 * Fixed many PEP8 violations that had gone unnoticed * Fix failure to detect an Anaconda python distribution if the python was install from the conda-forge channel. * current_branch will try and locate the vc binary once -* May now Crtl-C out of an infinite loop with a subprocess, such as +* May now Ctrl-C out of an infinite loop with a subprocess, such as ```while True: sleep 1``. * Fix for stdin redirects. * Backgrounding works with ``$XONSH_STORE_STDOUT`` diff --git a/README.rst b/README.rst index 3169d483e..fa2f61225 100644 --- a/README.rst +++ b/README.rst @@ -131,7 +131,8 @@ The xonsh shell is developed by a community of volunteers. There are a few ways - Solve a `popular issue `_ or `high priority issue `_ or a `good first issue `_. You can start with the `Developer guide `_. - Take an `idea `_ and `create a new xontrib `_. -- Become xonsh core by deep diving into xonsh and improve the threading and subprocess logic. +- Contribute to `xonsh API `_. +- Become xonsh core developer by deep diving into xonsh internals. E.g. we feel a lack of Windows support. - `Become a sponsor to xonsh `_. - `Write a tweet`_, post or an article to spread the good word about xonsh in the world. - Give a star to xonsh repository and to `xontribs `_ you like. diff --git a/docs/_templates/index.html b/docs/_templates/index.html index 49e46278d..64f6b9468 100644 --- a/docs/_templates/index.html +++ b/docs/_templates/index.html @@ -123,7 +123,7 @@

XONSH is a Python-powered shell


-

Xonsh is a modern, full-featured and cross-platform shell. The language is a superset of Python 3.6+ with additional shell primitives that you are used to from Bash and IPython. +

Xonsh is a modern, full-featured and cross-platform python shell. The language is a superset of Python 3.6+ with additional shell primitives that you are used to from Bash and IPython. It works on all major systems including Linux, OSX, and Windows. Xonsh is meant for the daily use of experts and novices.

Install Docs @@ -139,7 +139,7 @@

What is Xonsh?

-

The xonsh shell lets you easily mix Python and shell commands in a powerful and simplified approach to the command line.

+

The xonsh as a python shell lets you easily mix Python and shell commands in a powerful and simplified approach to the command line.

diff --git a/docs/aliases.rst b/docs/aliases.rst index 2c2d95fb2..e160d757c 100644 --- a/docs/aliases.rst +++ b/docs/aliases.rst @@ -70,7 +70,7 @@ independent of the setting of the $AUTO_CONTINUE option. ``EOF``, ``exit``, and ``quit`` =================================== The commands ``EOF``, ``exit``, and ``quit`` all alias the same action, which is to -leave xonsh in a safe manner. Typing ``Crtl-d`` is the same as typing ``EOF`` and +leave xonsh in a safe manner. Typing ``Ctrl-d`` is the same as typing ``EOF`` and pressing enter. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 954b0e257..5ee4b2f4b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -135,6 +135,17 @@ variable in Python. The same is true for deleting them too. Become the Lord of the Files >>> del $GOAL + >>> $NUM = "123" + >>> $EXT = $NUM + "456" + >>> $EXT + '123456' + >>> $FNUM = f"{$NUM}456" # Not working with Python 3.12+ (https://github.com/xonsh/xonsh/issues/5166). + >>> $FNUM = "{FILLME}456".format(FILLME=$NUM) + >>> $FNUM + '123456' + >>> "%s456" % $NUM + '123456' + Very nice. .. note:: @@ -142,6 +153,7 @@ Very nice. To update ``os.environ`` when the xonsh environment changes set :ref:`$UPDATE_OS_ENVIRON ` to ``True``. + The Environment Itself ``${...}`` --------------------------------- @@ -543,12 +555,12 @@ For example, the ``echo`` command has no interaction with the user and is captur However, some tools have mixed behavior and can be run for either interactive or non-interactive tasks. The best example of this is ``ssh``, which allows for remote terminal sessions and executing commands. -To handle different types of tasks, xonsh has the ``xthread`` and ``xunthread`` built-in aliases. -If you need to capture the output from an interactive tool that has a capturable mode use ``xthread`` to run: +To handle different types of tasks, xonsh has the ``@thread`` and ``@unthread`` built-in decorator aliases. +If you need to capture the output from an interactive tool that has a capturable mode use ``@thread`` to run: .. code-block:: xonshcon - @ !(xthread ssh host -T 'echo remote') + @ !(@thread ssh host -T 'echo remote') CommandPipeline(output="remote") diff --git a/docs/tutorial_macros.rst b/docs/tutorial_macros.rst index 27b36d543..06eb9b8d0 100644 --- a/docs/tutorial_macros.rst +++ b/docs/tutorial_macros.rst @@ -44,8 +44,6 @@ from being consumed. Other languages like Lisp, Forth, and Julia also provide their macro systems. Even restructured text (rST) directives could be considered macros. -Haskell and other more purely functional languages do not need macros (since -evaluation is lazy anyway), and so do not have them. If these seem unfamiliar to the Python world, note that Jupyter and IPython magics ``%`` and ``%%`` are macros! diff --git a/docs/xonshrc.rst b/docs/xonshrc.rst index caed37704..5d713444d 100644 --- a/docs/xonshrc.rst +++ b/docs/xonshrc.rst @@ -41,7 +41,7 @@ initializes your personal run control file (usually at ``~/.xonshrc``). To invo .. code-block:: xonshcon >>> xonfig web - Web config started at 'http://localhost:8421'. Hit Crtl+C to stop. + Web config started at 'http://localhost:8421'. Hit Ctrl+C to stop. 127.0.0.1 - - [23/Aug/2020 15:04:39] "GET / HTTP/1.1" 200 - This will open your default browser on a page served from a local server. You can exit the server by typing ``Ctrl+c`` at any time. diff --git a/news/fix_bad_file_descriptor.rst b/news/fix_bad_file_descriptor.rst deleted file mode 100644 index fa0de084a..000000000 --- a/news/fix_bad_file_descriptor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Partial fix for "Bad file descriptor" in case of callable alias with execx invocation inside e.g. ExecAlias (#5645). - -**Security:** - -* diff --git a/news/fix_python_only_completion.rst b/news/fix_python_only_completion.rst deleted file mode 100644 index 496dd6d96..000000000 --- a/news/fix_python_only_completion.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* completer: Fixed exception when in python-only completion context (#5632). - -**Security:** - -* diff --git a/news/fix_safe_readlines.rst b/news/fix_safe_readlines.rst deleted file mode 100644 index fe4988298..000000000 --- a/news/fix_safe_readlines.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Fixed exception "object has no attribute readlines" in case of redirect callable alias output. - -**Security:** - -* diff --git a/news/fix_autocd.rst b/news/fr-exit-str.rst similarity index 73% rename from news/fix_autocd.rst rename to news/fr-exit-str.rst index 5a0cd4d4c..450a46c15 100644 --- a/news/fix_autocd.rst +++ b/news/fr-exit-str.rst @@ -16,7 +16,7 @@ **Fixed:** -* Fixed ``$AUTO_CD`` regress after previous refactoring. +* Fixed non-int sys.exit codes raising ValueError. **Security:** diff --git a/news/locate_relative.rst b/news/locate_relative.rst deleted file mode 100644 index ff7696bf1..000000000 --- a/news/locate_relative.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* executables: Added ``locate_relative_path`` functionality to ``locate_file``. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/multiline_prompt.rst b/news/multiline_prompt.rst deleted file mode 100644 index 8115b4b75..000000000 --- a/news/multiline_prompt.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* - -**Changed:** - -* ``$MULTILINE_PROMPT`` changed from ``'.'`` to ``' '`` (space) - to have an ability to copy the multiline functions from shell history without deleting the dot manually (#5624 #5634). - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/pyproject.toml b/pyproject.toml index fa6e2688b..a125aa83f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,7 @@ authors = [{ name = "Anthony Scopatz" }, { email = "scopatz@gmail.com" }] maintainers = [{ name = "Xonsh Community" }, { email = "xonsh@gil.forsyth.dev" }] license = { text = "BSD 2-Clause License" } requires-python = ">=3.9" -dependencies = [ - "case-insensitive-dictionary; platform_system=='Windows'", -] +dependencies = [] [tool.setuptools.dynamic] version = {attr = "xonsh.__version__"} @@ -137,7 +135,6 @@ doc = [ "psutil", "pyzmq", "matplotlib", - "doctr", "tornado", "runthis-sphinxext", "livereload", @@ -204,6 +201,7 @@ ignore = [ "E402", # Module level import not at top of file "E501", # line length "E731", # Do not assign a lambda expression, use a def + "UP031", # Use format specifiers instead of percent format ] select = [ "B", # https://beta.ruff.rs/docs/rules/#flake8-bugbear-b diff --git a/tests/parsers/test_parser.py b/tests/parsers/test_parser.py index 8e3684ea9..971855fb3 100644 --- a/tests/parsers/test_parser.py +++ b/tests/parsers/test_parser.py @@ -10,6 +10,7 @@ from xonsh.parser import Parser from xonsh.parsers.ast import AST, Call, Pass, With, is_const_str from xonsh.parsers.fstring_adaptor import FStringAdaptor from xonsh.pytest.tools import ( + ON_WINDOWS, VER_MAJOR_MINOR, nodes_equal, skip_if_pre_3_8, @@ -2652,7 +2653,14 @@ def test_echo_slash_question(check_xonsh_ast): @pytest.mark.parametrize( "case", [ - "[]", + pytest.param( + "[]", + marks=pytest.mark.xfail( + ON_WINDOWS, + reason="non-zero exit code being raised by brackets", + strict=True, + ), # TODO: fix this on a windows machine + ), "[[]]", "[a]", "[a][b]", diff --git a/tests/procs/test_pipelines.py b/tests/procs/test_pipelines.py index 089760ecb..79f1c170e 100644 --- a/tests/procs/test_pipelines.py +++ b/tests/procs/test_pipelines.py @@ -8,7 +8,19 @@ import pytest from xonsh.platform import ON_WINDOWS from xonsh.procs.pipelines import CommandPipeline -from xonsh.pytest.tools import skip_if_on_unix, skip_if_on_windows +from xonsh.pytest.tools import ( + VER_MAJOR_MINOR, + skip_if_on_unix, + skip_if_on_windows, +) + +# TODO: track down which pipeline + spec test is hanging CI +# Skip entire test file for Linux on Python 3.12 +pytestmark = pytest.mark.skipif( + not ON_WINDOWS and VER_MAJOR_MINOR == (3, 12), + reason="Backgrounded test is hanging on CI on 3.12 only", + allow_module_level=True, +) @pytest.fixture(autouse=True) @@ -122,6 +134,7 @@ def test_casting(cmdline, result, xonsh_execer): @skip_if_on_windows +@skip_if_on_unix def test_background_pgid(xonsh_session, monkeypatch): monkeypatch.setitem(xonsh_session.env, "XONSH_INTERACTIVE", True) pipeline = xonsh_session.execer.eval("![echo hi &]") diff --git a/tests/procs/test_specs.py b/tests/procs/test_specs.py index 8294a2e12..e991e7e8d 100644 --- a/tests/procs/test_specs.py +++ b/tests/procs/test_specs.py @@ -17,9 +17,17 @@ from xonsh.procs.specs import ( cmds_to_specs, run_subproc, ) -from xonsh.pytest.tools import skip_if_on_windows +from xonsh.pytest.tools import ON_WINDOWS, VER_MAJOR_MINOR, skip_if_on_windows from xonsh.tools import XonshError +# TODO: track down which pipeline + spec test is hanging CI +# Skip entire test file for Linux on Python 3.12 +pytestmark = pytest.mark.skipif( + not ON_WINDOWS and VER_MAJOR_MINOR == (3, 12), + reason="Backgrounded test is hanging on CI on 3.12 only", + allow_module_level=True, +) + def cmd_sig(sig): return [ diff --git a/tests/shell/test_ptk_highlight.py b/tests/shell/test_ptk_highlight.py index 6b1dd8dc8..b4386fe0f 100644 --- a/tests/shell/test_ptk_highlight.py +++ b/tests/shell/test_ptk_highlight.py @@ -46,16 +46,6 @@ def check_token(xsh): _cases = { - "ls": { - "ls -al": [ - (Name.Builtin, "ls"), - ], - }, - "ls-bin": { - "/bin/ls -al": [ - (Name.Builtin, "/bin/ls"), - ], - }, "print": { 'print("hello")': [ (Name.Builtin, "print"), @@ -94,6 +84,69 @@ _cases = { (Error, "non-existance-cmd"), ], }, + "nested": { + "print($(cd))": [ + (Name.Builtin, "print"), + (Punctuation, "("), + (Keyword, "$"), + (Punctuation, "("), + (Name.Builtin, "cd"), + (Punctuation, ")"), + (Punctuation, ")"), + (Text.Whitespace, "\n"), + ], + }, + "subproc-args": { + "cd 192.168.0.1": [ + (Text, "192.168.0.1"), + ], + }, + "backtick": { + r"echo g`.*\w+`": [ + (String.Affix, "g"), + (String.Backtick, "`"), + (String.Regex, "."), + (String.Regex, "*"), + (String.Escape, r"\w"), + ], + }, + "macro": { + r"g!(42, *, 65)": [ + (Name, "g"), + (Keyword, "!"), + (Punctuation, "("), + (Number.Integer, "42"), + ], + r"bash -c ! export var=42; echo $var": [ + (Name.Builtin, "bash"), + (Text, "-c"), + (Keyword, "!"), + (String, "export var=42; echo $var"), + ], + }, +} +_cases_no_win = { + "ls": { + "ls -al": [ + (Name.Builtin, "ls"), + ], + }, + "ls-bin": { + "/bin/ls -al": [ + (Name.Builtin, "/bin/ls"), + ], + }, + "print": { + 'print("hello")': [ + (Name.Builtin, "print"), + (Punctuation, "("), + (Literal.String.Double, '"'), + (Literal.String.Double, "hello"), + (Literal.String.Double, '"'), + (Punctuation, ")"), + (Text.Whitespace, "\n"), + ] + }, "nested": { 'echo @("hello")': [ (Name.Builtin, "echo"), @@ -125,20 +178,6 @@ _cases = { (Text.Whitespace, "\n"), ], }, - "subproc-args": { - "cd 192.168.0.1": [ - (Text, "192.168.0.1"), - ], - }, - "backtick": { - r"echo g`.*\w+`": [ - (String.Affix, "g"), - (String.Backtick, "`"), - (String.Regex, "."), - (String.Regex, "*"), - (String.Escape, r"\w"), - ], - }, "macro": { r"g!(42, *, 65)": [ (Name, "g"), @@ -167,12 +206,23 @@ def _convert_cases(): yield pytest.param(*item, id=f"{title}-{idx}") +def _convert_cases_no_win(): + for title, input_dict in _cases_no_win.items(): + for idx, item in enumerate(input_dict.items()): + yield pytest.param(*item, id=f"{title}-{idx}") + + @pytest.mark.parametrize("inp, expected", list(_convert_cases())) -@skip_if_on_windows def test_xonsh_lexer(inp, expected, check_token): check_token(inp, expected) +@pytest.mark.parametrize("inp, expected", list(_convert_cases_no_win())) +@skip_if_on_windows +def test_xonsh_lexer_no_win(inp, expected, check_token): + check_token(inp, expected) + + # can't seem to get thie test to import pyghooks and define on_lscolors_change handler like live code does. # so we declare the event handler directly here. @pytest.fixture diff --git a/tests/test_commands_cache.py b/tests/test_commands_cache.py index b8825ba1b..d90dec63c 100644 --- a/tests/test_commands_cache.py +++ b/tests/test_commands_cache.py @@ -8,6 +8,7 @@ import pytest from xonsh.commands_cache import ( SHELL_PREDICTOR_PARSER, + CaseInsensitiveDict, CommandsCache, _Commands, executables_in, @@ -306,3 +307,77 @@ def test_executables_in(xession): else: result = set(executables_in(test_path)) assert expected == result + + +def test_caseinsdict_constructor(): + actual = CaseInsensitiveDict({"key1": "val1", "Key2": "Val2"}) + assert isinstance(actual, CaseInsensitiveDict) + assert actual["key1"] == "val1" + assert actual["Key2"] == "Val2" + + +def test_caseinsdict_getitem(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert actual["Key1"] == "Val1" + assert actual["key1"] == "Val1" + + +def test_caseinsdict_setitem(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + actual["Key1"] = "Val2" + assert actual["Key1"] == "Val2" + assert actual["key1"] == "Val2" + actual["key1"] = "Val3" + assert actual["Key1"] == "Val3" + assert actual["key1"] == "Val3" + + +def test_caseinsdict_delitem(): + actual = CaseInsensitiveDict({"Key1": "Val1", "Key2": "Val2"}) + del actual["Key1"] + assert actual == CaseInsensitiveDict({"Key2": "Val2"}) + del actual["key2"] + assert actual == CaseInsensitiveDict({}) + + +def test_caseinsdict_contains(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert actual.__contains__("Key1") + assert actual.__contains__("key1") + assert not actual.__contains__("key2") + + +def test_caseinsdict_get(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert actual.get("Key1") == "Val1" + assert actual.get("key1") == "Val1" + assert actual.get("key2", "no val") == "no val" + assert actual.get("key1", "no val") == "Val1" + + +def test_caseinsdict_update(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + actual.update({"Key2": "Val2"}) + assert actual["key2"] == "Val2" + + +def test_caseinsdict_keys(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert next(actual.keys()) == "Key1" + + +def test_caseinsdict_items(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert next(actual.items()) == ("Key1", "Val1") + + +def test_caseinsdict_repr(): + actual = CaseInsensitiveDict({"Key1": "Val1"}) + assert actual.__repr__() == "CaseInsensitiveDict({'Key1': 'Val1'})" + + +def test_caseinsdict_copy(): + initial = CaseInsensitiveDict({"Key1": "Val1"}) + actual = initial.copy() + assert actual == initial + assert id(actual) != id(initial) diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 8733d5dee..ef9d7a31a 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -17,6 +17,7 @@ from xonsh.pytest.tools import ( ON_DARWIN, ON_TRAVIS, ON_WINDOWS, + VER_FULL, skip_if_on_darwin, skip_if_on_msys, skip_if_on_unix, @@ -329,6 +330,22 @@ f "hello\n", 0, ), + # test system exit in unthreadable alias (see #5689) + ( + """ +from xonsh.tools import unthreadable + +@unthreadable +def _f(): + import sys + sys.exit(42) + +aliases['f'] = _f +print(![f].returncode) +""", + "42\n", + 0, + ), # test ambiguous globs ( """ @@ -594,8 +611,9 @@ first ), # testing alias stack: parallel threaded callable aliases. # This breaks if the __ALIAS_STACK variables leak between threads. - ( - """ + pytest.param( + ( + """ from time import sleep aliases['a'] = lambda: print(1, end="") or sleep(0.2) or print(1, end="") aliases['b'] = 'a' @@ -604,8 +622,14 @@ a | a a | b | a a | a | b | b """, - "1" * 2 * 4, - 0, + "1" * 2 * 4, + 0, + ), + # TODO: investigate errors on Python 3.13 + marks=pytest.mark.skipif( + VER_FULL > (3, 12) and not ON_WINDOWS, + reason="broken pipes on Python 3.13 likely due to changes in threading behavior", + ), ), # test $SHLVL ( @@ -723,7 +747,7 @@ if not ON_WINDOWS: @skip_if_no_xonsh @pytest.mark.parametrize("case", ALL_PLATFORMS) -@pytest.mark.flaky(reruns=3, reruns_delay=2) +@pytest.mark.flaky(reruns=4, reruns_delay=2) def test_script(case): script, exp_out, exp_rtn = case if ON_DARWIN: diff --git a/tests/test_virtualenv_activator.py b/tests/test_virtualenv_activator.py index 345e7969d..0fc9d4bd9 100644 --- a/tests/test_virtualenv_activator.py +++ b/tests/test_virtualenv_activator.py @@ -2,12 +2,15 @@ import sys from pathlib import Path from subprocess import check_output +import pytest + from xonsh.pytest.tools import ON_WINDOWS -def test_xonsh_activator(tmp_path): +@pytest.mark.parametrize("dir_name", ["venv", "venv with space"]) +def test_xonsh_activator(tmp_path, dir_name): # Create virtualenv - venv_dir = tmp_path / "venv" + venv_dir = tmp_path / dir_name assert b"XonshActivator" in check_output( [sys.executable, "-m", "virtualenv", str(venv_dir)] ) @@ -35,7 +38,13 @@ def test_xonsh_activator(tmp_path): # Activate venv_python = check_output( - [sys.executable, "-m", "xonsh", "-c", f"source {activate_path}; which python"] + [ + sys.executable, + "-m", + "xonsh", + "-c", + f"source r'{activate_path}'; which python", + ] ).decode() assert Path(venv_python).parent == bin_path @@ -46,7 +55,7 @@ def test_xonsh_activator(tmp_path): "-m", "xonsh", "-c", - f"source {activate_path}; deactivate; " + f"source r'{activate_path}'; deactivate; " "import shutil; shutil.which('python') or shutil.which('python3')", ] ).decode() diff --git a/xonsh/__init__.py b/xonsh/__init__.py index b0d7306ae..11ac8e1a9 100644 --- a/xonsh/__init__.py +++ b/xonsh/__init__.py @@ -1 +1 @@ -__version__ = "0.18.2" +__version__ = "0.19.0" diff --git a/xonsh/api/README.md b/xonsh/api/README.md new file mode 100644 index 000000000..b8922509d --- /dev/null +++ b/xonsh/api/README.md @@ -0,0 +1,2 @@ +Xonsh API was introduced to reduce boilerplate code in a solutions that based on xonsh. +We're very welcome to extend Xonsh API with shortcuts, code snippets and helpful functions. diff --git a/xonsh/cli_utils.py b/xonsh/cli_utils.py index 2128f88e6..3c16fab11 100644 --- a/xonsh/cli_utils.py +++ b/xonsh/cli_utils.py @@ -354,7 +354,9 @@ class ArgParser(ap.ArgumentParser): add_args(parser, func, allowed_params=args, doc=doc) return parser - def _parse_known_args(self, arg_strings: list[str], namespace: ap.Namespace): + def _parse_known_args( + self, arg_strings: list[str], namespace: ap.Namespace, *args, **kwargs + ): arg_set = set(arg_strings) if ( self.commands @@ -363,7 +365,7 @@ class ArgParser(ap.ArgumentParser): and (set(self.commands.choices).isdisjoint(arg_set)) ): arg_strings = [self.default_command] + arg_strings - return super()._parse_known_args(arg_strings, namespace) + return super()._parse_known_args(arg_strings, namespace, *args, **kwargs) def run_with_partial_args(func: tp.Callable, ns: dict[str, tp.Any]): @@ -543,6 +545,9 @@ class ArgparseCompleter: if not act_res: # it is not a option string: pass break + if isinstance(act_res, list): + assert len(act_res) == 1 + act_res = act_res[0] # it is a valid option and advance self.remaining_args = self.remaining_args[1:] act, *_, value = act_res diff --git a/xonsh/commands_cache.py b/xonsh/commands_cache.py index 0e8191584..bfd50f859 100644 --- a/xonsh/commands_cache.py +++ b/xonsh/commands_cache.py @@ -23,8 +23,52 @@ from xonsh.procs.executables import ( is_executable_in_windows, ) + +class CaseInsensitiveDict(dict[tp.Any, tp.Any]): + def __init__(self, *args, **kwargs): + super().__init__() + self._store = {} + self.update(*args, **kwargs) + + def __setitem__(self, key, value): + # Store the key in lowercase but preserve the original case for display + self._store[key.casefold()] = key + super().__setitem__(key.casefold(), value) + + def __getitem__(self, key): + return super().__getitem__(key.casefold()) + + def __delitem__(self, key): + del self._store[key.casefold()] + super().__delitem__(key.casefold()) + + def __contains__(self, key): + return key.casefold() in self._store + + def get(self, key, default=None): + return super().get(key.casefold(), default) + + def update(self, *args, **kwargs): + for k, v in dict(*args, **kwargs).items(): + self[k] = v + + def keys(self): + # Return the original keys with their original casing + return (self._store[k] for k in self._store) + + def items(self): + return ((self._store[k], self[k]) for k in self._store) + + def __repr__(self): + return f"{self.__class__.__name__}({dict(self.items())})" + + def copy(self): + return CaseInsensitiveDict(self.items()) + + +CacheDict: tp.Union[type[CaseInsensitiveDict], type[dict]] if ON_WINDOWS: - from case_insensitive_dict import CaseInsensitiveDict as CacheDict + CacheDict = CaseInsensitiveDict else: CacheDict = dict diff --git a/xonsh/completers/commands.py b/xonsh/completers/commands.py index f6a67e491..4a40f7d15 100644 --- a/xonsh/completers/commands.py +++ b/xonsh/completers/commands.py @@ -33,7 +33,7 @@ def complete_command(command: CommandContext): kwargs = {} if show_desc: kwargs["description"] = "Alias" if is_alias else path - yield RichCompletion(s, append_space=True, **kwargs) + yield RichCompletion(s, append_space=True, **kwargs) # type: ignore if xp.ON_WINDOWS: for i in executables_in("."): if i.startswith(cmd): diff --git a/xonsh/environ.py b/xonsh/environ.py index a005e9cd8..b49cd8f56 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -1627,6 +1627,10 @@ class PromptSetting(Xettings): "For example, to have stderr appear on a red background, the " 'prefix & postfix pair would be "{BACKGROUND_RED}" & "{RESET}".', ) + XONSH_SUPPRESS_WELCOME = Var.with_default( + False, + "Suppresses the welcome message.", + ) class PromptHistorySetting(Xettings): diff --git a/xonsh/lib/README.md b/xonsh/lib/README.md new file mode 100644 index 000000000..7725ffde9 --- /dev/null +++ b/xonsh/lib/README.md @@ -0,0 +1 @@ +Xonsh library contains the code that independent to xonsh core or a code that is fork from another projects. diff --git a/xonsh/main.py b/xonsh/main.py index fa1fa790d..17ce7ac8c 100644 --- a/xonsh/main.py +++ b/xonsh/main.py @@ -548,6 +548,7 @@ def main_xonsh(args): ignore_sigtstp() if ( env["XONSH_INTERACTIVE"] + and not env["XONSH_SUPPRESS_WELCOME"] and sys.stdin.isatty() # In case the interactive mode is forced but no tty (input from pipe). and not any(os.path.isfile(i) for i in env["XONSHRC"]) and not any(os.path.isdir(i) for i in env["XONSHRC_DIR"]) @@ -605,7 +606,14 @@ def main_xonsh(args): err_type, err, _ = exc_info if err_type is SystemExit: code = getattr(exc_info[1], "code", 0) - exit_code = int(code) if code is not None else 0 + if code is None: + exit_code = 0 + else: + exit_code = code + try: + exit_code = int(code) + except ValueError: + pass XSH.exit = exit_code else: exit_code = 1 diff --git a/xonsh/parser.py b/xonsh/parser.py index 8d6b0f24c..429b5e03c 100644 --- a/xonsh/parser.py +++ b/xonsh/parser.py @@ -6,7 +6,9 @@ from xonsh.platform import PYTHON_VERSION_INFO @lazyobject def Parser(): - if PYTHON_VERSION_INFO > (3, 10): + if PYTHON_VERSION_INFO >= (3, 13): + from xonsh.parsers.v313 import Parser as p + elif PYTHON_VERSION_INFO > (3, 10): from xonsh.parsers.v310 import Parser as p elif PYTHON_VERSION_INFO > (3, 9): from xonsh.parsers.v39 import Parser as p diff --git a/xonsh/parsers/ast.py b/xonsh/parsers/ast.py index bd5833737..9f73d9630 100644 --- a/xonsh/parsers/ast.py +++ b/xonsh/parsers/ast.py @@ -109,8 +109,10 @@ from ast import ( # noqa # pylint: disable=unused-import walk, withitem, ) +from typing import Optional from xonsh.built_ins import XSH +from xonsh.platform import PYTHON_VERSION_INFO from xonsh.tools import find_next_break, get_logical_line, subproc_toks STATEMENTS = ( @@ -139,8 +141,25 @@ STATEMENTS = ( ) -def const_str(s: str, **kwargs): - return Constant(value=s, kind="str", **kwargs) +def const_str( + s: str, + lineno: Optional[int] = None, + col_offset: Optional[int] = None, + is_raw: bool = True, +): + if PYTHON_VERSION_INFO >= (3, 13): + # looks like this attribute is no longer needed to be set explicitly + constant = Constant(value=s, kind="str") + else: + constant = Constant(value=s, kind="str") + if is_raw: + # this attribute is not documented within the ast object + constant.is_raw = is_raw # type: ignore + if lineno is not None: + constant.lineno = lineno + if col_offset is not None: + constant.col_offset = col_offset + return constant def is_const_str(node): diff --git a/xonsh/parsers/base.py b/xonsh/parsers/base.py index c1223cf83..239926d03 100644 --- a/xonsh/parsers/base.py +++ b/xonsh/parsers/base.py @@ -738,7 +738,9 @@ class BaseParser: def p_eval_input(self, p): """eval_input : testlist newlines_opt""" p1 = p[1] - p[0] = ast.Expression(body=p1, lineno=p1.lineno, col_offset=p1.col_offset) + p[0] = ast.Expression(body=p1) + p[0].lineno = p1.lineno + p[0].col_offset = p1.col_offset def p_func_call(self, p): """func_call : LPAREN arglist_opt RPAREN""" @@ -2155,7 +2157,8 @@ class BaseParser: | minus_tok term """ p1 = p[1] - op = self._term_binops[p1.value](lineno=p1.lineno, col_offset=p1.lexpos) + op = self._term_binops[p1.value]() + op.lineno, op.col_offset = p1.lineno, p1.lexpos p[0] = [op, p[2]] def p_term(self, p): @@ -2194,7 +2197,9 @@ class BaseParser: f"operation {p1!r} not supported", self.currloc(lineno=p.lineno, column=p.lexpos), ) - p[0] = [op(lineno=p1.lineno, col_offset=p1.lexpos), p[2]] + op_node = op() + op_node.lineno, op_node.col_offset = p1.lineno, p1.lexpos + p[0] = [op_node, p[2]] _factor_ops = {"+": ast.UAdd, "-": ast.USub, "~": ast.Invert} @@ -3047,7 +3052,7 @@ class BaseParser: else: targ = ensure_has_elts(targs) store_ctx(targ) - comp = ast.comprehension(target=targ, iter=it, ifs=[]) + comp = ast.comprehension(target=targ, iter=it, ifs=[], is_async=0) comps = [comp] p0 = {"comps": comps} if p5 is not None: diff --git a/xonsh/parsers/v313.py b/xonsh/parsers/v313.py new file mode 100644 index 000000000..22bc03265 --- /dev/null +++ b/xonsh/parsers/v313.py @@ -0,0 +1,264 @@ +# type: ignore +# TODO: remove line above once mypy understands the match statement + +"""Handles changes since PY313 + +handle +- import-alias requiring lineno +- match statement +""" + +from ast import match_case +from ast import parse as pyparse + +from xonsh.parsers import ast +from xonsh.parsers.ast import xonsh_call +from xonsh.parsers.base import ( + RE_STRINGPREFIX, + del_ctx, + ensure_has_elts, + lopen_loc, + store_ctx, +) +from xonsh.parsers.fstring_adaptor import FStringAdaptor +from xonsh.parsers.v310 import Parser as ThreeTenParser + + +class Parser(ThreeTenParser): + def p_eval_input(self, p): + """eval_input : testlist newlines_opt""" + p1 = p[1] + expression = ast.Expression(body=p1) + expression.lineno = p1.lineno + expression.col_offset = p1.col_offset + p[0] = expression + + def p_pm_term(self, p): + """ + pm_term : plus_tok term + | minus_tok term + """ + p1 = p[1] + op = self._term_binops[p1.value]() + op.lineno = p1.lineno + op.col_offset = p1.lexpos + p[0] = [op, p[2]] + + def p_atom_lbrace(self, p): + """atom : lbrace_tok dictorsetmaker_opt RBRACE""" + p1, p2 = p[1], p[2] + p1, p1_tok = p1.value, p1 + if p2 is None: + p0 = ast.Dict( + keys=[], + values=[], + lineno=self.lineno, + col_offset=self.col, + ) + p0.ctx = ast.Load() + else: + p0 = p2 + p0.lineno, p0.col_offset = p1_tok.lineno, p1_tok.lexpos + p[0] = p0 + + # case blocks + def p_case_block(self, p): + """ + case_block : case_tok patterns COLON suite + | case_tok patterns IF test COLON suite + """ + + loc = self.get_line_cols(p, 1) + match list(p): + case [_, _, pattern, _, suite]: + p[0] = match_case(pattern=pattern, body=suite) + p[0].lineno = loc["lineno"] + p[0].end_lineno = loc["end_lineno"] + p[0].col_offset = loc["col_offset"] + p[0].end_col_offset = loc["end_col_offset"] + case [_, _, pattern, _, guard, _, suite]: + p[0] = match_case(pattern=pattern, body=suite, guard=guard) + p[0].lineno = loc["lineno"] + p[0].end_lineno = loc["end_lineno"] + p[0].col_offset = loc["col_offset"] + p[0].end_col_offset = loc["end_col_offset"] + case _: + raise AssertionError() + + def p_string_literal(self, p): + """string_literal : string_tok""" + p1 = p[1] + prefix = RE_STRINGPREFIX.match(p1.value).group().lower() + if "p" in prefix and "f" in prefix: + new_pref = prefix.replace("p", "") + value_without_p = new_pref + p1.value[len(prefix) :] + try: + s = pyparse(value_without_p).body[0].value + except SyntaxError: + s = None + if s is None: + try: + s = FStringAdaptor( + value_without_p, new_pref, filename=self.lexer.fname + ).run() + except SyntaxError as e: + self._set_error( + str(e), self.currloc(lineno=p1.lineno, column=p1.lexpos) + ) + s = ast.increment_lineno(s, p1.lineno - 1) + p[0] = xonsh_call( + "__xonsh__.path_literal", [s], lineno=p1.lineno, col=p1.lexpos + ) + elif "p" in prefix: + value_without_p = prefix.replace("p", "") + p1.value[len(prefix) :] + s = ast.const_str( + s=ast.literal_eval(value_without_p), + lineno=p1.lineno, + col_offset=p1.lexpos, + ) + p[0] = xonsh_call( + "__xonsh__.path_literal", [s], lineno=p1.lineno, col=p1.lexpos + ) + elif "f" in prefix: + try: + s = pyparse(p1.value).body[0].value + except SyntaxError: + s = None + if s is None: + try: + s = FStringAdaptor( + p1.value, prefix, filename=self.lexer.fname + ).run() + except SyntaxError as e: + self._set_error( + str(e), self.currloc(lineno=p1.lineno, column=p1.lexpos) + ) + s = ast.increment_lineno(s, p1.lineno - 1) + if "r" in prefix: + s.is_raw = True + p[0] = s + else: + s = ast.literal_eval(p1.value) + is_bytes = "b" in prefix + is_raw = "r" in prefix + cls = ast.const_bytes if is_bytes else ast.const_str + p[0] = cls(s=s, lineno=p1.lineno, col_offset=p1.lexpos) + p[0].is_raw = is_raw + + def p_atom_expr_await(self, p): + """atom_expr : await_tok atom trailer_list_opt""" + p0 = self.apply_trailers(p[2], p[3]) + p1 = p[1] + p0 = ast.Await(value=p0, lineno=p1.lineno, col_offset=p1.lexpos) + p0.ctx = ast.Load() + p[0] = p0 + + # + # For normal assignments, additional restrictions enforced + # by the interpreter + # + def p_del_stmt(self, p): + """del_stmt : del_tok exprlist""" + p1 = p[1] + p2 = p[2] + for targ in p2: + del_ctx(targ) + p0 = ast.Delete(targets=p2, lineno=p1.lineno, col_offset=p1.lexpos) + p[0] = p0 + + # + # Dict or set maker + # + def p_dictorsetmaker_t6(self, p): + """dictorsetmaker : test COLON test comma_item_list comma_opt""" + p1, p4 = p[1], p[4] + keys = [p1] + vals = [p[3]] + for k, v in zip(p4[::2], p4[1::2]): + keys.append(k) + vals.append(v) + lineno, col = lopen_loc(p1) + p[0] = ast.Dict(keys=keys, values=vals, lineno=lineno, col_offset=col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_i4(self, p): + """dictorsetmaker : item comma_item_list comma_opt""" + p1, p2 = p[1], p[2] + keys = [p1[0]] + vals = [p1[1]] + for k, v in zip(p2[::2], p2[1::2]): + keys.append(k) + vals.append(v) + lineno, col = lopen_loc(p1[0] or p1[1]) + p[0] = ast.Dict(keys=keys, values=vals, lineno=lineno, col_offset=col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_t4_dict(self, p): + """dictorsetmaker : test COLON testlist""" + keys = [p[1]] + vals = self._list_or_elts_if_not_real_tuple(p[3]) + lineno, col = lopen_loc(p[1]) + p[0] = ast.Dict(keys=keys, values=vals, lineno=lineno, col_offset=col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_item_comma(self, p): + """dictorsetmaker : item comma_opt""" + p1 = p[1] + keys = [p1[0]] + vals = [p1[1]] + lineno, col = lopen_loc(p1[0] or p1[1]) + p[0] = ast.Dict(keys=keys, values=vals, lineno=lineno, col_offset=col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_t4_set(self, p): + """dictorsetmaker : test_or_star_expr comma_test_or_star_expr_list comma_opt""" + p[0] = ast.Set(elts=[p[1]] + p[2], lineno=self.lineno, col_offset=self.col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_test_comma(self, p): + """dictorsetmaker : test_or_star_expr comma_opt""" + elts = self._list_or_elts_if_not_real_tuple(p[1]) + p[0] = ast.Set(elts=elts, lineno=self.lineno, col_offset=self.col) + p[0].ctx = ast.Load() + + def p_dictorsetmaker_testlist(self, p): + """dictorsetmaker : testlist""" + elts = self._list_or_elts_if_not_real_tuple(p[1]) + p[0] = ast.Set(elts=elts, lineno=self.lineno, col_offset=self.col) + p[0].ctx = ast.Load() + + def p_op_factor(self, p): + """ + op_factor : times_tok factor + | at_tok factor + | divide_tok factor + | mod_tok factor + | doublediv_tok factor + """ + p1 = p[1] + op = self._term_binops[p1.value] + if op is None: + self._set_error( + f"operation {p1!r} not supported", + self.currloc(lineno=p.lineno, column=p.lexpos), + ) + op = op() + op.lineno = p1.lineno + op.col_offset = p1.lexpos + p[0] = [op, p[2]] + + def p_comp_for(self, p): + """comp_for : FOR exprlist IN or_test comp_iter_opt""" + targs, it, p5 = p[2], p[4], p[5] + if len(targs) == 1: + targ = targs[0] + else: + targ = ensure_has_elts(targs) + store_ctx(targ) + comp = ast.comprehension(target=targ, iter=it, ifs=[], is_async=0) + comps = [comp] + p0 = {"comps": comps} + if p5 is not None: + comps += p5.get("comps", []) + comp.ifs += p5.get("if", []) + p[0] = p0 diff --git a/xonsh/procs/proxies.py b/xonsh/procs/proxies.py index 3eb65133e..74fb2f145 100644 --- a/xonsh/procs/proxies.py +++ b/xonsh/procs/proxies.py @@ -816,6 +816,10 @@ class ProcProxy: "stack": spec.stack, }, ) + except SystemExit as e: + # the alias function is running in the main thread, so we need to + # catch SystemExit to prevent the entire shell from exiting (see #5689) + r = e.code if isinstance(e.code, int) else int(bool(e.code)) except Exception: xt.print_exception(source_msg="Exception in " + get_proc_proxy_name(self)) r = 1 diff --git a/xonsh/pytest/tools.py b/xonsh/pytest/tools.py index 41a1c5211..116d34b85 100644 --- a/xonsh/pytest/tools.py +++ b/xonsh/pytest/tools.py @@ -18,6 +18,7 @@ VER_MAJOR_MINOR = sys.version_info[:2] VER_FULL = sys.version_info[:3] ON_DARWIN = platform.system() == "Darwin" ON_WINDOWS = platform.system() == "Windows" +ON_LINUX = platform.system() == "Linux" ON_MSYS = sys.platform == "msys" ON_CONDA = True in [ conda in pytest.__file__.lower() for conda in ["conda", "anaconda", "miniconda"] diff --git a/xonsh/virtualenv/__init__.py b/xonsh/virtualenv/__init__.py index 46a1a5bfb..70664f235 100644 --- a/xonsh/virtualenv/__init__.py +++ b/xonsh/virtualenv/__init__.py @@ -5,6 +5,11 @@ class XonshActivator(ViaTemplateActivator): def templates(self): yield "activate.xsh" + @staticmethod + def quote(string): + # leave string unchanged since we do quoting in activate.xsh (see #5699) + return string + @classmethod def supports(cls, interpreter): return interpreter.version_info >= (3, 5) diff --git a/xonsh/webconfig/main.py b/xonsh/webconfig/main.py index b8536e893..16d051d0b 100644 --- a/xonsh/webconfig/main.py +++ b/xonsh/webconfig/main.py @@ -138,7 +138,7 @@ def bind_server_to( httpd = cls(("", port), handler_cls) url = f"http://localhost:{port}" - print(f"Web config started at '{url}'. Hit Crtl+C to stop.") + print(f"Web config started at '{url}'. Hit Ctrl+C to stop.") if browser: import webbrowser