1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-08-06 19:44:01 +08:00
Files
kubo/core/commands/commands.go
Amir Mohammad Fakhimi ced348366c feat: add zsh completions (#10040)
Co-authored-by: Henrique Dias <hacdias@gmail.com>
2023-08-17 16:43:27 +02:00

268 lines
6.7 KiB
Go

// Package commands implements the ipfs command interface
//
// Using github.com/ipfs/kubo/commands to define the command line and HTTP
// APIs. This is the interface available to folks using IPFS from outside of
// the Go language.
package commands
import (
"bytes"
"fmt"
"io"
"os"
"sort"
"strings"
cmds "github.com/ipfs/go-ipfs-cmds"
)
type commandEncoder struct {
w io.Writer
}
func (e *commandEncoder) Encode(v interface{}) error {
var (
cmd *Command
ok bool
)
if cmd, ok = v.(*Command); !ok {
return fmt.Errorf(`core/commands: unexpected type %T, expected *"core/commands".Command`, v)
}
for _, s := range cmdPathStrings(cmd, cmd.showOpts) {
_, err := e.w.Write([]byte(s + "\n"))
if err != nil {
return err
}
}
return nil
}
type Command struct {
Name string
Subcommands []Command
Options []Option
showOpts bool
}
type Option struct {
Names []string
}
const (
flagsOptionName = "flags"
)
// CommandsCmd takes in a root command,
// and returns a command that lists the subcommands in that root
func CommandsCmd(root *cmds.Command) *cmds.Command {
return &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List all available commands.",
ShortDescription: `Lists all available commands (and subcommands) and exits.`,
},
Subcommands: map[string]*cmds.Command{
"completion": CompletionCmd(root),
},
Options: []cmds.Option{
cmds.BoolOption(flagsOptionName, "f", "Show command flags"),
},
Extra: CreateCmdExtras(SetDoesNotUseRepo(true)),
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
rootCmd := cmd2outputCmd("ipfs", root)
rootCmd.showOpts, _ = req.Options[flagsOptionName].(bool)
return cmds.EmitOnce(res, &rootCmd)
},
Encoders: cmds.EncoderMap{
cmds.Text: func(req *cmds.Request) func(io.Writer) cmds.Encoder {
return func(w io.Writer) cmds.Encoder { return &commandEncoder{w} }
},
},
Type: Command{},
}
}
func cmd2outputCmd(name string, cmd *cmds.Command) Command {
opts := make([]Option, len(cmd.Options))
for i, opt := range cmd.Options {
opts[i] = Option{opt.Names()}
}
output := Command{
Name: name,
Subcommands: make([]Command, 0, len(cmd.Subcommands)),
Options: opts,
}
for name, sub := range cmd.Subcommands {
output.Subcommands = append(output.Subcommands, cmd2outputCmd(name, sub))
}
return output
}
func cmdPathStrings(cmd *Command, showOptions bool) []string {
var cmds []string
var recurse func(prefix string, cmd *Command)
recurse = func(prefix string, cmd *Command) {
newPrefix := prefix + cmd.Name
cmds = append(cmds, newPrefix)
if prefix != "" && showOptions {
for _, options := range cmd.Options {
var cmdOpts []string
for _, flag := range options.Names {
if len(flag) == 1 {
flag = "-" + flag
} else {
flag = "--" + flag
}
cmdOpts = append(cmdOpts, newPrefix+" "+flag)
}
cmds = append(cmds, strings.Join(cmdOpts, " / "))
}
}
for _, sub := range cmd.Subcommands {
recurse(newPrefix+" ", &sub)
}
}
recurse("", cmd)
sort.Strings(cmds)
return cmds
}
func CompletionCmd(root *cmds.Command) *cmds.Command {
return &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Generate shell completions.",
},
NoRemote: true,
Subcommands: map[string]*cmds.Command{
"bash": {
Helptext: cmds.HelpText{
Tagline: "Generate bash shell completions.",
ShortDescription: "Generates command completions for the bash shell.",
LongDescription: `
Generates command completions for the bash shell.
The simplest way to see it working is write the completions
to a file and then source it:
> ipfs commands completion bash > ipfs-completion.bash
> source ./ipfs-completion.bash
To install the completions permanently, they can be moved to
/etc/bash_completion.d or sourced from your ~/.bashrc file.
`,
},
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var buf bytes.Buffer
if err := writeBashCompletions(root, &buf); err != nil {
return err
}
res.SetLength(uint64(buf.Len()))
return res.Emit(&buf)
},
},
"zsh": {
Helptext: cmds.HelpText{
Tagline: "Generate zsh shell completions.",
ShortDescription: "Generates command completions for the zsh shell.",
LongDescription: `
Generates command completions for the zsh shell.
The simplest way to see it working is write the completions
to a file and then source it:
> ipfs commands completion zsh > ipfs-completion.zsh
> source ./ipfs-completion.zsh
To install the completions permanently, they can be moved to
/etc/zsh/completions or sourced from your ~/.zshrc file.
`,
},
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var buf bytes.Buffer
if err := writeZshCompletions(root, &buf); err != nil {
return err
}
res.SetLength(uint64(buf.Len()))
return res.Emit(&buf)
},
},
"fish": {
Helptext: cmds.HelpText{
Tagline: "Generate fish shell completions.",
ShortDescription: "Generates command completions for the fish shell.",
LongDescription: `
Generates command completions for the fish shell.
The simplest way to see it working is write the completions
to a file and then source it:
> ipfs commands completion fish > ipfs-completion.fish
> source ./ipfs-completion.fish
To install the completions permanently, they can be moved to
/etc/fish/completions or ~/.config/fish/completions or sourced from your ~/.config/fish/config.fish file.
`,
},
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var buf bytes.Buffer
if err := writeFishCompletions(root, &buf); err != nil {
return err
}
res.SetLength(uint64(buf.Len()))
return res.Emit(&buf)
},
},
},
}
}
type nonFatalError string
// streamResult is a helper function to stream results that possibly
// contain non-fatal errors. The helper function is allowed to panic
// on internal errors.
func streamResult(procVal func(interface{}, io.Writer) nonFatalError) func(cmds.Response, cmds.ResponseEmitter) error {
return func(res cmds.Response, re cmds.ResponseEmitter) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v", r)
}
re.Close()
}()
var errors bool
for {
v, err := res.Next()
if err != nil {
if err == io.EOF {
break
}
return err
}
errorMsg := procVal(v, os.Stdout)
if errorMsg != "" {
errors = true
fmt.Fprintf(os.Stderr, "%s\n", errorMsg)
}
}
if errors {
return fmt.Errorf("errors while displaying some entries")
}
return nil
}
}