whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -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
[![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.
@@ -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)
}
}
}