Skip to content

Commit

Permalink
Add Command.DisplayCLI method signature
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
jbouwman committed Aug 21, 2021
1 parent 744f3b3 commit 6782607
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cli/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func Parse(ctx context.Context, input []string, stdin *os.File, root *cmds.Comma
// if no encoding was specified by user, default to plaintext encoding
// (if command doesn't support plaintext, use JSON instead)
if enc := req.Options[cmds.EncLong]; enc == "" {
if req.Command.Encoders != nil && req.Command.Encoders[cmds.Text] != nil {
if req.Command.HasText() {
req.SetOption(cmds.EncLong, cmds.Text)
} else {
req.SetOption(cmds.EncLong, cmds.JSON)
Expand Down
2 changes: 1 addition & 1 deletion cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func Run(ctx context.Context, root *cmds.Command,
encType := cmds.EncodingType(encTypeStr)

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

Expand Down
11 changes: 11 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package cmds
import (
"errors"
"fmt"
"io"
"strings"

files "github.com/ipfs/go-ipfs-files"
Expand Down Expand Up @@ -66,6 +67,11 @@ type Command struct {
// encoding.
Encoders EncoderMap

// DisplayCLI provides console output in cases requiring
// access to a full response object rather than individual
// result values. It is always run in the local process.
DisplayCLI func(res Response, stdout, stderr io.Writer) error

// Helptext is the command's help text.
Helptext HelpText

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

// HasText is true if the Command has direct support for text output
func (c *Command) HasText() bool {
return c.DisplayCLI != nil || (c.Encoders != nil && c.Encoders[Text] != nil)
}

// Get resolves and returns the Command addressed by path
func (c *Command) Get(path []string) (*Command, error) {
cmds, err := c.Resolve(path)
Expand Down
45 changes: 43 additions & 2 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package cmds

import (
"context"
"io"
"io/ioutil"
"os"
)

type Executor interface {
Expand Down Expand Up @@ -32,6 +35,33 @@ type executor struct {
root *Command
}

// GetLocalEncoder provides special treatment for text encoding
// when Command.DisplayCLI field is non-nil, by defining an
// Encoder that delegates to a nested emitter that consumes a Response
// and writes to the underlying io.Writer using DisplayCLI.
func GetLocalEncoder(req *Request, w io.Writer, def EncodingType) (EncodingType, Encoder, error) {
encType, enc, err := GetEncoder(req, w, def)
if err != nil {
return encType, nil, err
}

if req.Command.DisplayCLI != nil && encType == Text {
emitter, response := NewChanResponsePair(req)
go req.Command.DisplayCLI(response, w, ioutil.Discard)
return encType, &emitterEncoder{emitter: emitter}, nil
}

return encType, enc, nil
}

type emitterEncoder struct {
emitter ResponseEmitter
}

func (enc *emitterEncoder) Encode(value interface{}) error {
return enc.emitter.Emit(value)
}

func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) error {
cmd := req.Command

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

// contains the error returned by PostRun
// contains the error returned by DisplayCLI or PostRun
errCh := make(chan error, 1)
if cmd.PostRun != nil {
if cmd.DisplayCLI != nil && GetEncoding(req, "json") == "text" {
var (
res Response
)

re, res = NewChanResponsePair(req)

go func() {
defer close(errCh)
errCh <- cmd.DisplayCLI(res, os.Stdout, os.Stderr)
}()
} else if cmd.PostRun != nil {
if typer, ok := re.(interface {
Type() PostRunType
}); ok && cmd.PostRun[typer.Type()] != nil {
Expand Down
6 changes: 6 additions & 0 deletions http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
"net/http"
"net/url"
"os"
"strings"

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

if cmd.DisplayCLI != nil &&
cmds.GetEncoding(req, cmds.Undefined) == cmds.Text {
return cmd.DisplayCLI(res, os.Stdout, os.Stderr)
}

return cmds.Copy(re, res)
}

Expand Down
2 changes: 1 addition & 1 deletion http/responseemitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var (

// NewResponseEmitter returns a new ResponseEmitter.
func NewResponseEmitter(w http.ResponseWriter, method string, req *cmds.Request, opts ...ResponseEmitterOption) (ResponseEmitter, error) {
encType, enc, err := cmds.GetEncoder(req, w, cmds.JSON)
encType, enc, err := cmds.GetLocalEncoder(req, w, cmds.JSON)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 6782607

Please sign in to comment.