diff --git a/dvc/cli/completion.py b/dvc/cli/completion.py index e1f82c5861..b257bc20b3 100644 --- a/dvc/cli/completion.py +++ b/dvc/cli/completion.py @@ -27,6 +27,10 @@ local _dvc_remotes=($(dvc remote list | cut -d' ' -f1)) compgen -W "${_dvc_remotes[*]}" -- $1 } + +_dvc_compgen_config_vars() { + compgen -W "${_dvc_config_vars[*]}" -- $1 +} """ ZSH_PREAMBLE = """ @@ -64,6 +68,10 @@ _dvc_compadd_remotes() { _describe 'remotes' "($(dvc remote list | cut -d' ' -f1))" } + +_dvc_compadd_config_vars() { + _describe 'config_vars' _dvc_config_vars +} """ PREAMBLE = { @@ -81,3 +89,21 @@ } EXPERIMENT = {"bash": "_dvc_compgen_exps", "zsh": "_dvc_compadd_exps"} REMOTE = {"bash": "_dvc_compgen_remotes", "zsh": "_dvc_compadd_remotes"} +CONFIG_VARS = {"bash": "_dvc_compgen_config_vars", "zsh": "_dvc_compadd_config_vars"} + + +def get_preamble() -> dict[str, str]: + from dvc.config_schema import config_vars_for_completion + + ret: dict[str, str] = {} + config_vars = list(config_vars_for_completion()) + + nl = "\n\t".expandtabs(4) + config_vars_arr = f""" +_dvc_config_vars=( + {nl.join(config_vars)} +) +""" + for shell, preamble in PREAMBLE.items(): + ret[shell] = config_vars_arr + preamble + return ret diff --git a/dvc/commands/completion.py b/dvc/commands/completion.py index 120856c5b3..ca492df39d 100644 --- a/dvc/commands/completion.py +++ b/dvc/commands/completion.py @@ -1,6 +1,6 @@ from dvc.cli import formatter from dvc.cli.command import CmdBaseNoRepo -from dvc.cli.completion import PREAMBLE +from dvc.cli.completion import get_preamble from dvc.cli.utils import append_doc_link from dvc.log import logger from dvc.ui import ui @@ -17,7 +17,7 @@ def run(self): shell = self.args.shell parser = self.args.parser - script = shtab.complete(parser, shell=shell, preamble=PREAMBLE) + script = shtab.complete(parser, shell=shell, preamble=get_preamble()) ui.write(script, force=True) return 0 diff --git a/dvc/commands/config.py b/dvc/commands/config.py index f3e650c5d0..70fdd4207c 100644 --- a/dvc/commands/config.py +++ b/dvc/commands/config.py @@ -3,7 +3,7 @@ from funcy import set_in -from dvc.cli import formatter +from dvc.cli import completion, formatter from dvc.cli.command import CmdBaseNoRepo from dvc.cli.utils import append_doc_link from dvc.log import logger @@ -224,7 +224,7 @@ def add_parser(subparsers, parent_parser): nargs="?", type=_name_type, help="Option name (section.option or remote.name.option).", - ) + ).complete = completion.CONFIG_VARS config_parser.add_argument("value", nargs="?", help="Option value.") config_parser.add_argument( "-l", diff --git a/dvc/config_schema.py b/dvc/config_schema.py index ff8535c2bf..a4e00ff8a2 100644 --- a/dvc/config_schema.py +++ b/dvc/config_schema.py @@ -1,4 +1,5 @@ import os +from typing import TYPE_CHECKING from urllib.parse import urlparse from funcy import once, walk_values @@ -10,6 +11,7 @@ Exclusive, Invalid, Lower, + Marker, Optional, Range, Schema, @@ -17,6 +19,9 @@ from dvc.log import logger +if TYPE_CHECKING: + from collections.abc import Iterator + logger = logger.getChild(__name__) Bool = All( @@ -112,17 +117,19 @@ def __call__(self, data): return ret +DEPRECATED = "==DEPRECATED==" + REMOTE_COMMON = { "url": str, "checksum_jobs": All(Coerce(int), Range(1)), "jobs": All(Coerce(int), Range(1)), Optional("worktree"): Bool, - Optional("no_traverse"): Bool, # obsoleted + Optional("no_traverse", description=DEPRECATED): Bool, # obsoleted Optional("version_aware"): Bool, } LOCAL_COMMON = { "type": supported_cache_type, - Optional("protected", default=False): Bool, # obsoleted + Optional("protected", default=False, description=DEPRECATED): Bool, # obsoleted "shared": All(Lower, Choices("group")), Optional("slow_link_warning", default=True): Bool, Optional("verify", default=False): Bool, @@ -152,6 +159,127 @@ def __call__(self, data): Optional("verify", default=False): Bool, } +REMOTE_SCHEMAS = { + "": LOCAL_COMMON | REMOTE_COMMON, + "s3": { + "region": str, + "profile": str, + "credentialpath": str, + "configpath": str, + "endpointurl": str, + "access_key_id": str, + "secret_access_key": str, + "session_token": str, + Optional( + "listobjects", default=False, description=DEPRECATED + ): Bool, # obsoleted + Optional("use_ssl", default=True): Bool, + Optional("allow_anonymous_login", default=False): Bool, + "ssl_verify": Any(Bool, str), + "sse": str, + "sse_kms_key_id": str, + "sse_customer_algorithm": str, + "sse_customer_key": str, + "acl": str, + "grant_read": str, + "grant_read_acp": str, + "grant_write_acp": str, + "grant_full_control": str, + "cache_regions": bool, + "read_timeout": Coerce(int), + "connect_timeout": Coerce(int), + Optional("verify", default=False): Bool, + **REMOTE_COMMON, + }, + "gs": { + "projectname": str, + "credentialpath": str, + "endpointurl": str, + Optional("verify", default=False): Bool, + Optional("allow_anonymous_login", default=False): Bool, + **REMOTE_COMMON, + }, + "ssh": { + "type": supported_cache_type, + "port": Coerce(int), + "user": str, + "password": str, + "ask_password": Bool, + "passphrase": str, + "ask_passphrase": Bool, + "keyfile": str, + "timeout": Coerce(int), + "gss_auth": Bool, + "allow_agent": Bool, + "max_sessions": Coerce(int), + Optional("verify", default=False): Bool, + **REMOTE_COMMON, + }, + "hdfs": { + "user": str, + "kerb_ticket": str, + "replication": int, + **REMOTE_COMMON, + }, + "webhdfs": { + "kerberos": Bool, + "kerberos_principal": str, + "proxy_to": str, + "ssl_verify": Any(Bool, str), + "token": str, + "use_https": Bool, + "user": str, + "password": str, + "data_proxy_target": str, + Optional("verify", default=False): Bool, + **REMOTE_COMMON, + }, + "azure": { + "connection_string": str, + "sas_token": str, + "account_name": str, + "account_key": str, + "tenant_id": str, + "client_id": str, + "client_secret": str, + "allow_anonymous_login": Bool, + "exclude_environment_credential": Bool, + "exclude_visual_studio_code_credential": Bool, + "exclude_shared_token_cache_credential": Bool, + "exclude_managed_identity_credential": Bool, + Optional("verify", default=False): Bool, + "timeout": Coerce(int), + "read_timeout": Coerce(int), + "connection_timeout": Coerce(int), + **REMOTE_COMMON, + }, + "oss": { + "oss_key_id": str, + "oss_key_secret": str, + "oss_endpoint": str, + Optional("verify", default=True): Bool, + **REMOTE_COMMON, + }, + "gdrive": { + "profile": str, + "gdrive_use_service_account": Bool, + "gdrive_client_id": str, + "gdrive_client_secret": str, + "gdrive_user_credentials_file": str, + "gdrive_service_account_user_email": str, + "gdrive_service_account_json_file_path": str, + Optional("gdrive_trash_only", default=False): Bool, + Optional("gdrive_acknowledge_abuse", default=False): Bool, + Optional("verify", default=True): Bool, + **REMOTE_COMMON, + }, + "http": HTTP_COMMON | REMOTE_COMMON, + "https": HTTP_COMMON | REMOTE_COMMON, + "webdav": WEBDAV_COMMON | REMOTE_COMMON, + "webdavs": WEBDAV_COMMON | REMOTE_COMMON, + "remote": {str: object}, # Any of the above options are valid +} + SCHEMA = { "core": { "remote": Lower, @@ -161,152 +289,37 @@ def __call__(self, data): Optional("hardlink_lock", default=False): Bool, Optional("no_scm", default=False): Bool, Optional("autostage", default=False): Bool, - Optional("experiments"): Bool, # obsoleted + Optional("experiments", description=DEPRECATED): Bool, # obsoleted Optional("check_update", default=True): Bool, "site_cache_dir": str, "machine": Lower, }, "cache": { - "local": str, # obsoleted - "s3": str, # obsoleted - "gs": str, # obsoleted - "hdfs": str, # obsoleted - "webhdfs": str, # obsoleted - "ssh": str, # obsoleted - "azure": str, # obsoleted + Marker("local", description=DEPRECATED): str, # obsoleted + Marker("s3", description=DEPRECATED): str, # obsoleted + Marker("gs", description=DEPRECATED): str, # obsoleted + Marker("hdfs", description=DEPRECATED): str, # obsoleted + Marker("webhdfs", description=DEPRECATED): str, # obsoleted + Marker("ssh", description=DEPRECATED): str, # obsoleted + Marker("azure", description=DEPRECATED): str, # obsoleted # This is for default local cache "dir": str, **LOCAL_COMMON, }, "remote": { - str: ByUrl( - { - "": LOCAL_COMMON | REMOTE_COMMON, - "s3": { - "region": str, - "profile": str, - "credentialpath": str, - "configpath": str, - "endpointurl": str, - "access_key_id": str, - "secret_access_key": str, - "session_token": str, - Optional("listobjects", default=False): Bool, # obsoleted - Optional("use_ssl", default=True): Bool, - Optional("allow_anonymous_login", default=False): Bool, - "ssl_verify": Any(Bool, str), - "sse": str, - "sse_kms_key_id": str, - "sse_customer_algorithm": str, - "sse_customer_key": str, - "acl": str, - "grant_read": str, - "grant_read_acp": str, - "grant_write_acp": str, - "grant_full_control": str, - "cache_regions": bool, - "read_timeout": Coerce(int), - "connect_timeout": Coerce(int), - Optional("verify", default=False): Bool, - **REMOTE_COMMON, - }, - "gs": { - "projectname": str, - "credentialpath": str, - "endpointurl": str, - Optional("verify", default=False): Bool, - Optional("allow_anonymous_login", default=False): Bool, - **REMOTE_COMMON, - }, - "ssh": { - "type": supported_cache_type, - "port": Coerce(int), - "user": str, - "password": str, - "ask_password": Bool, - "passphrase": str, - "ask_passphrase": Bool, - "keyfile": str, - "timeout": Coerce(int), - "gss_auth": Bool, - "allow_agent": Bool, - "max_sessions": Coerce(int), - Optional("verify", default=False): Bool, - **REMOTE_COMMON, - }, - "hdfs": { - "user": str, - "kerb_ticket": str, - "replication": int, - **REMOTE_COMMON, - }, - "webhdfs": { - "kerberos": Bool, - "kerberos_principal": str, - "proxy_to": str, - "ssl_verify": Any(Bool, str), - "token": str, - "use_https": Bool, - "user": str, - "password": str, - "data_proxy_target": str, - Optional("verify", default=False): Bool, - **REMOTE_COMMON, - }, - "azure": { - "connection_string": str, - "sas_token": str, - "account_name": str, - "account_key": str, - "tenant_id": str, - "client_id": str, - "client_secret": str, - "allow_anonymous_login": Bool, - "exclude_environment_credential": Bool, - "exclude_visual_studio_code_credential": Bool, - "exclude_shared_token_cache_credential": Bool, - "exclude_managed_identity_credential": Bool, - Optional("verify", default=False): Bool, - "timeout": Coerce(int), - "read_timeout": Coerce(int), - "connection_timeout": Coerce(int), - **REMOTE_COMMON, - }, - "oss": { - "oss_key_id": str, - "oss_key_secret": str, - "oss_endpoint": str, - Optional("verify", default=True): Bool, - **REMOTE_COMMON, - }, - "gdrive": { - "profile": str, - "gdrive_use_service_account": Bool, - "gdrive_client_id": str, - "gdrive_client_secret": str, - "gdrive_user_credentials_file": str, - "gdrive_service_account_user_email": str, - "gdrive_service_account_json_file_path": str, - Optional("gdrive_trash_only", default=False): Bool, - Optional("gdrive_acknowledge_abuse", default=False): Bool, - Optional("verify", default=True): Bool, - **REMOTE_COMMON, - }, - "http": HTTP_COMMON | REMOTE_COMMON, - "https": HTTP_COMMON | REMOTE_COMMON, - "webdav": WEBDAV_COMMON | REMOTE_COMMON, - "webdavs": WEBDAV_COMMON | REMOTE_COMMON, - "remote": {str: object}, # Any of the above options are valid - } - ) + str: ByUrl(REMOTE_SCHEMAS), }, "state": { - "dir": str, # obsoleted - "row_limit": All(Coerce(int), Range(1)), # obsoleted - "row_cleanup_quota": All(Coerce(int), Range(0, 100)), # obsoleted + Marker("dir", description=DEPRECATED): str, # obsoleted + Marker("row_limit", description=DEPRECATED): All( + Coerce(int), Range(1) + ), # obsoleted + Marker("row_cleanup_quota", description=DEPRECATED): All( + Coerce(int), Range(0, 100) + ), # obsoleted }, "index": { - "dir": str, # obsoleted + Marker("dir", description=DEPRECATED): str, # obsoleted }, "machine": { str: { @@ -336,13 +349,13 @@ def __call__(self, data): "out_dir": str, }, "exp": { - "code": str, - "data": str, - "models": str, - "metrics": str, - "params": str, - "plots": str, - "live": str, + Marker("code", description=DEPRECATED): str, + Marker("data", description=DEPRECATED): str, + Marker("models", description=DEPRECATED): str, + Marker("metrics", description=DEPRECATED): str, + Marker("params", description=DEPRECATED): str, + Marker("plots", description=DEPRECATED): str, + Marker("live", description=DEPRECATED): str, "auto_push": Bool, "git_remote": str, }, @@ -371,3 +384,21 @@ def __call__(self, data): }, }, } + + +def config_vars_for_completion(d: dict = SCHEMA, path: str = "") -> "Iterator[str]": + for k, v in d.items(): + if k in ("machine", "feature"): + continue + if isinstance(k, Marker): + if k.description == DEPRECATED: + continue + k = k.schema + if not isinstance(k, str): + continue + + keypath = path + k + if isinstance(v, dict): + yield from config_vars_for_completion(v, keypath + ".") + else: + yield keypath