diff --git a/README.md b/README.md index bdd5319..932f4e7 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,13 @@ args, err := p.Parse("./foo `echo $SHELL`") // args should be ["./foo", "/bin/bash"] ``` +```go +p := shellwords.NewParser() +p.ParseComment = true +args, err := p.Parse("./foo # comment") +// args should be ["./foo"] +``` + # Thanks This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). diff --git a/shellwords.go b/shellwords.go index 376e9e5..767a473 100644 --- a/shellwords.go +++ b/shellwords.go @@ -11,6 +11,7 @@ import ( var ( ParseEnv bool = false ParseBacktick bool = false + ParseComment bool = false ) func isSpace(r rune) bool { @@ -97,6 +98,7 @@ func replaceEnv(getenv func(string) string, s string) string { type Parser struct { ParseEnv bool ParseBacktick bool + ParseComment bool Position int Dir string @@ -109,6 +111,7 @@ func NewParser() *Parser { return &Parser{ ParseEnv: ParseEnv, ParseBacktick: ParseBacktick, + ParseComment: ParseComment, Position: 0, Dir: "", } @@ -125,7 +128,7 @@ const ( func (p *Parser) Parse(line string) ([]string, error) { args := []string{} buf := "" - var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool + var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote, comment bool backtick := "" pos := -1 @@ -135,6 +138,14 @@ func (p *Parser) Parse(line string) ([]string, error) { loop: for _, r := range line { i++ + + if comment { + if r == '\n' { + comment = false + } + continue + } + if escaped { if r == 't' { r = '\t' @@ -254,6 +265,11 @@ loop: pos = i break loop } + case '#': + if p.ParseComment && len(buf) == 0 && !(escaped || singleQuoted || doubleQuoted) { + comment = true + continue loop + } } got = argSingle diff --git a/shellwords_test.go b/shellwords_test.go index ef5809e..aac84cc 100644 --- a/shellwords_test.go +++ b/shellwords_test.go @@ -10,10 +10,12 @@ import ( "testing" ) -var testcases = []struct { +type testcase struct { line string expected []string -}{ +} + +var testcases = []testcase{ {``, []string{}}, {`""`, []string{``}}, {`''`, []string{``}}, @@ -53,6 +55,30 @@ func TestSimple(t *testing.T) { } } +func TestComment(t *testing.T) { + allCases := append(testcases, []testcase{ + {"# comment", []string{}}, + {"foo not#comment", []string{"foo", "not#comment"}}, + {`foo "bar # baz" # comment`, []string{"foo", "bar # baz"}}, + {"foo \"bar # baz\" # comment\nfoo\nbar # baz", + []string{"foo", "bar # baz", "foo", "bar"}}, + {"echo '# list all files' # line\\ncomment\n# whole line comment\nls -al '#' # more comment", + []string{"echo", "# list all files", "ls", "-al", "#"}}, + }...) + + parser := NewParser() + parser.ParseComment = true + for _, testcase := range allCases { + args, err := parser.Parse(testcase.line) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(args, testcase.expected) { + t.Fatalf("Expected %#v for %q, but %#v:", testcase.expected, testcase.line, args) + } + } +} + func TestError(t *testing.T) { _, err := Parse("foo '") if err == nil {