Skip to content

Commit d3d15e7

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

File tree

1 file changed

+137
-27
lines changed

1 file changed

+137
-27
lines changed

examples/adder/local/main.go

Lines changed: 137 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,163 @@ 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+
// `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+
1465
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+
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

Comments
 (0)