@@ -2,53 +2,166 @@ package main
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "os"
78
89 "github.com/ipfs/go-ipfs-cmds/examples/adder"
910
10- "github.com/ipfs/go-ipfs-cmds"
11+ cmds "github.com/ipfs/go-ipfs-cmds"
1112 "github.com/ipfs/go-ipfs-cmds/cli"
1213)
1314
15+ /* TODO:
16+ - there's too many words in here
17+ - text style consistency; sleepy case is currently used with Chicago norm expected as majority
18+ - We should also move a lot of this into the subcommand definition and refer to that
19+ instead, of documenting internals in very big comments
20+ - this would allow us to utilize env inside of `Run` which is important for demonstration
21+ - Need to trace this when done and make sure the comments are telling the truth about the execution path
22+ - Should probably throw in a custom emitter example somewhere since we defined one elsewhere
23+ - and many more
24+ */
25+
26+ // `cli.Run` will read a `cmds.Command`'s fields and perform standard behaviour
27+ // for package defined values.
28+ // For example, if the `Command` declares it has `cmds.OptLongHelp` or `cmds.OptShortHelp` options,
29+ // `cli.Run` will check for and handle these flags automatically (but they are not required).
30+ //
31+ // If needed, the caller may define an arbitrary "environment"
32+ // and expect to receive this environment during execution of the `Command`.
33+ // While not required we'll define one for the sake of example.
34+ type (
35+ envChan chan error
36+
37+ ourEnviornment struct {
38+ whateverWeNeed envChan
39+ }
40+ )
41+
42+ // A `Close` method is also optional; if defined, it's deferred inside of `cli.Run`.
43+ func (env * ourEnviornment ) Close () error {
44+ if env .whateverWeNeed != nil {
45+ close (env .whateverWeNeed )
46+ }
47+ return nil
48+ }
49+
50+ // If desired, the caller may define additional methods that
51+ // they may need during execution of the `cmds.Command`.
52+ // Considering the environment constructor returns an untyped interface,
53+ // it's a good idea to define additional interfaces that can be used for behaviour checking.
54+ // (Especially if you choose to return different concrete environments for different requests.)
55+ func (env * ourEnviornment ) getChan () envChan {
56+ return env .whateverWeNeed
57+ }
58+
59+ type specificEnvironment interface {
60+ getChan () envChan
61+ }
62+
63+ // While the environment itself is not be required,
64+ // its constructor and receiver methods are.
65+ // We'll define them here without any special request parsing, since we don't need it.
66+ func makeOurEnvironment (ctx context.Context , req * cmds.Request ) (cmds.Environment , error ) {
67+ return & ourEnviornment {
68+ whateverWeNeed : make (envChan ),
69+ }, nil
70+ }
71+ func makeOurExecutor (req * cmds.Request , env interface {}) (cmds.Executor , error ) {
72+ return cmds .NewExecutor (adder .RootCmd ), nil
73+ }
74+
1475func main () {
76+ var (
77+ ctx = context .TODO ()
78+ // If the environment constructor does not return an error
79+ // it will pass the environment to the `cmds.Executor` within `cli.Run`;
80+ // which passes it to the `Command`'s (optional)`PreRun` and/or `Run` methods.
81+ err = cli .Run (ctx , adder .RootCmd , os .Args , // pass in command and args to parse
82+ os .Stdin , os .Stdout , os .Stderr , // along with output writers
83+ makeOurEnvironment , makeOurExecutor ) // and our constructor+receiver pair
84+ )
85+ cliError := new (cli.ExitError )
86+ if errors .As (err , cliError ) {
87+ os .Exit (int ((* cliError )))
88+ }
89+ }
90+
91+ // `cli.Run` is a convenient wrapper and not required.
92+ // If desired, the caller may define the entire means to process a `cmds.Command` themselves.
93+ func altMain () {
94+ ctx := context .TODO ()
95+
1596 // parse the command path, arguments and options from the command line
16- req , err := cli .Parse (context . TODO () , os .Args [1 :], os .Stdin , adder .RootCmd )
97+ request , err := cli .Parse (ctx , os .Args [1 :], os .Stdin , adder .RootCmd )
1798 if err != nil {
1899 panic (err )
19100 }
101+ request .Options [cmds .EncLong ] = cmds .Text
20102
21- req .Options ["encoding" ] = cmds .Text
103+ // create an environment from the request
104+ cmdEnv , err := makeOurEnvironment (ctx , request )
105+ if err != nil {
106+ panic (err )
107+ }
22108
23- // create an emitter
24- cliRe , err := cli . NewResponseEmitter ( os . Stdout , os . Stderr , req )
109+ // get values specific to our request+environment pair
110+ wait , err := customPreRun ( request , cmdEnv )
25111 if err != nil {
26112 panic (err )
27113 }
28114
29- wait := make ( chan struct {})
30- var re cmds. ResponseEmitter = cliRe
31- if pr , ok := req . Command . PostRun [ cmds . CLI ]; ok {
32- var (
33- res cmds. Response
34- lower = re
35- )
36-
37- re , res = cmds . NewChanResponsePair ( req )
38-
39- go func () {
40- defer close ( wait )
41- err := pr ( res , lower )
42- if err != nil {
43- fmt . Println ( "error: " , err )
44- }
45- }( )
46- } else {
47- close ( wait )
115+ // This emitter's `Emit` method will be called from within the `Command`'s `Run` method.
116+ // If `Run` encounters a fatal error, the emitter should be closed with `emitter.CloseWithError(err)`
117+ // otherwise, it will be closed automatically after `Run` within `Call`.
118+ var emitter cmds. ResponseEmitter
119+ emitter , err = cli . NewResponseEmitter ( os . Stdout , os . Stderr , request )
120+ if err != nil {
121+ panic ( err )
122+ }
123+
124+ // if the command has a `PostRun` method, emit responses to it instead
125+ emitter = maybePostRun ( request , emitter , wait )
126+
127+ // call the actual `Run` method on the command
128+ adder . RootCmd . Call ( request , emitter , cmdEnv )
129+ err = <- wait
130+
131+ cliError := new (cli. ExitError )
132+ if errors . As ( err , cliError ) {
133+ os . Exit ( int (( * cliError )) )
48134 }
135+ }
136+
137+ func customPreRun (req * cmds.Request , env cmds.Environment ) (envChan , error ) {
138+ // check that the constructor passed us the environment we expect/need
139+ ourEnvIntf , ok := env .(specificEnvironment )
140+ if ! ok {
141+ return nil , fmt .Errorf ("environment received does not satisfy expected interface" )
142+ }
143+ return ourEnvIntf .getChan (), nil
144+ }
49145
50- adder .RootCmd .Call (req , re , nil )
51- <- wait
146+ func maybePostRun (req * cmds.Request , emitter cmds.ResponseEmitter , wait envChan ) cmds.ResponseEmitter {
147+ postRun , provided := req .Command .PostRun [cmds .CLI ]
148+ if ! provided { // no `PostRun` command was defined
149+ close (wait ) // don't do anything and unblock instantly
150+ return emitter
151+ }
152+
153+ var ( // store the emitter passed to us
154+ postRunEmitter = emitter
155+ response cmds.Response
156+ )
157+ // replace the caller's emitter with one that emits to this `Response` interface
158+ emitter , response = cmds .NewChanResponsePair (req )
159+
160+ go func () { // start listening for emission on the emitter
161+ // wait for `PostRun` to return, and send its value to the caller
162+ wait <- postRun (response , postRunEmitter )
163+ close (wait )
164+ }()
52165
53- os . Exit ( cliRe . Status ())
166+ return emitter
54167}
0 commit comments