Skip to content

Commit

Permalink
ignore those last bits, this time its for real
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Jeromy <why@ipfs.io>
  • Loading branch information
whyrusleeping committed Jul 8, 2016
1 parent a6af6c5 commit 1aeda7e
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 188 deletions.
28 changes: 22 additions & 6 deletions commands/cli/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,17 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi
stringArgs, inputs = append(stringArgs, inputs[0]), inputs[1:]
} else {
if stdin != nil && argDef.SupportsStdin && !fillingVariadic {
if err := printReadInfo(stdin, msgStdinInfo); err == nil {
fileArgs[stdin.Name()] = files.NewReaderFile("", stdin.Name(), stdin, nil)
stdin = nil
fname := ""
istty, err := isTty(stdin)
if err != nil {
return nil, nil, err
}
if istty {
fname = "*stdin*"
}

fileArgs[stdin.Name()] = files.NewReaderFile(fname, "", stdin, nil)
stdin = nil
}
}
case cmds.ArgFile:
Expand Down Expand Up @@ -417,15 +424,24 @@ func appendFile(fpath string, argDef *cmds.Argument, recursive, hidden bool) (fi

// Inform the user if a file is waiting on input
func printReadInfo(f *os.File, msg string) error {
fInfo, err := f.Stat()
isTty, err := isTty(f)
if err != nil {
log.Error(err)
return err
}

if (fInfo.Mode() & os.ModeCharDevice) != 0 {
if isTty {
fmt.Fprintf(os.Stderr, msg, f.Name())
}

return nil
}

func isTty(f *os.File) (bool, error) {
fInfo, err := f.Stat()
if err != nil {
log.Error(err)
return false, err
}

return (fInfo.Mode() & os.ModeCharDevice) != 0, nil
}
141 changes: 60 additions & 81 deletions commands/cli/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -178,49 +177,32 @@ func TestArgumentParsing(t *testing.T) {
commands.StringArg("b", true, false, "another arg"),
},
},
"FileArg": {
"stdinenabled": {
Arguments: []commands.Argument{
commands.FileArg("a", true, false, "some arg"),
commands.StringArg("a", true, true, "some arg").EnableStdin(),
},
},
"FileArg+Variadic": {
Arguments: []commands.Argument{
commands.FileArg("a", true, true, "some arg"),
},
},
"FileArg+Stdin": {
Arguments: []commands.Argument{
commands.FileArg("a", true, true, "some arg").EnableStdin(),
},
},
"StringArg+FileArg": {
Arguments: []commands.Argument{
commands.StringArg("a", true, false, "some arg"),
commands.FileArg("a", true, false, "some arg"),
},
},
"StringArg+FileArg+Stdin": {
"stdinenabled2args": &commands.Command{
Arguments: []commands.Argument{
commands.StringArg("a", true, false, "some arg"),
commands.FileArg("a", true, true, "some arg").EnableStdin(),
commands.StringArg("b", true, true, "another arg").EnableStdin(),
},
},
"StringArg+FileArg+Variadic": {
"stdinenablednotvariadic": &commands.Command{
Arguments: []commands.Argument{
commands.StringArg("a", true, false, "some arg"),
commands.FileArg("a", true, true, "some arg"),
commands.StringArg("a", true, false, "some arg").EnableStdin(),
},
},
"StringArg+FileArg+Variadic+Stdin": {
"stdinenablednotvariadic2args": &commands.Command{
Arguments: []commands.Argument{
commands.StringArg("a", true, false, "some arg"),
commands.FileArg("a", true, true, "some arg"),
commands.StringArg("b", true, false, "another arg").EnableStdin(),
},
},
},
}

test := func(cmd words, f *os.File, exp words) {
test := func(cmd words, f *os.File, res words) {
if f != nil {
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
t.Fatal(err)
Expand All @@ -230,18 +212,8 @@ func TestArgumentParsing(t *testing.T) {
if err != nil {
t.Errorf("Command '%v' should have passed parsing: %v", cmd, err)
}

parsedWords := make([]string, len(req.Arguments()))
copy(parsedWords, req.Arguments())

if files := req.Files(); files != nil {
for file, err := files.NextFile(); err != io.EOF; file, err = files.NextFile() {
parsedWords = append(parsedWords, file.FullPath())
}
}

if !sameWords(parsedWords, exp) {
t.Errorf("Arguments parsed from '%v' are '%v' instead of '%v'", cmd, parsedWords, exp)
if !sameWords(req.Arguments(), res) {
t.Errorf("Arguments parsed from '%v' are '%v' instead of '%v'", cmd, req.Arguments(), res)
}
}

Expand Down Expand Up @@ -281,52 +253,59 @@ func TestArgumentParsing(t *testing.T) {
testFail([]string{"reversedoptional"}, nil, "didn't provide any args, 1 required")
testFail([]string{"reversedoptional", "value1", "value2", "value3"}, nil, "provided too many args, only takes 1")

// Since FileArgs are presently stored ordered by Path, the enum string
// is used to construct a predictably ordered sequence of filenames.
tmpFile := func(t *testing.T, enum string) *os.File {
f, err := ioutil.TempFile("", enum)
// Use a temp file to simulate stdin
fileToSimulateStdin := func(t *testing.T, content string) *os.File {
fstdin, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
fn, err := filepath.EvalSymlinks(f.Name())
if err != nil {
t.Fatal(err)
}
f.Close()
f, err = os.Create(fn)
if err != nil {
defer os.Remove(fstdin.Name())

if _, err := io.WriteString(fstdin, content); err != nil {
t.Fatal(err)
}

return f
return fstdin
}
file1 := tmpFile(t, "1")
file2 := tmpFile(t, "2")
file3 := tmpFile(t, "3")
defer os.Remove(file3.Name())
defer os.Remove(file2.Name())
defer os.Remove(file1.Name())

test([]string{"noarg"}, file1, []string{})
test([]string{"FileArg", file1.Name()}, nil, []string{file1.Name()})
test([]string{"FileArg+Variadic", file1.Name(), file2.Name()}, nil,
[]string{file1.Name(), file2.Name()})
test([]string{"FileArg+Stdin"}, file1, []string{file1.Name()})
test([]string{"FileArg+Stdin", "-"}, file1, []string{file1.Name()})
test([]string{"FileArg+Stdin", file1.Name(), "-"}, file2,
[]string{file1.Name(), file2.Name()})
test([]string{"StringArg+FileArg",
"foo", file1.Name()}, nil, []string{"foo", file1.Name()})
test([]string{"StringArg+FileArg+Variadic",
"foo", file1.Name(), file2.Name()}, nil,
[]string{"foo", file1.Name(), file2.Name()})
test([]string{"StringArg+FileArg+Stdin",
"foo", file1.Name(), "-"}, file2,
[]string{"foo", file1.Name(), file2.Name()})
test([]string{"StringArg+FileArg+Variadic+Stdin",
"foo", file1.Name(), file2.Name()}, file3,
[]string{"foo", file1.Name(), file2.Name()})
test([]string{"StringArg+FileArg+Variadic+Stdin",
"foo", file1.Name(), file2.Name(), "-"}, file3,
[]string{"foo", file1.Name(), file2.Name(), file3.Name()})

test([]string{"stdinenabled", "value1", "value2"}, nil, []string{"value1", "value2"})

fstdin := fileToSimulateStdin(t, "stdin1")
test([]string{"stdinenabled"}, fstdin, []string{"stdin1"})
test([]string{"stdinenabled", "value1"}, fstdin, []string{"value1"})
test([]string{"stdinenabled", "value1", "value2"}, fstdin, []string{"value1", "value2"})

fstdin = fileToSimulateStdin(t, "stdin1\nstdin2")
test([]string{"stdinenabled"}, fstdin, []string{"stdin1", "stdin2"})

fstdin = fileToSimulateStdin(t, "stdin1\nstdin2\nstdin3")
test([]string{"stdinenabled"}, fstdin, []string{"stdin1", "stdin2", "stdin3"})

test([]string{"stdinenabled2args", "value1", "value2"}, nil, []string{"value1", "value2"})

fstdin = fileToSimulateStdin(t, "stdin1")
test([]string{"stdinenabled2args", "value1"}, fstdin, []string{"value1", "stdin1"})
test([]string{"stdinenabled2args", "value1", "value2"}, fstdin, []string{"value1", "value2"})
test([]string{"stdinenabled2args", "value1", "value2", "value3"}, fstdin, []string{"value1", "value2", "value3"})

fstdin = fileToSimulateStdin(t, "stdin1\nstdin2")
test([]string{"stdinenabled2args", "value1"}, fstdin, []string{"value1", "stdin1", "stdin2"})

test([]string{"stdinenablednotvariadic", "value1"}, nil, []string{"value1"})

fstdin = fileToSimulateStdin(t, "stdin1")
test([]string{"stdinenablednotvariadic"}, fstdin, []string{"stdin1"})
test([]string{"stdinenablednotvariadic", "value1"}, fstdin, []string{"value1"})

test([]string{"stdinenablednotvariadic2args", "value1", "value2"}, nil, []string{"value1", "value2"})

fstdin = fileToSimulateStdin(t, "stdin1")
test([]string{"stdinenablednotvariadic2args", "value1"}, fstdin, []string{"value1", "stdin1"})
test([]string{"stdinenablednotvariadic2args", "value1", "value2"}, fstdin, []string{"value1", "value2"})
testFail([]string{"stdinenablednotvariadic2args"}, fstdin, "cant use stdin for non stdin arg")

fstdin = fileToSimulateStdin(t, "stdin1")
test([]string{"noarg"}, fstdin, []string{})

fstdin = fileToSimulateStdin(t, "stdin1")
test([]string{"optionalsecond", "value1", "value2"}, fstdin, []string{"value1", "value2"})
}
9 changes: 7 additions & 2 deletions commands/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (c *Command) GetOptions(path []string) (map[string]Option, error) {
}

func (c *Command) CheckArguments(req Request) error {
args := req.Arguments()
args := req.(*request).arguments

// count required argument definitions
numRequired := 0
Expand All @@ -218,7 +218,7 @@ func (c *Command) CheckArguments(req Request) error {

// iterate over the arg definitions
valueIndex := 0 // the index of the current value (in `args`)
for _, argDef := range c.Arguments {
for i, argDef := range c.Arguments {
// skip optional argument definitions if there aren't
// sufficient remaining values
if len(args)-valueIndex <= numRequired && !argDef.Required ||
Expand All @@ -235,6 +235,11 @@ func (c *Command) CheckArguments(req Request) error {
valueIndex++
}

// in the case of a non-variadic required argument that supports stdin
if !found && len(c.Arguments)-1 == i && argDef.SupportsStdin {
found = true
}

err := checkArgValue(v, found, argDef)
if err != nil {
return err
Expand Down
50 changes: 40 additions & 10 deletions commands/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ func (r *request) SetOptions(opts OptMap) error {

// Arguments returns the arguments slice
func (r *request) Arguments() []string {
if r.haveVarArgsFromStdin() {
err := r.VarArgs(func(s string) error {
r.arguments = append(r.arguments, s)
return nil
})
if err != nil && err != io.EOF {
log.Error(err)
}
}

return r.arguments
}

Expand All @@ -189,10 +199,22 @@ func (r *request) Context() context.Context {
return r.rctx
}

func (r *request) haveVarArgsFromStdin() bool {
// we expect varargs if we have a variadic required argument and no arguments to
// fill it
if len(r.cmd.Arguments) == 0 {
return false
}

last := r.cmd.Arguments[len(r.cmd.Arguments)-1]
return last.SupportsStdin && last.Type == ArgString &&
len(r.arguments) < len(r.cmd.Arguments)
}

func (r *request) VarArgs(f func(string) error) error {
var i int
for i = 0; i < len(r.cmd.Arguments); i++ {
if r.cmd.Arguments[i].Variadic {
if r.cmd.Arguments[i].Variadic || r.cmd.Arguments[i].SupportsStdin {
break
}
}
Expand All @@ -208,19 +230,27 @@ func (r *request) VarArgs(f func(string) error) error {

return nil
} else {
fi, err := r.files.NextFile()
if err != nil {
return err
}

scan := bufio.NewScanner(fi)
for scan.Scan() {
err := f(scan.Text())
if r.files != nil {
fi, err := r.files.NextFile()
if err != nil {
return err
}

if fi.FileName() == "*stdin*" {
fmt.Fprintln(os.Stderr, "ipfs: Reading from stdin; send Ctrl-d to stop.")
}

scan := bufio.NewScanner(fi)
for scan.Scan() {
err := f(scan.Text())
if err != nil {
return err
}
}
return nil
} else {
return fmt.Errorf("expected more arguments from stdin")
}
return nil
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/commands/bitswap.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var unwantCmd = &cmds.Command{
Tagline: "Remove a given block from your wantlist.",
},
Arguments: []cmds.Argument{
cmds.StringArg("key", true, true, "Key(s) to remove from your wantlist."),
cmds.StringArg("key", true, true, "Key(s) to remove from your wantlist.").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
Expand Down
4 changes: 2 additions & 2 deletions core/commands/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ on raw ipfs blocks. It outputs the following to stdout:
},

Arguments: []cmds.Argument{
cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get."),
cmds.StringArg("key", true, false, "The base58 multihash of an existing block to stat.").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
b, err := getBlockForKey(req, req.Arguments()[0])
Expand Down Expand Up @@ -88,7 +88,7 @@ It outputs to stdout, and <key> is a base58 encoded multihash.
},

Arguments: []cmds.Argument{
cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get."),
cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get.").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
b, err := getBlockForKey(req, req.Arguments()[0])
Expand Down
Loading

0 comments on commit 1aeda7e

Please sign in to comment.