whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
language: 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.1
|
||||
@@ -0,0 +1,24 @@
|
||||
The 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.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Argv
|
||||
|
||||
[](https://godoc.org/github.com/cosiner/argv)
|
||||
[](https://travis-ci.org/cosiner/argv)
|
||||
[](https://coveralls.io/github/cosiner/argv)
|
||||
[](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.
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package 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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/cosiner/argv
|
||||
|
||||
go 1.13
|
||||
@@ -0,0 +1,246 @@
|
||||
package 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package 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
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user