Skip to content

Commit

Permalink
Merge pull request #83 from ipfs/fix/forward-args
Browse files Browse the repository at this point in the history
forward the remaining of the stdin args to the server
  • Loading branch information
Stebalien authored Mar 18, 2018
2 parents 5ed4174 + 9160bdc commit b6ffc22
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 17 deletions.
92 changes: 92 additions & 0 deletions arguments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cmds

import (
"bufio"
"io"
)

// StdinArguments is used to iterate through arguments piped through stdin.
//
// It closely mimics the bufio.Scanner interface but also implements the
// ReadCloser interface.
type StdinArguments interface {
io.ReadCloser

// Scan reads in the next argument and returns true if there is an
// argument to read.
Scan() bool

// Argument returns the next argument.
Argument() string

// Err returns any errors encountered when reading in arguments.
Err() error
}

type arguments struct {
argument string
err error
reader *bufio.Reader
closer io.Closer
}

func newArguments(r io.ReadCloser) *arguments {
return &arguments{
reader: bufio.NewReader(r),
closer: r,
}
}

// Read implements the io.Reader interface.
func (a *arguments) Read(b []byte) (int, error) {
return a.reader.Read(b)
}

// Close implements the io.Closer interface.
func (a *arguments) Close() error {
return a.closer.Close()
}

// WriteTo implements the io.WriterTo interface.
func (a *arguments) WriteTo(w io.Writer) (int64, error) {
return a.reader.WriteTo(w)
}

// Err returns any errors encountered when reading in arguments.
func (a *arguments) Err() error {
if a.err == io.EOF {
return nil
}
return a.err
}

// Argument returns the last argument read in.
func (a *arguments) Argument() string {
return a.argument
}

// Scan reads in the next argument and returns true if there is an
// argument to read.
func (a *arguments) Scan() bool {
if a.err != nil {
return false
}

s, err := a.reader.ReadString('\n')
if err != nil {
a.err = err
if err == io.EOF && len(s) > 0 {
a.argument = s
return true
}
return false
}

l := len(s)
if l >= 2 && s[l-2] == '\r' {
a.argument = s[:l-2]
} else {
a.argument = s[:l-1]
}
return true
}
112 changes: 112 additions & 0 deletions arguments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package cmds

import (
"bytes"
"io/ioutil"
"testing"
)

