@@ -2,53 +2,166 @@ package main
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"os"
7
8
8
9
"github.com/ipfs/go-ipfs-cmds/examples/adder"
9
10
10
- "github.com/ipfs/go-ipfs-cmds"
11
+ cmds "github.com/ipfs/go-ipfs-cmds"
11
12
"github.com/ipfs/go-ipfs-cmds/cli"
12
13
)
13
14
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
+
14
75
func 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
+
15
96
// 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 )
17
98
if err != nil {
18
99
panic (err )
19
100
}
101
+ request .Options [cmds .EncLong ] = cmds .Text
20
102
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
+ }
22
108
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 )
25
111
if err != nil {
26
112
panic (err )
27
113
}
28
114
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 )) )
48
134
}
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
+ }
49
145
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
+ }()
52
165
53
- os . Exit ( cliRe . Status ())
166
+ return emitter
54
167
}
0 commit comments