Skip to content

Commit 6782607

Browse files
committed
Add Command.DisplayCLI method signature
Following the approach described in #115, define a new method signature on `Command` that supports full processing of the `Response` object when text encoding is requested. Add an encoding check and dispatch to DisplayCLI in local, http client, and http handler code paths. Unblocks resolution of `encoding` option processing in multiple go-ipfs issues. - ipfs/kubo#7050 json encoding for `ls` - ipfs/kubo#1121 json encoding for `add` - ipfs/kubo#5594 json encoding for `stats bw`
1 parent 744f3b3 commit 6782607

File tree

6 files changed

+63
-5
lines changed

6 files changed

+63
-5
lines changed

cli/parse.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func Parse(ctx context.Context, input []string, stdin *os.File, root *cmds.Comma
4848
// if no encoding was specified by user, default to plaintext encoding
4949
// (if command doesn't support plaintext, use JSON instead)
5050
if enc := req.Options[cmds.EncLong]; enc == "" {
51-
if req.Command.Encoders != nil && req.Command.Encoders[cmds.Text] != nil {
51+
if req.Command.HasText() {
5252
req.SetOption(cmds.EncLong, cmds.Text)
5353
} else {
5454
req.SetOption(cmds.EncLong, cmds.JSON)

cli/run.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func Run(ctx context.Context, root *cmds.Command,
123123
encType := cmds.EncodingType(encTypeStr)
124124

125125
// use JSON if text was requested but the command doesn't have a text-encoder
126-
if _, ok := cmd.Encoders[encType]; encType == cmds.Text && !ok {
126+
if encType == cmds.Text && !cmd.HasText() {
127127
req.Options[cmds.EncLong] = cmds.JSON
128128
}
129129

command.go

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package cmds
1111
import (
1212
"errors"
1313
"fmt"
14+
"io"
1415
"strings"
1516

1617
files "github.com/ipfs/go-ipfs-files"
@@ -66,6 +67,11 @@ type Command struct {
6667
// encoding.
6768
Encoders EncoderMap
6869

70+
// DisplayCLI provides console output in cases requiring
71+
// access to a full response object rather than individual
72+
// result values. It is always run in the local process.
73+
DisplayCLI func(res Response, stdout, stderr io.Writer) error
74+
6975
// Helptext is the command's help text.
7076
Helptext HelpText
7177

@@ -194,6 +200,11 @@ func (c *Command) Resolve(pth []string) ([]*Command, error) {
194200
return cmds, nil
195201
}
196202

203+
// HasText is true if the Command has direct support for text output
204+
func (c *Command) HasText() bool {
205+
return c.DisplayCLI != nil || (c.Encoders != nil && c.Encoders[Text] != nil)
206+
}
207+
197208
// Get resolves and returns the Command addressed by path
198209
func (c *Command) Get(path []string) (*Command, error) {
199210
cmds, err := c.Resolve(path)

executor.go

+43-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package cmds
22

33
import (
44
"context"
5+
"io"
6+
"io/ioutil"
7+
"os"
58
)
69

710
type Executor interface {
@@ -32,6 +35,33 @@ type executor struct {
3235
root *Command
3336
}
3437

38+
// GetLocalEncoder provides special treatment for text encoding
39+
// when Command.DisplayCLI field is non-nil, by defining an
40+
// Encoder that delegates to a nested emitter that consumes a Response
41+
// and writes to the underlying io.Writer using DisplayCLI.
42+
func GetLocalEncoder(req *Request, w io.Writer, def EncodingType) (EncodingType, Encoder, error) {
43+
encType, enc, err := GetEncoder(req, w, def)
44+
if err != nil {
45+
return encType, nil, err
46+
}
47+
48+
if req.Command.DisplayCLI != nil && encType == Text {
49+
emitter, response := NewChanResponsePair(req)
50+
go req.Command.DisplayCLI(response, w, ioutil.Discard)
51+
return encType, &emitterEncoder{emitter: emitter}, nil
52+
}
53+
54+
return encType, enc, nil
55+
}
56+
57+
type emitterEncoder struct {
58+
emitter ResponseEmitter
59+
}
60+
61+
func (enc *emitterEncoder) Encode(value interface{}) error {
62+
return enc.emitter.Emit(value)
63+
}
64+
3565
func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) error {
3666
cmd := req.Command
3767

@@ -51,9 +81,20 @@ func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) er
5181
}
5282
}
5383

54-
// contains the error returned by PostRun
84+
// contains the error returned by DisplayCLI or PostRun
5585
errCh := make(chan error, 1)
56-
if cmd.PostRun != nil {
86+
if cmd.DisplayCLI != nil && GetEncoding(req, "json") == "text" {
87+
var (
88+
res Response
89+
)
90+
91+
re, res = NewChanResponsePair(req)
92+
93+
go func() {
94+
defer close(errCh)
95+
errCh <- cmd.DisplayCLI(res, os.Stdout, os.Stderr)
96+
}()
97+
} else if cmd.PostRun != nil {
5798
if typer, ok := re.(interface {
5899
Type() PostRunType
59100
}); ok && cmd.PostRun[typer.Type()] != nil {

http/client.go

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

1213
"github.com/ipfs/go-ipfs-cmds"
@@ -132,6 +133,11 @@ func (c *client) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.En
132133
}
133134
}
134135

136+
if cmd.DisplayCLI != nil &&
137+
cmds.GetEncoding(req, cmds.Undefined) == cmds.Text {
138+
return cmd.DisplayCLI(res, os.Stdout, os.Stderr)
139+
}
140+
135141
return cmds.Copy(re, res)
136142
}
137143

http/responseemitter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var (
2727

2828
// NewResponseEmitter returns a new ResponseEmitter.
2929
func NewResponseEmitter(w http.ResponseWriter, method string, req *cmds.Request, opts ...ResponseEmitterOption) (ResponseEmitter, error) {
30-
encType, enc, err := cmds.GetEncoder(req, w, cmds.JSON)
30+
encType, enc, err := cmds.GetLocalEncoder(req, w, cmds.JSON)
3131
if err != nil {
3232
return nil, err
3333
}

0 commit comments

Comments
 (0)