mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-11 01:41:57 -04:00
completion: support SortBySpec type options
This commit is contained in:
parent
8646c4e045
commit
e376d8910c
2 changed files with 121 additions and 4 deletions
|
|
@ -4,7 +4,8 @@ import shtab
|
|||
|
||||
from ._common import process_epilog
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import archivename_validator # used to detect ARCHIVE args for dynamic completion
|
||||
from ..helpers import archivename_validator, SortBySpec # used to detect ARCHIVE args for dynamic completion
|
||||
from ..manifest import AI_HUMAN_SORT_KEYS
|
||||
|
||||
# Dynamic completion for archive IDs (aid:...)
|
||||
#
|
||||
|
|
@ -23,11 +24,15 @@ from ..helpers import archivename_validator # used to detect ARCHIVE args for d
|
|||
AID_BASH_FN_NAME = "_borg_complete_aid"
|
||||
AID_ZSH_FN_NAME = "_borg_complete_aid"
|
||||
|
||||
# Name of the helper function inserted for completing SortBySpec options
|
||||
SORTBY_BASH_FN_NAME = "_borg_complete_sortby"
|
||||
SORTBY_ZSH_FN_NAME = "_borg_complete_sortby"
|
||||
|
||||
# Global bash preamble that is prepended to the generated completion script.
|
||||
# It aggregates only what we need:
|
||||
# - wordbreak fixes for ':' and '=' so tokens like 'aid:' and '--repo=/path' stay intact
|
||||
# - a minimal dynamic completion helper for aid: archive IDs
|
||||
BASH_PREAMBLE = r"""
|
||||
BASH_PREAMBLE_TMPL = r"""
|
||||
# keep ':' and '=' intact so tokens like 'aid:' and '--repo=/path' stay whole
|
||||
if [[ ${COMP_WORDBREAKS-} == *:* ]]; then COMP_WORDBREAKS=${COMP_WORDBREAKS//:}; fi
|
||||
if [[ ${COMP_WORDBREAKS-} == *=* ]]; then COMP_WORDBREAKS=${COMP_WORDBREAKS//=}; fi
|
||||
|
|
@ -75,6 +80,52 @@ _borg_complete_aid() {
|
|||
done <<< "$out"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Complete comma-separated sort keys for any option with type=SortBySpec.
|
||||
# Keys are validated against Borg's AI_HUMAN_SORT_KEYS.
|
||||
_borg_complete_sortby() {
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
# Extract value part for --opt=value forms; otherwise the value is the word itself
|
||||
local val prefix_eq
|
||||
if [[ "$cur" == *=* ]]; then
|
||||
prefix_eq="${cur%%=*}="
|
||||
val="${cur#*=}"
|
||||
else
|
||||
prefix_eq=""
|
||||
val="$cur"
|
||||
fi
|
||||
|
||||
# Split into head (selected keys + trailing comma if any) and fragment (last token being typed)
|
||||
local head frag
|
||||
if [[ "$val" == *,* ]]; then
|
||||
head="${val%,*},"
|
||||
frag="${val##*,}"
|
||||
else
|
||||
head=""
|
||||
frag="$val"
|
||||
fi
|
||||
|
||||
# Build a comma-delimited list for cheap membership testing
|
||||
local headlist
|
||||
if [[ -n "$head" ]]; then
|
||||
headlist=",${head%,},"
|
||||
else
|
||||
headlist="," # nothing selected yet
|
||||
fi
|
||||
|
||||
# Valid keys (embedded at generation time)
|
||||
local keys=(___SORT_KEYS___)
|
||||
|
||||
local k
|
||||
for k in "${keys[@]}"; do
|
||||
# skip already-selected keys
|
||||
[[ "$headlist" == *",${k},"* ]] && continue
|
||||
# match prefix of last fragment
|
||||
[[ -n "$frag" && "$k" != "$frag"* ]] && continue
|
||||
printf '%s\n' "${prefix_eq}${head}${k}"
|
||||
done
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -84,7 +135,7 @@ _borg_complete_aid() {
|
|||
# - We use zsh's $words/$CURRENT arrays to inspect the command line.
|
||||
# - Candidates are returned via `compadd`.
|
||||
# - We try to detect repo context from --repo=V, --repo V, -r=V, -rV, -r V.
|
||||
ZSH_PREAMBLE = r"""
|
||||
ZSH_PREAMBLE_TMPL = r"""
|
||||
# Complete aid:<hex-prefix> archive IDs by querying "borg repo-list --short"
|
||||
# Note: we only suggest the first 8 hex digits (short ID) for completion.
|
||||
_borg_complete_aid() {
|
||||
|
|
@ -134,6 +185,50 @@ _borg_complete_aid() {
|
|||
compadd -Q -- $candidates
|
||||
return 0
|
||||
}
|
||||
|
||||
# Complete comma-separated sort keys for any option with type=SortBySpec.
|
||||
_borg_complete_sortby() {
|
||||
local cur
|
||||
cur="${words[$CURRENT]}"
|
||||
|
||||
local val prefix_eq
|
||||
if [[ "$cur" == *"="* ]]; then
|
||||
prefix_eq="${cur%%\=*}="
|
||||
val="${cur#*=}"
|
||||
else
|
||||
prefix_eq=""
|
||||
val="$cur"
|
||||
fi
|
||||
|
||||
local head frag
|
||||
if [[ "$val" == *","* ]]; then
|
||||
head="${val%,*},"
|
||||
frag="${val##*,}"
|
||||
else
|
||||
head=""
|
||||
frag="$val"
|
||||
fi
|
||||
|
||||
local headlist
|
||||
if [[ -n "$head" ]]; then
|
||||
headlist=",${head%,},"
|
||||
else
|
||||
headlist="," # nothing selected yet
|
||||
fi
|
||||
|
||||
# Valid keys (embedded at generation time)
|
||||
local -a keys=(___SORT_KEYS___)
|
||||
|
||||
local -a candidates=()
|
||||
local k
|
||||
for k in ${keys[@]}; do
|
||||
[[ "$headlist" == *",${k},"* ]] && continue
|
||||
[[ -n "$frag" && "$k" != "$frag"* ]] && continue
|
||||
candidates+=( "${prefix_eq}${head}${k}" )
|
||||
done
|
||||
compadd -Q -- $candidates
|
||||
return 0
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -156,6 +251,20 @@ def _attach_aid_completion(parser: argparse.ArgumentParser):
|
|||
action.complete = {"bash": AID_BASH_FN_NAME, "zsh": AID_ZSH_FN_NAME} # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def _attach_sortby_completion(parser: argparse.ArgumentParser):
|
||||
"""Tag all arguments with type SortBySpec with sort-key completion."""
|
||||
|
||||
for action in parser._actions:
|
||||
# Recurse into subparsers
|
||||
if isinstance(action, argparse._SubParsersAction):
|
||||
for sub in action.choices.values():
|
||||
_attach_sortby_completion(sub)
|
||||
continue
|
||||
|
||||
if action.type is SortBySpec:
|
||||
action.complete = {"bash": SORTBY_BASH_FN_NAME, "zsh": SORTBY_ZSH_FN_NAME} # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class CompletionMixIn:
|
||||
def do_completion(self, args):
|
||||
"""Output shell completion script for the given shell."""
|
||||
|
|
@ -165,7 +274,13 @@ class CompletionMixIn:
|
|||
# to enumerate archives and does not introduce any new commands or caching.
|
||||
parser = self.build_parser()
|
||||
_attach_aid_completion(parser)
|
||||
preamble = {"bash": BASH_PREAMBLE, "zsh": ZSH_PREAMBLE}
|
||||
_attach_sortby_completion(parser)
|
||||
|
||||
# Build preambles with embedded SortBy keys
|
||||
sort_keys = " ".join(AI_HUMAN_SORT_KEYS)
|
||||
bash_preamble = BASH_PREAMBLE_TMPL.replace("___SORT_KEYS___", sort_keys)
|
||||
zsh_preamble = ZSH_PREAMBLE_TMPL.replace("___SORT_KEYS___", sort_keys)
|
||||
preamble = {"bash": bash_preamble, "zsh": zsh_preamble}
|
||||
script = shtab.complete(parser, shell=args.shell, preamble=preamble) # nosec B604
|
||||
print(script)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ def test_bash_completion(archivers, request):
|
|||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "bash")
|
||||
assert "_borg_complete_aid() {" in output
|
||||
assert "_borg_complete_sortby() {" in output
|
||||
|
||||
|
||||
def test_zsh_completion(archivers, request):
|
||||
|
|
@ -15,3 +16,4 @@ def test_zsh_completion(archivers, request):
|
|||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "zsh")
|
||||
assert "_borg_complete_aid() {" in output
|
||||
assert "_borg_complete_sortby() {" in output
|
||||
|
|
|
|||
Loading…
Reference in a new issue