@@ -2,53 +2,163 @@ 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
+ // `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
+
14
65
func 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
+
15
86
// 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 )
17
88
if err != nil {
18
89
panic (err )
19
90
}
91
+ request .Options [cmds .EncLong ] = cmds .Text
20
92
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
+ }
22
98
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 )
25
101
if err != nil {
26
102
panic (err )
27
103
}
28
104
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 )))
48
131
}
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
+ }
49
142
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
+ }()
52
162
53
- os . Exit ( cliRe . Status ())
163
+ return emitter
54
164
}
0 commit comments