func TestArguments(t *testing.T) {
var testCases = []struct {
input string
arguments []string
}{
{
input: "",
arguments: []string{},
},
{
input: "\n",
arguments: []string{""},
},
{
input: "\r\n",
arguments: []string{""},
},
{
input: "\r",
arguments: []string{"\r"},
},
{
input: "one",
arguments: []string{"one"},
},
{
input: "one\n",
arguments: []string{"one"},
},
{
input: "one\r\n",
arguments: []string{"one"},
},
{
input: "one\r",
arguments: []string{"one\r"},
},
{
input: "one\n\ntwo",
arguments: []string{"one", "", "two"},
},
{
input: "first\nsecond\nthird",
arguments: []string{"first", "second", "third"},
},
{
input: "first\r\nsecond\nthird",
arguments: []string{"first", "second", "third"},
},
{
input: "first\nsecond\nthird\n",
arguments: []string{"first", "second", "third"},
},
{
input: "first\r\nsecond\r\nthird\r\n",
arguments: []string{"first", "second", "third"},
},
{
input: "first\nsecond\nthird\n\n",
arguments: []string{"first", "second", "third", ""},
},
{
input: "\nfirst\nsecond\nthird\n",
arguments: []string{"", "first", "second", "third"},
},
}

for i, tc := range testCases {
for cut := 0; cut <= len(tc.arguments); cut++ {
args := newArguments(ioutil.NopCloser(bytes.NewBufferString(tc.input)))
for j, arg := range tc.arguments[:cut] {
if !args.Scan() {
t.Errorf("in test case %d, missing argument %d", i, j)
continue
}
got := args.Argument()
if got != arg {
t.Errorf("in test case %d, expected argument %d to be %s, got %s", i, j, arg, got)
}
if args.Err() != nil {
t.Error(args.Err())
}
}
args = newArguments(args)
// Tests stopping in the middle.
for j, arg := range tc.arguments[cut:] {
if !args.Scan() {
t.Errorf("in test case %d, missing argument %d", i, j+cut)
continue
}
got := args.Argument()
if got != arg {
t.Errorf("in test case %d, expected argument %d to be %s, got %s", i, j+cut, arg, got)
}
if args.Err() != nil {
t.Error(args.Err())
}
}
if args.Scan() {
t.Errorf("in test case %d, got too many arguments", i)
}
}
}
}
5 changes: 4 additions & 1 deletion cli/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,10 @@ func TestBodyArgs(t *testing.T) {

var bodyArgs words
for s.Scan() {
bodyArgs = append(bodyArgs, s.Text())
bodyArgs = append(bodyArgs, s.Argument())
}
if err := s.Err(); err != nil {
t.Fatal(err)
}

if !sameWords(bodyArgs, tc.varArgs) {
Expand Down
6 changes: 2 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ output to the user, including text, JSON, and XML marshallers.
package cmds

import (
"bufio"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -234,7 +233,7 @@ func (c *Command) CheckArguments(req *Request) error {
switch err {
case io.EOF:
case nil:
req.bodyArgs = bufio.NewScanner(fi)
req.bodyArgs = newArguments(fi)
// Can't pass files and stdin arguments.
req.Files = nil
default:
Expand Down Expand Up @@ -269,10 +268,9 @@ func (c *Command) CheckArguments(req *Request) error {
if argDef.SupportsStdin && req.bodyArgs != nil {
if req.bodyArgs.Scan() {
// Found it!
req.Arguments = append(req.Arguments, req.bodyArgs.Text())
req.Arguments = append(req.Arguments, req.bodyArgs.Argument())
continue
}
// Nope! Maybe we had a read error?
if err := req.bodyArgs.Err(); err != nil {
return err
}
Expand Down
10 changes: 6 additions & 4 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,10 @@ func TestCancel(t *testing.T) {
go func() {
err := re.Emit("abc")
if err != context.Canceled {
t.Fatalf("re: expected context.Canceled but got %v", err)
t.Errorf("re: expected context.Canceled but got %v", err)
} else {
t.Log("re.Emit err:", err)
}
t.Log("re.Emit err:", err)
re.Close()
close(wait)
}()
Expand All @@ -394,8 +395,9 @@ func TestCancel(t *testing.T) {

_, err = res.Next()
if err != context.Canceled {
t.Fatalf("res: expected context.Canceled but got %v", err)
t.Errorf("res: expected context.Canceled but got %v", err)
} else {
t.Log("res.Emit err:", err)
}
t.Log("res.Emit err:", err)
<-wait
}
10 changes: 8 additions & 2 deletions http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,14 @@ func (c *client) Send(req *cmds.Request) (cmds.Response, error) {

var fileReader *files.MultiFileReader
var reader io.Reader

if req.Files != nil {
if bodyArgs := req.BodyArgs(); bodyArgs != nil {
// In the end, this wraps a file reader in a file reader.
// However, such is life.
fileReader = files.NewMultiFileReader(files.NewSliceFile("", "", []files.File{
files.NewReaderFile("stdin", "", bodyArgs, nil),
}), true)
reader = fileReader
} else if req.Files != nil {
fileReader = files.NewMultiFileReader(req.Files, true)
reader = fileReader
}
Expand Down
19 changes: 13 additions & 6 deletions request.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmds

import (
"bufio"
"context"
"fmt"
"reflect"
Expand All @@ -21,7 +20,7 @@ type Request struct {

Files files.File

bodyArgs *bufio.Scanner
bodyArgs *arguments
}

// NewRequest returns a request initialized with given arguments
Expand Down Expand Up @@ -50,8 +49,17 @@ func NewRequest(ctx context.Context, path []string, opts cmdkit.OptMap, args []s
}

// BodyArgs returns a scanner that returns arguments passed in the body as tokens.
func (req *Request) BodyArgs() *bufio.Scanner {
return req.bodyArgs
//
// Returns nil if there are no arguments to be consumed via stdin.
func (req *Request) BodyArgs() StdinArguments {
// dance to make sure we return an *untyped* nil.
// DO NOT just return `req.bodyArgs`.
// If you'd like to complain, go to
// https://github.com/golang/go/issues/.
if req.bodyArgs != nil {
return req.bodyArgs
}
return nil
}

func (req *Request) ParseBodyArgs() error {
Expand All @@ -61,9 +69,8 @@ func (req *Request) ParseBodyArgs() error {
}

for s.Scan() {
req.Arguments = append(req.Arguments, s.Text())
req.Arguments = append(req.Arguments, s.Argument())
}

return s.Err()
}

Expand Down

0 comments on commit b6ffc22

Please sign in to comment.