1

I am trying to write a tab-completion script for borg.

So far, I have managed to define completions for borg itself, as well as borg key with its subcommands and borg benchmark with its singular subcommand. However, I am now trying to define completion for borg init and I am having trouble.

The issue presents itself only when I define two arguments under the borg init command to use the same description text; i.e. both -e and --encryption should use the same description, as they are practically the same argument. This has worked fine for borg's arguments, but now it breaks.

This is my code, slightly redacted to spare you the redundancy:

compdef _borg borg

function _borg {
    local line ret=1
    local -a argus

    local logs="--critical --error --warning --debug --info -v --verbose"

    argus+=(
        "(*)"{-h,--help}"[Show help and exit]"
        "(*)-V[Show Borg version and exit]"
        "($logs)--critical[Work on log level CRITICAL]"
        "($logs)--error[Work on log level ERROR]"
        "($logs)--warning[Work on log level WARNING (default)]"
        "($logs)"{--info,-v,--verbose}"[Work on log level INFO]"
        "($logs)--debug[Enable debug output; log level DEBUG]"
        {-p,--progress}"[Show progress]"
        "--log-json[Output one JSON object per log line instead of formatted text]"
        "--show-version[Show/log borg version]"
        "--show-rc[Show/log returncode]"
        "--consider-part-files[treat part files like normal files (e.g. to list/extract them)]"
        "--lock-wait[Wait at most SECONDS for acquiring a repository/cache lock (default 1)]:SECONDS:()"
        "--umask[Set umask to M (local and remote; default 0077)]:M (umask value, e.g. 0077):()"
        "--remote-path[Use PATH as borg executable on the remote (default: \"borg\")]:PATH:()"
        "--remote-ratelimit[Set remote network upload rate limit in kiByte/s (default: 0=unlimited)]:RATE:()"
        "--debug-profile[Write execution profile in Borg format into FILE.]:FILE:_files"
        "--rsh[Use this command to connect to the \"borg serve\" process (default: \"ssh\")]:RSH:()"
        "1: :((init\:\"Initialize a new repository\" \
                create\:\"Create a new archive\" \
                extract\:\"Extract the contents of an archive\" \
                check\:\"Verifies consistency of a repository and its archives\" \
                rename\:\"Renames an archive in a repository\" \
                list\:\"Lists contents of a repository or archive\" \
                diff\:\"Finds differences between archives\" \
                delete\:\"Deletes an archive or an entire repository (and its cache)\" \
                prune\:\"Prunes a repository\" \
                info\:\"Shows info about a repository or archive\" \
                mount\:\"Mounts an archive as a FUSE filesystem\" \
                unmount\:\"Unmounts a FUSE filesystem mounted with \\\"borg mount\\\"\" \
                key\:\"Keyword for key-related functions\" \
                upgrade\:\"Upgrade a local Borg repository\" \
                recreate\:\"EXPERIMENTAL: Recreates contents of existing archives\" \
                export-tar\:\"Creates a tarball from an archive\" \
                serve\:\"Starts repository server process. Not usually used manually.\" \
                config\:\"Gets and sets options in local repository and cache config files\" \
                with-lock\:\"Executes another command with the repository lock held\" \
                break-lock\:\"Breaks the repository and cache locks\" \
                benchmark\:\"Keyword for the benchmark function\"))" \
        "*::arg:->args"
    )

    _arguments -w -s -S -C $argus[@] && ret=0

    case $line[1] in
        benchmark)
            _borg_benchmark
            ;;
        init)
            _borg_init
            ;;
        key)
            _borg_key
            ;;
    esac
    return ret
}

function _borg_benchmark {
# stuff
}

function _borg_benchmark_crud {
# stuff again
}

function _borg_init {
    local line ret=1
    local -a argus

    argus+=(
        "-t[This is a test]"
        "--test[This is a test]"
        "(--append-only)--append-only[Create an append-only mode repository]"
        "*::arg:->args"
    )

    _arguments -w -s -S -C $argus[@] && ret=0

    return ret
}

function _borg_key {
# key stuff
}

function _borg_key_changepassphrase {
# stuff
}

function _borg_key_export {
# more stuff
}

function _borg_key_import {
# other stuff
}

If I try to tab-complete borg init - using this setup, I get the following output:

$ borg init -
Completing option
--append-only                                              
--test                                                     

-t                                                         
-- Create an append-only mode repository                   
-- This is a test                                          
--append-only                                              
--test                                                     

-t                                                         
-- Create an append-only mode repository                   
-- This is a test                                          
--append-only                                              
--test                                                     

-t                                                         
-- Create an append-only mode repository                   
-- This is a test                                          
--append-only                                              
--test                                                     

-t                                                         
-- Create an append-only mode repository                   
-- This is a test

The completion appears to forget what tabs are and repeats itself four times. If I change --test[This is a test] to --test[This is another test] in _borg_init, I instead get the following completion:

$ borg init -
Completing option
--append-only  -- Create an append-only mode repository
--test         -- This is another test
-t             -- This is a test

The above is "correct", in the sense that it's not broken, but I cannot seem to define arguments that share a description in a subcommand. How should I do that? And, more generally, how are you supposed to define completions for commands with subcommands (which may, in turn, have more arguments)?

SLDR
  • 19
  • 4
  • I don't know much about `zsh`, I've never tried to do what you are doing and I use a different shell, but to help you debug I notice one disrepancy: looking at your first snippet of code I see `{-p,--progress}` and so I'd expect this in your `_borg_init`-function `{-t,--test}`. https://sourceforge.net/p/zsh/code/ci/master/tree/Completion/Unix/Command/_git has this consistency (line 57 and 8130 for example) – Filip Allberg Apr 11 '20 at 07:02
  • @FilipAllberg Unfortunately, using the `{-t,--test}` syntax gives the exact same error. – SLDR Apr 12 '20 at 12:07

0 Answers0