github.com/coveo/gotemplate@v2.7.7+incompatible/collections/convert_data.go (about) 1 package collections 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "reflect" 8 "regexp" 9 "strconv" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 14 "github.com/coveo/gotemplate/errors" 15 ) 16 17 // TypeConverters is used to register the available converters 18 var TypeConverters = make(map[string]func([]byte, interface{}) error) 19 20 // ConvertData returns a go representation of the supplied string (YAML, JSON or HCL) 21 func ConvertData(data string, out interface{}) (err error) { 22 trySimplified := func() error { 23 if strings.Count(data, "=") == 0 { 24 return fmt.Errorf("Not simplifiable") 25 } 26 // Special case where we want to have a map and the supplied string is simplified such as "a = 10 b = string" 27 // so we try transform the supplied string in valid YAML 28 simplified := regexp.MustCompile(`[ \t]*=[ \t]*`).ReplaceAllString(data, ":") 29 simplified = regexp.MustCompile(`[ \t]+`).ReplaceAllString(simplified, "\n") 30 simplified = strings.Replace(simplified, ":", ": ", -1) + "\n" 31 return ConvertData(simplified, out) 32 } 33 var errs errors.Array 34 35 defer func() { 36 if err == nil { 37 // YAML converter returns a string if it encounter invalid data, so we check the result to ensure that is is different from the input. 38 if out, isItf := out.(*interface{}); isItf && data == fmt.Sprint(*out) && strings.ContainsAny(data, "=:{}") { 39 if _, isString := (*out).(string); isString { 40 if trySimplified() == nil && data != fmt.Sprint(*out) { 41 err = nil 42 return 43 } 44 45 err = errs 46 *out = nil 47 } 48 } 49 } else { 50 if _, e := TryAsList(out); e == nil && trySimplified() == nil { 51 err = nil 52 } 53 } 54 }() 55 56 for _, key := range AsDictionary(TypeConverters).KeysAsString() { 57 err = TypeConverters[key.Str()]([]byte(data), out) 58 if err == nil { 59 return 60 } 61 errs = append(errs, fmt.Errorf("Trying %s: %v", key, err)) 62 } 63 64 switch len(errs) { 65 case 0: 66 return nil 67 case 1: 68 return errs[0] 69 default: 70 return errs 71 } 72 } 73 74 // LoadData returns a go representation of the supplied file name (YAML, JSON or HCL) 75 func LoadData(filename string, out interface{}) (err error) { 76 var content []byte 77 if content, err = ioutil.ReadFile(filename); err == nil { 78 return ConvertData(string(content), out) 79 } 80 return 81 } 82 83 // ToBash returns the bash 4 variable representation of value 84 func ToBash(value interface{}) string { 85 return toBash(ToNativeRepresentation(value), 0) 86 } 87 88 func toBash(value interface{}, level int) (result string) { 89 if value, isString := value.(string); isString { 90 result = value 91 if strings.ContainsAny(value, " \t\n[]()") { 92 result = fmt.Sprintf("%q", value) 93 } 94 return 95 } 96 97 if value, err := TryAsList(value); err == nil { 98 results := value.Strings() 99 for i := range results { 100 results[i] = quote(results[i]) 101 } 102 fmt.Println(results) 103 switch level { 104 case 2: 105 result = strings.Join(results, ",") 106 default: 107 result = fmt.Sprintf("(%s)", strings.Join(results, " ")) 108 } 109 return 110 } 111 112 if value, err := TryAsDictionary(value); err == nil { 113 results := make([]string, value.Len()) 114 vMap := value.AsMap() 115 switch level { 116 case 0: 117 for i, key := range value.KeysAsString() { 118 key := key.Str() 119 val := toBash(vMap[key], level+1) 120 if _, err := TryAsList(vMap[key]); err == nil { 121 results[i] = fmt.Sprintf("declare -a %[1]s\n%[1]s=%[2]v", key, val) 122 } else if _, err := TryAsDictionary(vMap[key]); err == nil { 123 results[i] = fmt.Sprintf("declare -A %[1]s\n%[1]s=%[2]v", key, val) 124 } else { 125 results[i] = fmt.Sprintf("%s=%v", key, val) 126 } 127 } 128 result = strings.Join(results, "\n") 129 case 1: 130 for i, key := range value.KeysAsString() { 131 key := key.Str() 132 val := toBash(vMap[key], level+1) 133 val = strings.Replace(val, `$`, `\$`, -1) 134 results[i] = fmt.Sprintf("[%s]=%s", key, val) 135 } 136 result = fmt.Sprintf("(%s)", strings.Join(results, " ")) 137 default: 138 for i, key := range value.KeysAsString() { 139 key := key.Str() 140 val := toBash(vMap[key], level+1) 141 results[i] = fmt.Sprintf("%s=%s", key, quote(val)) 142 } 143 result = strings.Join(results, ",") 144 } 145 return 146 } 147 return fmt.Sprint(value) 148 } 149 150 // ToNativeRepresentation converts any object to native (literals, maps, slices) 151 func ToNativeRepresentation(value interface{}) interface{} { 152 if value == nil { 153 return nil 154 } 155 156 typ, val := reflect.TypeOf(value), reflect.ValueOf(value) 157 if typ.Kind() == reflect.Ptr { 158 if val.IsNil() { 159 return nil 160 } 161 val = val.Elem() 162 typ = val.Type() 163 } 164 switch typ.Kind() { 165 case reflect.String: 166 return reflect.ValueOf(value).String() 167 168 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 169 return int(val.Int()) 170 171 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 172 return uint(val.Uint()) 173 174 case reflect.Int64: 175 return val.Int() 176 177 case reflect.Uint64: 178 return val.Uint() 179 180 case reflect.Float32, reflect.Float64: 181 return must(strconv.ParseFloat(fmt.Sprint(value), 64)).(float64) 182 183 case reflect.Bool: 184 return must(strconv.ParseBool(fmt.Sprint(value))).(bool) 185 186 case reflect.Slice, reflect.Array: 187 result := make([]interface{}, val.Len()) 188 for i := range result { 189 result[i] = ToNativeRepresentation(val.Index(i).Interface()) 190 } 191 if len(result) == 1 && reflect.TypeOf(result[0]).Kind() == reflect.Map { 192 // If the result is an array of one map, we just return the inner element 193 return result[0] 194 } 195 return result 196 197 case reflect.Map: 198 result := make(map[string]interface{}, val.Len()) 199 for _, key := range val.MapKeys() { 200 result[fmt.Sprintf("%v", key)] = ToNativeRepresentation(val.MapIndex(key).Interface()) 201 } 202 return result 203 204 case reflect.Struct: 205 result := make(map[string]interface{}, typ.NumField()) 206 for i := 0; i < typ.NumField(); i++ { 207 sf := typ.Field(i) 208 if sf.Anonymous { 209 t := sf.Type 210 if t.Kind() == reflect.Ptr { 211 t = t.Elem() 212 } 213 // If embedded, StructField.PkgPath is not a reliable 214 // indicator of whether the field is exported. 215 // See https://golang.org/issue/21122 216 if !IsExported(t.Name()) && t.Kind() != reflect.Struct { 217 // Ignore embedded fields of unexported non-struct collections. 218 // Do not ignore embedded fields of unexported struct types 219 // since they may have exported fields. 220 continue 221 } 222 } else if sf.PkgPath != "" { 223 // Ignore unexported non-embedded fields. 224 continue 225 } 226 tag := sf.Tag.Get("hcl") 227 if tag == "" { 228 // If there is no hcl specific tag, we rely on json tag if there is 229 tag = sf.Tag.Get("json") 230 } 231 if tag == "-" { 232 continue 233 } 234 235 split := strings.Split(tag, ",") 236 name := split[0] 237 if name == "" { 238 name = sf.Name 239 } 240 options := make(map[string]bool, len(split[1:])) 241 for i := range split[1:] { 242 options[split[i+1]] = true 243 } 244 245 if !IsExported(name) || options["omitempty"] && IsEmptyValue(val.Field(i)) { 246 continue 247 } 248 249 if options["inline"] { 250 for key, value := range ToNativeRepresentation(val.Field(i).Interface()).(map[string]interface{}) { 251 result[key] = value 252 } 253 } else { 254 result[name] = ToNativeRepresentation(val.Field(i).Interface()) 255 } 256 } 257 return result 258 default: 259 fmt.Fprintf(os.Stderr, "Unknown type %T %v : %v\n", value, typ.Kind(), value) 260 return fmt.Sprintf("%v", value) 261 } 262 } 263 264 // IsExported reports whether the identifier is exported. 265 func IsExported(id string) bool { 266 r, _ := utf8.DecodeRuneInString(id) 267 return unicode.IsUpper(r) 268 } 269 270 func quote(s string) string { 271 if strings.ContainsAny(s, " \t,[]()") { 272 s = fmt.Sprintf("%q", s) 273 } 274 return s 275 }