Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func getOwnCommands() []ownCommand {
"--json-schema [--version 0.15] [v1|v2]": "generate a JSON schema that validates resticprofile configuration files in YAML or JSON format",
"--bash-completion": "generate a shell completion script for bash",
"--zsh-completion": "generate a shell completion script for zsh",
"--fish-completion": "generate a shell completion script for fish",
},
},
// commands that need the configuration
Expand Down Expand Up @@ -182,7 +183,7 @@ func completeCommand(output io.Writer, ctx commandContext) error {

// Parse requester as first argument. Format "[kind]:v[version]", e.g. "bash:v1"
if len(args) > 0 {
matcher := regexp.MustCompile(`^(bash|zsh):v(\d+)$`)
matcher := regexp.MustCompile(`^(bash|zsh|fish):v(\d+)$`)
if matches := matcher.FindStringSubmatch(args[0]); matches != nil {
requester = matches[1]
if v, err := strconv.Atoi(matches[2]); err == nil {
Expand All @@ -202,7 +203,12 @@ func completeCommand(output io.Writer, ctx commandContext) error {
return nil
}

completions := NewCompleter(ctx.ownCommands.All(), DefaultFlagsLoader).Complete(args)
includeDescription := false
if requester == "fish" {
includeDescription = true
}

completions := NewCompleter(ctx.ownCommands.All(), DefaultFlagsLoader, includeDescription).Complete(args)
if len(completions) > 0 {
for _, completion := range completions {
fmt.Fprintln(output, completion)
Expand Down
5 changes: 5 additions & 0 deletions commands_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var bashCompletionScript string
//go:embed contrib/completion/zsh-completion.sh
var zshCompletionScript string

//go:embed contrib/completion/fish-completion.fish
var fishCompletionScript string

func generateCommand(output io.Writer, ctx commandContext) (err error) {
args := ctx.request.arguments
// enforce no-log
Expand All @@ -44,6 +47,8 @@ func generateCommand(output io.Writer, ctx commandContext) (err error) {
err = randomKey(output, ctx)
} else if slices.Contains(args, "--zsh-completion") {
_, err = fmt.Fprintln(output, zshCompletionScript)
} else if slices.Contains(args, "--fish-completion") {
_, err = fmt.Fprintln(output, fishCompletionScript)
} else {
err = fmt.Errorf("nothing to generate for: %s", strings.Join(args, ", "))
}
Expand Down
14 changes: 13 additions & 1 deletion commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ _ = 0
}

func TestCompleteCall(t *testing.T) {
completer := NewCompleter(ownCommands.All(), DefaultFlagsLoader)
completer := NewCompleter(ownCommands.All(), DefaultFlagsLoader, false)
completer.init(nil)
newline := fmt.Sprintln("")
expectedFlags := strings.Join(completer.completeFlagSet(""), newline) + newline
Expand All @@ -178,13 +178,18 @@ func TestCompleteCall(t *testing.T) {
expectedCommands := strings.Join(commandNames, newline) + newline +
RequestResticCompletion + newline

completerWithDescriptions := NewCompleter(ownCommands.All(), DefaultFlagsLoader, true)
completerWithDescriptions.init(nil)
expectedFlagsWithDescriptions := strings.Join(completerWithDescriptions.completeFlagSet(""), newline) + newline

testTable := []struct {
args []string
expected string
}{
{args: []string{"--"}, expected: expectedFlags},
{args: []string{"--config=does-not-exist", ""}, expected: expectedCommands},
{args: []string{"bash:v1", "--"}, expected: expectedFlags},
{args: []string{"fish:v1", "--"}, expected: expectedFlagsWithDescriptions},
{args: []string{"bash:v10", "--"}, expected: ""},
{args: []string{"zsh:v1", "--"}, expected: ""},
}
Expand Down Expand Up @@ -223,6 +228,13 @@ func TestGenerateCommand(t *testing.T) {
assert.Contains(t, zshCompletionScript, "#!/usr/bin/env zsh")
})

t.Run("--fish-completion", func(t *testing.T) {
buffer.Reset()
assert.Nil(t, generateCommand(buffer, contextWithArguments([]string{"--fish-completion"})))
assert.Equal(t, strings.TrimSpace(fishCompletionScript), strings.TrimSpace(buffer.String()))
assert.Contains(t, fishCompletionScript, "#!/usr/bin/env fish")
})

t.Run("--config-reference", func(t *testing.T) {
buffer.Reset()
assert.NoError(t, generateCommand(buffer, contextWithArguments([]string{"--config-reference", "--to", t.TempDir()})))
Expand Down
19 changes: 14 additions & 5 deletions complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Completer struct {
ownCommands []ownCommand
profiles []string
enableProfilePrefixes bool
includeDescription bool
}

type FlagsLoader func(args []string) *pflag.FlagSet
Expand All @@ -38,8 +39,8 @@ func DefaultFlagsLoader(args []string) (flags *pflag.FlagSet) {
return
}

func NewCompleter(commands []ownCommand, loader FlagsLoader) *Completer {
return &Completer{ownCommands: commands, loader: loader}
func NewCompleter(commands []ownCommand, loader FlagsLoader, includeDescription bool) *Completer {
return &Completer{ownCommands: commands, loader: loader, includeDescription: includeDescription}
}

func (c *Completer) init(args []string) {
Expand All @@ -63,10 +64,15 @@ func (c *Completer) init(args []string) {
}

func (c *Completer) formatFlag(flag *pflag.Flag, shorthand bool) string {
description := ""
if c.includeDescription {
description = fmt.Sprintf("\t%s", flag.Usage)
}

if shorthand {
return fmt.Sprintf("-%s", flag.Shorthand)
return fmt.Sprintf("-%s%s", flag.Shorthand, description)
} else {
return fmt.Sprintf("--%s", flag.Name)
return fmt.Sprintf("--%s%s", flag.Name, description)
}
}

Expand Down Expand Up @@ -169,7 +175,10 @@ func (c *Completer) completeProfileNamePrefixes(word string) (completions []stri
}

func (c *Completer) formatOwnCommand(command ownCommand) string {
return command.name
if !c.includeDescription {
return command.name
}
return fmt.Sprintf("%s\t%s", command.name, command.description)
}

func (c *Completer) completeOwnCommands(word string) (completions []string) {
Expand Down
2 changes: 1 addition & 1 deletion complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestCompleter(t *testing.T) {
completer := NewCompleter(ownCommands.All(), DefaultFlagsLoader)
completer := NewCompleter(ownCommands.All(), DefaultFlagsLoader, false)
completer.init(nil)

expectedProfiles := func() []string {
Expand Down
84 changes: 84 additions & 0 deletions contrib/completion/fish-completion.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env fish
#
# resticprofile fish completion script
# Usage: see https://fishshell.com/docs/current/completions.html#where-to-put-completions

function __resticprofile_completion
set --local full_cmdline (commandline -x)
set --local cmdline_to_cursor \
(string split -- " " (string escape -- (commandline -cx; commandline -ct)))
set --local current_token_pos (math (count $cmdline_to_cursor) - 1)

#send commandline to 'resticprofile complete' in the format it expects
set --local completions ("$full_cmdline[1]" complete "fish:v1" "__POS:$current_token_pos" "$full_cmdline[2..]")

if test (count $completions) = 0
return
end

#handle the directive returned from resticprofile
switch $completions[-1]
case "__complete_file"
set --erase completions[-1]

set --local file $full_cmdline[-1]
#if file starts with '-', remove it
test (string sub --length 1 -- "$file") = "-"; and set file ""

#do path completion
set --append completions (__fish_complete_path "$file")

case "__complete_restic"
#string match --regex returns list where first element is the whole string
#and the rest are capture group matches.
set --local prefix (string match --regex '^(.*)\.' -- "$completions[-1]")[2]
set --local suffix (string match --regex '\.([^.]+)$' -- "$completions[-1]")[2]
set --erase completions[-1]

#This removes profile prefixes before forwarding the completion to restic
set --local restic_words
for word in $cmdline_to_cursor[2..]
if test (string match --regex ".*\/.*" -- "$word")
set --append restic_words (string unescape -- "$word")
else
#take everything after last dot, if there is one
set --append restic_words (string match --regex '([^.]+)$' -- $word)[2]
end
end

#Add a space if the cursor is past the last token so that 'complete' doesn't
#return a command that's already fully typed out
if test "$restic_words[-1]" = "''"
set restic_words[-1] " "
end

#get restic completions
set --local restic_values (complete --do-complete "restic $restic_words")

if test (count $restic_values) = 0
for x in $completions
echo $x
end
return
end

for value in $restic_values
#remove empty values
string match --regex '^[\r\n\t ]+$' -- "$value"; and return

#add prefix back to completion if applicable
if test -n $prefix && test "$prefix" != "$suffix"
set --append completions "$prefix.$value"
else
set --append completions "$value"
end
end
end

for x in $completions
echo $x
end

end

complete --command resticprofile --no-files --arguments "(__resticprofile_completion)"
1 change: 1 addition & 0 deletions docs/content/configuration/getting_started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ Flags:
--json-schema [--version 0.15] [v1|v2] generate a JSON schema that validates resticprofile configuration files in YAML or JSON format
--random-key [size] generate a cryptographically secure random key to use as a restic keyfile (size defaults to 1024 when omitted)
--zsh-completion generate a shell completion script for zsh
--fish-completion generate a shell completion script for fish

```

Expand Down
8 changes: 7 additions & 1 deletion docs/content/installation/shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ weight: 100
---


Shell command line completions are provided for `bash` and `zsh`.
Shell command line completions are provided for `bash`, `fish`, and `zsh`.

To load the command completions in shell, use:

```shell
# bash
eval "$(resticprofile generate --bash-completion)"

# fish
resticprofile generate --fish-completion | source

# zsh
eval "$(resticprofile generate --zsh-completion)"
```
Expand All @@ -21,4 +24,7 @@ To install them permanently:
```shell
resticprofile generate --bash-completion > /etc/bash_completion.d/resticprofile
chmod +x /etc/bash_completion.d/resticprofile

resticprofile generate --fish-completion > /etc/fish/completions/resticprofile.fish
chmod +x /etc/fish/completions/resticprofile.fish
```