Skip to content

Commit 11083f0

Browse files
committed
Post-run emitter helper
- When both PostRun (output transformer) and DisplayCLI (terminal output) are present, ensure that both are run. `EmitResponse` helper in executor.go is invoked by both local and HTTP client executors: the client fibs the command.Run interface via anonymous function.
1 parent 16f14e9 commit 11083f0

File tree

4 files changed

+174
-59
lines changed

4 files changed

+174
-59
lines changed

Diff for: examples/adder/cmd.go

+135-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ var RootCmd = &cmds.Command{
9393
}),
9494
},
9595
},
96-
// the best UX
96+
// using stdio via PostRun
9797
"postRunAdd": {
9898
Arguments: []cmds.Argument{
9999
cmds.StringArg("summands", true, true, "values that are supposed to be summed"),
@@ -151,6 +151,140 @@ var RootCmd = &cmds.Command{
151151
},
152152
},
153153
},
154+
// DisplayCLI for terminal control
155+
"displayCliAdd": {
156+
Arguments: []cmds.Argument{
157+
cmds.StringArg("summands", true, true, "values that are supposed to be summed"),
158+
},
159+
// this is the same as for encoderAdd
160+
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
161+
sum := 0
162+
163+
for i, str := range req.Arguments {
164+
num, err := strconv.Atoi(str)
165+
if err != nil {
166+
return err
167+
}
168+
169+
sum += num
170+
err = re.Emit(&AddStatus{
171+
Current: sum,
172+
Left: len(req.Arguments) - i - 1,
173+
})
174+
if err != nil {
175+
return err
176+
}
177+
178+
time.Sleep(200 * time.Millisecond)
179+
}
180+
return nil
181+
},
182+
Type: &AddStatus{},
183+
DisplayCLI: func(res cmds.Response, stdout, stderr io.Writer) error {
184+
defer fmt.Fprintln(stdout)
185+
186+
// length of line at last iteration
187+
var lastLen int
188+
189+
for {
190+
v, err := res.Next()
191+
if err == io.EOF {
192+
return nil
193+
}
194+
if err != nil {
195+
return err
196+
}
197+
198+
fmt.Fprint(stdout, "\r" + strings.Repeat(" ", lastLen))
199+
200+
s := v.(*AddStatus)
201+
if s.Left > 0 {
202+
lastLen, _ = fmt.Fprintf(stdout, "\rcalculation sum... current: %d; left: %d", s.Current, s.Left)
203+
} else {
204+
lastLen, _ = fmt.Fprintf(stdout, "\rsum is %d.", s.Current)
205+
}
206+
}
207+
},
208+
},
209+
// PostRun and DisplayCLI: PostRun intercepts and doubles the sum
210+
"defectiveAdd": {
211+
Arguments: []cmds.Argument{
212+
cmds.StringArg("summands", true, true, "values that are supposed to be summed"),
213+
},
214+
// this is the same as for encoderAdd
215+
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
216+
sum := 0
217+
218+
for i, str := range req.Arguments {
219+
num, err := strconv.Atoi(str)
220+
if err != nil {
221+
return err
222+
}
223+
224+
sum += num
225+
err = re.Emit(&AddStatus{
226+
Current: sum,
227+
Left: len(req.Arguments) - i - 1,
228+
})
229+
if err != nil {
230+
return err
231+
}
232+
233+
time.Sleep(200 * time.Millisecond)
234+
}
235+
return nil
236+
},
237+
Type: &AddStatus{},
238+
PostRun: cmds.PostRunMap{
239+
cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
240+
defer re.Close()
241+
242+
for {
243+
v, err := res.Next()
244+
if err == io.EOF {
245+
return nil
246+
}
247+
if err != nil {
248+
return err
249+
}
250+
251+
s := v.(*AddStatus)
252+
err = re.Emit(&AddStatus{
253+
Current: s.Current + s.Current,
254+
Left: s.Left,
255+
})
256+
if err != nil {
257+
return err
258+
}
259+
}
260+
},
261+
},
262+
DisplayCLI: func(res cmds.Response, stdout, stderr io.Writer) error {
263+
defer fmt.Fprintln(stdout)
264+
265+
// length of line at last iteration
266+
var lastLen int
267+
268+
for {
269+
v, err := res.Next()
270+
if err == io.EOF {
271+
return nil
272+
}
273+
if err != nil {
274+
return err
275+
}
276+
277+
fmt.Fprint(stdout, "\r" + strings.Repeat(" ", lastLen))
278+
279+
s := v.(*AddStatus)
280+
if s.Left > 0 {
281+
lastLen, _ = fmt.Fprintf(stdout, "\rcalculation sum... current: %d; left: %d", s.Current, s.Left)
282+
} else {
283+
lastLen, _ = fmt.Fprintf(stdout, "\rsum is %d.", s.Current)
284+
}
285+
}
286+
},
287+
},
154288
// how to set program's return value
155289
"exitAdd": {
156290
Arguments: []cmds.Argument{

Diff for: examples/adder/local/main.go

+4-23
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"context"
5-
"fmt"
65
"os"
76

87
"github.com/ipfs/go-ipfs-cmds/examples/adder"
@@ -26,29 +25,11 @@ func main() {
2625
panic(err)
2726
}
2827

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)
28+
exec := cmds.NewExecutor(adder.RootCmd)
29+
err = exec.Execute(req, cliRe, nil)
30+
if err != nil {
31+
panic(err)
4832
}
4933

50-
adder.RootCmd.Call(req, re, nil)
51-
<-wait
52-
5334
os.Exit(cliRe.Status())
5435
}

