generate: allow passing - for stdout output

Since generating completions to stdout for multiple shells does not make
sense, enforce `-` is supplied only once.
This commit is contained in:
Roman Inflianskas 2024-09-13 15:33:49 +03:00
parent c1532179d4
commit 3d976562fa
No known key found for this signature in database
3 changed files with 93 additions and 28 deletions

View file

@ -0,0 +1,6 @@
Enhancement: Allow generating shell completions to stdout
Restic `generate` now supports passing `-` passed as file name to `--[shell]-completion` option.
https://github.com/restic/restic/issues/2511
https://github.com/restic/restic/pull/5053

View file

@ -1,6 +1,8 @@
package main
import (
"io"
"os"
"time"
"github.com/restic/restic/internal/errors"
@ -41,10 +43,10 @@ func init() {
cmdRoot.AddCommand(cmdGenerate)
fs := cmdGenerate.Flags()
fs.StringVar(&genOpts.ManDir, "man", "", "write man pages to `directory`")
fs.StringVar(&genOpts.BashCompletionFile, "bash-completion", "", "write bash completion `file`")
fs.StringVar(&genOpts.FishCompletionFile, "fish-completion", "", "write fish completion `file`")
fs.StringVar(&genOpts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file`")
fs.StringVar(&genOpts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file`")
fs.StringVar(&genOpts.BashCompletionFile, "bash-completion", "", "write bash completion `file` (`-` for stdout)")
fs.StringVar(&genOpts.FishCompletionFile, "fish-completion", "", "write fish completion `file` (`-` for stdout)")
fs.StringVar(&genOpts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file` (`-` for stdout)")
fs.StringVar(&genOpts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)")
}
func writeManpages(dir string) error {
@ -65,32 +67,44 @@ func writeManpages(dir string) error {
return doc.GenManTree(cmdRoot, header, dir)
}
func writeBashCompletion(file string) error {
func writeCompletion(filename string, shell string, generate func(w io.Writer) error) (err error) {
if stdoutIsTerminal() {
Verbosef("writing bash completion file to %v\n", file)
Verbosef("writing %s completion file to %v\n", shell, filename)
}
return cmdRoot.GenBashCompletionFile(file)
var outWriter io.Writer
if filename != "-" {
var outFile *os.File
outFile, err = os.Create(filename)
if err != nil {
return
}
defer func() { err = outFile.Close() }()
outWriter = outFile
} else {
outWriter = globalOptions.stdout
}
err = generate(outWriter)
return
}
func writeFishCompletion(file string) error {
if stdoutIsTerminal() {
Verbosef("writing fish completion file to %v\n", file)
func checkStdoutForSingleShell(opts generateOptions) error {
completionFileOpts := []string{
opts.BashCompletionFile,
opts.FishCompletionFile,
opts.ZSHCompletionFile,
opts.PowerShellCompletionFile,
}
return cmdRoot.GenFishCompletionFile(file, true)
}
func writeZSHCompletion(file string) error {
if stdoutIsTerminal() {
Verbosef("writing zsh completion file to %v\n", file)
seenIsStdout := false
for _, completionFileOpt := range completionFileOpts {
if completionFileOpt == "-" {
if seenIsStdout {
return errors.Fatal("the generate command can generate shell completions to stdout for single shell only")
}
return cmdRoot.GenZshCompletionFile(file)
}
func writePowerShellCompletion(file string) error {
if stdoutIsTerminal() {
Verbosef("writing powershell completion file to %v\n", file)
seenIsStdout = true
}
return cmdRoot.GenPowerShellCompletionFile(file)
}
return nil
}
func runGenerate(opts generateOptions, args []string) error {
@ -105,29 +119,34 @@ func runGenerate(opts generateOptions, args []string) error {
}
}
err := checkStdoutForSingleShell(opts)
if err != nil {
return err
}
if opts.BashCompletionFile != "" {
err := writeBashCompletion(opts.BashCompletionFile)
err := writeCompletion(opts.BashCompletionFile, "bash", cmdRoot.GenBashCompletion)
if err != nil {
return err
}
}
if opts.FishCompletionFile != "" {
err := writeFishCompletion(opts.FishCompletionFile)
err := writeCompletion(opts.FishCompletionFile, "fish", func(w io.Writer) error { return cmdRoot.GenFishCompletion(w, true) })
if err != nil {
return err
}
}
if opts.ZSHCompletionFile != "" {
err := writeZSHCompletion(opts.ZSHCompletionFile)
err := writeCompletion(opts.ZSHCompletionFile, "zsh", cmdRoot.GenZshCompletion)
if err != nil {
return err
}
}
if opts.PowerShellCompletionFile != "" {
err := writePowerShellCompletion(opts.PowerShellCompletionFile)
err := writeCompletion(opts.PowerShellCompletionFile, "powershell", cmdRoot.GenPowerShellCompletion)
if err != nil {
return err
}

View file

@ -0,0 +1,40 @@
package main
import (
"bytes"
"strings"
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestGenerateStdout(t *testing.T) {
testCases := []struct {
name string
opts generateOptions
}{
{"bash", generateOptions{BashCompletionFile: "-"}},
{"fish", generateOptions{FishCompletionFile: "-"}},
{"zsh", generateOptions{ZSHCompletionFile: "-"}},
{"powershell", generateOptions{PowerShellCompletionFile: "-"}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
globalOptions.stdout = buf
err := runGenerate(tc.opts, []string{})
rtest.OK(t, err)
completionString := buf.String()
rtest.Assert(t, strings.Contains(completionString, "# "+tc.name+" completion for restic"), "has no expected completion header")
})
}
t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) {
buf := bytes.NewBuffer(nil)
globalOptions.stdout = buf
opts := generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"}
err := runGenerate(opts, []string{})
rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails")
})
}