1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-08-02 09:12:44 +08:00
Files
kubo/commands/command.go
Steven Allen b2cbfd299c gx: update go-log
License: MIT
Signed-off-by: Steven Allen <steven@stebalien.com>
2018-01-31 21:54:22 -08:00

297 lines
7.4 KiB
Go

/*
Package commands provides an API for defining and parsing commands.
Supporting nested commands, options, arguments, etc. The commands
package also supports a collection of marshallers for presenting
output to the user, including text, JSON, and XML marshallers.
*/
package commands
import (
"errors"
"fmt"
"io"
"reflect"
"github.com/ipfs/go-ipfs/path"
logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
cmds "gx/ipfs/QmZ9hww8R3FKrDRCYPxhN13m6XgjPDpaSvdUfisPvERzXz/go-ipfs-cmds"
cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
)
var log = logging.Logger("command")
// Function is the type of function that Commands use.
// It reads from the Request, and writes results to the Response.
type Function func(Request, Response)
// Marshaler is a function that takes in a Response, and returns an io.Reader
// (or an error on failure)
type Marshaler func(Response) (io.Reader, error)
// MarshalerMap is a map of Marshaler functions, keyed by EncodingType
// (or an error on failure)
type MarshalerMap map[EncodingType]Marshaler
// Command is a runnable command, with input arguments and options (flags).
// It can also have Subcommands, to group units of work into sets.
type Command struct {
Options []cmdkit.Option
Arguments []cmdkit.Argument
PreRun func(req Request) error
// Run is the function that processes the request to generate a response.
// Note that when executing the command over the HTTP API you can only read
// after writing when using multipart requests. The request body will not be
// available for reading after the HTTP connection has been written to.
Run Function
PostRun Function
Marshalers map[EncodingType]Marshaler
Helptext cmdkit.HelpText
// External denotes that a command is actually an external binary.
// fewer checks and validations will be performed on such commands.
External bool
// Type describes the type of the output of the Command's Run Function.
// In precise terms, the value of Type is an instance of the return type of
// the Run Function.
//
// ie. If command Run returns &Block{}, then Command.Type == &Block{}
Type interface{}
Subcommands map[string]*Command
}
// ErrNotCallable signals a command that cannot be called.
var ErrNotCallable = ClientError("This command can't be called directly. Try one of its subcommands.")
var ErrNoFormatter = ClientError("This command cannot be formatted to plain text")
var ErrIncorrectType = errors.New("The command returned a value with a different type than expected")
// Call invokes the command for the given Request
func (c *Command) Call(req Request) Response {
res := NewResponse(req)
cmds, err := c.Resolve(req.Path())
if err != nil {
res.SetError(err, cmdkit.ErrClient)
return res
}
cmd := cmds[len(cmds)-1]
if cmd.Run == nil {
res.SetError(ErrNotCallable, cmdkit.ErrClient)
return res
}
err = cmd.CheckArguments(req)
if err != nil {
res.SetError(err, cmdkit.ErrClient)
return res
}
err = req.ConvertOptions()
if err != nil {
res.SetError(err, cmdkit.ErrClient)
return res
}
cmd.Run(req, res)
if res.Error() != nil {
return res
}
output := res.Output()
isChan := false
actualType := reflect.TypeOf(output)
if actualType != nil {
if actualType.Kind() == reflect.Ptr {
actualType = actualType.Elem()
}
// test if output is a channel
isChan = actualType.Kind() == reflect.Chan
}
// If the command specified an output type, ensure the actual value
// returned is of that type
if cmd.Type != nil && !isChan {
expectedType := reflect.TypeOf(cmd.Type)
if actualType != expectedType {
res.SetError(ErrIncorrectType, cmdkit.ErrNormal)
return res
}
}
return res
}
// Resolve returns the subcommands at the given path
func (c *Command) Resolve(pth []string) ([]*Command, error) {
cmds := make([]*Command, len(pth)+1)
cmds[0] = c
cmd := c
for i, name := range pth {
cmd = cmd.Subcommand(name)
if cmd == nil {
pathS := path.Join(pth[:i])
return nil, fmt.Errorf("Undefined command: '%s'", pathS)
}
cmds[i+1] = cmd
}
return cmds, nil
}
// Get resolves and returns the Command addressed by path
func (c *Command) Get(path []string) (*Command, error) {
cmds, err := c.Resolve(path)
if err != nil {
return nil, err
}
return cmds[len(cmds)-1], nil
}
// GetOptions returns the options in the given path of commands
func (c *Command) GetOptions(path []string) (map[string]cmdkit.Option, error) {
options := make([]cmdkit.Option, 0, len(c.Options))
cmds, err := c.Resolve(path)
if err != nil {
return nil, err
}
cmds = append(cmds, globalCommand)
for _, cmd := range cmds {
options = append(options, cmd.Options...)
}
optionsMap := make(map[string]cmdkit.Option)
for _, opt := range options {
for _, name := range opt.Names() {
if _, found := optionsMap[name]; found {
return nil, fmt.Errorf("Option name '%s' used multiple times", name)
}
optionsMap[name] = opt
}
}
return optionsMap, nil
}
func (c *Command) CheckArguments(req Request) error {
args := req.(*request).arguments
// count required argument definitions
numRequired := 0
for _, argDef := range c.Arguments {
if argDef.Required {
numRequired++
}
}
// iterate over the arg definitions
valueIndex := 0 // the index of the current value (in `args`)
for i, argDef := range c.Arguments {
// skip optional argument definitions if there aren't
// sufficient remaining values
if len(args)-valueIndex <= numRequired && !argDef.Required ||
argDef.Type == cmdkit.ArgFile {
continue
}
// the value for this argument definition. can be nil if it
// wasn't provided by the caller
v, found := "", false
if valueIndex < len(args) {
v = args[valueIndex]
found = true
valueIndex++
}
// in the case of a non-variadic required argument that supports stdin
if !found && len(c.Arguments)-1 == i && argDef.SupportsStdin {
found = true
}
err := checkArgValue(v, found, argDef)
if err != nil {
return err
}
// any additional values are for the variadic arg definition
if argDef.Variadic && valueIndex < len(args)-1 {
for _, val := range args[valueIndex:] {
err := checkArgValue(val, true, argDef)
if err != nil {
return err
}
}
}
}
return nil
}
// Subcommand returns the subcommand with the given id
func (c *Command) Subcommand(id string) *Command {
return c.Subcommands[id]
}
type CommandVisitor func(*Command)
// Walks tree of all subcommands (including this one)
func (c *Command) Walk(visitor CommandVisitor) {
visitor(c)
for _, cm := range c.Subcommands {
cm.Walk(visitor)
}
}
func (c *Command) ProcessHelp() {
c.Walk(func(cm *Command) {
ht := &cm.Helptext
if len(ht.LongDescription) == 0 {
ht.LongDescription = ht.ShortDescription
}
})
}
// checkArgValue returns an error if a given arg value is not valid for the
// given Argument
func checkArgValue(v string, found bool, def cmdkit.Argument) error {
if def.Variadic && def.SupportsStdin {
return nil
}
if !found && def.Required {
return fmt.Errorf("Argument '%s' is required", def.Name)
}
return nil
}
func ClientError(msg string) error {
return &cmdkit.Error{Code: cmdkit.ErrClient, Message: msg}
}
// global options, added to every command
var globalOptions = []cmdkit.Option{
cmds.OptionEncodingType,
cmds.OptionStreamChannels,
cmds.OptionTimeout,
}
// the above array of Options, wrapped in a Command
var globalCommand = &Command{
Options: globalOptions,
}