fix remote config completion

This commit is contained in:
Saugat Pachhai (सौगात)
2025-10-07 14:34:39 +05:45
parent ffd32dfe8a
commit f2b1c41cdf
4 changed files with 109 additions and 21 deletions
+61 -6
View File
@@ -31,6 +31,27 @@ _dvc_compgen_remotes() {
_dvc_compgen_config_vars() { _dvc_compgen_config_vars() {
compgen -W "${_dvc_config_vars[*]}" -- $1 compgen -W "${_dvc_config_vars[*]}" -- $1
} }
_dvc_compgen_remote_config_vars() {
local cur prev words cword remote_name filtered
_init_completion || return
# last non-option word before cursor
local nonopts=()
for w in "${words[@]:1:$((cword-1))}"; do
[[ $w == -* ]] || nonopts+=("$w")
done
remote_name="${nonopts[-1]}"
if [[ -n $remote_name ]]; then
local _dvc_remote_config_vars=($(
dvc config --available-options -q 2> /dev/null \
| grep "^remote\\.${remote_name}\\." \
| sed "s/^remote\\.${remote_name}\\.//"
))
compgen -W "${_dvc_remote_config_vars[*]}" -- $1
fi
}
""" """
ZSH_PREAMBLE = """ ZSH_PREAMBLE = """
@@ -69,9 +90,27 @@ _dvc_compadd_remotes() {
_describe 'remotes' "($(dvc remote list | cut -d' ' -f1))" _describe 'remotes' "($(dvc remote list | cut -d' ' -f1))"
} }
_dvc_config_config_vars_for_completion() {
dvc config --available-options -q 2> /dev/null
}
_dvc_compadd_config_vars() { _dvc_compadd_config_vars() {
_describe 'config_vars' _dvc_config_vars _describe 'config_vars' _dvc_config_vars
} }
_dvc_compadd_remote_config_vars() {
local remote_name filtered
# last non-option word before cursor
remote_name=${${words[1,CURRENT-1]##-*}[-1]}
if [[ -n $remote_name ]]; then
filtered=$(_dvc_config_config_vars_for_completion \\
| grep "^remote.${remote_name}\\\\." \\
| sed "s/^remote\\\\.${remote_name}\\\\.//"
)
compadd -- $=filtered
fi
}
""" """
FISH_PREAMBLE = """ FISH_PREAMBLE = """
@@ -100,6 +139,17 @@ end
function __fish_complete_dvc_remotes function __fish_complete_dvc_remotes
dvc remote list | cut -d' ' -f1 dvc remote list | cut -d' ' -f1
end end
function __fish_complete_dvc_remote_config_vars
# last non-option word before cursor
set -l remote_name (commandline -opc | string match -rv '^-' | tail -n 1)
if test -n "$remote_name"
dvc config --available-options -q 2>/dev/null |
string match -re "^remote\\.$remote_name\\." |
string replace -r "^remote\\.$remote_name\\." ''
end
end
""" """
PREAMBLE = { PREAMBLE = {
@@ -113,32 +163,37 @@ DIR = shtab.DIRECTORY
DVC_FILE = { DVC_FILE = {
"bash": "_dvc_compgen_DVCFiles", "bash": "_dvc_compgen_DVCFiles",
"zsh": "_dvc_compadd_DVCFiles", "zsh": "_dvc_compadd_DVCFiles",
"fish": "__fish_complete_dvc_files", "fish": '-f -a "(__fish_complete_dvc_files)"',
} }
STAGE = { STAGE = {
"bash": "_dvc_compgen_stages", "bash": "_dvc_compgen_stages",
"zsh": "_dvc_compadd_stages", "zsh": "_dvc_compadd_stages",
"fish": "__fish_complete_dvc_stages", "fish": '-f -a "(__fish_complete_dvc_stages)"',
} }
DVCFILES_AND_STAGE = { DVCFILES_AND_STAGE = {
"bash": "_dvc_compgen_stages_and_files", "bash": "_dvc_compgen_stages_and_files",
"zsh": "_dvc_compadd_stages_and_files", "zsh": "_dvc_compadd_stages_and_files",
"fish": "__fish_complete_dvc_stages_and_files", "fish": '-f -a "(__fish_complete_dvc_stages_and_files)"',
} }
EXPERIMENT = { EXPERIMENT = {
"bash": "_dvc_compgen_exps", "bash": "_dvc_compgen_exps",
"zsh": "_dvc_compadd_exps", "zsh": "_dvc_compadd_exps",
"fish": "__fish_complete_dvc_experiments", "fish": '-f -a "(__fish_complete_dvc_experiments)"',
} }
REMOTE = { REMOTE = {
"bash": "_dvc_compgen_remotes", "bash": "_dvc_compgen_remotes",
"zsh": "_dvc_compadd_remotes", "zsh": "_dvc_compadd_remotes",
"fish": "__fish_complete_dvc_remotes", "fish": '-f -a "(__fish_complete_dvc_remotes)"',
} }
CONFIG_VARS = { CONFIG_VARS = {
"bash": "_dvc_compgen_config_vars", "bash": "_dvc_compgen_config_vars",
"zsh": "_dvc_compadd_config_vars", "zsh": "_dvc_compadd_config_vars",
"fish": "__fish_complete_dvc_config_vars", "fish": '-f -a "(__fish_complete_dvc_config_vars)"',
}
REMOTE_CONFIG_VARS = {
"bash": "_dvc_compgen_remote_config_vars",
"zsh": "_dvc_compadd_remote_config_vars",
"fish": '-f -a "(__fish_complete_dvc_remote_config_vars)"',
} }
+12
View File
@@ -40,6 +40,15 @@ class CmdConfig(CmdBaseNoRepo):
self.config = Config.from_cwd(validate=False) self.config = Config.from_cwd(validate=False)
def run(self): def run(self):
if self.args.available_options:
from dvc.config_schema import contextual_config_vars_for_completion
ui.write(
"\n".join(list(contextual_config_vars_for_completion(self.config))),
force=True,
)
return 0
if self.args.show_origin and (self.args.value or self.args.unset): if self.args.show_origin and (self.args.value or self.args.unset):
logger.error( logger.error(
"--show-origin can't be used together with any of these " "--show-origin can't be used together with any of these "
@@ -212,6 +221,9 @@ def add_parser(subparsers, parent_parser):
help=CONFIG_HELP, help=CONFIG_HELP,
formatter_class=formatter.RawDescriptionHelpFormatter, formatter_class=formatter.RawDescriptionHelpFormatter,
) )
config_parser.add_argument(
"--available-options", default=False, action="store_true"
)
config_parser.add_argument( config_parser.add_argument(
"-u", "-u",
"--unset", "--unset",
+3 -1
View File
@@ -233,7 +233,9 @@ def add_parser(subparsers, parent_parser):
remote_modify_parser.add_argument( remote_modify_parser.add_argument(
"name", help="Name of the remote" "name", help="Name of the remote"
).complete = completion.REMOTE ).complete = completion.REMOTE
remote_modify_parser.add_argument("option", help="Name of the option to modify.") remote_modify_parser.add_argument(
"option", help="Name of the option to modify."
).complete = completion.REMOTE_CONFIG_VARS
remote_modify_parser.add_argument( remote_modify_parser.add_argument(
"value", nargs="?", help="(optional) Value of the option." "value", nargs="?", help="(optional) Value of the option."
) )
+33 -14
View File
@@ -60,23 +60,27 @@ def Choices(*choices): # noqa: N802
return Any(*choices, msg="expected one of {}".format(", ".join(choices))) return Any(*choices, msg="expected one of {}".format(", ".join(choices)))
def _get_schema_from_url(url: str) -> str:
parsed = urlparse(url)
# Windows absolute paths should really have scheme == "" (local)
if os.name == "nt" and len(parsed.scheme) == 1 and not parsed.netloc:
return ""
if not parsed.netloc:
return ""
return parsed.scheme
def ByUrl(mapping): # noqa: N802 def ByUrl(mapping): # noqa: N802
schemas = walk_values(Schema, mapping) schemas: dict[str, Schema] = walk_values(Schema, mapping)
def validate(data): def validate(data):
if "url" not in data: if "url" not in data:
raise Invalid("expected 'url'") raise Invalid("expected 'url'")
parsed = urlparse(data["url"]) scheme = _get_schema_from_url(data["url"])
# Windows absolute paths should really have scheme == "" (local) if scheme not in schemas:
if os.name == "nt" and len(parsed.scheme) == 1 and not parsed.netloc: raise Invalid(f"Unsupported URL type {scheme}://")
return schemas[""](data) return schemas[scheme](data)
if not parsed.netloc:
return schemas[""](data)
if parsed.scheme not in schemas:
raise Invalid(f"Unsupported URL type {parsed.scheme}://")
return schemas[parsed.scheme](data)
return validate return validate
@@ -396,9 +400,24 @@ def config_vars_for_completion(d: dict = SCHEMA, path: str = "") -> "Iterator[st
k = k.schema k = k.schema
if not isinstance(k, str): if not isinstance(k, str):
continue continue
keypath = f"{path}.{k}" if path else k
keypath = path + k
if isinstance(v, dict): if isinstance(v, dict):
yield from config_vars_for_completion(v, keypath + ".") yield from config_vars_for_completion(v, keypath)
else: else:
yield keypath yield keypath
def contextual_config_vars_for_completion(config) -> "Iterator[str]":
yield from config_vars_for_completion()
for name, data in config["remote"].items():
if "url" not in data:
continue
scheme = _get_schema_from_url(data["url"])
if scheme not in REMOTE_SCHEMAS:
continue
schema = REMOTE_SCHEMAS[scheme]
if schema:
yield from config_vars_for_completion(schema, f"remote.{name}") # type: ignore[arg-type]
for name in config["db"]:
yield from config_vars_for_completion(SCHEMA["db"], f"db.{name}") # type: ignore[arg-type]