diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b50dcdb9ef..7c1d3fbefb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ * [ ] ❗ I have followed the [Contributing to DVC](https://dvc.org/doc/user-guide/contributing/core) checklist. -* [ ] 📖 If this PR requires [documentation](https://dvc.org/doc) updates, I have created a separate PR (or issue, at least) in [dvc.org](https://github.com/iterative/dvc.org) and linked it here. If the CLI API is changed, I have updated [tab completion scripts](https://github.com/iterative/dvc/tree/master/scripts/completion). +* [ ] 📖 If this PR requires [documentation](https://dvc.org/doc) updates, I have created a separate PR (or issue, at least) in [dvc.org](https://github.com/iterative/dvc.org) and linked it here. * [ ] ❌ I will check DeepSource, CodeClimate, and other sanity checks below. (We consider them recommendatory and don't expect everything to be addressed. Please fix things that actually improve code or fix bugs.) diff --git a/dvc/cli.py b/dvc/cli.py index 2de78ff600..9f3e39807a 100644 --- a/dvc/cli.py +++ b/dvc/cli.py @@ -8,6 +8,7 @@ cache, checkout, commit, + completion, config, daemon, dag, @@ -70,6 +71,7 @@ dag, daemon, commit, + completion, diff, version, update, @@ -140,15 +142,7 @@ def get_parent_parser(): return parent_parser -def parse_args(argv=None): - """Parses CLI arguments. - - Args: - argv: optional list of arguments to parse. sys.argv is used by default. - - Raises: - dvc.exceptions.DvcParserError: raised for argument parsing errors. - """ +def get_main_parser(): parent_parser = get_parent_parser() # Main parser @@ -196,6 +190,18 @@ def parse_args(argv=None): for cmd in COMMANDS: cmd.add_parser(subparsers, parent_parser) - args = parser.parse_args(argv) + return parser + +def parse_args(argv=None): + """Parses CLI arguments. + + Args: + argv: optional list of arguments to parse. sys.argv is used by default. + + Raises: + dvc.exceptions.DvcParserError: raised for argument parsing errors. + """ + parser = get_main_parser() + args = parser.parse_args(argv) return args diff --git a/dvc/command/add.py b/dvc/command/add.py index cb5199c23a..0131c982a7 100644 --- a/dvc/command/add.py +++ b/dvc/command/add.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException, RecursiveAddingWhileUsingFilename @@ -62,6 +63,9 @@ def add_parser(subparsers, parent_parser): metavar="", ) parser.add_argument( - "targets", nargs="+", help="Input files/directories to add." + "targets", + nargs="+", + help="Input files/directories to add.", + choices=completion.Required.FILE, ) parser.set_defaults(func=CmdAdd) diff --git a/dvc/command/cache.py b/dvc/command/cache.py index f87667b11d..67a1215944 100644 --- a/dvc/command/cache.py +++ b/dvc/command/cache.py @@ -1,5 +1,6 @@ import argparse +from dvc.command import completion from dvc.command.base import append_doc_link, fix_subparsers from dvc.command.config import CmdConfig @@ -55,5 +56,6 @@ def add_parser(subparsers, parent_parser): help="Path to cache directory. Relative paths are resolved relative " "to the current directory and saved to config relative to the " "config file location.", + choices=completion.Required.DIR, ) cache_dir_parser.set_defaults(func=CmdCacheDir) diff --git a/dvc/command/checkout.py b/dvc/command/checkout.py index 104eb859b3..13af05fe8a 100644 --- a/dvc/command/checkout.py +++ b/dvc/command/checkout.py @@ -4,6 +4,7 @@ import colorama +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import CheckoutError from dvc.utils.humanize import get_summary @@ -113,5 +114,6 @@ def add_parser(subparsers, parent_parser): nargs="*", help="DVC-files to checkout. Optional. " "(Finds all DVC-files in the workspace by default.)", + choices=completion.Optional.DVC_FILE, ) checkout_parser.set_defaults(func=CmdCheckout) diff --git a/dvc/command/commit.py b/dvc/command/commit.py index dc4a9e0d00..f20da2b1a2 100644 --- a/dvc/command/commit.py +++ b/dvc/command/commit.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -66,5 +67,6 @@ def add_parser(subparsers, parent_parser): nargs="*", help="DVC-files to commit. Optional. " "(Finds all DVC-files in the workspace by default.)", + choices=completion.Optional.DVC_FILE, ) commit_parser.set_defaults(func=CmdCommit) diff --git a/dvc/command/completion.py b/dvc/command/completion.py new file mode 100644 index 0000000000..6cef7c526a --- /dev/null +++ b/dvc/command/completion.py @@ -0,0 +1,68 @@ +import argparse +import logging + +import shtab + +from dvc.command.base import CmdBaseNoRepo, append_doc_link + +logger = logging.getLogger(__name__) +CHOICE_FUNCTIONS = { + "bash": {"DVCFile": "_dvc_compgen_DVCFiles"}, + "zsh": {"DVCFile": "_files -g '(*?.dvc|Dvcfile|dvc.yaml)'"}, +} +PREAMBLE = { + "bash": """ +# $1=COMP_WORDS[1] +_dvc_compgen_DVCFiles() { + compgen -d -S '/' -- $1 # recurse into subdirs + compgen -f -X '!*?.dvc' -- $1 + compgen -f -X '!*Dvcfile' -- $1 + compgen -f -X '!*dvc.yaml' -- $1 +} +""", + "zsh": "", +} + + +class Optional(shtab.Optional): + DVC_FILE = [shtab.Choice("DVCFile", required=False)] + + +class Required(shtab.Required): + DVC_FILE = [shtab.Choice("DVCFile", required=True)] + + +class CmdCompletion(CmdBaseNoRepo): + def run(self): + from dvc.cli import get_main_parser + + parser = get_main_parser() + shell = self.args.shell + script = shtab.complete( + parser, + shell=shell, + preamble=PREAMBLE[shell], + choice_functions=CHOICE_FUNCTIONS[shell], + ) + print(script) + return 0 + + +def add_parser(subparsers, parent_parser): + COMPLETION_HELP = "Generate shell tab completion." + COMPLETION_DESCRIPTION = "Prints out shell tab completion scripts." + completion_parser = subparsers.add_parser( + "completion", + parents=[parent_parser], + description=append_doc_link(COMPLETION_DESCRIPTION, "completion"), + help=COMPLETION_HELP, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + completion_parser.add_argument( + "-s", + "--shell", + help="Shell syntax for completions.", + default="bash", + choices=["bash", "zsh"], + ) + completion_parser.set_defaults(func=CmdCompletion) diff --git a/dvc/command/daemon.py b/dvc/command/daemon.py index ef5d10483b..15ed9d6210 100644 --- a/dvc/command/daemon.py +++ b/dvc/command/daemon.py @@ -1,3 +1,4 @@ +from dvc.command import completion from dvc.command.base import CmdBaseNoRepo, fix_subparsers @@ -64,5 +65,7 @@ def add_parser(subparsers, parent_parser): description=DAEMON_ANALYTICS_HELP, help=DAEMON_ANALYTICS_HELP, ) - daemon_analytics_parser.add_argument("target", help="Analytics file.") + daemon_analytics_parser.add_argument( + "target", help="Analytics file.", choices=completion.Required.FILE + ) daemon_analytics_parser.set_defaults(func=CmdDaemonAnalytics) diff --git a/dvc/command/data_sync.py b/dvc/command/data_sync.py index bf384befa3..ce883d30ab 100644 --- a/dvc/command/data_sync.py +++ b/dvc/command/data_sync.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.command.checkout import log_changes from dvc.exceptions import CheckoutError, DvcException @@ -104,6 +105,7 @@ def shared_parent_parser(): nargs="*", help="Limit command scope to these DVC-files. " "Using -R, directories to search DVC-files in can also be given.", + choices=completion.Optional.DVC_FILE, ) return parent_parser diff --git a/dvc/command/freeze.py b/dvc/command/freeze.py index ea1f3feec4..7505f11974 100644 --- a/dvc/command/freeze.py +++ b/dvc/command/freeze.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -39,7 +40,10 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) freeze_parser.add_argument( - "targets", nargs="+", help="DVC-files to freeze." + "targets", + nargs="+", + help="DVC-files to freeze.", + choices=completion.Required.DVC_FILE, ) freeze_parser.set_defaults(func=CmdFreeze) @@ -52,6 +56,9 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) unfreeze_parser.add_argument( - "targets", nargs="+", help="DVC-files to unfreeze." + "targets", + nargs="+", + help="DVC-files to unfreeze.", + choices=completion.Required.DVC_FILE, ) unfreeze_parser.set_defaults(func=CmdUnfreeze) diff --git a/dvc/command/get.py b/dvc/command/get.py index e31e76c3b6..44170ea016 100644 --- a/dvc/command/get.py +++ b/dvc/command/get.py @@ -3,6 +3,7 @@ from dvc.exceptions import DvcException +from . import completion from .base import CmdBaseNoRepo, append_doc_link logger = logging.getLogger(__name__) @@ -62,7 +63,9 @@ def add_parser(subparsers, parent_parser): "url", help="Location of DVC or Git repository to download from" ) get_parser.add_argument( - "path", help="Path to a file or directory within the repository" + "path", + help="Path to a file or directory within the repository", + choices=completion.Required.FILE, ) get_parser.add_argument( "-o", @@ -70,6 +73,7 @@ def add_parser(subparsers, parent_parser): nargs="?", help="Destination path to download files to", metavar="", + choices=completion.Optional.DIR, ) get_parser.add_argument( "--rev", diff --git a/dvc/command/get_url.py b/dvc/command/get_url.py index 3a31e19877..b40fac26ec 100644 --- a/dvc/command/get_url.py +++ b/dvc/command/get_url.py @@ -3,6 +3,7 @@ from dvc.exceptions import DvcException +from . import completion from .base import CmdBaseNoRepo, append_doc_link logger = logging.getLogger(__name__) @@ -33,6 +34,9 @@ def add_parser(subparsers, parent_parser): "url", help="See `dvc import-url -h` for full list of supported URLs." ) get_parser.add_argument( - "out", nargs="?", help="Destination path to put data to." + "out", + nargs="?", + help="Destination path to put data to.", + choices=completion.Optional.DIR, ) get_parser.set_defaults(func=CmdGetUrl) diff --git a/dvc/command/imp.py b/dvc/command/imp.py index 692b2468b5..3e2f3dccaa 100644 --- a/dvc/command/imp.py +++ b/dvc/command/imp.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -43,7 +44,9 @@ def add_parser(subparsers, parent_parser): "url", help="Location of DVC or Git repository to download from" ) import_parser.add_argument( - "path", help="Path to a file or directory within the repository" + "path", + help="Path to a file or directory within the repository", + choices=completion.Required.FILE, ) import_parser.add_argument( "-o", @@ -51,6 +54,7 @@ def add_parser(subparsers, parent_parser): nargs="?", help="Destination path to download files to", metavar="", + choices=completion.Optional.DIR, ) import_parser.add_argument( "--rev", diff --git a/dvc/command/imp_url.py b/dvc/command/imp_url.py index d547fa0351..18591891b0 100644 --- a/dvc/command/imp_url.py +++ b/dvc/command/imp_url.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -54,11 +55,15 @@ def add_parser(subparsers, parent_parser): "remote://myremote/path/to/file (see `dvc remote`)", ) import_parser.add_argument( - "out", nargs="?", help="Destination path to put files to." + "out", + nargs="?", + help="Destination path to put files to.", + choices=completion.Optional.DIR, ) import_parser.add_argument( "--file", help="Specify name of the DVC-file this command will generate.", metavar="", + choices=completion.Optional.DIR, ) import_parser.set_defaults(func=CmdImportUrl) diff --git a/dvc/command/ls/__init__.py b/dvc/command/ls/__init__.py index 6bcc88c2b6..ea18ed3548 100644 --- a/dvc/command/ls/__init__.py +++ b/dvc/command/ls/__init__.py @@ -2,6 +2,7 @@ import logging import sys +from dvc.command import completion from dvc.command.base import CmdBaseNoRepo, append_doc_link from dvc.command.ls.ls_colors import LsColors from dvc.exceptions import DvcException @@ -74,5 +75,6 @@ def add_parser(subparsers, parent_parser): "path", nargs="?", help="Path to directory within the repository to list outputs for", + choices=completion.Optional.DIR, ) list_parser.set_defaults(func=CmdList) diff --git a/dvc/command/metrics.py b/dvc/command/metrics.py index 8bca98a5ee..8e1e3ce43c 100644 --- a/dvc/command/metrics.py +++ b/dvc/command/metrics.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link, fix_subparsers from dvc.exceptions import BadMetricError, DvcException @@ -169,6 +170,7 @@ def add_parser(subparsers, parent_parser): "Limit command scope to these metric files. Using -R, " "directories to search metric files in can also be given." ), + choices=completion.Optional.FILE, ) metrics_show_parser.add_argument( "-a", @@ -235,6 +237,7 @@ def add_parser(subparsers, parent_parser): "directories to search metric files in can also be given." ), metavar="", + choices=completion.Optional.FILE, ) metrics_diff_parser.add_argument( "-R", diff --git a/dvc/command/move.py b/dvc/command/move.py index 0890baade5..e0565754c8 100644 --- a/dvc/command/move.py +++ b/dvc/command/move.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -36,7 +37,11 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) move_parser.add_argument( - "src", help="Source path to a data file or directory." + "src", + help="Source path to a data file or directory.", + choices=completion.Required.FILE, + ) + move_parser.add_argument( + "dst", help="Destination path.", choices=completion.Required.FILE, ) - move_parser.add_argument("dst", help="Destination path.") move_parser.set_defaults(func=CmdMove) diff --git a/dvc/command/plots.py b/dvc/command/plots.py index c1d0b377e8..6c548dc21a 100644 --- a/dvc/command/plots.py +++ b/dvc/command/plots.py @@ -2,6 +2,7 @@ import logging import os +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link, fix_subparsers from dvc.exceptions import DvcException from dvc.schema import PLOT_PROPS @@ -128,6 +129,7 @@ def add_parser(subparsers, parent_parser): "targets", nargs="*", help="Plots files to visualize. Shows all plots by default.", + choices=completion.Optional.FILE, ) _add_props_arguments(plots_show_parser) _add_output_arguments(plots_show_parser) @@ -149,6 +151,7 @@ def add_parser(subparsers, parent_parser): nargs="*", help="Plots file to visualize. Shows all plots by default.", metavar="", + choices=completion.Optional.FILE, ) plots_diff_parser.add_argument( "revisions", nargs="*", default=None, help="Git commits to plot from", @@ -166,7 +169,9 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) plots_modify_parser.add_argument( - "target", help="Metric file to set properties to", + "target", + help="Metric file to set properties to", + choices=completion.Required.FILE, ) _add_props_arguments(plots_modify_parser) plots_modify_parser.add_argument( @@ -191,6 +196,7 @@ def _add_props_arguments(parser): ) ), metavar="", + choices=completion.Optional.FILE, ) parser.add_argument( "-x", default=None, help="Field name for X axis.", metavar="" @@ -223,6 +229,7 @@ def _add_output_arguments(parser): default=None, help="Destination path to save plots to", metavar="", + choices=completion.Optional.DIR, ) parser.add_argument( "--show-vega", diff --git a/dvc/command/remove.py b/dvc/command/remove.py index 4409abf1df..a76071b3fb 100644 --- a/dvc/command/remove.py +++ b/dvc/command/remove.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -34,6 +35,9 @@ def add_parser(subparsers, parent_parser): help="Remove outputs as well.", ) remove_parser.add_argument( - "targets", nargs="+", help="DVC-files to remove." + "targets", + nargs="+", + help="DVC-files to remove.", + choices=completion.Required.DVC_FILE, ) remove_parser.set_defaults(func=CmdRemove) diff --git a/dvc/command/repro.py b/dvc/command/repro.py index 810b37db49..2de9dfdad8 100644 --- a/dvc/command/repro.py +++ b/dvc/command/repro.py @@ -2,6 +2,7 @@ import logging import os +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.command.metrics import show_metrics from dvc.command.status import CmdDataStatus @@ -71,6 +72,7 @@ def add_parser(subparsers, parent_parser): "targets", nargs="*", help=f"Stages to reproduce. '{PIPELINE_FILE}' by default.", + choices=completion.Optional.DVC_FILE, ) repro_parser.add_argument( "-f", diff --git a/dvc/command/unprotect.py b/dvc/command/unprotect.py index 896e978614..eeb381d481 100644 --- a/dvc/command/unprotect.py +++ b/dvc/command/unprotect.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -32,6 +33,9 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) unprotect_parser.add_argument( - "targets", nargs="+", help="Data files/directories to unprotect." + "targets", + nargs="+", + help="Data files/directories to unprotect.", + choices=completion.Required.FILE, ) unprotect_parser.set_defaults(func=CmdUnprotect) diff --git a/dvc/command/update.py b/dvc/command/update.py index 718f08cdae..7760e296c9 100644 --- a/dvc/command/update.py +++ b/dvc/command/update.py @@ -1,6 +1,7 @@ import argparse import logging +from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -32,7 +33,10 @@ def add_parser(subparsers, parent_parser): formatter_class=argparse.RawDescriptionHelpFormatter, ) update_parser.add_argument( - "targets", nargs="+", help="DVC-files to update." + "targets", + nargs="+", + help="DVC-files to update.", + choices=completion.Required.DVC_FILE, ) update_parser.add_argument( "--rev", diff --git a/scripts/build_posix.sh b/scripts/build_posix.sh index 83386e40d7..0c3cee7dd7 100755 --- a/scripts/build_posix.sh +++ b/scripts/build_posix.sh @@ -115,10 +115,10 @@ build_dvc() { # [1] https://github.com/iterative/dvc/issues/2585 if [[ "$(uname)" == 'Linux' ]]; then mkdir -p $BUILD_DIR/$BASH_CMPLT_DIR - cp scripts/completion/dvc.bash $BUILD_DIR/$BASH_CMPLT_DIR/dvc + $LIB_DIR/dvc/dvc completion -s bash > $BUILD_DIR/$BASH_CMPLT_DIR/dvc mkdir -p $BUILD_DIR/$ZSH_CMPLT_DIR - cp scripts/completion/dvc.zsh $BUILD_DIR/$ZSH_CMPLT_DIR + $LIB_DIR/dvc/dvc completion -s zsh > $BUILD_DIR/$ZSH_CMPLT_DIR/_dvc fi } diff --git a/scripts/completion/dvc.bash b/scripts/completion/dvc.bash deleted file mode 100644 index 92bfe52b56..0000000000 --- a/scripts/completion/dvc.bash +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env bash -# References: -# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html -# - https://opensource.com/article/18/3/creating-bash-completion-script -# - https://stackoverflow.com/questions/12933362 - -_dvc_commands='add cache checkout commit config dag destroy diff fetch freeze \ - get-url get gc import-url import init install list metrics move params \ - plots pull push remote remove repro root run status unfreeze unprotect \ - update version' - -_dvc_options='-h --help -V --version' -_dvc_global_options='-h --help -q --quiet -v --verbose' - -_dvc_add='-R --recursive -f --file --no-commit --external' -_dvc_add_COMPGEN=_dvc_compgen_files -_dvc_cache='dir' -_dvc_cache_dir=' --global --system --local -u --unset' -_dvc_checkout='-d --with-deps -R --recursive -f --force --relink --summary' -_dvc_checkout_COMPGEN=_dvc_compgen_DVCFiles -_dvc_commit='-f --force -d --with-deps -R --recursive' -_dvc_commit_COMPGEN=_dvc_compgen_DVCFiles -_dvc_config='-u --unset --local --system --global' -_dvc_dag='--dot --full' -_dvc_dag_COMPGEN=_dvc_compgen_DVCFiles -_dvc_destroy='-f --force' -_dvc_diff='-t --show-json --show-hash --show-md' -_dvc_fetch='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive' -_dvc_fetch_COMPGEN=_dvc_compgen_DVCFiles -_dvc_gc='-a --all-branches --all-commits -T --all-tags -w --workspace -c --cloud -r --remote -f --force -p --projects -j --jobs' -_dvc_get='-o --out --rev --show-url' -_dvc_get_url='' -_dvc_import='-o --out --rev' -_dvc_import_url='-f --file' -_dvc_init='--no-scm -f --force' -_dvc_install='' -_dvc_list='-R --recursive --dvc-only --rev' -_dvc_list_COMPGEN=_dvc_compgen_files -_dvc_freeze='' -_dvc_freeze_COMPGEN=_dvc_compgen_DVCFiles -_dvc_metrics='diff show' -_dvc_metrics_diff='--targets -t --type -x --xpath -R --show-json --show-md --no-path --old' -_dvc_metrics_show='-t --type -x --xpath -a --all-branches -T --all-tags -R --recursive' -_dvc_metrics_show_COMPGEN=_dvc_compgen_files -_dvc_move='' -_dvc_move_COMPGEN=_dvc_compgen_files -_dvc_params='diff' -_dvc_params_diff='--all --show-json --show-md --no-path' -_dvc_plots='show diff' -_dvc_plots_show='-t --template -o --out -x -y --show-vega --no-csv-header --title --x-label --y-label' -_dvc_plots_diff='-t --template --targets -o --out -x -y --show-vega --no-csv-header --title --x-label --y-label' -_dvc_plots_modify='-t --template -x -y --no-csv-header --title --x-label --y-label' -_dvc_pull='-j --jobs -r --remote -a --all-branches -T --all-tags -f --force -d --with-deps -R --recursive' -_dvc_pull_COMPGEN=_dvc_compgen_DVCFiles -_dvc_push='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive' -_dvc_push_COMPGEN=_dvc_compgen_DVCFiles -_dvc_remote='add default list modify remove rename' -_dvc_remote_add='--global --system --local -d --default -f --force' -_dvc_remote_default='--global --system --local -u --unset' -_dvc_remote_list='--global --system --local' -_dvc_remote_modify='--global --system --local -u --unset' -_dvc_remote_remove='--global --system --local' -_dvc_remote_rename='--global --system --local' -_dvc_remove='-o --outs -p --purge -f --force' -_dvc_remove_COMPGEN=_dvc_compgen_DVCFiles -_dvc_repro='-f --force -s --single-item -c --cwd -m --metrics --dry -i --interactive -p --pipeline -P --all-pipelines --no-run-cache --force-downstream --no-commit -R --recursive --downstream' -_dvc_repro_COMPGEN=_dvc_compgen_DVCFiles -_dvc_root='' -_dvc_run='--no-exec -f --file -d --deps -o --outs -O --outs-no-cache --outs-persist --outs-persist-no-cache -m --metrics -M --metrics-no-cache --overwrite-dvcfile --no-run-cache --no-commit -w --wdir --external' -_dvc_run_COMPGEN=_dvc_compgen_DVCFiles -_dvc_status='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -c --cloud' -_dvc_status_COMPGEN=_dvc_compgen_DVCFiles -_dvc_unfreeze_COMPGEN=_dvc_compgen_DVCFiles -_dvc_unprotect_COMPGEN=_dvc_compgen_files -_dvc_update='--rev' -_dvc_update_COMPGEN=_dvc_compgen_DVCFiles -_dvc_version='' - -# $1=COMP_WORDS[1] -_dvc_compgen_DVCFiles() { - compgen -f -X '!*?.dvc' -- $1 - compgen -d -S '/' -- $1 # recurse into subdirs - # Note that the recurse into dirs is only for looking for DVC-files. - # Since dirs themselves are not required, we need `-o nospace` at the bottom - # unfortunately :( -} - -# $1=COMP_WORDS[1] -_dvc_compgen_files() { - compgen -f -- $1 - compgen -d -S '/' -- $1 # recurse into subdirs -} - -# $1=COMP_WORDS[1] -_dvc_replace_hyphen() { - echo $1 | sed 's/-/_/g' -} - -# $1=COMP_WORDS[1] -_dvc_compgen_command() { - local flags_list="_dvc_$(_dvc_replace_hyphen $1)" - local args_gen="${flags_list}_COMPGEN" - COMPREPLY=( $(compgen -W "$_dvc_global_options ${!flags_list}" -- "$word"; [ -n "${!args_gen}" ] && ${!args_gen} "$word") ) -} - -# $1=COMP_WORDS[1] -# $2=COMP_WORDS[2] -_dvc_compgen_subcommand() { - local flags_list="_dvc_$(_dvc_replace_hyphen $1)_$(_dvc_replace_hyphen $2)" - local args_gen="${flags_list}_COMPGEN" - [ -n "${!args_gen}" ] && local opts_more="$(${!args_gen} "$word")" - local opts="${!flags_list}" - if [ -z "$opts$opts_more" ]; then - _dvc_compgen_command $1 - else - COMPREPLY=( $(compgen -W "$_dvc_global_options $opts" -- "$word"; [ -n "$opts_more" ] && echo "$opts_more") ) - fi -} - -# Notes: -# `COMPREPLY` contains what will be rendered after completion is triggered -# `word` refers to the current typed word -# `${!var}` is to evaluate the content of `var` and expand its content as a variable -# hello="world" -# x="hello" -# ${!x} -> ${hello} -> "world" -_dvc() { - local word="${COMP_WORDS[COMP_CWORD]}" - - COMPREPLY=() - - if [ "${COMP_CWORD}" -eq 1 ]; then - case "$word" in - -*) COMPREPLY=($(compgen -W "$_dvc_options" -- "$word")) ;; - *) COMPREPLY=($(compgen -W "$_dvc_commands" -- "$word")) ;; - esac - elif [ "${COMP_CWORD}" -eq 2 ]; then - _dvc_compgen_command ${COMP_WORDS[1]} - elif [ "${COMP_CWORD}" -ge 3 ]; then - _dvc_compgen_subcommand ${COMP_WORDS[1]} ${COMP_WORDS[2]} - fi - - return 0 -} - -complete -o nospace -F _dvc dvc diff --git a/scripts/completion/dvc.zsh b/scripts/completion/dvc.zsh deleted file mode 100644 index 5097a1f4f7..0000000000 --- a/scripts/completion/dvc.zsh +++ /dev/null @@ -1,340 +0,0 @@ -#compdef dvc - -#---------------------------------------------------------- -# Repository: https://github.com/iterative/dvc -# -# References: -# - https://github.com/zsh-users/zsh-completions -# - http://zsh.sourceforge.net/Doc/Release/Completion-System.html -# - https://mads-hartmann.com/2017/08/06/writing-zsh-completion-scripts.html -# - http://www.linux-mag.com/id/1106/ -#---------------------------------------------------------- - -_dvc_commands() { - local _commands=( - "add:Track data files or directories with DVC." - "cache:Manage cache settings." - "checkout:Checkout data files from cache." - "commit:Save changed data to cache and update DVC-files." - "config:Get or set config settings." - "dag:Visualize DVC project DAG." - "destroy:Remove DVC-files, local DVC config and data cache." - "diff:Show added, modified, or deleted data between commits in the DVC repository, or between a commit and the workspace." - "fetch:Get files or directories tracked by DVC from remote storage into the cache." - "get-url:Download or copy files from URL." - "get:Download data from DVC repository." - "gc:Garbage collect unused objects from cache or remote storage." - "import-url:Download or copy file from URL and take it under DVC control." - "import:Download data from DVC repository and take it under DVC control." - "init:Initialize DVC in the current directory." - "install:Install DVC git hooks into the repository." - "list:List repository contents, including files and directories tracked by DVC and by Git." - "lock:Lock DVC-file." - "metrics:Commands to display and compare metrics." - "move:Rename or move a DVC controlled data file or a directory." - "params:Commands to display params." - "pull:Pull data files from a DVC remote storage." - "push:Push data files to a DVC remote storage." - "plots:Generate plot for metrics structured as JSON, CSV or TSV." - "remote:Manage remote storage configuration." - "remove:Remove outputs of DVC-file." - "repro:Check for changes and reproduce DVC-file and dependencies." - "root:Relative path to project's directory." - "run:Generate a stage file from a command and execute the command." - "status:Show changed stages, compare local cache and a remote storage." - "unlock:Unlock DVC-file." - "unprotect:Unprotect data file/directory." - "update:Update data artifacts imported from other DVC repositories." - "version:Show DVC version and system/environment information." - ) - - _describe 'dvc commands' _commands -} - -_dvc_options=( - "(-)"{-h,--help}"[Show help message.]" - "(-)"{-V,--version}"[Show program's version]" -) - -_dvc_global_options=( - "(-)"{-h,--help}"[Show help message related to the command.]" - "(-)"{-q,--quiet}"[Be quiet.]" - "(-)"{-v,--verbose}"[Be verbose.]" -) - -_dvc_add=( - {-R,--recursive}"[Recursively add each file under the directory.]" - "--no-commit[Don't put files/directories into cache.]" - {-f,--file}"[Specify name of the DVC-file it generates.]:File:_files" - "--external[Allow targets that are outside of the DVC project.]" - "1:File:_files" -) - -_dvc_cache=( - "1:Sub command:(dir)" -) - -_dvc_checkout=( - {-d,--with-deps}"[Checkout all dependencies of the specified target.]" - {-R,--recursive}"[Checkout all subdirectories of the specified directory.]" - {-f,--force}"[Do not prompt when removing working directory files.]" - "--relink[Recreate links or copies from cache to workspace.]" - "--summary[Show summary of the changes.]" - "1:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_commit=( - "*:Stages:_files -g '(*.dvc|Dvcfile)'" - {-f,--force}"[Commit even if checksums for dependencies/outputs changed.]" - {-d,--with-deps}"[Commit all dependencies of the specified target.]" - {-R,--recursive}"[Commit cache for subdirectories of the specified directory.]" -) - -_dvc_config=( - "--global[Use global config.]" - "--system[Use system config.]" - "--local[Use local config.]" - {-u,--unset}"[Unset option.]" -) - -_dvc_dag=( - "--dot[Print DAG in DOT format.]" - "--full[Show full DAG that the target belongs too, instead of showing DAG consisting only of ancestors.]" - "1:Stage:" -) - -_dvc_destroy=( - {-f,--force}"[Force destruction.]" -) - -_dvc_diff=( - "--show-json[Format the output into a JSON]" - "--show-hash[Display hash value for each entry]" - "--show-md[Format the output into a Markdown table]" - "1:Old Git commit to compare (defaults to HEAD):" - "2:New Git commit to compare (defaults to the current workspace):" -) - -_dvc_fetch=( - {-j,--jobs}"[Number of jobs to run simultaneously.]:Number of jobs:" - {-r,--remote}"[Remote repository to fetch from.]:Remote repository:" - {-a,--all-branches}"[Fetch cache for all branches.]" - {-T,--all-tags}"[Fetch cache for all tags.]" - {-d,--with-deps}"[Fetch cache for all dependencies of the specified target.]" - {-R,--recursive}"[Fetch cache for subdirectories of specified directory.]" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_geturl=( - "1:URL:" - "2:Output:" -) - -_dvc_get=( - {-o,--out}"[Destination path to put data to.]:OUT:_files -/" - "--rev[Git revision (e.g. SHA, branch, tag)]:Revision:" - "--show-url[Returns path/url to the location in remote for given path]" - "1:URL:" - "2:Path:" -) - -_dvc_gc=( - {-w,--workspace}"[Keep data files used in the current workspace.]" - {-a,--all-branches}"[Keep data files for the tips of all Git branches.]" - "--all-commits[Keep data files for all Git commits.]" - {-T,--all-tags}"[Keep data files for all Git tags.]" - {-c,--cloud}"[Collect garbage in remote repository.]" - {-r,--remote}"[Remote storage to collect garbage in.]:Remote repository:" - {-f,--force}"[Force garbage collection - automatically agree to all prompts.]:Repos:_files" - {-j,--jobs}"[Number of jobs to run simultaneously.]:Number of jobs:" - {-p,--projects}"[Keep data files required by these projects in addition to the current one.]:Repos:_files" -) - -_dvc_importurl=( - {-f,--file}"[Specify name of the DVC-file it generates.]:File:_files" - "1:URL:" - "2:Output:" -) - -_dvc_import=( - {-o,--out}"[Destination path to put data to.]:OUT:_files -/" - "--rev[Git revision (e.g. SHA, branch, tag)]:Commit hash:" - "1:URL:" - "2:Path:" -) - -_dvc_init=( - "--no-scm[Initiate dvc in directory that is not tracked by any scm tool.]" - {-f,--force}"[Overwrite existing '.dvc' directory. This operation removes local cache.]" -) - -_dvc_install=() - -_dvc_freeze=( - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_list=( - "--rev[Git revision (e.g. branch, tag, SHA)]:Revision:" - {-R,--recursive}"[Recursively add each file under the directory.]" - "--dvc-only[Only outputs DVC-outs.]" - "1:URL:" - "2:Path:" -) - -_dvc_metrics=( - "1:Sub command:(show diff)" -) - -_dvc_move=( - "1:Source:_files" - "2:Destination:" -) - -_dvc_params=( - "1:Sub command:(diff)" -) - -_dvc_pull=( - {-j,--jobs}"[Number of jobs to run simultaneously.]:Number of jobs:" - {-r,--remote}"[Remote repository to pull from.]:Remote repository:" - {-a,--all-branches}"[Fetch cache for all branches.]" - {-T,--all-tags}"[Fetch cache for all tags.]" - {-d,--with-deps}"[Fetch cache for all dependencies of the specified target.]" - {-f,--force}"[Do not prompt when removing working directory files.]" - {-R,--recursive}"[Pull cache for subdirectories of the specified directory.]" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_push=( - {-j,--jobs}"[Number of jobs to run simultaneously.]:Number of jobs:" - {-r,--remote}"[Remote repository to push to.]:Remote repository:" - {-a,--all-branches}"[Push cache for all branches.]" - {-T,--all-tags}"[Push cache for all tags.]" - {-d,--with-deps}"[Push cache for all dependencies of the specified target.]" - {-R,--recursive}"[Push cache for subdirectories of specified directory.]" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_plots=( - "1:Sub command:(show diff modify)" -) - -_dvc_remote=( - "1:Sub command:(add default remove modify list rename)" -) - -_dvc_remove=( - "*:Stages:_files -g '(*.dvc|Dvcfile)'" - "--dry[Only print the commands that would be executed without actually executing]" - {-o,--outs}"[Only remove DVC-file outputs. (Default)]" - {-p,--purge}"[Remove DVC-file and all its outputs.]" - {-f,--force}"[Force purge.]" -) - -_dvc_repro=( - {-f,--force}"[Reproduce even if dependencies were not changed.]" - {-s,--single-item}"[Reproduce only single data item without recursive dependencies check.]" - {-c,--cwd}"[Directory within your repo to reproduce from.]:CWD:_files -/" - {-m,--metrics}"[Show metrics after reproduction.]" - "--dry[Only print the commands that would be executed without actually executing]" - {-i,--interactive}"[Ask for confirmation before reproducing each stage.]" - {-p,--pipeline}"[Reproduce the whole pipeline that the specified stage file belongs to.]" - {-P,--all-pipelines}"[Reproduce all pipelines in the repo.]" - {-R,--recursive}"[Reproduce all stages in the specified directory.]" - "--force-downstream[Reproduce all descendants of a changed stage even if their direct dependencies didn't change.]" - "--no-run-cache[Run changed stage even if it has been already ran with the same command/dependencies/outputs/etc before.]" - "--no-commit[Don't put files/directories into cache.]" - "--downstream[Start from the specified stages when reproducing pipelines.]" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_root=() - -_dvc_run=( - "*"{-d,--deps}"[Declare dependencies for reproducible cmd.]:Dependency:_files" - "*"{-o,--outs}"[Declare output file or directory.]:Output data:_files" - "*"{-O,--outs-no-cache}"[Declare output file or directory (do not put into DVC cache).]:Output regular:_files" - "*"{-m,--metrics}"[Declare output metric file or directory.]:Metrics:_files" - "*"{-M,--metrics-no-cache}"[Declare output metric file or directory (do not put into DVC cache).]:Metrics (no cache):_files" - {-f,--file}"[Specify name of the DVC-file it generates.]:File:_files" - {-c,--cwd}"[Deprecated, use -w and -f instead.]:CWD:_files -/" - {-w,--wdir}"[Directory within your repo to run your command in.]:WDIR:_files -/" - "--no-exec[Only create stage file without actually running it.]" - {-y,--yes}"[Deprecated, use --overwrite-dvcfile instead]" - "--overwrite-dvcfile[Overwrite existing DVC-file without asking for confirmation.]" - "--no-run-cache[Run this stage even if it has been already ran with the same command/dependencies/outputs/etc before.]" - "--remove-outs[Deprecated, this is now the default behavior]" - "--no-commit[Don't put files/directories into cache.]" - "--outs-persist[Declare output file or directory that will not be removed upon repro.]:Output persistent:_files" - "--outs-persist-no-cache[Declare output file or directory that will not be removed upon repro (do not put into DVC cache).]:Output persistent regular:_files" - "--external[Allow outputs that are outside of the DVC project.]" -) - -_dvc_status=( - {-j,--jobs}"[Number of jobs to run simultaneously.]:Number of jobs:" - {-q,--quiet}"[Suppresses all output. Exit with 0 if pipelines are up to date, otherwise 1.]" - {-c,--cloud}"[Show status of a local cache compared to a remote repository.]" - {-r,--remote}"[Remote repository to compare local cache to.]:Remote repository:" - {-a,--all-branches}"[Show status of a local cache compared to a remote repository for all branches.]" - {-T,--all-tags}"[Show status of a local cache compared to a remote repository for all tags.]" - {-d,--with-deps}"[Show status for all dependencies of the specified target.]" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_unfreeze=( - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -_dvc_unprotect=( - "*:Data files:_files" -) - -_dvc_update=( - "--rev[Git revision (e.g. SHA, branch, tag)]:Revision:" - "*:Stages:_files -g '(*.dvc|Dvcfile)'" -) - -typeset -A opt_args -local context state line curcontext="$curcontext" - -_arguments \ - $_dvc_options \ - '1: :_dvc_commands' \ - '*::args:->args' - -case $words[1] in - add) _arguments $_dvc_global_options $_dvc_add ;; - cache) _arguments $_dvc_global_options $_dvc_cache ;; - checkout) _arguments $_dvc_global_options $_dvc_checkout ;; - commit) _arguments $_dvc_global_options $_dvc_commit ;; - config) _arguments $_dvc_global_options $_dvc_config ;; - dag) _arguments $_dvc_global_options $_dvc_dag ;; - destroy) _arguments $_dvc_global_options $_dvc_destroy ;; - diff) _arguments $_dvc_global_options $_dvc_diff ;; - fetch) _arguments $_dvc_global_options $_dvc_fetch ;; - freeze) _arguments $_dvc_global_options $_dvc_freeze ;; - get-url) _arguments $_dvc_global_options $_dvc_geturl ;; - get) _arguments $_dvc_global_options $_dvc_get ;; - gc) _arguments $_dvc_global_options $_dvc_gc ;; - import-url) _arguments $_dvc_global_options $_dvc_importurl ;; - import) _arguments $_dvc_global_options $_dvc_import ;; - init) _arguments $_dvc_global_options $_dvc_init ;; - install) _arguments $_dvc_global_options $_dvc_install ;; - list) _arguments $_dvc_global_options $_dvc_list ;; - metrics) _arguments $_dvc_global_options $_dvc_metrics ;; - move) _arguments $_dvc_global_options $_dvc_move ;; - params) _arguments $_dvc_global_options $_dvc_params ;; - pull) _arguments $_dvc_global_options $_dvc_pull ;; - push) _arguments $_dvc_global_options $_dvc_push ;; - plots) _arguments $_dvc_global_options $_dvc_plots ;; - remote) _arguments $_dvc_global_options $_dvc_remote ;; - remove) _arguments $_dvc_global_options $_dvc_remove ;; - repro) _arguments $_dvc_global_options $_dvc_repro ;; - root) _arguments $_dvc_global_options $_dvc_root ;; - run) _arguments $_dvc_global_options $_dvc_run ;; - status) _arguments $_dvc_global_options $_dvc_status ;; - unfreeze) _arguments $_dvc_global_options $_dvc_unfreeze ;; - unprotect) _arguments $_dvc_global_options $_dvc_unprotect ;; - update) _arguments $_dvc_global_options $_dvc_update ;; -esac diff --git a/setup.cfg b/setup.cfg index 2d6e2132f3..a50ee2af0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ select=B,C,E,F,W,T4,B9 [isort] include_trailing_comma=true known_first_party=dvc,tests -known_third_party=PyInstaller,RangeHTTPServer,boto3,colorama,configobj,distro,dpath,flaky,flufl,funcy,git,google,grandalf,mock,moto,nanotime,networkx,packaging,paramiko,pathspec,pytest,requests,ruamel,setuptools,shortuuid,tqdm,voluptuous,yaml,zc +known_third_party=PyInstaller,RangeHTTPServer,boto3,colorama,configobj,distro,dpath,flaky,flufl,funcy,git,google,grandalf,mock,moto,nanotime,networkx,packaging,paramiko,pathspec,pytest,requests,ruamel,setuptools,shortuuid,shtab,tqdm,voluptuous,yaml,zc line_length=79 force_grid_wrap=0 use_parentheses=True diff --git a/setup.py b/setup.py index 8570cdf263..8c80f8da4f 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ def run(self): "tabulate>=0.8.7", "pygtrie==2.3.2", "dpath>=2.0.1,<3", + "shtab>=0.0.2", ] diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 75d0f7daaa..6d3160ed91 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -31,17 +31,21 @@ parts: snapcraftctl pull snapcraftctl set-version $(cd $SNAPCRAFT_PART_SRC && git describe --tags) git diff --quiet || error_dirty_build - echo 'PKG = "snap"' > $SNAPCRAFT_PART_SRC/dvc/utils/build.py + echo 'PKG = "snap"' > dvc/utils/build.py # install all optional extras - sed -ri 's/(=install_requires)/\1+all_remotes+hdfs/' $SNAPCRAFT_PART_SRC/setup.py + sed -ri 's/(=install_requires)/\1+all_remotes+hdfs/' setup.py # ensure dvc knows the state isn't really dirty - sed -rin 's/.*git.*diff.*--quiet.*//' $SNAPCRAFT_PART_SRC/dvc/version.py + sed -rin 's/.*git.*diff.*--quiet.*//' dvc/version.py override-build: | snapcraftctl build # prevent user site packages interfering with this snap - reference: # https://github.com/snapcore/snapcraft/blob/19393ef36cd773a28131cec10cc0bfb3bf9c7e77/tools/snapcraft-override-build.sh#L18 sed -ri 's/^(ENABLE_USER_SITE = )None$/\1False/' $SNAPCRAFT_PART_INSTALL/usr/lib/python*/site.py - cp $SNAPCRAFT_PART_BUILD/scripts/completion/dvc.bash $SNAPCRAFT_PART_INSTALL/completion.sh + # make plugin aware that a file (generated later) needs to be staged + touch $SNAPCRAFT_PART_INSTALL/completion.sh + override-stage: | + snapcraftctl stage + usr/bin/python3 -m dvc completion -s bash > completion.sh apps: dvc: command: bin/dvc