github.com/lyraproj/hiera@v1.0.0-rc4/api/key.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "reflect" 6 "strconv" 7 8 "github.com/tada/catch" 9 10 "github.com/lyraproj/dgo/util" 11 12 "github.com/lyraproj/dgo/dgo" 13 "github.com/lyraproj/dgo/tf" 14 "github.com/lyraproj/dgo/vf" 15 ) 16 17 // A Key is a parsed version of the possibly dot-separated key to lookup. The 18 // parts of a key will be strings or integers 19 type ( 20 Key interface { 21 dgo.Value 22 23 // Return the result of using this key to dig into the given value. Nil is returned 24 // unless the dig was a success 25 Dig(Invocation, dgo.Value) dgo.Value 26 27 // Bury is the opposite of Dig. It returns the value that represents what would be found 28 // using the root of this key. If this key has one part, the value itself is returned, otherwise 29 // a nested chain of single entry hashes is returned. 30 Bury(dgo.Value) dgo.Value 31 32 // Return the parts of this key. Each part is either a string or an int value 33 Parts() []interface{} 34 35 // Return the root key, i.e. the first part. 36 Root() string 37 38 // Source returns the string that this key was created from 39 Source() string 40 } 41 42 key struct { 43 source string 44 parts []interface{} 45 } 46 ) 47 48 // NewKey parses the given string into a Key 49 func NewKey(str string) Key { 50 b := bytes.NewBufferString(``) 51 return &key{str, parseUnquoted(b, str, str, []interface{}{})} 52 } 53 54 var keyType = tf.NewNamed(`hiera.key`, 55 func(v dgo.Value) dgo.Value { 56 return NewKey(v.String()) 57 }, 58 func(v dgo.Value) dgo.Value { 59 return vf.String(v.(*key).source) 60 }, 61 reflect.TypeOf(&key{}), 62 reflect.TypeOf((*Key)(nil)).Elem(), nil) 63 64 func (k *key) Bury(value dgo.Value) dgo.Value { 65 for i := len(k.parts) - 1; i > 0; i-- { 66 p := k.parts[i] 67 var kx dgo.Value 68 if ix, ok := p.(int); ok { 69 kx = vf.Int64(int64(ix)) 70 } else { 71 kx = vf.String(p.(string)) 72 } 73 value = vf.Map(kx, value) 74 } 75 return value 76 } 77 78 func (k *key) Dig(ic Invocation, v dgo.Value) dgo.Value { 79 t := len(k.parts) 80 if t == 1 { 81 return v 82 } 83 84 return ic.WithSubLookup(k, func() dgo.Value { 85 for i := 1; i < t; i++ { 86 p := k.parts[i] 87 v = ic.WithSegment(p, func() dgo.Value { 88 switch vc := v.(type) { 89 case dgo.Array: 90 if ix, ok := p.(int); ok { 91 if ix >= 0 && ix < vc.Len() { 92 v = vc.Get(ix) 93 ic.ReportFound(p, v) 94 return v 95 } 96 } 97 case dgo.Map: 98 var kx dgo.Value 99 if ix, ok := p.(int); ok { 100 kx = vf.Int64(int64(ix)) 101 } else { 102 kx = vf.String(p.(string)) 103 } 104 if v := vc.Get(kx); v != nil { 105 ic.ReportFound(p, v) 106 return v 107 } 108 } 109 ic.ReportNotFound(p) 110 return nil 111 }) 112 if v == nil { 113 break 114 } 115 } 116 return v 117 }) 118 } 119 120 func (k *key) Equals(value interface{}) bool { 121 if ov, ok := value.(*key); ok { 122 return k.source == ov.source 123 } 124 return false 125 } 126 127 func (k *key) HashCode() int32 { 128 return util.StringHash(k.source) 129 } 130 131 func (k *key) Parts() []interface{} { 132 return k.parts 133 } 134 135 func (k *key) Type() dgo.Type { 136 return keyType 137 } 138 139 func (k *key) Root() string { 140 return k.parts[0].(string) 141 } 142 143 func (k *key) Source() string { 144 return k.source 145 } 146 147 func (k *key) String() string { 148 return k.Type().(dgo.NamedType).ValueString(k) 149 } 150 151 func parseUnquoted(b *bytes.Buffer, key, part string, parts []interface{}) []interface{} { 152 mungedPart := func(ix int, part string) interface{} { 153 if i, err := strconv.ParseInt(part, 10, 32); err == nil { 154 if ix == 0 { 155 panic(catch.Error(`key '%s' first segment cannot be an index`, key)) 156 } 157 return int(i) 158 } 159 if part == `` { 160 panic(catch.Error(`key '%s' contains an empty segment`, key)) 161 } 162 return part 163 } 164 165 for i, c := range part { 166 switch c { 167 case '\'', '"': 168 return parseQuoted(b, c, key, part[i+1:], parts) 169 case '.': 170 parts = append(parts, mungedPart(len(parts), b.String())) 171 b.Reset() 172 default: 173 _, _ = b.WriteRune(c) 174 } 175 } 176 return append(parts, mungedPart(len(parts), b.String())) 177 } 178 179 func parseQuoted(b *bytes.Buffer, q rune, key, part string, parts []interface{}) []interface{} { 180 for i, c := range part { 181 if c == q { 182 if i == len(part)-1 { 183 return append(parts, b.String()) 184 } 185 return parseUnquoted(b, key, part[i+1:], parts) 186 } 187 _, _ = b.WriteRune(c) 188 } 189 panic(catch.Error(`unterminated quote in key '%s'`, key)) 190 }