mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-15 21:37:09 +09:00
Fix autocomplete behavior for empty args
This also adds a modified version of go-shellwords as a dependency and removes the dependency on the original go-shellwords.
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -55,6 +55,3 @@
|
|||||||
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
|
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
|
||||||
path = cmd/micro/vendor/github.com/flynn/json5
|
path = cmd/micro/vendor/github.com/flynn/json5
|
||||||
url = https://github.com/flynn/json5
|
url = https://github.com/flynn/json5
|
||||||
[submodule "cmd/micro/vendor/github.com/mattn/go-shellwords"]
|
|
||||||
path = cmd/micro/vendor/github.com/mattn/go-shellwords
|
|
||||||
url = https://github.com/mattn/go-shellwords
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/yuin/gopher-lua"
|
"github.com/yuin/gopher-lua"
|
||||||
"github.com/zyedidia/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
|
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||||
"github.com/zyedidia/tcell"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -997,7 +998,7 @@ func (v *View) SaveAs(usePlugin bool) bool {
|
|||||||
filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
|
filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
|
||||||
if !canceled {
|
if !canceled {
|
||||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||||
args, err := SplitCommandArgs(filename)
|
args, err := shellwords.Split(filename)
|
||||||
filename = strings.Join(args, " ")
|
filename = strings.Join(args, " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
messenger.Error("Error parsing arguments: ", err)
|
messenger.Error("Error parsing arguments: ", err)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
|
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
|
||||||
@@ -285,7 +286,7 @@ func Open(args []string) {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
filename := args[0]
|
filename := args[0]
|
||||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||||
args, err := SplitCommandArgs(filename)
|
args, err := shellwords.Split(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
messenger.Error("Error parsing args ", err)
|
messenger.Error("Error parsing args ", err)
|
||||||
return
|
return
|
||||||
@@ -500,7 +501,7 @@ func Bind(args []string) {
|
|||||||
// Run runs a shell command in the background
|
// Run runs a shell command in the background
|
||||||
func Run(args []string) {
|
func Run(args []string) {
|
||||||
// Run a shell command in the background (openTerm is false)
|
// Run a shell command in the background (openTerm is false)
|
||||||
HandleShellCommand(JoinCommandArgs(args...), false, true)
|
HandleShellCommand(shellwords.Join(args...), false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit closes the main view
|
// Quit closes the main view
|
||||||
@@ -645,7 +646,7 @@ func ReplaceAll(args []string) {
|
|||||||
|
|
||||||
// RunShellCommand executes a shell command and returns the output/error
|
// RunShellCommand executes a shell command and returns the output/error
|
||||||
func RunShellCommand(input string) (string, error) {
|
func RunShellCommand(input string) (string, error) {
|
||||||
args, err := SplitCommandArgs(input)
|
args, err := shellwords.Split(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -665,7 +666,7 @@ func RunShellCommand(input string) (string, error) {
|
|||||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||||
// or interacting with stdin)
|
// or interacting with stdin)
|
||||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||||
args, err := SplitCommandArgs(input)
|
args, err := shellwords.Split(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -735,7 +736,7 @@ func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
|||||||
|
|
||||||
// HandleCommand handles input from the user
|
// HandleCommand handles input from the user
|
||||||
func HandleCommand(input string) {
|
func HandleCommand(input string) {
|
||||||
args, err := SplitCommandArgs(input)
|
args, err := shellwords.Split(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
messenger.Error("Error parsing args ", err)
|
messenger.Error("Error parsing args ", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/zyedidia/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
|
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||||
"github.com/zyedidia/tcell"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -272,7 +273,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
|||||||
response, canceled = m.response, false
|
response, canceled = m.response, false
|
||||||
m.history[historyType][len(m.history[historyType])-1] = response
|
m.history[historyType][len(m.history[historyType])-1] = response
|
||||||
case tcell.KeyTab:
|
case tcell.KeyTab:
|
||||||
args, err := SplitCommandArgs(m.response)
|
args, err := shellwords.Split(m.response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -322,7 +323,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chosen != "" {
|
if chosen != "" {
|
||||||
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
|
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
|
||||||
m.cursorx = Count(m.response)
|
m.cursorx = Count(m.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
cmd/micro/shellwords/LICENSE
Normal file
21
cmd/micro/shellwords/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Yasuhiro Matsumoto
|
||||||
|
|
||||||
|
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.
|
||||||
49
cmd/micro/shellwords/README.md
Normal file
49
cmd/micro/shellwords/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
This is a modified version of `go-shellwords` for the micro editor.
|
||||||
|
|
||||||
|
# go-shellwords
|
||||||
|
|
||||||
|
[](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||||
|
[](https://travis-ci.org/mattn/go-shellwords)
|
||||||
|
|
||||||
|
Parse line as shell words.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
args, err := shellwords.Parse("./foo --bar=baz")
|
||||||
|
// args should be ["./foo", "--bar=baz"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
os.Setenv("FOO", "bar")
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
p.ParseEnv = true
|
||||||
|
args, err := p.Parse("./foo $FOO")
|
||||||
|
// args should be ["./foo", "bar"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
p.ParseBacktick = true
|
||||||
|
args, err := p.Parse("./foo `echo $SHELL`")
|
||||||
|
// args should be ["./foo", "/bin/bash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
shellwords.ParseBacktick = true
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
args, err := p.Parse("./foo `echo $SHELL`")
|
||||||
|
// args should be ["./foo", "/bin/bash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Thanks
|
||||||
|
|
||||||
|
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
under the MIT License: http://mattn.mit-license.org/2017
|
||||||
|
|
||||||
|
# Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
||||||
189
cmd/micro/shellwords/shellwords.go
Normal file
189
cmd/micro/shellwords/shellwords.go
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||||
|
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case ' ', '\t', '\r', '\n':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceEnv(s string) string {
|
||||||
|
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||||
|
s = s[1:]
|
||||||
|
if s[0] == '{' {
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
}
|
||||||
|
return os.Getenv(s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
Position int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser() *Parser {
|
||||||
|
return &Parser{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) Parse(line string) ([]string, error) {
|
||||||
|
args := []string{}
|
||||||
|
buf := ""
|
||||||
|
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
|
||||||
|
backtick := ""
|
||||||
|
|
||||||
|
pos := -1
|
||||||
|
got := false
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for i, r := range line {
|
||||||
|
if escaped {
|
||||||
|
buf += string(r)
|
||||||
|
escaped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '\\' {
|
||||||
|
if singleQuoted {
|
||||||
|
buf += string(r)
|
||||||
|
} else {
|
||||||
|
escaped = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSpace(r) {
|
||||||
|
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||||
|
buf += string(r)
|
||||||
|
backtick += string(r)
|
||||||
|
} else if got {
|
||||||
|
buf = replaceEnv(buf)
|
||||||
|
args = append(args, buf)
|
||||||
|
buf = ""
|
||||||
|
got = false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case '`':
|
||||||
|
if !singleQuoted && !doubleQuoted && !dollarQuote {
|
||||||
|
if backQuote {
|
||||||
|
out, err := shellRun(backtick)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = out
|
||||||
|
}
|
||||||
|
backtick = ""
|
||||||
|
backQuote = !backQuote
|
||||||
|
continue
|
||||||
|
backtick = ""
|
||||||
|
backQuote = !backQuote
|
||||||
|
}
|
||||||
|
case ')':
|
||||||
|
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||||
|
if dollarQuote {
|
||||||
|
out, err := shellRun(backtick)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = out
|
||||||
|
}
|
||||||
|
backtick = ""
|
||||||
|
dollarQuote = !dollarQuote
|
||||||
|
continue
|
||||||
|
backtick = ""
|
||||||
|
dollarQuote = !dollarQuote
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||||
|
if !dollarQuote && len(buf) > 0 && buf == "$" {
|
||||||
|
dollarQuote = true
|
||||||
|
buf += "("
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("invalid command line string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
if !singleQuoted && !dollarQuote {
|
||||||
|
doubleQuoted = !doubleQuoted
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case '\'':
|
||||||
|
if !doubleQuoted && !dollarQuote {
|
||||||
|
singleQuoted = !singleQuoted
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case ';', '&', '|', '<', '>':
|
||||||
|
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
||||||
|
pos = i
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got = true
|
||||||
|
buf += string(r)
|
||||||
|
if backQuote || dollarQuote {
|
||||||
|
backtick += string(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = replaceEnv(buf)
|
||||||
|
args = append(args, buf)
|
||||||
|
|
||||||
|
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||||
|
return nil, errors.New("invalid command line string")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Position = pos
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Split(line string) ([]string, error) {
|
||||||
|
return NewParser().Parse(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Join(args ...string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i, w := range args {
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
}
|
||||||
|
if w == "" {
|
||||||
|
buf.WriteString("''")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range w {
|
||||||
|
switch b {
|
||||||
|
case 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||||
|
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u',
|
||||||
|
'v', 'w', 'x', 'y', 'z',
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||||
|
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||||
|
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||||
|
'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'_', '-', '.', ',', ':', '/', '@':
|
||||||
|
buf.WriteString(string(b))
|
||||||
|
default:
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteString(string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
229
cmd/micro/shellwords/shellwords_test.go
Normal file
229
cmd/micro/shellwords/shellwords_test.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testcases = []struct {
|
||||||
|
line string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{`var --bar=baz`, []string{`var`, `--bar=baz`}},
|
||||||
|
{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
|
||||||
|
{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
|
||||||
|
{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
|
||||||
|
{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
|
||||||
|
{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
|
||||||
|
{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
|
||||||
|
{`var --bar='\'`, []string{`var`, `--bar=\`}},
|
||||||
|
{`var "--bar baz"`, []string{`var`, `--bar baz`}},
|
||||||
|
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||||
|
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimple(t *testing.T) {
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
args, err := Parse(testcase.line)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(args, testcase.expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", testcase.expected, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
_, err := Parse("foo '")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
_, err = Parse(`foo "`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Parse("foo `")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLastSpace(t *testing.T) {
|
||||||
|
args, err := Parse("foo bar\\ ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(args) != 2 {
|
||||||
|
t.Fatal("Should have two elements")
|
||||||
|
}
|
||||||
|
if args[0] != "foo" {
|
||||||
|
t.Fatal("1st element should be `foo`")
|
||||||
|
}
|
||||||
|
if args[1] != "bar " {
|
||||||
|
t.Fatal("1st element should be `bar `")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBacktick(t *testing.T) {
|
||||||
|
goversion, err := shellRun("go version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseBacktick = true
|
||||||
|
args, err := parser.Parse("echo `go version`")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := []string{"echo", goversion}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err = parser.Parse(`echo $(echo foo)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected = []string{"echo", "foo"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.ParseBacktick = false
|
||||||
|
args, err = parser.Parse(`echo $(echo "foo")`)
|
||||||
|
expected = []string{"echo", `$(echo "foo")`}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
args, err = parser.Parse("echo $(`echo1)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected = []string{"echo", "$(`echo1)"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBacktickError(t *testing.T) {
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseBacktick = true
|
||||||
|
_, err := parser.Parse("echo `go Version`")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
expected := "exit status 2:go: unknown subcommand \"Version\"\nRun 'go help' for usage.\n"
|
||||||
|
if expected != err.Error() {
|
||||||
|
t.Fatalf("Expected %q, but %q", expected, err.Error())
|
||||||
|
}
|
||||||
|
_, err = parser.Parse(`echo $(echo1)`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
_, err = parser.Parse(`echo $(echo1`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
_, err = parser.Parse(`echo $ (echo1`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
_, err = parser.Parse(`echo (echo1`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
_, err = parser.Parse(`echo )echo1`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should be an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnv(t *testing.T) {
|
||||||
|
os.Setenv("FOO", "bar")
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseEnv = true
|
||||||
|
args, err := parser.Parse("echo $FOO")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := []string{"echo", "bar"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoEnv(t *testing.T) {
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseEnv = true
|
||||||
|
args, err := parser.Parse("echo $BAR")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := []string{"echo", ""}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDupEnv(t *testing.T) {
|
||||||
|
os.Setenv("FOO", "bar")
|
||||||
|
os.Setenv("FOO_BAR", "baz")
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseEnv = true
|
||||||
|
args, err := parser.Parse("echo $$FOO$")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := []string{"echo", "$bar$"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err = parser.Parse("echo $${FOO_BAR}$")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected = []string{"echo", "$baz$"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHaveMore(t *testing.T) {
|
||||||
|
parser := NewParser()
|
||||||
|
parser.ParseEnv = true
|
||||||
|
|
||||||
|
line := "echo foo; seq 1 10"
|
||||||
|
args, err := parser.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
expected := []string{"echo", "foo"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parser.Position == 0 {
|
||||||
|
t.Fatalf("Commands should be remaining")
|
||||||
|
}
|
||||||
|
|
||||||
|
line = string([]rune(line)[parser.Position+1:])
|
||||||
|
args, err = parser.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
expected = []string{"seq", "1", "10"}
|
||||||
|
if !reflect.DeepEqual(args, expected) {
|
||||||
|
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parser.Position > 0 {
|
||||||
|
t.Fatalf("Commands should not be remaining")
|
||||||
|
}
|
||||||
|
}
|
||||||
22
cmd/micro/shellwords/util_posix.go
Normal file
22
cmd/micro/shellwords/util_posix.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shellRun(line string) (string, error) {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
b, err := exec.Command(shell, "-c", line).Output()
|
||||||
|
if err != nil {
|
||||||
|
if eerr, ok := err.(*exec.ExitError); ok {
|
||||||
|
b = eerr.Stderr
|
||||||
|
}
|
||||||
|
return "", errors.New(err.Error() + ":" + string(b))
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(b)), nil
|
||||||
|
}
|
||||||
20
cmd/micro/shellwords/util_windows.go
Normal file
20
cmd/micro/shellwords/util_windows.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shellRun(line string) (string, error) {
|
||||||
|
shell := os.Getenv("COMSPEC")
|
||||||
|
b, err := exec.Command(shell, "/c", line).Output()
|
||||||
|
if err != nil {
|
||||||
|
if eerr, ok := err.(*exec.ExitError); ok {
|
||||||
|
b = eerr.Stderr
|
||||||
|
}
|
||||||
|
return "", errors.New(err.Error() + ":" + string(b))
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(b)), nil
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/mattn/go-shellwords"
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -277,55 +275,6 @@ func ShortFuncName(i interface{}) string {
|
|||||||
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
|
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitCommandArgs separates multiple command arguments which may be quoted.
|
|
||||||
// The returned slice contains at least one string
|
|
||||||
func SplitCommandArgs(input string) ([]string, error) {
|
|
||||||
shellwords.ParseEnv = true
|
|
||||||
shellwords.ParseBacktick = true
|
|
||||||
return shellwords.Parse(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
|
||||||
func JoinCommandArgs(args ...string) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i, w := range args {
|
|
||||||
if i != 0 {
|
|
||||||
buf.WriteByte(' ')
|
|
||||||
}
|
|
||||||
if w == "" {
|
|
||||||
buf.WriteString("''")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
strBytes := []byte(w)
|
|
||||||
for _, b := range strBytes {
|
|
||||||
switch b {
|
|
||||||
case
|
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
|
||||||
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
||||||
'o', 'p', 'q', 'r', 's', 't', 'u',
|
|
||||||
'v', 'w', 'x', 'y', 'z',
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
|
||||||
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
||||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
|
||||||
'V', 'W', 'X', 'Y', 'Z',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'_', '-', '.', ',', ':', '/', '@':
|
|
||||||
buf.WriteByte(b)
|
|
||||||
case '\n':
|
|
||||||
buf.WriteString("'\n'")
|
|
||||||
default:
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
buf.WriteByte(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return buf.String()
|
|
||||||
// buf.WriteString(w)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
||||||
// home directory. Does nothing if the path does not start with '~'.
|
// home directory. Does nothing if the path does not start with '~'.
|
||||||
func ReplaceHome(path string) string {
|
func ReplaceHome(path string) string {
|
||||||
|
|||||||
1
cmd/micro/vendor/github.com/mattn/go-shellwords
generated
vendored
1
cmd/micro/vendor/github.com/mattn/go-shellwords
generated
vendored
Submodule cmd/micro/vendor/github.com/mattn/go-shellwords deleted from 95c860c189
Reference in New Issue
Block a user