|
@@ -12,6 +12,7 @@ import (
|
|
|
"fmt"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
+ "unicode"
|
|
|
)
|
|
|
|
|
|
var (
|
|
@@ -41,17 +42,139 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
|
|
// parse environment like statements. Note that this does *not* handle
|
|
|
// variable interpolation, which will be handled in the evaluator.
|
|
|
func parseEnv(rest string) (*Node, map[string]bool, error) {
|
|
|
- node := &Node{}
|
|
|
- rootnode := node
|
|
|
- strs := TOKEN_WHITESPACE.Split(rest, 2)
|
|
|
+ // This is kind of tricky because we need to support the old
|
|
|
+ // variant: ENV name value
|
|
|
+ // as well as the new one: ENV name=value ...
|
|
|
+ // The trigger to know which one is being used will be whether we hit
|
|
|
+ // a space or = first. space ==> old, "=" ==> new
|
|
|
+
|
|
|
+ const (
|
|
|
+ inSpaces = iota // looking for start of a word
|
|
|
+ inWord
|
|
|
+ inQuote
|
|
|
+ )
|
|
|
+
|
|
|
+ words := []string{}
|
|
|
+ phase := inSpaces
|
|
|
+ word := ""
|
|
|
+ quote := '\000'
|
|
|
+ blankOK := false
|
|
|
+ var ch rune
|
|
|
+
|
|
|
+ for pos := 0; pos <= len(rest); pos++ {
|
|
|
+ if pos != len(rest) {
|
|
|
+ ch = rune(rest[pos])
|
|
|
+ }
|
|
|
+
|
|
|
+ if phase == inSpaces { // Looking for start of word
|
|
|
+ if pos == len(rest) { // end of input
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if unicode.IsSpace(ch) { // skip spaces
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ phase = inWord // found it, fall thru
|
|
|
+ }
|
|
|
+ if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
|
|
|
+ if blankOK || len(word) > 0 {
|
|
|
+ words = append(words, word)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if phase == inWord {
|
|
|
+ if unicode.IsSpace(ch) {
|
|
|
+ phase = inSpaces
|
|
|
+ if blankOK || len(word) > 0 {
|
|
|
+ words = append(words, word)
|
|
|
+
|
|
|
+ // Look for = and if no there assume
|
|
|
+ // we're doing the old stuff and
|
|
|
+ // just read the rest of the line
|
|
|
+ if !strings.Contains(word, "=") {
|
|
|
+ word = strings.TrimSpace(rest[pos:])
|
|
|
+ words = append(words, word)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ word = ""
|
|
|
+ blankOK = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if ch == '\'' || ch == '"' {
|
|
|
+ quote = ch
|
|
|
+ blankOK = true
|
|
|
+ phase = inQuote
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if ch == '\\' {
|
|
|
+ if pos+1 == len(rest) {
|
|
|
+ continue // just skip \ at end
|
|
|
+ }
|
|
|
+ pos++
|
|
|
+ ch = rune(rest[pos])
|
|
|
+ }
|
|
|
+ word += string(ch)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if phase == inQuote {
|
|
|
+ if ch == quote {
|
|
|
+ phase = inWord
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if ch == '\\' {
|
|
|
+ if pos+1 == len(rest) {
|
|
|
+ phase = inWord
|
|
|
+ continue // just skip \ at end
|
|
|
+ }
|
|
|
+ pos++
|
|
|
+ ch = rune(rest[pos])
|
|
|
+ }
|
|
|
+ word += string(ch)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if len(strs) < 2 {
|
|
|
- return nil, nil, fmt.Errorf("ENV must have two arguments")
|
|
|
+ if len(words) == 0 {
|
|
|
+ return nil, nil, fmt.Errorf("ENV must have some arguments")
|
|
|
}
|
|
|
|
|
|
- node.Value = strs[0]
|
|
|
- node.Next = &Node{}
|
|
|
- node.Next.Value = strs[1]
|
|
|
+ // Old format (ENV name value)
|
|
|
+ var rootnode *Node
|
|
|
+
|
|
|
+ if !strings.Contains(words[0], "=") {
|
|
|
+ node := &Node{}
|
|
|
+ rootnode = node
|
|
|
+ strs := TOKEN_WHITESPACE.Split(rest, 2)
|
|
|
+
|
|
|
+ if len(strs) < 2 {
|
|
|
+ return nil, nil, fmt.Errorf("ENV must have two arguments")
|
|
|
+ }
|
|
|
+
|
|
|
+ node.Value = strs[0]
|
|
|
+ node.Next = &Node{}
|
|
|
+ node.Next.Value = strs[1]
|
|
|
+ } else {
|
|
|
+ var prevNode *Node
|
|
|
+ for i, word := range words {
|
|
|
+ if !strings.Contains(word, "=") {
|
|
|
+ return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word)
|
|
|
+ }
|
|
|
+ parts := strings.SplitN(word, "=", 2)
|
|
|
+
|
|
|
+ name := &Node{}
|
|
|
+ value := &Node{}
|
|
|
+
|
|
|
+ name.Next = value
|
|
|
+ name.Value = parts[0]
|
|
|
+ value.Value = parts[1]
|
|
|
+
|
|
|
+ if i == 0 {
|
|
|
+ rootnode = name
|
|
|
+ } else {
|
|
|
+ prevNode.Next = name
|
|
|
+ }
|
|
|
+ prevNode = value
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
return rootnode, nil, nil
|
|
|
}
|