Unverified Commit b6ffc22b authored by Steven Allen's avatar Steven Allen Committed by GitHub

Merge pull request #83 from ipfs/fix/forward-args

forward the remaining of the stdin args to the server
parents 5ed41745 9160bdcc
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
}
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)
}
}
}
}
......@@ -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) {
......
......@@ -9,7 +9,6 @@ output to the user, including text, JSON, and XML marshallers.
package cmds
import (
"bufio"
"errors"
"fmt"
"io"
......@@ -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:
......@@ -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
}
......
......@@ -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)
}()
......@@ -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
}
......@@ -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
}
......
package cmds
import (
"bufio"
"context"
"fmt"
"reflect"
......@@ -21,7 +20,7 @@ type Request struct {
Files files.File
bodyArgs *bufio.Scanner
bodyArgs *arguments
}
// NewRequest returns a request initialized with given arguments
......@@ -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 {
......@@ -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()
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment