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  }