github.com/verrazzano/verrazzano@v1.7.0/pkg/yaml/expand.go (about) 1 // Copyright (c) 2021, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package yaml 5 6 import ( 7 "errors" 8 "strings" 9 ) 10 11 // Expand a dot notated name to a YAML string. The value can be a string or string list. 12 // The simplest YAML is: 13 // a: b 14 // 15 // Nested values are expanded as follows: 16 // 17 // a.b.c : v1 18 // expands to 19 // a: 20 // b: 21 // c: v1 22 // 23 // If there is more than one value then 24 // 25 // a.b : {v1,v2} 26 // expands to 27 // a: 28 // b: 29 // - v1 30 // - v2 31 // 32 // The last segment of the name might be a quoted string, for example: 33 // 34 // controller.service.annotations."service\.beta\.kubernetes\.io/oci-load-balancer-shape" : flexible 35 // 36 // which translates to 37 // 38 // controller: 39 // service: 40 // annotations: 41 // service.beta.kubernetes.io/oci-load-balancer-shape: flexible 42 // 43 // If forcelist is true then always use the list format. 44 func Expand(leftMargin int, forceList bool, name string, vals ...string) (string, error) { 45 const indent = 2 46 b := strings.Builder{} 47 48 // Remove trailing quote and split the string at a quote if is exists 49 name = strings.TrimSpace(name) 50 name = strings.TrimRight(name, "\"") 51 quoteSegs := strings.Split(name, "\"") 52 if len(quoteSegs) > 2 { 53 return "", errors.New("Name/Value pair has invalid name with more than 1 quoted string") 54 } 55 if len(quoteSegs) == 0 { 56 return "", errors.New("Name/Value pair has invalid name") 57 } 58 // Remove any trailing dot and split the first part of the string at the dots. 59 unquotedPart := strings.TrimRight(quoteSegs[0], ".") 60 // Replace backslashed dots because of no negative lookbehind 61 placeholder := "/*placeholder*/" 62 unquotedPart = strings.Replace(unquotedPart, "\\.", placeholder, -1) 63 nameSegs := strings.Split(unquotedPart, ".") 64 if len(quoteSegs) == 2 { 65 // Add back the original quoted string if it existed 66 // e.g. change service\.beta\.kubernetes\.io/oci-load-balancer-shape to 67 // service.beta.kubernetes.io/oci-load-balancer-shape 68 s := strings.Replace(quoteSegs[1], "\\", "", -1) 69 nameSegs = append(nameSegs, s) 70 } 71 // Loop through all the name segments, for example, these 4: 72 // controller, service, annotations, service.beta.kubernetes.io/oci-load-balancer-shape 73 listIndents := 0 74 nextValueList := false 75 indentVal := " " 76 for i, seg := range nameSegs { 77 // Get rid of placeholder 78 seg = strings.Replace(seg, placeholder, ".", -1) 79 80 // Create the padded indent 81 pad := strings.Repeat(indentVal, leftMargin+(indent*(i+listIndents))) 82 83 // last value for formatting 84 lastVal := i == len(nameSegs)-1 85 86 // Check if current value is a new list value 87 listValueString := "" 88 if nextValueList { 89 listValueString = "- " 90 listIndents++ 91 nextValueList = false 92 } 93 94 // Check if internal list value next 95 splitList := strings.Split(seg, `[`) 96 if len(splitList) > 1 { 97 seg = splitList[0] 98 nextValueList = true 99 } 100 101 // Write the indent padding, then name followed by colon 102 if _, err := b.WriteString(pad + listValueString + seg + ":"); err != nil { 103 return "", err 104 } 105 // If this is the last segment then write the value, else LF 106 if lastVal { 107 // indent is different based on if the last value was a list 108 indentSize := 1 109 if nextValueList { 110 indentSize = 2 111 } 112 pad += strings.Repeat(indentVal, indent*indentSize) 113 if err := writeVals(&b, forceList || nextValueList, pad, vals...); err != nil { 114 return "", err 115 } 116 } else { 117 if _, err := b.WriteString("\n"); err != nil { 118 return "", err 119 } 120 } 121 } 122 return b.String(), nil 123 } 124 125 // writeVals writes a single value or a list of values to the string builder. 126 // If forcelist is true then always use the list format. 127 func writeVals(b *strings.Builder, forceList bool, pad string, vals ...string) error { 128 // check for multiline value 129 if len(vals) == 1 && strings.Contains(vals[0], "\n") { 130 b.WriteString(" |\n") 131 b.WriteString(pad + strings.Replace(vals[0], "\n", "\n"+pad, -1)) 132 return nil 133 } 134 if len(vals) == 1 && !forceList { 135 // Write the single value, for example: 136 // key: val1 137 _, err := b.WriteString(" " + vals[0]) 138 return err 139 } 140 // Write the list of values, for example 141 // key: 142 // - val1 143 // - val2 144 for _, val := range vals { 145 if _, err := b.WriteString("\n"); err != nil { 146 return err 147 } 148 if _, err := b.WriteString(pad + "- " + val); err != nil { 149 return err 150 } 151 } 152 return nil 153 }