From 620d35c892b29a7a8a839fe34a7999ffbf8a5391 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sun, 4 Aug 2019 16:24:38 -0700 Subject: [PATCH 1/4] add shell autocomplete --- docs/usage.rst | 37 +++++++++++++++++++++++++++++++++++++ nox/_option_set.py | 9 +++++++-- nox/_options.py | 34 ++++++++++++++++++++++++++++++++++ setup.py | 1 + 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 042f6624..dc200345 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -313,3 +313,40 @@ This will create a ``noxfile.py`` based on the environments in your ``tox.ini``. .. _Generative environments: http://tox.readthedocs.io/en/latest/config.html#generating-environments-conditional-settings .. _substitutions: http://tox.readthedocs.io/en/latest/config.html#substitutions + + +Shell Completion +---------------- +Add the appropriate command to your shell's config file +so that it is run on startup. You will likely have to restart +or re-login for the autocompletion to start working. + +bash + +.. code-block:: console + + eval "$(register-python-argcomplete nox)" + +zsh + +.. code-block:: console + + # To activate completions for zsh you need to have + # bashcompinit enabled in zsh: + autoload -U bashcompinit + bashcompinit + + # Afterwards you can enable completion for nox: + eval "$(register-python-argcomplete nox)" + +tcsh + +.. code-block:: console + + eval `register-python-argcomplete --shell tcsh nox` + +fish + +.. code-block:: console + + register-python-argcomplete --shell fish nox | . \ No newline at end of file diff --git a/nox/_option_set.py b/nox/_option_set.py index 2b4830bc..0cc76538 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -17,11 +17,11 @@ and surfaced in documentation.""" +import argcomplete import argparse import collections import functools - Namespace = argparse.Namespace ArgumentError = argparse.ArgumentError @@ -68,6 +68,7 @@ def __init__( finalizer_func=None, default=None, hidden=False, + completer=None, **kwargs ): self.name = name @@ -78,6 +79,7 @@ def __init__( self.merge_func = merge_func self.finalizer_func = finalizer_func self.hidden = hidden + self.completer = completer self.kwargs = kwargs self._default = default @@ -187,9 +189,11 @@ def add_group(self, name, *args, **kwargs): self.groups[name] = (args, kwargs) def _add_to_parser(self, parser, option): - parser.add_argument( + argument = parser.add_argument( *option.flags, help=option.help, default=option.default, **option.kwargs ) + if option.completer: + argument.completer = option.completer def parser(self): """Returns an ``ArgumentParser`` for this option set. @@ -233,6 +237,7 @@ def _finalize_args(self, args): def parse_args(self): parser = self.parser() + argcomplete.autocomplete(parser) args = parser.parse_args() try: diff --git a/nox/_options.py b/nox/_options.py index d421d902..2535ac0c 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -17,6 +17,7 @@ import os import sys +from nox.tasks import load_nox_module, discover_manifest, filter_manifest from nox import _option_set """All of nox's configuration options.""" @@ -97,6 +98,38 @@ def _color_finalizer(value, args): return sys.stdin.isatty() +def _session_completer(*args, **kwargs): + global_config = argparse.Namespace( + color=False, + envdir=".nox", # TODO determine this dynamically + error_on_external_run=None, + error_on_missing_interpreters=None, + forcecolor=False, + help=None, + install_only=None, + keywords=None, + list_sessions=True, + no_error_on_external_run=None, + no_error_on_missing_interpreters=None, + no_reuse_existing_virtualenvs=None, + no_stop_on_first_error=None, + nocolor=False, + non_interactive=None, + noxfile="noxfile.py", # # TODO determine this dynamically + reuse_existing_virtualenvs=None, + sessions=None, + stop_on_first_error=None, + version=None, + ) + + module = load_nox_module(global_config) + manifest = discover_manifest(module, global_config) + filtered_manifest = filter_manifest(manifest, global_config) + return [ + session.friendly_name for session, _ in filtered_manifest.list_all_sessions() + ] + + def _posargs_finalizer(value, unused_args): """Removes any leading "--"s in the posargs array.""" if value and value[0] == "--": @@ -141,6 +174,7 @@ def _posargs_finalizer(value, unused_args): nargs="*", default=_sessions_default, help="Which sessions to run. By default, all sessions will run.", + completer=_session_completer, ), _option_set.Option( "keywords", diff --git a/setup.py b/setup.py index 0224dc1c..8d829e68 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ packages=["nox"], include_package_data=True, install_requires=[ + "argcomplete>=1.9.4, <2.0", "colorlog>=2.6.1,<4.0.0", "py>=1.4.0,<2.0.0", "virtualenv>=14.0.0", From 7cde7ffd145d63b5f4fa288744601d4ac0db70dd Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sun, 4 Aug 2019 16:54:05 -0700 Subject: [PATCH 2/4] use existing args to complete sessions --- nox/_options.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 2535ac0c..ce6d7322 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -98,30 +98,8 @@ def _color_finalizer(value, args): return sys.stdin.isatty() -def _session_completer(*args, **kwargs): - global_config = argparse.Namespace( - color=False, - envdir=".nox", # TODO determine this dynamically - error_on_external_run=None, - error_on_missing_interpreters=None, - forcecolor=False, - help=None, - install_only=None, - keywords=None, - list_sessions=True, - no_error_on_external_run=None, - no_error_on_missing_interpreters=None, - no_reuse_existing_virtualenvs=None, - no_stop_on_first_error=None, - nocolor=False, - non_interactive=None, - noxfile="noxfile.py", # # TODO determine this dynamically - reuse_existing_virtualenvs=None, - sessions=None, - stop_on_first_error=None, - version=None, - ) - +def _session_completer(prefix, parsed_args, **kwargs): + global_config = parsed_args module = load_nox_module(global_config) manifest = discover_manifest(module, global_config) filtered_manifest = filter_manifest(manifest, global_config) From 72ac97969580b8cc46c39bef70c3bb5a98860608 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sat, 10 Aug 2019 17:24:41 -0700 Subject: [PATCH 3/4] fix lint errors --- nox/_option_set.py | 3 ++- nox/_options.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nox/_option_set.py b/nox/_option_set.py index 0cc76538..a5492afc 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -17,11 +17,12 @@ and surfaced in documentation.""" -import argcomplete import argparse import collections import functools +import argcomplete # type: ignore + Namespace = argparse.Namespace ArgumentError = argparse.ArgumentError diff --git a/nox/_options.py b/nox/_options.py index ce6d7322..c2da68c5 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -17,8 +17,8 @@ import os import sys -from nox.tasks import load_nox_module, discover_manifest, filter_manifest from nox import _option_set +from nox.tasks import discover_manifest, filter_manifest, load_nox_module """All of nox's configuration options.""" From fe296ccee946bb50a67d68f6289dc8e7d4ecc46a Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sat, 10 Aug 2019 17:42:29 -0700 Subject: [PATCH 4/4] add test --- tests/test__option_set.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test__option_set.py b/tests/test__option_set.py index 323467b5..a4b64a4b 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -12,9 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys +from unittest import mock + import pytest from nox import _option_set +from nox import _options # The vast majority of _option_set is tested by test_main, but the test helper @@ -45,3 +49,14 @@ def test_namespace_non_existant_options_with_values(self): with pytest.raises(KeyError): optionset.namespace(non_existant_option="meep") + + def test_session_completer(self): + with mock.patch("sys.argv", [sys.executable]): + parsed_args = _options.options.parse_args() + all_nox_sessions = _options._session_completer( + prefix=None, parsed_args=parsed_args + ) + # if noxfile.py changes, this will have to change as well since these are + # some of the actual sessions found in noxfile.py + some_expected_sessions = ["cover", "blacken", "lint", "docs"] + assert len(set(some_expected_sessions) - set(all_nox_sessions)) == 0