package expander import ( "fmt" "regexp" "strconv" "strings" ) // these are helper functions that bring bash-substitution to the drone yaml file. // see http://tldp.org/LDP/abs/html/parameter-substitution.html type substituteFunc func(str, key, val string) string var substitutors = []substituteFunc{ substituteQ, substitute, substitutePrefix, substituteSuffix, substituteDefault, substituteReplace, substituteLeft, substituteSubstr, } // substitute is a helper function that substitutes a simple parameter using // ${parameter} notation. func substitute(str, key, val string) string { key = fmt.Sprintf("${%s}", key) return strings.Replace(str, key, val, -1) } // substituteQ is a helper function that substitutes a simple parameter using // "${parameter}" notation with the escaped value, using %q. func substituteQ(str, key, val string) string { key = fmt.Sprintf(`"${%s}"`, key) val = fmt.Sprintf("%q", val) return strings.Replace(str, key, val, -1) } // substitutePrefix is a helper function that substitutes parameters using // ${parameter##prefix} notation with the parameter value minus the trimmed prefix. func substitutePrefix(str, key, val string) string { key = fmt.Sprintf("\\${%s##(.+)}", key) reg, err := regexp.Compile(key) if err != nil { return str } for _, match := range reg.FindAllStringSubmatch(str, -1) { if len(match) != 2 { continue } val_ := strings.TrimPrefix(val, match[1]) str = strings.Replace(str, match[0], val_, -1) } return str } // substituteSuffix is a helper function that substitutes parameters using // ${parameter%%suffix} notation with the parameter value minus the trimmed suffix. func substituteSuffix(str, key, val string) string { key = fmt.Sprintf("\\${%s%%%%(.+)}", key) reg, err := regexp.Compile(key) if err != nil { return str } for _, match := range reg.FindAllStringSubmatch(str, -1) { if len(match) != 2 { continue } val_ := strings.TrimSuffix(val, match[1]) str = strings.Replace(str, match[0], val_, -1) } return str } // substituteDefault is a helper function that substitutes parameters using // ${parameter=default} notation with the parameter value. When empty the // default value is used. func substituteDefault(str, key, val string) string { key = fmt.Sprintf("\\${%s=(.+)}", key) reg, err := regexp.Compile(key) if err != nil { return str } for _, match := range reg.FindAllStringSubmatch(str, -1) { if len(match) != 2 { continue } if len(val) == 0 { str = strings.Replace(str, match[0], match[1], -1) } else { str = strings.Replace(str, match[0], val, -1) } } return str } // unescapeBackslash is a helper function to unescape any backslashes in str. // Note that no actual literal conversions are done. func unescapeBackslash(str string) string { re := regexp.MustCompile(`\\(.)`) return string(re.ReplaceAll([]byte(str), []byte("$1"))) } // substituteReplace is a helper function that substitutes parameters using // ${parameter/old/new} notation with the parameter value. A find and replace // is performed before injecting the strings, replacing the old pattern with // the new value. func substituteReplace(str, key, val string) string { key = fmt.Sprintf(`\${%s/((?:\\.|[^\\])+)/(.+)}`, key) reg, err := regexp.Compile(key) if err != nil { return str } match := reg.FindStringSubmatch(str) if match == nil { return str } old := unescapeBackslash(match[1]) new := unescapeBackslash(match[2]) with := strings.Replace(val, old, new, -1) return strings.Replace(str, match[0], with, -1) } // substituteLeft is a helper function that substitutes parameters using // ${parameter:pos} notation with the parameter value, sliced up to the // specified position. func substituteLeft(str, key, val string) string { key = fmt.Sprintf("\\${%s:([0-9]*)}", key) reg, err := regexp.Compile(key) if err != nil { return str } for _, match := range reg.FindAllStringSubmatch(str, -1) { if len(match) != 2 { continue } index, err := strconv.Atoi(match[1]) if err != nil { continue // skip } if index > len(val)-1 { continue // skip } str = strings.Replace(str, match[0], val[:index], -1) } return str } // substituteLeft is a helper function that substitutes parameters using // ${parameter:pos:len} notation with the parameter value as a substring, // starting at the specified position for the specified length. func substituteSubstr(str, key, val string) string { key = fmt.Sprintf("\\${%s:([0-9]*):([0-9]*)}", key) reg, err := regexp.Compile(key) if err != nil { return str } for _, match := range reg.FindAllStringSubmatch(str, -1) { if len(match) != 3 { continue } pos, err := strconv.Atoi(match[1]) if err != nil { continue // skip } length, err := strconv.Atoi(match[2]) if err != nil { continue // skip } if pos+length > len(val)-1 { continue // skip } str = strings.Replace(str, match[0], val[pos:pos+length], -1) } return str }