Diff for: executor.go

+32-15
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ type executor struct {
3333
root *Command
3434
}
3535

36-
type emitterEncoder struct {
37-
emitter ResponseEmitter
38-
}
39-
40-
func (enc *emitterEncoder) Encode(value interface{}) error {
41-
return enc.emitter.Emit(value)
42-
}
43-
4436
func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) error {
4537
cmd := req.Command
4638

@@ -59,19 +51,44 @@ func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) er
5951
return err
6052
}
6153
}
54+
55+
return EmitResponse(cmd.Run, req, re, env)
56+
}
57+
58+
// Helper for Execute that handles post-Run emitter logic
59+
func EmitResponse(run Function, req *Request, re ResponseEmitter, env Environment) error {
60+
61+
// Keep track of the lowest emitter to select the correct
62+
// PostRun method.
63+
lowest := re
64+
cmd := req.Command
65+
66+
// contains the error returned by DisplayCLI or PostRun
67+
errCh := make(chan error, 1)
68+
69+
if cmd.DisplayCLI != nil && GetEncoding(req, "json") == "text" {
70+
var res Response
71+
72+
// This overwrites the emitter provided as an
73+
// argument. Maybe it's better to provide the
74+
// 'DisplayCLI emitter' as an argument to Execute.
75+
re, res = NewChanResponsePair(req)
76+
77+
go func() {
78+
defer close(errCh)
79+
errCh <- cmd.DisplayCLI(res, os.Stdout, os.Stderr)
80+
}()
81+
}
82+
83+
6284
maybeStartPostRun := func(formatters PostRunMap) <-chan error {
6385
var (
6486
postRun func(Response, ResponseEmitter) error
6587
postRunCh = make(chan error)
6688
)
6789

68-
if postRun == nil {
69-
close(postRunCh)
70-
return postRunCh
71-
}
72-
7390
// check if we have a formatter for this emitter type
74-
typer, isTyper := re.(interface {
91+
typer, isTyper := lowest.(interface {
7592
Type() PostRunType
7693
})
7794
if isTyper &&
@@ -97,7 +114,7 @@ func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) er
97114
}
98115

99116
postRunCh := maybeStartPostRun(cmd.PostRun)
100-
runCloseErr := re.CloseWithError(cmd.Run(req, re, env))
117+
runCloseErr := re.CloseWithError(run(req, re, env))
101118
postCloseErr := <-postRunCh
102119
switch runCloseErr {
103120
case ErrClosingClosedEmitter, nil:

Diff for: http/client.go

+3-20
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"net"
88
"net/http"
99
"net/url"
10-
"os"
1110
"strings"
1211

1312
"github.com/ipfs/go-ipfs-cmds"
@@ -118,27 +117,11 @@ func (c *client) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.En
118117
return err
119118
}
120119

121-
if cmd.PostRun != nil {
122-
if typer, ok := re.(interface {
123-
Type() cmds.PostRunType
124-
}); ok && cmd.PostRun[typer.Type()] != nil {
125-
err := cmd.PostRun[typer.Type()](res, re)
126-
closeErr := re.CloseWithError(err)
127-
if closeErr == cmds.ErrClosingClosedEmitter {
128-
// ignore double close errors
129-
return nil
130-
}
131-
132-
return closeErr
133-
}
134-
}
135-
136-
if cmd.DisplayCLI != nil &&
137-
cmds.GetEncoding(req, cmds.Undefined) == cmds.Text {
138-
return cmd.DisplayCLI(res, os.Stdout, os.Stderr)
120+
copy := func(_ *cmds.Request, re cmds.ResponseEmitter, _ cmds.Environment) error {
121+
return cmds.Copy(re, res)
139122
}
140123

141-
return cmds.Copy(re, res)
124+
return cmds.EmitResponse(copy, req, re, env)
142125
}
143126

144127
func (c *client) toHTTPRequest(req *cmds.Request) (*http.Request, error) {

0 commit comments

Comments
 (0)