|
|
@@ -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)
|
|
|
|