pax_global_header00006660000000000000000000000064136423215020014510gustar00rootroot0000000000000052 comment=0e2b0bed5d2192501434fcddeca7c485a929f513 argv-0.1.0/000077500000000000000000000000001364232150200124455ustar00rootroot00000000000000argv-0.1.0/.travis.yml000066400000000000000000000016301364232150200145560ustar00rootroot00000000000000language: go sudo: false go: - 1.8 - 1.7.5 - 1.7.4 - 1.7.3 - 1.7.2 - 1.7.1 - 1.7 - tip - 1.6.4 - 1.6.3 - 1.6.2 - 1.6.1 - 1.6 - 1.5.4 - 1.5.3 - 1.5.2 - 1.5.1 - 1.5 - 1.4.3 - 1.4.2 - 1.4.1 - 1.4 - 1.3.3 - 1.3.2 - 1.3.1 - 1.3 - 1.2.2 - 1.2.1 - 1.2 - 1.1.2 - 1.1.1 - 1.1 before_install: - go get github.com/mattn/goveralls script: - $HOME/gopath/bin/goveralls -service=travis-ci notifications: email: on_success: never matrix: fast_finish: true allow_failures: - go: tip - go: 1.6.4 - go: 1.6.3 - go: 1.6.2 - go: 1.6.1 - go: 1.6 - go: 1.5.4 - go: 1.5.3 - go: 1.5.2 - go: 1.5.1 - go: 1.5 - go: 1.4.3 - go: 1.4.2 - go: 1.4.1 - go: 1.4 - go: 1.3.3 - go: 1.3.2 - go: 1.3.1 - go: 1.3 - go: 1.2.2 - go: 1.2.1 - go: 1.2 - go: 1.1.2 - go: 1.1.1 - go: 1.1argv-0.1.0/LICENSE000066400000000000000000000020641364232150200134540ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 aihui zhu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. argv-0.1.0/README.md000066400000000000000000000020621364232150200137240ustar00rootroot00000000000000# Argv [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/cosiner/argv) [![Build Status](https://travis-ci.org/cosiner/argv.svg?branch=master&style=flat)](https://travis-ci.org/cosiner/argv) [![Coverage Status](https://coveralls.io/repos/github/cosiner/argv/badge.svg?style=flat)](https://coveralls.io/github/cosiner/argv) [![Go Report Card](https://goreportcard.com/badge/github.com/cosiner/argv?style=flat)](https://goreportcard.com/report/github.com/cosiner/argv) Argv is a library for [Go](https://golang.org) to split command line string into arguments array. # Documentation Documentation can be found at [Godoc](https://godoc.org/github.com/cosiner/argv) # Example ```Go func TestArgv(t *testing.T) { args, err := argv.Argv([]rune(" ls `echo /` | wc -l "), argv.ParseEnv(os.Environ()), argv.Run) if err != nil { t.Fatal(err) } expects := [][]string{ []string{"ls", "/"}, []string{"wc", "-l"}, } if !reflect.DeepEqual(args, expects) { t.Fatal(args) } } ``` # LICENSE MIT. argv-0.1.0/argv.go000066400000000000000000000007731364232150200137420ustar00rootroot00000000000000// Package argv parse command line string into arguments array using the bash syntax. package argv // Argv split cmdline string as array of argument array by the '|' character. // // The parsing rules is same as bash. The environment variable will be replaced // and string surround by '`' will be passed to reverse quote parser. func Argv(cmdline string, backquoteExpander, stringExpander Expander) ([][]string, error) { return NewParser(NewScanner(cmdline), backquoteExpander, stringExpander).Parse() } argv-0.1.0/argv_test.go000066400000000000000000000016401364232150200147730ustar00rootroot00000000000000package argv import ( "reflect" "testing" ) func TestArgv(t *testing.T) { type testCase struct { Input string Sections [][]string Error error } cases := []testCase{ { Input: " a | a|a |a`ls ~/``ls /` ", Sections: [][]string{ {"a"}, {"a"}, {"a"}, {"als ~/ls /"}, }, }, { Input: "aaa |", Error: ErrInvalidSyntax, }, { Input: "aaa | | aa", Error: ErrInvalidSyntax, }, { Input: " | aa", Error: ErrInvalidSyntax, }, { Input: `aa"aaa`, Error: ErrInvalidSyntax, }, } for i, c := range cases { gots, err := Argv(c.Input, func(s string) (string, error) { return s, nil }, nil) if err != c.Error { t.Errorf("test failed: %d, expect error:%s, but got %s", i, c.Error, err) } if err != nil { continue } if !reflect.DeepEqual(gots, c.Sections) { t.Errorf("parse failed %d, expect: %v, got %v", i, c.Sections, gots) } } } argv-0.1.0/cmd.go000066400000000000000000000020161364232150200135360ustar00rootroot00000000000000package argv import ( "errors" "io" "os" "os/exec" ) func Cmds(args ...[]string) ([]*exec.Cmd, error) { var cmds []*exec.Cmd for _, argv := range args { if len(argv) == 0 { return nil, errors.New("invalid cmd") } cmds = append(cmds, exec.Command(argv[0], argv[1:]...)) } return cmds, nil } func Pipe(stdin io.Reader, stdout, stderr io.Writer, cmds ...*exec.Cmd) error { if stdin == nil { stdin = os.Stdin } if stdout == nil { stdout = os.Stdout } if stderr == nil { stderr = os.Stderr } l := len(cmds) if l == 0 { return nil } var err error for i := 0; i < l; i++ { if i == 0 { cmds[i].Stdin = stdin } else { cmds[i].Stdin, err = cmds[i-1].StdoutPipe() if stderr != nil { break } } cmds[i].Stderr = stderr if i == l-1 { cmds[i].Stdout = stdout } } if err != nil { return err } for i := range cmds { err = cmds[i].Start() if err != nil { return err } } for i := range cmds { err = cmds[i].Wait() if err != nil { return err } } return nil } argv-0.1.0/go.mod000066400000000000000000000000501364232150200135460ustar00rootroot00000000000000module github.com/cosiner/argv go 1.13 argv-0.1.0/parser.go000066400000000000000000000117621364232150200142770ustar00rootroot00000000000000package argv import ( "errors" "fmt" "os" ) type ( Expander func(string) (string, error) // Parser take tokens from Scanner, and do syntax checking, and generate the splitted arguments array. Parser struct { s *Scanner tokbuf []Token backQuoteExpander Expander stringExpander Expander sections [][]string currSection []string currStrValid bool currStr []rune } ) // NewParser create a cmdline string parser. func NewParser(s *Scanner, backQuoteExpander, stringExpander Expander) *Parser { if backQuoteExpander == nil { backQuoteExpander = func(r string) (string, error) { return r, fmt.Errorf("back quote doesn't allowed") } } if stringExpander == nil { stringExpander = func(runes string) (string, error) { s := os.ExpandEnv(string(runes)) return s, nil } } return &Parser{ s: s, backQuoteExpander: backQuoteExpander, stringExpander: stringExpander, } } func (p *Parser) nextToken() (Token, error) { if l := len(p.tokbuf); l > 0 { tok := p.tokbuf[l-1] p.tokbuf = p.tokbuf[:l-1] return tok, nil } return p.s.Next() } var ( // ErrInvalidSyntax was reported if there is a syntax error in command line string. ErrInvalidSyntax = errors.New("invalid syntax") ) func (p *Parser) unreadToken(tok Token) { p.tokbuf = append(p.tokbuf, tok) } // Parse split command line string into arguments array. // // EBNF: // Cmdline = Section [ Pipe Cmdline ] // Section = [Space] SpacedSection { SpacedSection } // SpacedSection = MultipleUnit [Space] // MultipleUnit = Unit {Unit} // Unit = String | BackQuote | SingleQuote | DoubleQuote func (p *Parser) Parse() ([][]string, error) { err := p.cmdline() if err != nil { return nil, err } return p.sections, nil } func (p *Parser) cmdline() error { err := p.section() if err != nil { return err } p.endSection() tok, err := p.nextToken() if err != nil { return err } if tok.Type == TokEOF { return nil } if !p.accept(tok.Type, TokPipe) { return ErrInvalidSyntax } return p.cmdline() } func (p *Parser) section() error { leftSpace, err := p.optional(TokSpace) if err != nil { return err } var hasUnit bool for { unit, ok, err := p.spacedSection() if err != nil { return err } if !ok { break } hasUnit = true err = p.appendUnit(leftSpace, unit) if err != nil { return err } leftSpace = unit.rightSpace } if !hasUnit { return ErrInvalidSyntax } return nil } type unit struct { rightSpace bool toks []Token } func (p *Parser) spacedSection() (u unit, ok bool, err error) { u.toks, ok, err = p.multipleUnit() if err != nil { return } if !ok { return u, false, nil } u.rightSpace, err = p.optional(TokSpace) return } func (p *Parser) multipleUnit() ([]Token, bool, error) { var ( toks []Token ) for { tok, ok, err := p.unit() if err != nil { return nil, false, err } if !ok { break } toks = append(toks, tok) } return toks, len(toks) > 0, nil } func (p *Parser) unit() (Token, bool, error) { tok, err := p.nextToken() if err != nil { return tok, false, err } if p.accept(tok.Type, TokString, TokStringSingleQuote, TokStringDoubleQuote, TokBackQuote) { return tok, true, nil } p.unreadToken(tok) return tok, false, nil } func (p *Parser) optional(typ TokenType) (bool, error) { tok, err := p.nextToken() if err != nil { return false, err } var ok bool if ok = p.accept(tok.Type, typ); !ok { p.unreadToken(tok) } return ok, nil } func (p *Parser) accept(t TokenType, types ...TokenType) bool { for _, typ := range types { if t == typ { return true } } return false } func (p *Parser) appendUnit(leftSpace bool, u unit) error { if leftSpace { p.currStr = p.currStr[:0] } for _, tok := range u.toks { switch tok.Type { case TokBackQuote: expanded, err := p.backQuoteExpander(string(tok.Value)) if err != nil { return fmt.Errorf("expand string back quoted failed: %s, %w", string(tok.Value), err) } p.currStr = append(p.currStr, []rune(expanded)...) case TokString: expanded, err := p.stringExpander(string(tok.Value)) if err != nil { return fmt.Errorf("expand string failed: %s, %w", string(tok.Value), err) } p.currStr = append(p.currStr, []rune(expanded)...) case TokStringSingleQuote: p.currStr = append(p.currStr, tok.Value...) case TokStringDoubleQuote: expanded, err := p.stringExpander(string(tok.Value)) if err != nil { return fmt.Errorf("expand string double quoted failed: %s, %w", string(tok.Value), err) } p.currStr = append(p.currStr, []rune(expanded)...) } } p.currStrValid = true if u.rightSpace { p.currSection = append(p.currSection, string(p.currStr)) p.currStr = p.currStr[:0] p.currStrValid = false } return nil } func (p *Parser) endSection() { if p.currStrValid { p.currSection = append(p.currSection, string(p.currStr)) } p.currStr = p.currStr[:0] p.currStrValid = false if len(p.currSection) > 0 { p.sections = append(p.sections, p.currSection) p.currSection = nil } } argv-0.1.0/scanner.go000066400000000000000000000075431364232150200144360ustar00rootroot00000000000000package argv import ( "unicode" ) // Scanner is a cmdline string scanner. // // It split cmdline string to tokens: space, string, pipe, reverse quote string. type Scanner struct { text []rune rpos int } // NewScanner create a scanner and init it's internal states. func NewScanner(text string) *Scanner { return &Scanner{ text: []rune(text), } } const _RuneEOF = 0 func (s *Scanner) nextRune() rune { if s.rpos >= len(s.text) { return _RuneEOF } r := s.text[s.rpos] s.rpos++ return r } func (s *Scanner) unreadRune(r rune) { if r != _RuneEOF { s.rpos-- } } func (s *Scanner) isEscapeChars(r rune) (rune, bool) { switch r { case 'a': return '\a', true case 'b': return '\b', true case 'f': return '\f', true case 'n': return '\n', true case 'r': return '\r', true case 't': return '\t', true case 'v': return '\v', true case '\\': return '\\', true case '$': return '$', true } return r, false } // TokenType is the type of tokens recognized by the scanner. type TokenType uint32 // Token is generated by the scanner with a type and value. type Token struct { Type TokenType Value []rune } const ( // TokString for string TokString TokenType = iota + 1 TokStringSingleQuote TokStringDoubleQuote // TokPipe is the '|' character TokPipe // TokBackQuote is reverse quoted string TokBackQuote // TokSpace represent space character sequence TokSpace // TokEOF means the input end. TokEOF ) // Next return next token, if it reach the end, TOK_EOF will be returned. // // Error is returned for invalid syntax such as unpaired quotes. func (s *Scanner) Next() (Token, error) { const ( Initial = iota + 1 Space BackQuote String StringSingleQuote StringDoubleQuote ) var ( tok Token state uint8 = Initial ) for { r := s.nextRune() switch state { case Initial: switch { case r == _RuneEOF: tok.Type = TokEOF return tok, nil case r == '|': tok.Type = TokPipe return tok, nil case r == '`': state = BackQuote case unicode.IsSpace(r): state = Space s.unreadRune(r) case r == '\'': state = StringSingleQuote case r == '"': state = StringDoubleQuote default: state = String s.unreadRune(r) } case Space: if r == _RuneEOF || !unicode.IsSpace(r) { s.unreadRune(r) tok.Type = TokSpace return tok, nil } case BackQuote: switch r { case _RuneEOF: return tok, ErrInvalidSyntax case '`': tok.Type = TokBackQuote return tok, nil default: tok.Value = append(tok.Value, r) } case String: switch { case r == _RuneEOF, r == '|', r == '`', r == '\'', r == '"', unicode.IsSpace(r): tok.Type = TokString s.unreadRune(r) return tok, nil case r == '\\': nr := s.nextRune() if nr == _RuneEOF { return tok, ErrInvalidSyntax } tok.Value = append(tok.Value, nr) default: tok.Value = append(tok.Value, r) } case StringSingleQuote, StringDoubleQuote: switch r { case _RuneEOF: return tok, ErrInvalidSyntax case '\'', '"': if singleQuote := state == StringSingleQuote; singleQuote == (r == '\'') { if singleQuote { tok.Type = TokStringSingleQuote } else { tok.Type = TokStringDoubleQuote } return tok, nil } else { tok.Value = append(tok.Value, r) } case '\\': nr := s.nextRune() if escape, ok := s.isEscapeChars(nr); ok { tok.Value = append(tok.Value, escape) } else { tok.Value = append(tok.Value, r) s.unreadRune(nr) } default: tok.Value = append(tok.Value, r) } } } } // Scan is a utility function help split input text as tokens. func Scan(text string) ([]Token, error) { s := NewScanner(text) var tokens []Token for { tok, err := s.Next() if err != nil { return nil, err } tokens = append(tokens, tok) if tok.Type == TokEOF { break } } return tokens, nil } argv-0.1.0/scanner_test.go000066400000000000000000000057711364232150200154760ustar00rootroot00000000000000package argv import ( "math" "testing" ) var ( parseText = ` a aa a'aa' a"aa"a a$PATH a"$PATH" a'$PATH' a"$*" a"$0" a"$\" a| a|a a"\A" a"\a\b\f\n\r\t\v\\\$" \t a'\A' a'\t'` + " a`ls /` `ls ~`" ) func TestScanner(t *testing.T) { gots, err := Scan( parseText, ) if err != nil { t.Fatal(err) } expects := []Token{ {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokSpace}, {Type: TokString, Value: []rune("aa")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringSingleQuote, Value: []rune("aa")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("aa")}, {Type: TokString, Value: []rune("a")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a$PATH")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("$PATH")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringSingleQuote, Value: []rune("$PATH")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("$*")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("$0")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("$\\")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokPipe}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokPipe}, {Type: TokString, Value: []rune("a")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("\\A")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringDoubleQuote, Value: []rune("\a\b\f\n\r\t\v\\$")}, {Type: TokSpace}, {Type: TokString, Value: []rune("t")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringSingleQuote, Value: []rune("\\A")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokStringSingleQuote, Value: []rune("\t")}, {Type: TokSpace}, {Type: TokString, Value: []rune("a")}, {Type: TokBackQuote, Value: []rune("ls /")}, {Type: TokSpace}, {Type: TokBackQuote, Value: []rune("ls ~")}, {Type: TokEOF}, } if len(gots) != len(expects) { t.Errorf("token count is not equal: expect %d, got %d", len(expects), len(gots)) } l := int(math.Min(float64(len(gots)), float64(len(expects)))) for i := 0; i < l; i++ { got := gots[i] expect := expects[i] if got.Type != expect.Type { t.Errorf("token type is not equal: %d: expect %d, got %d", i, expect.Type, got.Type) } if expect.Type != TokSpace && string(got.Value) != string(expect.Value) { t.Errorf("token value is not equal: %d: expect %s, got %s", i, string(expect.Value), string(got.Value)) } } for _, text := range []string{ `a"`, `a'`, `a"\`, "`ls ~", `a\`, } { _, err := Scan(text) if err != ErrInvalidSyntax { t.Errorf("expect unexpected eof error, but got: %v", err) } } }