Просмотр исходного кода

completion: support FilesCacheMode type options

Thomas Waldmann 1 месяц назад
Родитель
Сommit
3aa7f53b09
2 измененных файлов с 144 добавлено и 3 удалено
  1. 142 3
      src/borg/archiver/completion_cmd.py
  2. 2 0
      src/borg/testsuite/archiver/completion_cmd_test.py

+ 142 - 3
src/borg/archiver/completion_cmd.py

@@ -4,7 +4,7 @@ import shtab
 
 
 from ._common import process_epilog
 from ._common import process_epilog
 from ..constants import *  # NOQA
 from ..constants import *  # NOQA
-from ..helpers import archivename_validator, SortBySpec  # used to detect ARCHIVE args for dynamic completion
+from ..helpers import archivename_validator, SortBySpec, FilesCacheMode
 from ..manifest import AI_HUMAN_SORT_KEYS
 from ..manifest import AI_HUMAN_SORT_KEYS
 
 
 # Dynamic completion for archive IDs (aid:...)
 # Dynamic completion for archive IDs (aid:...)
@@ -28,6 +28,10 @@ AID_ZSH_FN_NAME = "_borg_complete_aid"
 SORTBY_BASH_FN_NAME = "_borg_complete_sortby"
 SORTBY_BASH_FN_NAME = "_borg_complete_sortby"
 SORTBY_ZSH_FN_NAME = "_borg_complete_sortby"
 SORTBY_ZSH_FN_NAME = "_borg_complete_sortby"
 
 
+# Name of the helper function inserted for completing FilesCacheMode options
+FCM_BASH_FN_NAME = "_borg_complete_filescachemode"
+FCM_ZSH_FN_NAME = "_borg_complete_filescachemode"
+
 # Global bash preamble that is prepended to the generated completion script.
 # Global bash preamble that is prepended to the generated completion script.
 # It aggregates only what we need:
 # It aggregates only what we need:
 # - wordbreak fixes for ':' and '=' so tokens like 'aid:' and '--repo=/path' stay intact
 # - wordbreak fixes for ':' and '=' so tokens like 'aid:' and '--repo=/path' stay intact
@@ -126,6 +130,67 @@ _borg_complete_sortby() {
     printf '%s\n' "${prefix_eq}${head}${k}"
     printf '%s\n' "${prefix_eq}${head}${k}"
   done
   done
 }
 }
+
+# Complete comma-separated files cache mode tokens for options with type=FilesCacheMode.
+_borg_complete_filescachemode() {
+  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 tokens (embedded at generation time)
+  local keys=(___FCM_KEYS___)
+
+  # If 'disabled' is already selected, there is nothing else to suggest.
+  if [[ "$headlist" == *",disabled,"* ]]; then
+    return 0
+  fi
+
+  local k
+  for k in "${keys[@]}"; do
+    # skip duplicates
+    [[ "$headlist" == *",${k},"* ]] && continue
+    # do not suggest 'disabled' if any other token is already selected
+    if [[ -n "$head" && "$k" == "disabled" ]]; then
+      continue
+    fi
+    # ctime/mtime are mutually exclusive: don't suggest the other if one is present
+    if [[ "$k" == "ctime" && "$headlist" == *",mtime,"* ]]; then
+      continue
+    fi
+    if [[ "$k" == "mtime" && "$headlist" == *",ctime,"* ]]; then
+      continue
+    fi
+    # match prefix of last fragment
+    [[ -n "$frag" && "$k" != "$frag"* ]] && continue
+    printf '%s\n' "${prefix_eq}${head}${k}"
+  done
+}
 """
 """
 
 
 
 
@@ -229,6 +294,64 @@ _borg_complete_sortby() {
   compadd -Q -- $candidates
   compadd -Q -- $candidates
   return 0
   return 0
 }
 }
