github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/config/interpolation.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "github.com/Sirupsen/logrus" 9 ) 10 11 func isNum(c uint8) bool { 12 return c >= '0' && c <= '9' 13 } 14 15 func validVariableNameChar(c uint8) bool { 16 return c == '_' || 17 c >= 'A' && c <= 'Z' || 18 c >= 'a' && c <= 'z' || 19 isNum(c) 20 } 21 22 func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) { 23 var buffer bytes.Buffer 24 25 for ; pos < len(line); pos++ { 26 c := line[pos] 27 28 switch { 29 case validVariableNameChar(c): 30 buffer.WriteByte(c) 31 default: 32 return mapping(buffer.String()), pos - 1, true 33 } 34 } 35 36 return mapping(buffer.String()), pos, true 37 } 38 39 func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) { 40 var buffer bytes.Buffer 41 42 for ; pos < len(line); pos++ { 43 c := line[pos] 44 45 switch { 46 case c == '}': 47 bufferString := buffer.String() 48 49 if bufferString == "" { 50 return "", 0, false 51 } 52 53 return mapping(buffer.String()), pos, true 54 case validVariableNameChar(c): 55 buffer.WriteByte(c) 56 default: 57 return "", 0, false 58 } 59 } 60 61 return "", 0, false 62 } 63 64 func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) { 65 c := line[pos] 66 67 switch { 68 case c == '$': 69 return "$", pos, true 70 case c == '{': 71 return parseVariableWithBraces(line, pos+1, mapping) 72 case !isNum(c) && validVariableNameChar(c): 73 // Variables can't start with a number 74 return parseVariable(line, pos, mapping) 75 default: 76 return "", 0, false 77 } 78 } 79 80 func parseLine(line string, mapping func(string) string) (string, bool) { 81 var buffer bytes.Buffer 82 83 for pos := 0; pos < len(line); pos++ { 84 c := line[pos] 85 switch { 86 case c == '$': 87 var replaced string 88 var success bool 89 90 replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping) 91 92 if !success { 93 return "", false 94 } 95 96 buffer.WriteString(replaced) 97 default: 98 buffer.WriteByte(c) 99 } 100 } 101 102 return buffer.String(), true 103 } 104 105 func parseConfig(option, service string, data *interface{}, mapping func(string) string) error { 106 switch typedData := (*data).(type) { 107 case string: 108 var success bool 109 110 *data, success = parseLine(typedData, mapping) 111 112 if !success { 113 return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData) 114 } 115 case []interface{}: 116 for k, v := range typedData { 117 err := parseConfig(option, service, &v, mapping) 118 119 if err != nil { 120 return err 121 } 122 123 typedData[k] = v 124 } 125 case map[interface{}]interface{}: 126 for k, v := range typedData { 127 err := parseConfig(option, service, &v, mapping) 128 129 if err != nil { 130 return err 131 } 132 133 typedData[k] = v 134 } 135 } 136 137 return nil 138 } 139 140 // Interpolate replaces variables in the raw map representation of the project file 141 func Interpolate(environmentLookup EnvironmentLookup, config *RawServiceMap) error { 142 for k, v := range *config { 143 for k2, v2 := range v { 144 err := parseConfig(k2, k, &v2, func(s string) string { 145 values := environmentLookup.Lookup(s, k, nil) 146 147 if len(values) == 0 { 148 logrus.Warnf("The %s variable is not set. Substituting a blank string.", s) 149 return "" 150 } 151 152 // Use first result if many are given 153 value := values[0] 154 155 // Environment variables come in key=value format 156 // Return everything past first '=' 157 return strings.SplitN(value, "=", 2)[1] 158 }) 159 160 if err != nil { 161 return err 162 } 163 164 (*config)[k][k2] = v2 165 } 166 } 167 168 return nil 169 }