提交 f738e899 作者: Juan Batiz-Benet

cmd2: simplified main

Attention @maybebtc @mappum

I cleaned up + simplified the main flow. Now, all printing
is contained inside main itself! (:cheer:). I do this with
the help of a cmdInvocation struct that has both
a Parse and Run. The only major clunkiness left is that the
"CallCommand" is still its own function. But *shrug*.

Please test it works as we would expect. i changed much of
the flow, so likely that i missed a complicated edge case.

main roadmap:
- parse the commandline to get a cmdInvocation
- if user requests, help, print it and exit.
- run the command invocation
- output the response
- if anything fails, print error, maybe with help
上级 973a8f5c
......@@ -17,7 +17,6 @@ import (
cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
"github.com/jbenet/go-ipfs/config"
"github.com/jbenet/go-ipfs/core"
commands "github.com/jbenet/go-ipfs/core/commands2"
daemon "github.com/jbenet/go-ipfs/daemon2"
u "github.com/jbenet/go-ipfs/util"
)
......@@ -34,174 +33,170 @@ const (
errorFormat = "ERROR: %v\n\n"
)
type cmdInvocation struct {
path []string
cmd *cmds.Command
root *cmds.Command
req cmds.Request
}
// main roadmap:
// - parse the commandline to get a cmdInvocation
// - if user requests, help, print it and exit.
// - run the command invocation
// - output the response
// - if anything fails, print error, maybe with help
func main() {
err := run()
var invoc cmdInvocation
var err error
// we'll call this local helper to output errors.
// this is so we control how to print errors in one place.
printErr := func(err error) {
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
}
// this is a local helper to print out help text.
// there's some considerations that this makes easier.
printHelp := func(long bool) {
helpFunc := cmdsCli.ShortHelp
if long {
helpFunc = cmdsCli.LongHelp
}
helpFunc("ipfs", invoc.root, invoc.path, os.Stderr)
}
// parse the commandline into a command invocation
err = invoc.Parse(os.Args[1:])
// BEFORE handling the parse error, if we have enough information
// AND the user requested help, print it out and exit
if invoc.cmd != nil {
longH, shortH, err := invoc.requestedHelp()
if err != nil {
printErr(err)
os.Exit(1)
}
if longH || shortH {
printHelp(longH)
os.Exit(0)
}
}
// ok now handle handle parse error (which means cli input was wrong,
// e.g. incorrect number of args, or nonexistent subcommand)
if err != nil {
fmt.Println(err)
printErr(err)
// this was a user error, print help.
if invoc.cmd != nil {
// we need a newline space.
fmt.Fprintf(os.Stderr, "\n")
printHelp(false)
}
os.Exit(1)
}
}
func run() error {
handleInterrupt()
args := os.Args[1:]
req, root, err := createRequest(args)
// ok, finally, run the command invocation.
output, err := invoc.Run()
if err != nil {
// when the error is errOutputHelp, just exit gracefully.
if err == errHelpRequested {
return nil
printErr(err)
// if this error was a client error, print short help too.
if isClientError(err) {
printHelp(false)
}
return err
os.Exit(1)
}
debug, _, err := req.Option("debug").Bool()
// everything went better than expected :)
io.Copy(os.Stdout, output)
}
func (i *cmdInvocation) Run() (output io.Reader, err error) {
handleInterrupt()
// check if user wants to debug. option OR env var.
debug, _, err := i.req.Option("debug").Bool()
if err != nil {
return err
return nil, err
}
if debug {
if debug || u.GetenvBool("DEBUG") {
u.Debug = true
u.SetAllLoggers(logging.DEBUG)
}
// if debugging, let's profile.
// TODO maybe change this to its own option... profiling makes it slower.
if u.Debug {
stopProfilingFunc, err := startProfiling()
if err != nil {
return err
return nil, err
}
defer stopProfilingFunc() // to be executed as late as possible
}
helpTextDisplayed, err := handleHelpOption(req, root)
if err != nil {
return err
}
if helpTextDisplayed {
return nil
}
res, err := callCommand(req, root)
if err != nil {
return err
}
err = outputResponse(res, root)
res, err := callCommand(i.req, i.root)
if err != nil {
return err
return nil, err
}
return nil
return res.Reader()
}
func createRequest(args []string) (cmds.Request, *cmds.Command, error) {
req, root, cmd, path, err := cmdsCli.Parse(args, Root)
func (i *cmdInvocation) Parse(args []string) error {
var err error
// handle parse error (which means the commandline input was wrong,
// e.g. incorrect number of args, or nonexistent subcommand)
i.req, i.root, i.cmd, i.path, err = cmdsCli.Parse(args, Root)
if err != nil {
return nil, nil, handleParseError(req, root, cmd, path, err)
return err
}
configPath, err := getConfigRoot(req)
configPath, err := getConfigRoot(i.req)
if err != nil {
return nil, nil, err
return err
}
conf, err := getConfig(configPath)
if err != nil {
return nil, nil, err
return err
}
ctx := req.Context()
ctx := i.req.Context()
ctx.ConfigRoot = configPath
ctx.Config = conf
// if no encoding was specified by user, default to plaintext encoding
// (if command doesn't support plaintext, use JSON instead)
if !req.Option("encoding").Found() {
if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil {
req.SetOption("encoding", cmds.Text)
if !i.req.Option("encoding").Found() {
if i.req.Command().Marshallers != nil && i.req.Command().Marshallers[cmds.Text] != nil {
i.req.SetOption("encoding", cmds.Text)
} else {
req.SetOption("encoding", cmds.JSON)
i.req.SetOption("encoding", cmds.JSON)
}
}
return req, root, nil
}
func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string, parseError error) error {
var longHelp, shortHelp bool
if req != nil {
// help and h are defined in the root. We expect them to be bool.
var err error
longHelp, _, err = req.Option("help").Bool()
if err != nil {
return err
}
shortHelp, _, err = req.Option("h").Bool()
if err != nil {
return err
}
// override the error to avoid signaling other issues.
parseError = errHelpRequested
}
// if the -help flag wasn't specified, show the error message
// or if a path was returned (user specified a valid subcommand), show the error message
// (this means there was an option or argument error)
if path != nil && len(path) > 0 {
if !longHelp && !shortHelp {
fmt.Printf(errorFormat, parseError)
}
}
if cmd == nil {
root = commands.Root
}
// show the long help text if the -help flag was specified or we are at the root command
// otherwise, show short help text
helpFunc := cmdsCli.ShortHelp
if longHelp || len(path) == 0 {
helpFunc = cmdsCli.LongHelp
}
htErr := helpFunc("ipfs", root, path, os.Stdout)
if htErr != nil {
fmt.Println(htErr)
}
return parseError
return nil
}
func handleHelpOption(req cmds.Request, root *cmds.Command) (helpTextDisplayed bool, err error) {
longHelp, _, err := req.Option("help").Bool()
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
longHelp, _, err := i.req.Option("help").Bool()
if err != nil {
return false, err
return false, false, err
}
shortHelp, _, err := req.Option("h").Bool()
if err != nil {
return false, err
}
if !longHelp && !shortHelp {
return false, nil
}
helpFunc := cmdsCli.ShortHelp
if longHelp || len(req.Path()) == 0 {
helpFunc = cmdsCli.LongHelp
}
err = helpFunc("ipfs", root, req.Path(), os.Stdout)
shortHelp, _, err := i.req.Option("h").Bool()
if err != nil {
return false, err
return false, false, err
}
return true, nil
return longHelp, shortHelp, nil
}
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
var res cmds.Response
if root == Root { // TODO explain what it means when root == Root
// TODO explain what it means when root == Root
// @mappum o/
if root == Root {
res = root.Call(req)
} else {
......@@ -247,35 +242,20 @@ func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
return res, nil
}
func outputResponse(res cmds.Response, root *cmds.Command) error {
if res.Error() != nil {
fmt.Printf(errorFormat, res.Error().Error())
if res.Error().Code != cmds.ErrClient {
// TODO does ErrClient mean "error the client should see" or "this
// is an error caused by the user ie. user error"?
return res.Error()
}
// if this is a client error, we try to display help text
if res.Error().Code == cmds.ErrClient {
err := cmdsCli.ShortHelp("ipfs", root, res.Request().Path(), os.Stdout)
if err != nil {
fmt.Println(err)
}
}
emptyErr := errors.New("") // already displayed error text
return emptyErr
func isClientError(err error) bool {
// cast to cmds.Error
cmdErr, ok := err.(*cmds.Error)
if !ok {
return false
}
out, err := res.Reader()
if err != nil {
return err
// here we handle the case where commands with
// no Run func are invoked directly. As help requests.
if err == cmds.ErrNotCallable {
return true
}
io.Copy(os.Stdout, out)
return nil
return cmdErr.Code == cmds.ErrClient
}
func getConfigRoot(req cmds.Request) (string, error) {
......
......@@ -9,6 +9,9 @@ import (
cmds "github.com/jbenet/go-ipfs/commands"
)
// ErrInvalidSubcmd signals when the parse error is not found
var ErrInvalidSubcmd = errors.New("subcommand not found")
// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
// Multiple root commands are supported:
......@@ -41,7 +44,7 @@ func Parse(input []string, roots ...*cmds.Command) (cmds.Request, *cmds.Command,
}
if maxLength == 0 {
return nil, root, nil, path, errors.New("Not a valid subcommand")
return nil, root, nil, path, ErrInvalidSubcmd
}
args, err := parseArgs(stringArgs, cmd)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论