+
+# Complete comma-separated files cache mode tokens for options with type=FilesCacheMode.
+_borg_complete_filescachemode() {
+  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 tokens (embedded at generation time)
+  local -a keys=(___FCM_KEYS___)
+
+  # If 'disabled' is already selected, there is nothing else to suggest.
+  if [[ "$headlist" == *",disabled,"* ]]; then
+    return 0
+  fi
+
+  local -a candidates=()
+  local k
+  for k in ${keys[@]}; do
+    [[ "$headlist" == *",${k},"* ]] && continue
+    if [[ -n "$head" && "$k" == "disabled" ]]; then
+      continue
+    fi
+    if [[ "$k" == "ctime" && "$headlist" == *",mtime,"* ]]; then
+      continue
+    fi
+    if [[ "$k" == "mtime" && "$headlist" == *",ctime,"* ]]; then
+      continue
+    fi
+    [[ -n "$frag" && "$k" != "$frag"* ]] && continue
+    candidates+=( "${prefix_eq}${head}${k}" )
+  done
+  compadd -Q -- $candidates
+  return 0
+}
 """
 """
 
 
 
 
@@ -265,6 +388,20 @@ def _attach_sortby_completion(parser: argparse.ArgumentParser):
             action.complete = {"bash": SORTBY_BASH_FN_NAME, "zsh": SORTBY_ZSH_FN_NAME}  # type: ignore[attr-defined]
             action.complete = {"bash": SORTBY_BASH_FN_NAME, "zsh": SORTBY_ZSH_FN_NAME}  # type: ignore[attr-defined]
 
 
 
 
+def _attach_filescachemode_completion(parser: argparse.ArgumentParser):
+    """Tag all arguments with type FilesCacheMode with files-cache-mode completion."""
+
+    for action in parser._actions:
+        # Recurse into subparsers
+        if isinstance(action, argparse._SubParsersAction):
+            for sub in action.choices.values():
+                _attach_filescachemode_completion(sub)
+            continue
+
+        if action.type is FilesCacheMode:
+            action.complete = {"bash": FCM_BASH_FN_NAME, "zsh": FCM_ZSH_FN_NAME}  # type: ignore[attr-defined]
+
+
 class CompletionMixIn:
 class CompletionMixIn:
     def do_completion(self, args):
     def do_completion(self, args):
         """Output shell completion script for the given shell."""
         """Output shell completion script for the given shell."""
@@ -275,11 +412,13 @@ class CompletionMixIn:
         parser = self.build_parser()
         parser = self.build_parser()
         _attach_aid_completion(parser)
         _attach_aid_completion(parser)
         _attach_sortby_completion(parser)
         _attach_sortby_completion(parser)
+        _attach_filescachemode_completion(parser)
 
 
         # Build preambles with embedded SortBy keys
         # Build preambles with embedded SortBy keys
         sort_keys = " ".join(AI_HUMAN_SORT_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)
+        fcm_keys = " ".join(["ctime", "mtime", "size", "inode", "rechunk", "disabled"])  # keep in sync with parser
+        bash_preamble = BASH_PREAMBLE_TMPL.replace("___SORT_KEYS___", sort_keys).replace("___FCM_KEYS___", fcm_keys)
+        zsh_preamble = ZSH_PREAMBLE_TMPL.replace("___SORT_KEYS___", sort_keys).replace("___FCM_KEYS___", fcm_keys)
         preamble = {"bash": bash_preamble, "zsh": zsh_preamble}
         preamble = {"bash": bash_preamble, "zsh": zsh_preamble}
         script = shtab.complete(parser, shell=args.shell, preamble=preamble)  # nosec B604
         script = shtab.complete(parser, shell=args.shell, preamble=preamble)  # nosec B604
         print(script)
         print(script)

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

@@ -9,6 +9,7 @@ def test_bash_completion(archivers, request):
     output = cmd(archiver, "completion", "bash")
     output = cmd(archiver, "completion", "bash")
     assert "_borg_complete_aid() {" in output
     assert "_borg_complete_aid() {" in output
     assert "_borg_complete_sortby() {" in output
     assert "_borg_complete_sortby() {" in output
+    assert "_borg_complete_filescachemode() {" in output
 
 
 
 
 def test_zsh_completion(archivers, request):
 def test_zsh_completion(archivers, request):
@@ -17,3 +18,4 @@ def test_zsh_completion(archivers, request):
     output = cmd(archiver, "completion", "zsh")
     output = cmd(archiver, "completion", "zsh")
     assert "_borg_complete_aid() {" in output
     assert "_borg_complete_aid() {" in output
     assert "_borg_complete_sortby() {" in output
     assert "_borg_complete_sortby() {" in output
+    assert "_borg_complete_filescachemode() {" in output