Explorar o código

completion: support SortBySpec type options

Thomas Waldmann hai 1 mes
pai
achega
e376d8910c

+ 119 - 4
src/borg/archiver/completion_cmd.py

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

+ 2 - 0
src/borg/testsuite/archiver/completion_cmd_test.py

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