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