@cryptotaxi247 / kubo / commits / f738e899c

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

Juan Batiz-Benet committed Nov 12, 2014 at 17:48 UTC f738e899c2f25f1b226275b53ab9d2238038e0da
2 files changed +121 -138
cmd/ipfs2/main.go
+117 -137
@@ -17,7 +17,6 @@ import (
17 cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
18 "github.com/jbenet/go-ipfs/config"
19 "github.com/jbenet/go-ipfs/core"
20 - commands "github.com/jbenet/go-ipfs/core/commands2"
20 daemon "github.com/jbenet/go-ipfs/daemon2"
21 u "github.com/jbenet/go-ipfs/util"
22 )
@@ -34,174 +33,170 @@ const (
33 errorFormat = "ERROR: %v\n\n"
34 )
35
36 +type cmdInvocation struct {
37 + path []string
38 + cmd *cmds.Command
39 + root *cmds.Command
40 + req cmds.Request
41 +}
42 +
43 +// main roadmap:
44 +// - parse the commandline to get a cmdInvocation
45 +// - if user requests, help, print it and exit.
46 +// - run the command invocation
47 +// - output the response
48 +// - if anything fails, print error, maybe with help
49 func main() {
38 - err := run()
50 + var invoc cmdInvocation
51 + var err error
52 +
53 + // we'll call this local helper to output errors.
54 + // this is so we control how to print errors in one place.
55 + printErr := func(err error) {
56 + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
57 + }
58 +
59 + // this is a local helper to print out help text.
60 + // there's some considerations that this makes easier.
61 + printHelp := func(long bool) {
62 + helpFunc := cmdsCli.ShortHelp
63 + if long {
64 + helpFunc = cmdsCli.LongHelp
65 + }
66 +
67 + helpFunc("ipfs", invoc.root, invoc.path, os.Stderr)
68 + }
69 +
70 + // parse the commandline into a command invocation
71 + err = invoc.Parse(os.Args[1:])
72 +
73 + // BEFORE handling the parse error, if we have enough information
74 + // AND the user requested help, print it out and exit
75 + if invoc.cmd != nil {
76 + longH, shortH, err := invoc.requestedHelp()
77 + if err != nil {
78 + printErr(err)
79 + os.Exit(1)
80 + }
81 + if longH || shortH {
82 + printHelp(longH)
83 + os.Exit(0)
84 + }
85 + }
86 +
87 + // ok now handle handle parse error (which means cli input was wrong,
88 + // e.g. incorrect number of args, or nonexistent subcommand)
89 if err != nil {
40 - fmt.Println(err)
90 + printErr(err)
91 +
92 + // this was a user error, print help.
93 + if invoc.cmd != nil {
94 + // we need a newline space.
95 + fmt.Fprintf(os.Stderr, "\n")
96 + printHelp(false)
97 + }
98 os.Exit(1)
99 }
43 -}
100
45 -func run() error {
46 - handleInterrupt()
47 -
48 - args := os.Args[1:]
49 - req, root, err := createRequest(args)
101 + // ok, finally, run the command invocation.
102 + output, err := invoc.Run()
103 if err != nil {
51 - // when the error is errOutputHelp, just exit gracefully.
52 - if err == errHelpRequested {
53 - return nil
104 + printErr(err)
105 +
106 + // if this error was a client error, print short help too.
107 + if isClientError(err) {
108 + printHelp(false)
109 }
55 - return err
110 + os.Exit(1)
111 }
112
58 - debug, _, err := req.Option("debug").Bool()
113 + // everything went better than expected :)
114 + io.Copy(os.Stdout, output)
115 +}
116 +
117 +func (i *cmdInvocation) Run() (output io.Reader, err error) {
118 + handleInterrupt()
119 +
120 + // check if user wants to debug. option OR env var.
121 + debug, _, err := i.req.Option("debug").Bool()
122 if err != nil {
60 - return err
123 + return nil, err
124 }
62 - if debug {
125 + if debug || u.GetenvBool("DEBUG") {
126 u.Debug = true
127 u.SetAllLoggers(logging.DEBUG)
128 }
129
130 + // if debugging, let's profile.
131 + // TODO maybe change this to its own option... profiling makes it slower.
132 if u.Debug {
133 stopProfilingFunc, err := startProfiling()
134 if err != nil {
70 - return err
135 + return nil, err
136 }
137 defer stopProfilingFunc() // to be executed as late as possible
138 }
139
75 - helpTextDisplayed, err := handleHelpOption(req, root)
76 - if err != nil {
77 - return err
78 - }
79 - if helpTextDisplayed {
80 - return nil
81 - }
82 -
83 - res, err := callCommand(req, root)
84 - if err != nil {
85 - return err
86 - }
87 -
88 - err = outputResponse(res, root)
140 + res, err := callCommand(i.req, i.root)
141 if err != nil {
90 - return err
142 + return nil, err
143 }
144
93 - return nil
145 + return res.Reader()
146 }
147
96 -func createRequest(args []string) (cmds.Request, *cmds.Command, error) {
97 - req, root, cmd, path, err := cmdsCli.Parse(args, Root)
148 +func (i *cmdInvocation) Parse(args []string) error {
149 + var err error
150
99 - // handle parse error (which means the commandline input was wrong,
100 - // e.g. incorrect number of args, or nonexistent subcommand)
151 + i.req, i.root, i.cmd, i.path, err = cmdsCli.Parse(args, Root)
152 if err != nil {
102 - return nil, nil, handleParseError(req, root, cmd, path, err)
153 + return err
154 }
155
105 - configPath, err := getConfigRoot(req)
156 + configPath, err := getConfigRoot(i.req)
157 if err != nil {
107 - return nil, nil, err
158 + return err
159 }
160
161 conf, err := getConfig(configPath)
162 if err != nil {
112 - return nil, nil, err
163 + return err
164 }
114 - ctx := req.Context()
165 + ctx := i.req.Context()
166 ctx.ConfigRoot = configPath
167 ctx.Config = conf
168
169 // if no encoding was specified by user, default to plaintext encoding
170 // (if command doesn't support plaintext, use JSON instead)
120 - if !req.Option("encoding").Found() {
121 - if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil {
122 - req.SetOption("encoding", cmds.Text)
171 + if !i.req.Option("encoding").Found() {
172 + if i.req.Command().Marshallers != nil && i.req.Command().Marshallers[cmds.Text] != nil {
173 + i.req.SetOption("encoding", cmds.Text)
174 } else {
124 - req.SetOption("encoding", cmds.JSON)
175 + i.req.SetOption("encoding", cmds.JSON)
176 }
177 }
178
128 - return req, root, nil
129 -}
130 -
131 -func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string, parseError error) error {
132 - var longHelp, shortHelp bool
133 -
134 - if req != nil {
135 - // help and h are defined in the root. We expect them to be bool.
136 - var err error
137 - longHelp, _, err = req.Option("help").Bool()
138 - if err != nil {
139 - return err
140 - }
141 - shortHelp, _, err = req.Option("h").Bool()
142 - if err != nil {
143 - return err
144 - }
145 -
146 - // override the error to avoid signaling other issues.
147 - parseError = errHelpRequested
148 - }
149 -
150 - // if the -help flag wasn't specified, show the error message
151 - // or if a path was returned (user specified a valid subcommand), show the error message
152 - // (this means there was an option or argument error)
153 - if path != nil && len(path) > 0 {
154 - if !longHelp && !shortHelp {
155 - fmt.Printf(errorFormat, parseError)
156 - }
157 - }
158 -
159 - if cmd == nil {
160 - root = commands.Root
161 - }
162 -
163 - // show the long help text if the -help flag was specified or we are at the root command
164 - // otherwise, show short help text
165 - helpFunc := cmdsCli.ShortHelp
166 - if longHelp || len(path) == 0 {
167 - helpFunc = cmdsCli.LongHelp
168 - }
169 -
170 - htErr := helpFunc("ipfs", root, path, os.Stdout)
171 - if htErr != nil {
172 - fmt.Println(htErr)
173 - }
174 - return parseError
179 + return nil
180 }
181
177 -func handleHelpOption(req cmds.Request, root *cmds.Command) (helpTextDisplayed bool, err error) {
178 - longHelp, _, err := req.Option("help").Bool()
182 +func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
183 + longHelp, _, err := i.req.Option("help").Bool()
184 if err != nil {
180 - return false, err
185 + return false, false, err
186 }
182 - shortHelp, _, err := req.Option("h").Bool()
183 - if err != nil {
184 - return false, err
185 - }
186 - if !longHelp && !shortHelp {
187 - return false, nil
188 - }
189 - helpFunc := cmdsCli.ShortHelp
190 - if longHelp || len(req.Path()) == 0 {
191 - helpFunc = cmdsCli.LongHelp
192 - }
193 -
194 - err = helpFunc("ipfs", root, req.Path(), os.Stdout)
187 + shortHelp, _, err := i.req.Option("h").Bool()
188 if err != nil {
196 - return false, err
189 + return false, false, err
190 }
198 - return true, nil
191 + return longHelp, shortHelp, nil
192 }
193
194 func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
195 var res cmds.Response
196
204 - if root == Root { // TODO explain what it means when root == Root
197 + // TODO explain what it means when root == Root
198 + // @mappum o/
199 + if root == Root {
200 res = root.Call(req)
201
202 } else {
@@ -247,35 +242,20 @@ func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
242 return res, nil
243 }
244
250 -func outputResponse(res cmds.Response, root *cmds.Command) error {
251 - if res.Error() != nil {
252 - fmt.Printf(errorFormat, res.Error().Error())
253 -
254 - if res.Error().Code != cmds.ErrClient {
255 - // TODO does ErrClient mean "error the client should see" or "this
256 - // is an error caused by the user ie. user error"?
257 - return res.Error()
258 - }
259 -
260 - // if this is a client error, we try to display help text
261 - if res.Error().Code == cmds.ErrClient {
262 - err := cmdsCli.ShortHelp("ipfs", root, res.Request().Path(), os.Stdout)
263 - if err != nil {
264 - fmt.Println(err)
265 - }
266 - }
267 -
268 - emptyErr := errors.New("") // already displayed error text
269 - return emptyErr
245 +func isClientError(err error) bool {
246 + // cast to cmds.Error
247 + cmdErr, ok := err.(*cmds.Error)
248 + if !ok {
249 + return false
250 }
251
272 - out, err := res.Reader()
273 - if err != nil {
274 - return err
252 + // here we handle the case where commands with
253 + // no Run func are invoked directly. As help requests.
254 + if err == cmds.ErrNotCallable {
255 + return true
256 }
257
277 - io.Copy(os.Stdout, out)
278 - return nil
258 + return cmdErr.Code == cmds.ErrClient
259 }
260
261 func getConfigRoot(req cmds.Request) (string, error) {
commands/cli/parse.go
+4 -1
@@ -9,6 +9,9 @@ import (
9 cmds "github.com/jbenet/go-ipfs/commands"
10 )
11
12 +// ErrInvalidSubcmd signals when the parse error is not found
13 +var ErrInvalidSubcmd = errors.New("subcommand not found")
14 +
15 // Parse parses the input commandline string (cmd, flags, and args).
16 // returns the corresponding command Request object.
17 // Multiple root commands are supported:
@@ -41,7 +44,7 @@ func Parse(input []string, roots ...*cmds.Command) (cmds.Request, *cmds.Command,
44 }
45
46 if maxLength == 0 {
44 - return nil, root, nil, path, errors.New("Not a valid subcommand")
47 + return nil, root, nil, path, ErrInvalidSubcmd
48 }
49
50 args, err := parseArgs(stringArgs, cmd)