2013-03-02 00:34:31 -08:00
|
|
|
// String substitution and expansion.
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2014-08-04 12:30:57 -07:00
|
|
|
"regexp"
|
2013-03-03 17:51:00 -08:00
|
|
|
"strings"
|
|
|
|
|
"unicode/utf8"
|
2013-03-02 00:34:31 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Expand a word. This includes substituting variables and handling quotes.
|
2013-03-02 10:57:40 -08:00
|
|
|
func expand(input string, vars map[string][]string, expandBackticks bool) []string {
|
2013-03-03 17:51:00 -08:00
|
|
|
parts := make([]string, 0)
|
|
|
|
|
expanded := ""
|
2013-03-02 00:34:31 -08:00
|
|
|
var i, j int
|
|
|
|
|
for i = 0; i < len(input); {
|
2013-03-10 00:34:42 -08:00
|
|
|
j = strings.IndexAny(input[i:], "\"'`$\\")
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
if j < 0 {
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += input[i:]
|
2013-03-02 00:34:31 -08:00
|
|
|
break
|
|
|
|
|
}
|
2013-03-10 00:34:42 -08:00
|
|
|
j += i
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += input[i:j]
|
2013-03-02 00:34:31 -08:00
|
|
|
c, w := utf8.DecodeRuneInString(input[j:])
|
|
|
|
|
i = j + w
|
|
|
|
|
|
|
|
|
|
var off int
|
|
|
|
|
var out string
|
|
|
|
|
switch c {
|
|
|
|
|
case '\\':
|
|
|
|
|
out, off = expandEscape(input[i:])
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += out
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
case '"':
|
|
|
|
|
out, off = expandDoubleQuoted(input[i:], vars, expandBackticks)
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += out
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
case '\'':
|
|
|
|
|
out, off = expandSingleQuoted(input[i:])
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += out
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
case '`':
|
2013-03-03 17:51:00 -08:00
|
|
|
if expandBackticks {
|
2013-08-18 22:21:25 -07:00
|
|
|
var outparts []string
|
|
|
|
|
outparts, off = expandBackQuoted(input[i:], vars)
|
|
|
|
|
if len(outparts) > 0 {
|
|
|
|
|
outparts[0] = expanded + outparts[0]
|
|
|
|
|
expanded = outparts[len(outparts)-1]
|
|
|
|
|
parts = append(parts, outparts[:len(outparts)-1]...)
|
|
|
|
|
}
|
2013-03-03 17:51:00 -08:00
|
|
|
} else {
|
|
|
|
|
out = input
|
|
|
|
|
off = len(input)
|
2013-08-18 22:21:25 -07:00
|
|
|
expanded += out
|
2013-03-03 17:51:00 -08:00
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
case '$':
|
2013-03-03 17:51:00 -08:00
|
|
|
var outparts []string
|
|
|
|
|
outparts, off = expandSigil(input[i:], vars)
|
|
|
|
|
if len(outparts) > 0 {
|
2014-04-17 10:35:07 -07:00
|
|
|
firstpart := expanded + outparts[0]
|
2014-04-17 09:48:45 -07:00
|
|
|
if len(outparts) > 1 {
|
2014-04-17 10:35:07 -07:00
|
|
|
parts = append(parts, firstpart)
|
|
|
|
|
if len(outparts) > 2 {
|
|
|
|
|
parts = append(parts, outparts[1:len(outparts)-1]...)
|
|
|
|
|
}
|
|
|
|
|
expanded = outparts[len(outparts)-1]
|
|
|
|
|
} else {
|
|
|
|
|
expanded = firstpart
|
2014-04-17 09:48:45 -07:00
|
|
|
}
|
2013-03-03 17:51:00 -08:00
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i += off
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
if len(expanded) > 0 {
|
|
|
|
|
parts = append(parts, expanded)
|
|
|
|
|
}
|
2013-03-02 10:57:40 -08:00
|
|
|
|
|
|
|
|
return parts
|
2013-03-02 00:34:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expand following a '\\'
|
|
|
|
|
func expandEscape(input string) (string, int) {
|
|
|
|
|
c, w := utf8.DecodeRuneInString(input)
|
2014-02-01 18:19:26 -08:00
|
|
|
if c == '\t' || c == ' ' {
|
|
|
|
|
return string(c), w
|
|
|
|
|
}
|
2014-05-23 11:10:06 -07:00
|
|
|
return "\\" + string(c), w
|
2013-03-02 00:34:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expand a double quoted string starting after a '\"'
|
|
|
|
|
func expandDoubleQuoted(input string, vars map[string][]string, expandBackticks bool) (string, int) {
|
|
|
|
|
// find the first non-escaped "
|
|
|
|
|
j := 0
|
|
|
|
|
for {
|
|
|
|
|
j = strings.IndexAny(input[j:], "\"\\")
|
|
|
|
|
if j < 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c, w := utf8.DecodeRuneInString(input[j:])
|
|
|
|
|
j += w
|
|
|
|
|
|
|
|
|
|
if c == '"' {
|
2013-03-02 10:57:40 -08:00
|
|
|
return strings.Join(expand(input[:j], vars, expandBackticks), " "), (j + w)
|
2013-03-02 00:34:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c == '\\' {
|
|
|
|
|
if j+w < len(input) {
|
|
|
|
|
j += w
|
|
|
|
|
_, w := utf8.DecodeRuneInString(input[j:])
|
|
|
|
|
j += w
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return input, len(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expand a single quoted string starting after a '\''
|
|
|
|
|
func expandSingleQuoted(input string) (string, int) {
|
|
|
|
|
j := strings.Index(input, "'")
|
|
|
|
|
if j < 0 {
|
|
|
|
|
return input, len(input)
|
|
|
|
|
}
|
|
|
|
|
return input[:j], (j + 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expand something starting with at '$'.
|
2013-03-02 10:57:40 -08:00
|
|
|
func expandSigil(input string, vars map[string][]string) ([]string, int) {
|
2013-03-03 17:51:00 -08:00
|
|
|
c, w := utf8.DecodeRuneInString(input)
|
|
|
|
|
var offset int
|
|
|
|
|
var varname string
|
2014-08-04 12:30:57 -07:00
|
|
|
var namelist_pattern = regexp.MustCompile(`^\s*([^:]+)\s*:\s*([^%]*)%([^=]*)\s*=\s*([^%]*)%([^%]*)\s*`)
|
2014-08-04 10:52:31 -07:00
|
|
|
|
|
|
|
|
// escaping of "$" with "$$"
|
|
|
|
|
if c == '$' {
|
|
|
|
|
return []string{"$"}, 2
|
2014-08-04 12:30:57 -07:00
|
|
|
// match bracketed expansions: ${foo}, or ${foo:a%b=c%d}
|
2014-08-04 10:52:31 -07:00
|
|
|
} else if c == '{' {
|
2013-03-03 17:51:00 -08:00
|
|
|
j := strings.IndexRune(input[w:], '}')
|
|
|
|
|
if j < 0 {
|
|
|
|
|
return []string{"$" + input}, len(input)
|
|
|
|
|
}
|
2013-07-28 21:16:50 -07:00
|
|
|
varname = input[w : w+j]
|
|
|
|
|
offset = w + j + 1
|
2014-08-04 12:30:57 -07:00
|
|
|
|
|
|
|
|
// is this a namelist?
|
|
|
|
|
mat := namelist_pattern.FindStringSubmatch(varname)
|
|
|
|
|
if mat != nil && isValidVarName(mat[1]) {
|
|
|
|
|
// ${varname:a%b=c%d}
|
|
|
|
|
varname = mat[1]
|
|
|
|
|
a, b, c, d := mat[2], mat[3], mat[4], mat[5]
|
|
|
|
|
values, ok := vars[varname]
|
|
|
|
|
if !ok {
|
|
|
|
|
return []string{}, offset
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pat := regexp.MustCompile(strings.Join([]string{`^\Q`, a, `\E(.*)\Q`, b, `\E$`}, ""))
|
|
|
|
|
expanded_values := make([]string, len(values))
|
|
|
|
|
for i, value := range values {
|
|
|
|
|
value_match := pat.FindStringSubmatch(value)
|
|
|
|
|
if value_match != nil {
|
|
|
|
|
expanded_values[i] = strings.Join([]string{c, value_match[1], d}, "")
|
|
|
|
|
} else {
|
|
|
|
|
expanded_values[i] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expanded_values, offset
|
|
|
|
|
}
|
|
|
|
|
// bare variables: $foo
|
2013-03-03 17:51:00 -08:00
|
|
|
} else {
|
|
|
|
|
// try to match a variable name
|
|
|
|
|
i := 0
|
|
|
|
|
j := i
|
|
|
|
|
for j < len(input) {
|
|
|
|
|
c, w = utf8.DecodeRuneInString(input[j:])
|
|
|
|
|
if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
j += w
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if j > i {
|
|
|
|
|
varname = input[i:j]
|
|
|
|
|
offset = j
|
|
|
|
|
} else {
|
|
|
|
|
return []string{"$" + input}, len(input)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isValidVarName(varname) {
|
|
|
|
|
varvals, ok := vars[varname]
|
|
|
|
|
if ok {
|
|
|
|
|
return varvals, offset
|
2013-03-09 20:54:13 -08:00
|
|
|
} else {
|
2013-03-10 00:34:42 -08:00
|
|
|
return []string{"$" + input[:offset]}, offset
|
|
|
|
|
}
|
2013-03-03 17:51:00 -08:00
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
return []string{"$" + input}, len(input)
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
// Find and expand all sigils.
|
2013-03-02 10:57:40 -08:00
|
|
|
func expandSigils(input string, vars map[string][]string) []string {
|
2013-03-03 17:51:00 -08:00
|
|
|
parts := make([]string, 0)
|
|
|
|
|
expanded := ""
|
|
|
|
|
for i := 0; i < len(input); {
|
|
|
|
|
j := strings.IndexRune(input[i:], '$')
|
|
|
|
|
if j < 0 {
|
|
|
|
|
expanded += input[i:]
|
|
|
|
|
break
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
ex, k := expandSigil(input[j+1:], vars)
|
|
|
|
|
if len(ex) > 0 {
|
|
|
|
|
ex[0] = expanded + ex[0]
|
|
|
|
|
expanded = ex[len(ex)-1]
|
|
|
|
|
parts = append(parts, ex[:len(ex)-1]...)
|
|
|
|
|
}
|
|
|
|
|
i = k
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(expanded) > 0 {
|
|
|
|
|
parts = append(parts, expanded)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parts
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:50:00 -08:00
|
|
|
// Find and expand all sigils in a recipe, producing a flat string.
|
|
|
|
|
func expandRecipeSigils(input string, vars map[string][]string) string {
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded := ""
|
|
|
|
|
for i := 0; i < len(input); {
|
2013-03-03 18:57:14 -08:00
|
|
|
off := strings.IndexAny(input[i:], "$\\")
|
|
|
|
|
if off < 0 {
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded += input[i:]
|
|
|
|
|
break
|
|
|
|
|
}
|
2013-03-03 18:57:14 -08:00
|
|
|
expanded += input[i : i+off]
|
|
|
|
|
i += off
|
2013-03-03 17:50:00 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
c, w := utf8.DecodeRuneInString(input[i:])
|
|
|
|
|
if c == '$' {
|
|
|
|
|
i += w
|
|
|
|
|
ex, k := expandSigil(input[i:], vars)
|
|
|
|
|
expanded += strings.Join(ex, " ")
|
|
|
|
|
i += k
|
|
|
|
|
} else if c == '\\' {
|
|
|
|
|
i += w
|
|
|
|
|
c, w := utf8.DecodeRuneInString(input[i:])
|
|
|
|
|
if c == '$' {
|
|
|
|
|
expanded += "$"
|
|
|
|
|
} else {
|
|
|
|
|
expanded += "\\" + string(c)
|
|
|
|
|
}
|
|
|
|
|
i += w
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expanded
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
// Expand all unescaped '%' characters.
|
|
|
|
|
func expandSuffixes(input string, stem string) string {
|
2013-03-03 17:51:00 -08:00
|
|
|
expanded := make([]byte, 0)
|
|
|
|
|
for i := 0; i < len(input); {
|
|
|
|
|
j := strings.IndexAny(input[i:], "\\%")
|
|
|
|
|
if j < 0 {
|
|
|
|
|
expanded = append(expanded, input[i:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
c, w := utf8.DecodeRuneInString(input[j:])
|
2013-03-18 20:37:01 -07:00
|
|
|
expanded = append(expanded, input[i:j]...)
|
2013-03-03 17:51:00 -08:00
|
|
|
if c == '%' {
|
|
|
|
|
expanded = append(expanded, stem...)
|
2013-03-18 20:37:01 -07:00
|
|
|
i = j + w
|
2013-03-03 17:51:00 -08:00
|
|
|
} else {
|
|
|
|
|
j += w
|
|
|
|
|
c, w := utf8.DecodeRuneInString(input[j:])
|
|
|
|
|
if c == '%' {
|
|
|
|
|
expanded = append(expanded, '%')
|
|
|
|
|
i = j + w
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
return string(expanded)
|
|
|
|
|
}
|
2013-03-02 00:34:31 -08:00
|
|
|
|
|
|
|
|
// Expand a backtick quoted string, by executing the contents.
|
2013-08-18 22:21:25 -07:00
|
|
|
func expandBackQuoted(input string, vars map[string][]string) ([]string, int) {
|
2013-03-03 17:51:00 -08:00
|
|
|
// TODO: expand sigils?
|
2013-03-02 00:34:31 -08:00
|
|
|
j := strings.Index(input, "`")
|
|
|
|
|
if j < 0 {
|
2013-08-18 22:21:25 -07:00
|
|
|
return []string{input}, len(input)
|
2013-03-02 00:34:31 -08:00
|
|
|
}
|
|
|
|
|
|
2013-03-03 17:51:00 -08:00
|
|
|
// TODO: handle errors
|
2013-07-09 12:33:25 -07:00
|
|
|
output, _ := subprocess("sh", nil, input[:j], true)
|
2013-03-02 00:34:31 -08:00
|
|
|
|
2013-08-18 22:21:25 -07:00
|
|
|
parts := make([]string, 0)
|
|
|
|
|
_, tokens := lexWords(output)
|
|
|
|
|
for t := range tokens {
|
|
|
|
|
parts = append(parts, t.val)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parts, (j + 1)
|
|
|
|
|
}
|