github.com/coveo/gotemplate@v2.7.7+incompatible/hcl/utilities.go (about)

     1  package hcl
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"runtime/debug"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/coveo/gotemplate/collections"
    12  )
    13  
    14  // flatten converts array of map to single map if there is only one element in the array.
    15  // By default, hc.Unmarshal returns array of map even if there is only a single map in the definition.
    16  func flatten(source interface{}) interface{} {
    17  	switch value := source.(type) {
    18  	case []map[string]interface{}:
    19  		switch len(value) {
    20  		case 1:
    21  			source = flatten(value[0])
    22  		default:
    23  			result := make([]map[string]interface{}, len(value))
    24  			for i := range value {
    25  				result[i] = flatten(value[i]).(map[string]interface{})
    26  			}
    27  			source = result
    28  		}
    29  	case map[string]interface{}:
    30  		for key := range value {
    31  			value[key] = flatten(value[key])
    32  		}
    33  		source = value
    34  	case []interface{}:
    35  		for i, sub := range value {
    36  			value[i] = flatten(sub)
    37  		}
    38  		source = value
    39  	}
    40  	return source
    41  }
    42  
    43  func transform(out interface{}) {
    44  	result := transformElement(flatten(reflect.ValueOf(out).Elem().Interface()))
    45  	if _, isMap := out.(*map[string]interface{}); isMap {
    46  		// If the result is expected to be map[string]interface{}, we convert it back from internal dict type.
    47  		result = result.(hclIDict).Native()
    48  	}
    49  	reflect.ValueOf(out).Elem().Set(reflect.ValueOf(result))
    50  }
    51  
    52  func transformElement(source interface{}) interface{} {
    53  	if value, err := hclHelper.TryAsDictionary(source); err == nil {
    54  		for _, key := range value.KeysAsString() {
    55  			value.Set(key, transformElement(value.Get(key)))
    56  		}
    57  		source = value
    58  	} else if value, err := hclHelper.TryAsList(source); err == nil {
    59  		for i, sub := range value.AsArray() {
    60  			value.Set(i, transformElement(sub))
    61  		}
    62  		source = value
    63  	}
    64  	return source
    65  }
    66  
    67  func marshalHCL(value interface{}, fullHcl, head bool, prefix, indent string) (result string, err error) {
    68  	if value == nil {
    69  		result = "null"
    70  		return
    71  	}
    72  
    73  	ifIndent := func(vTrue, vFalse interface{}) interface{} { return collections.IIf(indent, vTrue, vFalse) }
    74  	const specialFormat = "#HCL_ARRAY_MAP#!"
    75  
    76  	switch value := value.(type) {
    77  	case int, uint, int64, uint64, float64, bool:
    78  		result = fmt.Sprint(value)
    79  	case string:
    80  		value = fmt.Sprintf("%q", value)
    81  		if indent != "" && strings.Contains(value, "\\n") {
    82  			// We unquote the value
    83  			unIndented := value[1 : len(value)-1]
    84  			// Then replace escaped characters, other escape chars are \a, \b, \f and \v are not managed
    85  			unIndented = strings.Replace(unIndented, `\n`, "\n", -1)
    86  			unIndented = strings.Replace(unIndented, `\\`, "\\", -1)
    87  			unIndented = strings.Replace(unIndented, `\"`, "\"", -1)
    88  			unIndented = strings.Replace(unIndented, `\r`, "\r", -1)
    89  			unIndented = strings.Replace(unIndented, `\t`, "\t", -1)
    90  			unIndented = collections.UnIndent(unIndented)
    91  			if strings.HasSuffix(unIndented, "\n") {
    92  				value = fmt.Sprintf("<<-EOF\n%sEOF", unIndented)
    93  			}
    94  		}
    95  		result = value
    96  
    97  	case []interface{}:
    98  		results := make([]string, len(value))
    99  		if fullHcl && isArrayOfMap(value) {
   100  			for i, element := range value {
   101  				element := element.(map[string]interface{})
   102  				for key := range element {
   103  					if results[i], err = marshalHCL(element[key], fullHcl, false, "", indent); err != nil {
   104  						return
   105  					}
   106  					if head {
   107  						results[i] = fmt.Sprintf(`%s%s%s`, id(key), ifIndent(" = ", ""), results[i])
   108  					} else {
   109  						results[i] = fmt.Sprintf(`%s %s %s`, specialFormat, id(key), results[i])
   110  					}
   111  				}
   112  			}
   113  			result = strings.Join(results, ifIndent("\n\n", " ").(string))
   114  			break
   115  		}
   116  		var totalLength int
   117  		var newLine bool
   118  		for i := range value {
   119  			if results[i], err = marshalHCL(value[i], fullHcl, false, "", indent); err != nil {
   120  				return
   121  			}
   122  			totalLength += len(results[i])
   123  			newLine = newLine || strings.Contains(results[i], "\n")
   124  		}
   125  		if totalLength > 60 && indent != "" || newLine {
   126  			result = fmt.Sprintf("[\n%s,\n]", collections.Indent(strings.Join(results, ",\n"), prefix+indent))
   127  		} else {
   128  			result = fmt.Sprintf("[%s]", strings.Join(results, ifIndent(", ", ",").(string)))
   129  		}
   130  
   131  	case map[string]interface{}:
   132  		if key := singleMap(value); fullHcl && key != "" {
   133  			var element string
   134  			if element, err = marshalHCL(value[key], fullHcl, false, "", indent); err != nil {
   135  				return
   136  			}
   137  			result = fmt.Sprintf(`%s %s`, id(key), element)
   138  			break
   139  		}
   140  
   141  		keys := make([]string, 0, len(value))
   142  		for key := range value {
   143  			keys = append(keys, key)
   144  		}
   145  		sort.Strings(keys)
   146  		rendered := make(map[string]string, len(keys))
   147  		keyLen := 0
   148  
   149  		for key, val := range value {
   150  			if rendered[key], err = marshalHCL(val, fullHcl, false, "", indent); err != nil {
   151  				return
   152  			}
   153  			if strings.Contains(rendered[key], "\n") {
   154  				continue
   155  			}
   156  			if len(key) > keyLen && indent != "" {
   157  				keyLen = len(key)
   158  			}
   159  		}
   160  
   161  		items := make([]string, 0, len(value)+2)
   162  		for _, multiline := range []bool{false, true} {
   163  			for _, key := range keys {
   164  				rendered := rendered[key]
   165  				lines := strings.Split(rendered, "\n")
   166  
   167  				// We process the multilines elements after the single line one
   168  				if len(lines) > 1 && !multiline || len(lines) == 1 && multiline {
   169  					continue
   170  				}
   171  
   172  				if multiline && len(items) > 0 {
   173  					// Add a blank line between multilines elements
   174  					items = append(items, "")
   175  					keyLen = 0
   176  				}
   177  
   178  				equal := ifIndent(" = ", "=").(string)
   179  				if _, err := hclHelper.TryAsDictionary(value[key]); err == nil {
   180  					if multiline {
   181  						equal = " "
   182  					} else if indent == "" {
   183  						equal = ""
   184  					}
   185  				}
   186  
   187  				if strings.Contains(rendered, specialFormat) {
   188  					items = append(items, strings.Replace(rendered, specialFormat, id(key), -1))
   189  
   190  				} else {
   191  					if indent == "" {
   192  						keyLen = 0
   193  						if equal == "" && !strings.HasPrefix(rendered, `{`) {
   194  							keyLen = len(id(key)) + 1
   195  						}
   196  					}
   197  					items = append(items, fmt.Sprintf("%*s%s%s", -keyLen, id(key), equal, rendered))
   198  				}
   199  			}
   200  		}
   201  
   202  		if head {
   203  			result = strings.Join(items, ifIndent("\n", " ").(string))
   204  			break
   205  		}
   206  
   207  		if indent == "" || len(items) == 0 {
   208  			result = fmt.Sprintf("{%s}", strings.Join(items, " "))
   209  			break
   210  		}
   211  
   212  		result = fmt.Sprintf("{\n%s\n}", collections.Indent(strings.Join(items, "\n"), prefix+indent))
   213  
   214  	default:
   215  		debug.PrintStack()
   216  		err = fmt.Errorf("marshalHCL Unknown type %[1]T %[1]v", value)
   217  	}
   218  	return
   219  }
   220  
   221  func isArrayOfMap(array []interface{}) bool {
   222  	if len(array) == 0 {
   223  		return false
   224  	}
   225  	for _, item := range array {
   226  		if item, isMap := item.([]map[string]interface{}); !isMap || len(item) != 1 {
   227  			return false
   228  		}
   229  	}
   230  	return true
   231  }
   232  
   233  func singleMap(m map[string]interface{}) string {
   234  	if len(m) != 1 {
   235  		return ""
   236  	}
   237  	for k := range m {
   238  		if _, isMap := m[k].(map[string]interface{}); isMap {
   239  			return k
   240  		}
   241  	}
   242  	return ""
   243  }
   244  
   245  var identifierRegex = regexp.MustCompile(`^[A-za-z][\w-]*$`)
   246  
   247  func id(key string) string {
   248  	if identifierRegex.MatchString(key) {
   249  		return key
   250  	}
   251  	// The identifier contains characters that may be considered invalid, we have to quote it
   252  	return fmt.Sprintf("%q", key)
   253  }