Hello community, here is the log from the commit of package python-invoke for openSUSE:Factory checked in at 2019-03-26 22:33:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-invoke (Old) and /work/SRC/openSUSE:Factory/.python-invoke.new.25356 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-invoke" Tue Mar 26 22:33:46 2019 rev:6 rq:688747 version:1.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-invoke/python-invoke.changes 2018-08-13 09:54:34.726860998 +0200 +++ /work/SRC/openSUSE:Factory/.python-invoke.new.25356/python-invoke.changes 2019-03-26 22:34:35.313674418 +0100 @@ -1,0 +2,6 @@ +Tue Mar 26 14:02:06 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.2.0: + * [Feature] #301: (via #414) Overhaul tab completion mechanisms so users can print a completion script which automatically matches the emitting binary’s configured names (compared to the previous hardcoded scripts, which only worked for inv/invoke by default). Thanks to Nicolas Höning for the foundational patchset. + +------------------------------------------------------------------- Old: ---- invoke-1.1.1.tar.gz New: ---- invoke-1.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-invoke.spec ++++++ --- /var/tmp/diff_new_pack.tsWff4/_old 2019-03-26 22:34:36.529674125 +0100 +++ /var/tmp/diff_new_pack.tsWff4/_new 2019-03-26 22:34:36.529674125 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-invoke # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,13 +12,13 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-invoke -Version: 1.1.1 +Version: 1.2.0 Release: 0 Summary: Pythonic Task Execution License: BSD-2-Clause @@ -55,6 +55,8 @@ %setup -q -n invoke-%{version} # Remove bundled libs, import will fallback to system provided libs rm -fr invoke/vendor/* +# remove pycache dir +rm -r invoke/completion/__pycache__/ %patch0 -p1 @@ -69,9 +71,7 @@ %python_clone -a %{buildroot}%{_bindir}/invoke %check -%{python_expand export PYTHONPATH=%{buildroot}%{$python_sitelib} -py.test-%{$python_bin_suffix} -} +%python_expand PYTHONPATH=%{buildroot}%{$python_sitelib} py.test-%{$python_bin_suffix} %post %{python_install_alternative inv invoke} ++++++ invoke-1.1.1.tar.gz -> invoke-1.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/MANIFEST.in new/invoke-1.2.0/MANIFEST.in --- old/invoke-1.1.1/MANIFEST.in 2018-06-19 05:52:58.000000000 +0200 +++ new/invoke-1.2.0/MANIFEST.in 2018-09-07 01:50:37.000000000 +0200 @@ -1,6 +1,7 @@ include LICENSE include README.rst include tasks.py +recursive-include invoke/completion * recursive-include sites * recursive-exclude sites/*/_build * include dev-requirements.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/PKG-INFO new/invoke-1.2.0/PKG-INFO --- old/invoke-1.1.1/PKG-INFO 2018-08-01 03:00:41.000000000 +0200 +++ new/invoke-1.2.0/PKG-INFO 2018-09-14 01:31:37.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: invoke -Version: 1.1.1 +Version: 1.2.0 Summary: Pythonic task execution Home-page: http://docs.pyinvoke.org Author: Jeff Forcier @@ -8,7 +8,7 @@ License: BSD Description: To find out what's new in this version of Invoke, please see `the changelog - <http://pyinvoke.org/changelog.html#1.1.1>`_. + <http://pyinvoke.org/changelog.html#1.2.0>`_. Welcome to Invoke! ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/_version.py new/invoke-1.2.0/invoke/_version.py --- old/invoke-1.1.1/invoke/_version.py 2018-08-01 03:00:35.000000000 +0200 +++ new/invoke-1.2.0/invoke/_version.py 2018-09-14 01:31:31.000000000 +0200 @@ -1,2 +1,2 @@ -__version_info__ = (1, 1, 1) +__version_info__ = (1, 2, 0) __version__ = ".".join(map(str, __version_info__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/complete.py new/invoke-1.2.0/invoke/complete.py --- old/invoke-1.1.1/invoke/complete.py 2018-06-26 23:26:37.000000000 +0200 +++ new/invoke-1.2.0/invoke/complete.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,92 +0,0 @@ -""" -Command-line completion mechanisms, executed by the core ``--complete`` flag. -""" - -import re -import shlex - -from .exceptions import Exit, ParseError -from .parser import Parser -from .util import debug, task_name_sort_key - - -def complete(core, initial_context, collection): - # Strip out program name (scripts give us full command line) - invocation = re.sub(r"^(inv|invoke) ", "", core.remainder) - debug("Completing for invocation: {!r}".format(invocation)) - # Tokenize (shlex will have to do) - tokens = shlex.split(invocation) - # Make ourselves a parser (can't just reuse original one as it's mutated / - # been overwritten) - parser = Parser(initial=initial_context, contexts=collection.to_contexts()) - # Handle flags (partial or otherwise) - if tokens and tokens[-1].startswith("-"): - tail = tokens[-1] - debug("Invocation's tail {!r} is flag-like".format(tail)) - # Gently parse invocation to obtain 'current' context. - # Use last seen context in case of failure (required for - # otherwise-invalid partial invocations being completed). - try: - debug("Seeking context name in tokens: {!r}".format(tokens)) - contexts = parser.parse_argv(tokens) - except ParseError as e: - msg = ( - "Got parser error ({!r}), grabbing its last-seen context {!r}" - ) # noqa - debug(msg.format(e, e.context)) - contexts = [e.context] - # Fall back to core context if no context seen. - debug("Parsed invocation, contexts: {!r}".format(contexts)) - if not contexts or not contexts[-1]: - context = initial_context - else: - context = contexts[-1] - debug("Selected context: {!r}".format(context)) - # Unknown flags (could be e.g. only partially typed out; could be - # wholly invalid; doesn't matter) complete with flags. - debug("Looking for {!r} in {!r}".format(tail, context.flags)) - if tail not in context.flags: - debug("Not found, completing with flag names") - # Long flags - partial or just the dashes - complete w/ long flags - if tail.startswith("--"): - for name in filter( - lambda x: x.startswith("--"), context.flag_names() - ): - print(name) - # Just a dash, completes with all flags - elif tail == "-": - for name in context.flag_names(): - print(name) - # Otherwise, it's something entirely invalid (a shortflag not - # recognized, or a java style flag like -foo) so return nothing - # (the shell will still try completing with files, but that doesn't - # hurt really.) - else: - pass - # Known flags complete w/ nothing or tasks, depending - else: - # Flags expecting values: do nothing, to let default (usually - # file) shell completion occur (which we actively want in this - # case.) - if context.flags[tail].takes_value: - debug("Found, and it takes a value, so no completion") - pass - # Not taking values (eg bools): print task names - else: - debug("Found, takes no value, printing task names") - print_task_names(collection) - # If not a flag, is either task name or a flag value, so just complete - # task names. - else: - debug("Last token isn't flag-like, just printing task names") - print_task_names(collection) - raise Exit - - -def print_task_names(collection): - for name in sorted(collection.task_names, key=task_name_sort_key): - print(name) - # Just stick aliases after the thing they're aliased to. Sorting isn't - # so important that it's worth bending over backwards here. - for alias in collection.task_names[name]: - print(alias) Binary files old/invoke-1.1.1/invoke/completion/__pycache__/__init__.cpython-36.pyc and new/invoke-1.2.0/invoke/completion/__pycache__/__init__.cpython-36.pyc differ Binary files old/invoke-1.1.1/invoke/completion/__pycache__/complete.cpython-36.pyc and new/invoke-1.2.0/invoke/completion/__pycache__/complete.cpython-36.pyc differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/completion/bash.completion new/invoke-1.2.0/invoke/completion/bash.completion --- old/invoke-1.1.1/invoke/completion/bash.completion 1970-01-01 01:00:00.000000000 +0100 +++ new/invoke-1.2.0/invoke/completion/bash.completion 2018-09-07 01:50:37.000000000 +0200 @@ -0,0 +1,32 @@ +# Invoke tab-completion script to be sourced with Bash shell. +# Known to work on Bash 3.x, untested on 4.x. + +_complete_{binary}() {{ + local candidates + + # COMP_WORDS contains the entire command string up til now (including + # program name). + # We hand it to Invoke so it can figure out the current context: spit back + # core options, task names, the current task's options, or some combo. + candidates=`{binary} --complete -- ${{COMP_WORDS[*]}}` + + # `compgen -W` takes list of valid options & a partial word & spits back + # possible matches. Necessary for any partial word completions (vs + # completions performed when no partial words are present). + # + # $2 is the current word or token being tabbed on, either empty string or a + # partial word, and thus wants to be compgen'd to arrive at some subset of + # our candidate list which actually matches. + # + # COMPREPLY is the list of valid completions handed back to `complete`. + COMPREPLY=( $(compgen -W "${{candidates}}" -- $2) ) +}} + + +# Tell shell builtin to use the above for completing our invocations. +# * -F: use given function name to generate completions. +# * -o default: when function generates no results, use filenames. +# * positional args: program names to complete for. +complete -F _complete_{binary} -o default {spaced_names} + +# vim: set ft=sh : diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/completion/complete.py new/invoke-1.2.0/invoke/completion/complete.py --- old/invoke-1.1.1/invoke/completion/complete.py 1970-01-01 01:00:00.000000000 +0100 +++ new/invoke-1.2.0/invoke/completion/complete.py 2018-09-07 01:50:37.000000000 +0200 @@ -0,0 +1,121 @@ +""" +Command-line completion mechanisms, executed by the core ``--complete`` flag. +""" + +import glob +import os +import re +import shlex + +from ..exceptions import Exit, ParseError +from ..parser import Parser +from ..util import debug, task_name_sort_key + + +def complete(names, core, initial_context, collection): + # Strip out program name (scripts give us full command line) + # TODO: this may not handle path/to/script though? + invocation = re.sub(r"^({}) ".format("|".join(names)), "", core.remainder) + debug("Completing for invocation: {!r}".format(invocation)) + # Tokenize (shlex will have to do) + tokens = shlex.split(invocation) + # Make ourselves a parser (can't just reuse original one as it's mutated / + # been overwritten) + parser = Parser(initial=initial_context, contexts=collection.to_contexts()) + # Handle flags (partial or otherwise) + if tokens and tokens[-1].startswith("-"): + tail = tokens[-1] + debug("Invocation's tail {!r} is flag-like".format(tail)) + # Gently parse invocation to obtain 'current' context. + # Use last seen context in case of failure (required for + # otherwise-invalid partial invocations being completed). + try: + debug("Seeking context name in tokens: {!r}".format(tokens)) + contexts = parser.parse_argv(tokens) + except ParseError as e: + msg = ( + "Got parser error ({!r}), grabbing its last-seen context {!r}" + ) # noqa + debug(msg.format(e, e.context)) + contexts = [e.context] + # Fall back to core context if no context seen. + debug("Parsed invocation, contexts: {!r}".format(contexts)) + if not contexts or not contexts[-1]: + context = initial_context + else: + context = contexts[-1] + debug("Selected context: {!r}".format(context)) + # Unknown flags (could be e.g. only partially typed out; could be + # wholly invalid; doesn't matter) complete with flags. + debug("Looking for {!r} in {!r}".format(tail, context.flags)) + if tail not in context.flags: + debug("Not found, completing with flag names") + # Long flags - partial or just the dashes - complete w/ long flags + if tail.startswith("--"): + for name in filter( + lambda x: x.startswith("--"), context.flag_names() + ): + print(name) + # Just a dash, completes with all flags + elif tail == "-": + for name in context.flag_names(): + print(name) + # Otherwise, it's something entirely invalid (a shortflag not + # recognized, or a java style flag like -foo) so return nothing + # (the shell will still try completing with files, but that doesn't + # hurt really.) + else: + pass + # Known flags complete w/ nothing or tasks, depending + else: + # Flags expecting values: do nothing, to let default (usually + # file) shell completion occur (which we actively want in this + # case.) + if context.flags[tail].takes_value: + debug("Found, and it takes a value, so no completion") + pass + # Not taking values (eg bools): print task names + else: + debug("Found, takes no value, printing task names") + print_task_names(collection) + # If not a flag, is either task name or a flag value, so just complete + # task names. + else: + debug("Last token isn't flag-like, just printing task names") + print_task_names(collection) + raise Exit + + +def print_task_names(collection): + for name in sorted(collection.task_names, key=task_name_sort_key): + print(name) + # Just stick aliases after the thing they're aliased to. Sorting isn't + # so important that it's worth bending over backwards here. + for alias in collection.task_names[name]: + print(alias) + + +def print_completion_script(shell, names): + # Grab all .completion files in invoke/completion/. (These used to have no + # suffix, but surprise, that's super fragile. + completions = { + os.path.splitext(os.path.basename(x))[0]: x + for x in glob.glob( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), "*.completion" + ) + ) + } + try: + path = completions[shell] + except KeyError: + err = 'Completion for shell "{}" not supported (options are: {}).' + raise ParseError(err.format(shell, ", ".join(sorted(completions)))) + debug("Printing completion script from {}".format(path)) + # Choose one arbitrary program name for script's own internal invocation + # (also used to construct completion function names when necessary) + binary = names[0] + with open(path, "r") as script: + print( + script.read().format(binary=binary, spaced_names=" ".join(names)) + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/completion/fish.completion new/invoke-1.2.0/invoke/completion/fish.completion --- old/invoke-1.1.1/invoke/completion/fish.completion 1970-01-01 01:00:00.000000000 +0100 +++ new/invoke-1.2.0/invoke/completion/fish.completion 2018-09-07 01:50:37.000000000 +0200 @@ -0,0 +1,10 @@ +# Invoke tab-completion script for the fish shell +# Copy it to the ~/.config/fish/completions directory + +function __complete_{binary} + {binary} --complete -- (commandline --tokenize) +end + +# --no-files: Don't complete files unless invoke gives an empty result +# TODO: find a way to honor all binary_names +complete --command {binary} --no-files --arguments '(__complete_{binary})' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/completion/zsh.completion new/invoke-1.2.0/invoke/completion/zsh.completion --- old/invoke-1.1.1/invoke/completion/zsh.completion 1970-01-01 01:00:00.000000000 +0100 +++ new/invoke-1.2.0/invoke/completion/zsh.completion 2018-09-07 01:50:37.000000000 +0200 @@ -0,0 +1,33 @@ +# Invoke tab-completion script to be sourced with the Z shell. +# Known to work on zsh 5.0.x, probably works on later 4.x releases as well (as +# it uses the older compctl completion system). + +_complete_{binary}() {{ + # `words` contains the entire command string up til now (including + # program name). + # + # We hand it to Invoke so it can figure out the current context: spit back + # core options, task names, the current task's options, or some combo. + # + # Before doing so, we attempt to tease out any collection flag+arg so we + # can ensure it is applied correctly. + collection_arg='' + if [[ "${{words}}" =~ "(-c|--collection) [^ ]+" ]]; then + collection_arg=$MATCH + fi + # `reply` is the array of valid completions handed back to `compctl`. + # Use ${{=...}} to force whitespace splitting in expansion of + # $collection_arg + reply=( $({binary} ${{=collection_arg}} --complete -- ${{words}}) ) +}} + + +# Tell shell builtin to use the above for completing our given binary name(s). +# * -K: use given function name to generate completions. +# * +: specifies 'alternative' completion, where options after the '+' are only +# used if the completion from the options before the '+' result in no matches. +# * -f: when function generates no results, use filenames. +# * positional args: program names to complete for. +compctl -K _complete_{binary} + -f {spaced_names} + +# vim: set ft=sh : diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/main.py new/invoke-1.2.0/invoke/main.py --- old/invoke-1.1.1/invoke/main.py 2018-06-26 23:04:40.000000000 +0200 +++ new/invoke-1.2.0/invoke/main.py 2018-09-07 01:50:37.000000000 +0200 @@ -6,4 +6,9 @@ from . import __version__, Program -program = Program(name="Invoke", binary="inv[oke]", version=__version__) +program = Program( + name="Invoke", + binary="inv[oke]", + binary_names=["invoke", "inv"], + version=__version__, +) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/program.py new/invoke-1.2.0/invoke/program.py --- old/invoke-1.1.1/invoke/program.py 2018-08-01 03:00:00.000000000 +0200 +++ new/invoke-1.2.0/invoke/program.py 2018-09-07 01:50:37.000000000 +0200 @@ -10,7 +10,7 @@ from .util import six from . import Collection, Config, Executor, FilesystemLoader -from .complete import complete +from .completion.complete import complete, print_completion_script from .parser import Parser, ParserContext, Argument from .exceptions import UnexpectedExit, CollectionNotFound, ParseError, Exit from .terminals import pty_size @@ -46,6 +46,12 @@ help="Print tab-completion candidates for given parse remainder.", # noqa ), Argument( + names=("print-completion-script",), + kind=str, + default="", + help="Print the tab-completion script for your preferred shell (bash|zsh|fish).", # noqa + ), + Argument( names=("debug", "d"), kind=bool, default=False, @@ -164,6 +170,7 @@ loader_class=None, executor_class=None, config_class=None, + binary_names=None, ): """ Create a new, parameterized `.Program` instance. @@ -193,15 +200,30 @@ binstub installed as ``foobar``, it will default to ``Foobar``. :param str binary: - The binary name as displayed in ``--help`` output. + Descriptive lowercase binary name string used in help text. + + For example, Invoke's own internal value for this is ``inv[oke]``, + denoting that it is installed as both ``inv`` and ``invoke``. As + this is purely text intended for help display, it may be in any + format you wish, though it should match whatever you've put into + your ``setup.py``'s ``console_scripts`` entry. If ``None`` (default), uses the first word in ``argv`` verbatim (as with ``name`` above, except not capitalized). - Giving this explicitly may be useful when you install your program - under multiple names, such as Invoke itself does - it installs as - both ``inv`` and ``invoke``, and sets ``binary="inv[oke]"`` so its - ``--help`` output implies both names. + :param list binary_names: + List of binary name strings, for use in completion scripts. + + This list ensures that the shell completion scripts generated by + :option:`--print-completion-script` instruct the shell to use + that completion for all of this program's installed names. + + For example, Invoke's internal default for this is ``["inv", + "invoke"]``. + + If ``None`` (the default), the first word in ``argv`` (in the + invocation of :option:`--print-completion-script`) is used in a + single-item list. :param loader_class: The `.Loader` subclass to use when loading task collections. @@ -217,11 +239,17 @@ The `.Config` subclass to use for the base config object. Defaults to `.Config`. + + .. versionchanged:: 1.2 + Added the ``binary_names`` argument. """ self.version = "unknown" if version is None else version self.namespace = namespace self._name = name + # TODO 2.0: rename binary to binary_help_name or similar. (Or write + # code to autogenerate it from binary_names.) self._binary = binary + self._binary_names = binary_names self.argv = None self.loader_class = loader_class or FilesystemLoader self.executor_class = executor_class or Executor @@ -374,12 +402,20 @@ if self.args.debug.value: enable_logging() - # Print version & exit if necessary + # Short-circuit if --version if self.args.version.value: debug("Saw --version, printing version & exiting") self.print_version() raise Exit + # Print (dynamic, no tasks required) completion script if requested + if self.args["print-completion-script"].value: + print_completion_script( + shell=self.args["print-completion-script"].value, + names=self.binary_names, + ) + raise Exit + def parse_collection(self): """ Load a tasks collection & project-level config. @@ -461,7 +497,12 @@ # Print completion helpers if necessary if self.args.complete.value: - complete(self.core, self.initial_context, self.collection) + complete( + names=self.binary_names, + core=self.core, + initial_context=self.initial_context, + collection=self.collection, + ) # Fallback behavior if no tasks were given & no default specified # (mostly a subroutine for overriding purposes) @@ -523,13 +564,34 @@ return self._name or self.binary.capitalize() @property + def called_as(self): + """ + Returns the program name we were actually called as. + + Specifically, this is the (Python's os module's concept of a) basename + of the first argument in the parsed argument vector. + + .. versionadded:: 1.2 + """ + return os.path.basename(self.argv[0]) + + @property def binary(self): """ Derive program's help-oriented binary name(s) from init args & argv. .. versionadded:: 1.0 """ - return self._binary or os.path.basename(self.argv[0]) + return self._binary or self.called_as + + @property + def binary_names(self): + """ + Derive program's completion-oriented binary name(s) from args & argv. + + .. versionadded:: 1.2 + """ + return self._binary_names or [self.called_as] @property def args(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke/runners.py new/invoke-1.2.0/invoke/runners.py --- old/invoke-1.1.1/invoke/runners.py 2018-07-31 01:34:06.000000000 +0200 +++ new/invoke-1.2.0/invoke/runners.py 2018-09-07 01:50:37.000000000 +0200 @@ -170,7 +170,7 @@ ``hide=True`` will override ``echo=True`` if both are given. :param dict env: - By default, subprocesses recieve a copy of Invoke's own environment + By default, subprocesses receive a copy of Invoke's own environment (i.e. ``os.environ``). Supply a dict here to update that child environment. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke.egg-info/PKG-INFO new/invoke-1.2.0/invoke.egg-info/PKG-INFO --- old/invoke-1.1.1/invoke.egg-info/PKG-INFO 2018-08-01 03:00:41.000000000 +0200 +++ new/invoke-1.2.0/invoke.egg-info/PKG-INFO 2018-09-14 01:31:37.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: invoke -Version: 1.1.1 +Version: 1.2.0 Summary: Pythonic task execution Home-page: http://docs.pyinvoke.org Author: Jeff Forcier @@ -8,7 +8,7 @@ License: BSD Description: To find out what's new in this version of Invoke, please see `the changelog - <http://pyinvoke.org/changelog.html#1.1.1>`_. + <http://pyinvoke.org/changelog.html#1.2.0>`_. Welcome to Invoke! ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/invoke.egg-info/SOURCES.txt new/invoke-1.2.0/invoke.egg-info/SOURCES.txt --- old/invoke-1.1.1/invoke.egg-info/SOURCES.txt 2018-08-01 03:00:41.000000000 +0200 +++ new/invoke-1.2.0/invoke.egg-info/SOURCES.txt 2018-09-14 01:31:37.000000000 +0200 @@ -10,7 +10,6 @@ invoke/__main__.py invoke/_version.py invoke/collection.py -invoke/complete.py invoke/config.py invoke/context.py invoke/env.py @@ -30,6 +29,13 @@ invoke.egg-info/entry_points.txt invoke.egg-info/pbr.json invoke.egg-info/top_level.txt +invoke/completion/__init__.py +invoke/completion/bash.completion +invoke/completion/complete.py +invoke/completion/fish.completion +invoke/completion/zsh.completion +invoke/completion/__pycache__/__init__.cpython-36.pyc +invoke/completion/__pycache__/complete.cpython-36.pyc invoke/parser/__init__.py invoke/parser/argument.py invoke/parser/context.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/setup.py new/invoke-1.2.0/setup.py --- old/invoke-1.1.1/setup.py 2018-07-26 21:04:29.000000000 +0200 +++ new/invoke-1.2.0/setup.py 2018-09-07 01:50:37.000000000 +0200 @@ -38,6 +38,7 @@ author_email="j...@bitprophet.org", url="http://docs.pyinvoke.org", packages=find_packages(exclude=exclude), + include_package_data=True, entry_points={ "console_scripts": [ "invoke = invoke.main:program.run", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/sites/docs/concepts/library.rst new/invoke-1.2.0/sites/docs/concepts/library.rst --- old/invoke-1.1.1/sites/docs/concepts/library.rst 2018-06-19 05:52:58.000000000 +0200 +++ new/invoke-1.2.0/sites/docs/concepts/library.rst 2018-09-07 01:50:37.000000000 +0200 @@ -78,7 +78,7 @@ Usage: tester [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts] Core options: - ... core Invoke options here ... + ... core Invoke options here ... $ tester --list Can't find any collection named 'tasks'! @@ -148,6 +148,10 @@ 'tasks'); the list of specific subcommands is now printed as part of ``--help``; and ``--list`` has been removed from the options. +You can enable :ref:`tab-completion<tab-completion>` for your distinct +binary and subcommands. + + Modifying core parser arguments ------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/sites/docs/invoke.rst new/invoke-1.2.0/sites/docs/invoke.rst --- old/invoke-1.1.1/sites/docs/invoke.rst 2018-08-01 03:00:00.000000000 +0200 +++ new/invoke-1.2.0/sites/docs/invoke.rst 2018-09-07 01:50:37.000000000 +0200 @@ -1,12 +1,13 @@ .. _inv: ======================== -``inv[oke]`` core usage +``inv[oke]`` core usage ======================== .. seealso:: - This page documents ``invoke``'s core arguments, options and behavior. For - details on invoking user-specified tasks, see + This page documents ``invoke``'s core arguments, options and behavior + (which includes options present in :ref:`custom Invoke-based binaries + <reusing-as-a-binary>`). For details on invoking user-specified tasks, see :doc:`/concepts/invoking-tasks`. @@ -46,8 +47,19 @@ --foo-arg --foo-arg-2 - For detailed examples of how to use this option, see the bundled completion - scripts stored in ``completion/`` in the source distribution. + For more details on how to make best use of this option, see + :option:`--print-completion-script`. + +.. _print-completion-script: + +.. option:: --print-completion-script=SHELL + + Print a completion script for desired ``SHELL`` (e.g. ``bash``, ``zsh``, + etc). This can be sourced into the current session in order to enjoy + :ref:`tab-completion for tasks and options <tab-completion>`. + + These scripts are bundled with Invoke's distributed codebase, and + internally make use of :option:`--complete`. .. option:: --hide=STRING @@ -179,33 +191,115 @@ Shell tab completion ==================== +Generating a completion script +------------------------------ + Invoke's philosophy is to implement generic APIs and then "bake in" a few -common use cases built on top of those APIs, and tab completion is no -different. Generic tab completion functionality is provided by the -:option:`--complete` core CLI option described above, and we distribute a -handful of ready-made wrapper scripts aimed at the most common shells such as -``bash`` and ``zsh`` (plus others). To use one of these scripts: - -* Obtain the source distribution, or visit the ``/completion/`` folder `on Github - <https://github.com/pyinvoke/invoke/blob/master/completion/>`_, and place a - copy of the appropriate file (e.g. ``/completion/bash`` for Bash users) - somewhere on your local system. -* ``source`` the file in your shell login file (e.g. ``.bash_profile``, - ``.zshrc``). -* By default, tabbing after typing ``inv`` or ``invoke`` will display task +common use cases built on top of those APIs; tab completion is no different. +Generic tab completion functionality (outputting a shell-compatible list of +completion tokens for a given command line context) is provided by the +:option:`--complete` core CLI option described above. + +However, you probably won't need to use that flag yourself: we distribute a +handful of ready-made wrapper scripts aimed at the most common shells like +``bash`` and ``zsh`` (plus others). These scripts can be automatically +generated from Invoke or :ref:`any Invoke-driven command-line tool +<reusing-as-a-binary>`, using :option:`--print-completion-script`; the printed +scripts will contain the correct binary name(s) for the program generating +them. + +For example, the following command prints (to stdout) a script which works for +``zsh``, instructs ``zsh`` to use it for the ``inv`` and ``invoke`` programs, +and calls ``invoke --complete`` at runtime to get dynamic completion +information:: + + $ invoke --print-completion-script zsh + +.. note:: + You'll probably want to source this command or store its output somewhere + permanently; more on that in the next section. + +Similarly, the `Fabric <http://fabfile.org>`_ tool inherits from Invoke, and +only has a single binary name (``fab``); if you wanted to get Fabric completion +in ``bash``, you would say:: + + $ fab --print-completion-script bash + +In the rest of this section, we'll use ``inv`` in examples, but please remember +to replace it with the program you're actually using, if it's not Invoke +itself! + +Sourcing the script +------------------- + +There are a few ways to utilize the output of the above commands, depending on +your needs, where the program is installed, and your shell: + +- The simplest and least disruptive method is to ``source`` the printed + completion script inline, which doesn't place anything on disk, and will only + affect the current shell session:: + + $ source <(inv --print-completion-script zsh) + +- If you've got the program available in your system's global Python + interpreter (and you're okay with running the program at the startup of each + shell session - Python's speed is admittedly not its strong point) you could + add that snippet to your shell's startup file, such as ``~/.zshrc`` or + ``~/.bashrc``. +- If the program's available globally but you'd prefer to *avoid* running an + extra Python program at shell startup, you can cache the output of the + command in its own file; where this file lives is entirely up to you and how + your shell is configured. For example, you might just drop it into your home + directory as a hidden file:: + + $ inv --print-completion-script zsh > ~/.invoke-completion.sh + + and then perhaps add the following to the end of ``~/.zshrc``:: + + source ~/.invoke-completion.sh + + But again, this is entirely up to you and your shell. + + .. note:: + If you're using ``fish``, you *must* use this tactic, as our fish + completion script is not suitable for direct sourcing. Fish shell users + should direct the output of the command to a file in the + ``~/.config/fish/completions/`` directory. + +- Finally, if your copy of the needing-completion program is only installed in + a specific environment like a virtualenv, you can use either of the above + techniques: + + - Caching the output and referencing it in a global shell startup file will + still work in this case, as it does not require the program to be + available when the shell loads -- only when you actually attempt to tab + complete. + - Using the ``source <(inv --print-completion-script yourshell)`` approach + will work *as long as* you place it in some appropriate per-environment + startup file, which will vary depending on how you manage Python + environments. For example, if you use ``virtualenvwrapper``, you could + append the ``source`` line in ``/path/to/virtualenv/bin/postactivate``. + +Utilizing tab completion itself +------------------------------- + +You've ensured that the completion script is active in your environment - what +have you gained? + +- By default, tabbing after typing ``inv`` or ``invoke`` will display task names from your current directory/project's tasks file. -* Tabbing after typing a dash (``-``) or double dash (``--``) will display +- Tabbing after typing a dash (``-``) or double dash (``--``) will display valid options/flags for the current context: core Invoke options if no task names have been typed yet; options for the most recently typed task otherwise. - * Tabbing while typing a partial long option will complete matching long + - Tabbing while typing a partial long option will complete matching long options, using your shell's native substring completion. E.g. if no task names have been typed yet, ``--e<tab>`` will offer ``--echo`` as a completion option. -* Hitting tab when the most recent typed/completed token is a flag which takes +- Hitting tab when the most recent typed/completed token is a flag which takes a value, will 'fall through' to your shell's native filename completion. - * For example, prior to typing a task name, ``--config <tab>`` will + - For example, prior to typing a task name, ``--config <tab>`` will complete local file paths to assist in filling in a config file. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/sites/www/changelog.rst new/invoke-1.2.0/sites/www/changelog.rst --- old/invoke-1.1.1/sites/www/changelog.rst 2018-08-01 03:00:33.000000000 +0200 +++ new/invoke-1.2.0/sites/www/changelog.rst 2018-09-14 01:31:25.000000000 +0200 @@ -2,6 +2,12 @@ Changelog ========= +- :release:`1.2.0 <2018-09-13>` +- :feature:`301` (via :issue:`414`) Overhaul tab completion mechanisms so users + can :ref:`print a completion script <print-completion-script>` which + automatically matches the emitting binary's configured names (compared to the + previous hardcoded scripts, which only worked for ``inv``/``invoke`` by + default). Thanks to Nicolas Höning for the foundational patchset. - :release:`1.1.1 <2018-07-31>` - :release:`1.0.2 <2018-07-31>` - :bug:`556` (also `fabric/fabric#1823 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/sites/www/conf.py new/invoke-1.2.0/sites/www/conf.py --- old/invoke-1.1.1/sites/www/conf.py 2018-06-26 23:04:40.000000000 +0200 +++ new/invoke-1.2.0/sites/www/conf.py 2018-09-07 01:50:37.000000000 +0200 @@ -17,7 +17,7 @@ target = "http://docs.pyinvoke.org/en/latest/" intersphinx_mapping["docs"] = (target, None) -# Sister-site links to API docs +# Sister-site links to documentation html_theme_options["extra_nav_links"] = { - "API Docs": "http://docs.pyinvoke.org" + "Documentation": "http://docs.pyinvoke.org" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/sites/www/contact.rst new/invoke-1.2.0/sites/www/contact.rst --- old/invoke-1.1.1/sites/www/contact.rst 2018-08-01 02:35:07.000000000 +0200 +++ new/invoke-1.2.0/sites/www/contact.rst 2018-09-07 01:50:37.000000000 +0200 @@ -8,7 +8,20 @@ <http://contribution-guide.org>`_, then check out our `GitHub page <https://github.com/pyinvoke/invoke>`_. * IRC: ``#invoke`` on Freenode -* Twitter: `@pyinvoke <https://twitter.com/pyinvoke>`_ (unfortunately not - updated as frequently as we'd like - you might also want to follow - `@bitprophet <https://twitter.com/bitprophet>`_. +* Twitter: you've got a few options here: + + * `@bitprophet <https://twitter.com/bitprophet>`_ is the canonical source + for updates, but is also the developer's personal account (hint: you can + turn off retweets and only see original content!) + * `@pyfabric <https://twitter.com/pyfabric>`_ is a much lower-traffic, + announcement-only account that also serves the `Fabric + <http://fabfile.org>`_ project; given how much Fabric is built directly + on top of Invoke, many of the posts will be relevant to Invoke-only + users. + * `@pyinvoke <https://twitter.com/pyinvoke>`_ was set up for + Invoke-specific announcements, but it only has a dozen followers so we've + unfortunately let it languish. Should we automate our release process + further, this account may get posts again, and we'll update this page + accordingly. + * Blog: TK diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/tests/_util.py new/invoke-1.2.0/tests/_util.py --- old/invoke-1.1.1/tests/_util.py 2018-06-26 23:04:40.000000000 +0200 +++ new/invoke-1.2.0/tests/_util.py 2018-09-07 01:50:37.000000000 +0200 @@ -19,6 +19,7 @@ support = os.path.join(os.path.dirname(__file__), "_support") +ROOT = os.path.abspath(os.path.sep) def skip_if_windows(fn): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/tests/completion.py new/invoke-1.2.0/tests/completion.py --- old/invoke-1.1.1/tests/completion.py 2018-06-26 23:04:40.000000000 +0200 +++ new/invoke-1.2.0/tests/completion.py 2018-09-07 01:50:37.000000000 +0200 @@ -1,25 +1,100 @@ +import os import sys from invoke import Program import pytest -from _util import expect, trap +from _util import expect, trap, ROOT pytestmark = pytest.mark.usefixtures("integration") @trap -def _complete(invocation, collection=None): +def _complete(invocation, collection=None, **kwargs): colstr = "" if collection: colstr = "-c {}".format(collection) command = "inv --complete {0} -- inv {0} {1}".format(colstr, invocation) - Program().run(command, exit=False) + Program(**kwargs).run(command, exit=False) return sys.stdout.getvalue() +# TODO: remove in favor of direct asserts, needs non shite way of getting at +# stderr instead of just stdout. +def _assert_contains(haystack, needle): + assert needle in haystack + + +class CompletionScriptPrinter: + """ + Printing the completion script + """ + + def setup(self): + self.prev_cwd = os.getcwd() + # Chdir to system root to (hopefully) avoid any tasks.py. This will + # prove that --print-completion-script works w/o nearby tasks. + os.chdir(ROOT) + + def teardown(self): + os.chdir(self.prev_cwd) + + def only_accepts_certain_shells(self): + expect( + "--print-completion-script", + err="needed value and was not given one", + test=_assert_contains, + ) + expect( + "--print-completion-script bla", + # NOTE: this needs updating when the real world changes, just like + # eg our --help output tests. That's OK & better than just + # reimplementing the code under test here. + err='Completion for shell "bla" not supported (options are: bash, fish, zsh).', # noqa + test=_assert_contains, + ) + + def prints_for_custom_binary_names(self): + out, err = expect( + "myapp --print-completion-script zsh", + program=Program(binary_names=["mya", "myapp"]), + invoke=False, + ) + # Combines some sentinels from vanilla test, with checks that it's + # really replacing 'invoke' with desired binary names + assert "_complete_mya() {" in out + assert "invoke" not in out + assert " mya myapp" in out + + def default_binary_names_is_completing_argv_0(self): + out, err = expect( + "someappname --print-completion-script zsh", + program=Program(binary_names=None), + invoke=False, + ) + assert "_complete_someappname() {" in out + assert " someappname" in out + + def bash_works(self): + out, err = expect( + "someappname --print-completion-script bash", invoke=False + ) + assert "_complete_someappname() {" in out + assert "complete -F" in out + for line in out.splitlines(): + if line.startswith("complete -F"): + assert line.endswith(" someappname") + + def fish_works(self): + out, err = expect( + "someappname --print-completion-script fish", invoke=False + ) + assert "function __complete_someappname" in out + assert "complete --command someappname" in out + + class ShellCompletion: """ Shell tab-completion behavior @@ -28,6 +103,25 @@ def no_input_means_just_task_names(self): expect("-c simple_ns_list --complete", out="z-toplevel\na.b.subtask\n") + def custom_binary_name_completes(self): + expect( + "myapp -c integration --complete -- ba", + program=Program(binary="myapp"), + invoke=False, + out="bar", + test=_assert_contains, + ) + + def aliased_custom_binary_name_completes(self): + for used_binary in ("my", "myapp"): + expect( + "{0} -c integration --complete -- ba".format(used_binary), + program=Program(binary="my[app]"), + invoke=False, + out="bar", + test=_assert_contains, + ) + def no_input_with_no_tasks_yields_empty_response(self): expect("-c empty --complete", out="") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/invoke-1.1.1/tests/program.py new/invoke-1.2.0/tests/program.py --- old/invoke-1.1.1/tests/program.py 2018-08-01 03:00:00.000000000 +0200 +++ new/invoke-1.2.0/tests/program.py 2018-09-07 01:50:37.000000000 +0200 @@ -22,6 +22,7 @@ from invoke.util import cd from _util import ( + ROOT, expect, load, run, @@ -31,9 +32,6 @@ ) -ROOT = os.path.abspath(os.path.sep) - - pytestmark = pytest.mark.usefixtures("integration") @@ -158,6 +156,38 @@ assert "myapp [--core-opts]" in stdout assert "/usr/local/bin" not in stdout + class called_as: + # NOTE: these tests are meh due to Program's lifecycle design + # (attributes get modified during run(), such as things based on + # observed argv). It's not great, but, whatever. + @trap + def is_the_whole_deal_when_just_a_name(self): + p = Program() + p.run("whatever --help", exit=False) + assert p.called_as == "whatever" + + @trap + def is_basename_when_given_a_path(self): + p = Program() + p.run("/usr/local/bin/whatever --help", exit=False) + assert p.called_as == "whatever" + + class binary_names: + # NOTE: this is currently only used for completion stuff, so we use + # that to test. TODO: maybe make this more unit-y... + def defaults_to_argv_when_None(self): + stdout, _ = run("foo --print-completion-script zsh", invoke=False) + assert " foo" in stdout + + def can_be_given_directly(self): + program = Program(binary_names=["foo", "bar"]) + stdout, _ = run( + "foo --print-completion-script zsh", + invoke=False, + program=program, + ) + assert " foo bar" in stdout + class print_version: def displays_name_and_version(self): expect( @@ -425,31 +455,33 @@ Core options: - --complete Print tab-completion candidates for given - parse remainder. - --hide=STRING Set default value of run()'s 'hide' kwarg. - --no-dedupe Disable task deduplication. - --prompt-for-sudo-password Prompt user at start of session for the - sudo.password config value. - --write-pyc Enable creation of .pyc files. - -c STRING, --collection=STRING Specify collection name to load. - -d, --debug Enable debug output. - -D INT, --list-depth=INT When listing tasks, only show the first INT - levels. - -e, --echo Echo executed commands before running. - -f STRING, --config=STRING Runtime configuration file to use. - -F STRING, --list-format=STRING Change the display format used when listing - tasks. Should be one of: flat (default), - nested, json. - -h [STRING], --help[=STRING] Show core or per-task help and exit. - -l [STRING], --list[=STRING] List available tasks, optionally limited to - a namespace. - -p, --pty Use a pty when executing shell commands. - -r STRING, --search-root=STRING Change root directory used for finding task - modules. - -V, --version Show version and exit. - -w, --warn-only Warn, instead of failing, when shell - commands fail. + --complete Print tab-completion candidates for given + parse remainder. + --hide=STRING Set default value of run()'s 'hide' kwarg. + --no-dedupe Disable task deduplication. + --print-completion-script=STRING Print the tab-completion script for your + preferred shell (bash|zsh|fish). + --prompt-for-sudo-password Prompt user at start of session for the + sudo.password config value. + --write-pyc Enable creation of .pyc files. + -c STRING, --collection=STRING Specify collection name to load. + -d, --debug Enable debug output. + -D INT, --list-depth=INT When listing tasks, only show the first + INT levels. + -e, --echo Echo executed commands before running. + -f STRING, --config=STRING Runtime configuration file to use. + -F STRING, --list-format=STRING Change the display format used when + listing tasks. Should be one of: flat + (default), nested, json. + -h [STRING], --help[=STRING] Show core or per-task help and exit. + -l [STRING], --list[=STRING] List available tasks, optionally limited + to a namespace. + -p, --pty Use a pty when executing shell commands. + -r STRING, --search-root=STRING Change root directory used for finding + task modules. + -V, --version Show version and exit. + -w, --warn-only Warn, instead of failing, when shell + commands fail. """.lstrip() for flag in ["-h", "--help"]: