Skip to content

Commit a2f92a0

Browse files
committed
WIP: update examples
1 parent 4ade007 commit a2f92a0

File tree

1 file changed

+140
-27
lines changed

1 file changed

+140
-27
lines changed

examples/adder/local/main.go

Lines changed: 140 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,166 @@ package main
22

33
import (
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+
1475
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+
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

Comments
 (0)