github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/os/environ_methods.go (about) 1 package os 2 3 /**! 4 Loosely adapted from 5 https://github.com/google/starlark-go 6 Forked the code for dict() methods and made them work for environ. 7 8 Copyright (c) 2017 The Bazel Authors. All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions are 12 met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in the 19 documentation and/or other materials provided with the 20 distribution. 21 22 3. Neither the name of the copyright holder nor the names of its 23 contributors may be used to endorse or promote products derived 24 from this software without specific prior written permission. 25 26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 import ( 40 "fmt" 41 "sort" 42 43 "go.starlark.net/starlark" 44 ) 45 46 type builtinMethod func(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) 47 48 // methods of built-in types 49 // https://github.com/google/starlark-go/blob/master/doc/spec.md#built-in-methods 50 var ( 51 environMethods = map[string]builtinMethod{ 52 "clear": environ_clear, 53 "get": environ_get, 54 "items": environ_items, 55 "keys": environ_keys, 56 "pop": environ_pop, 57 "popitem": environ_popitem, 58 "setdefault": environ_setdefault, 59 "update": environ_update, 60 "values": environ_values, 61 } 62 ) 63 64 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·get 65 func environ_get(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 66 var key, dflt starlark.Value 67 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &key, &dflt); err != nil { 68 return nil, err 69 } 70 if v, ok, err := b.Receiver().(Environ).Get(key); err != nil { 71 return nil, nameErr(b, err) 72 } else if ok { 73 return v, nil 74 } else if dflt != nil { 75 return dflt, nil 76 } 77 return starlark.None, nil 78 } 79 80 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·clear 81 func environ_clear(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 82 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil { 83 return nil, err 84 } 85 return starlark.None, b.Receiver().(Environ).Clear() 86 } 87 88 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·items 89 func environ_items(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 90 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil { 91 return nil, err 92 } 93 items := b.Receiver().(Environ).Items() 94 res := make([]starlark.Value, len(items)) 95 for i, item := range items { 96 res[i] = item // convert [2]starlark.Value to starlark.Value 97 } 98 return starlark.NewList(res), nil 99 } 100 101 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·keys 102 func environ_keys(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 103 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil { 104 return nil, err 105 } 106 return starlark.NewList(b.Receiver().(Environ).Keys()), nil 107 } 108 109 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·pop 110 func environ_pop(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 111 var k, d starlark.Value 112 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &k, &d); err != nil { 113 return nil, err 114 } 115 if v, found, err := b.Receiver().(Environ).Delete(k); err != nil { 116 return nil, nameErr(b, err) // environ is frozen or key is unhashable 117 } else if found { 118 return v, nil 119 } else if d != nil { 120 return d, nil 121 } 122 return nil, nameErr(b, "missing key") 123 } 124 125 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·popitem 126 func environ_popitem(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 127 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil { 128 return nil, err 129 } 130 recv := b.Receiver().(Environ) 131 keys := recv.Keys() 132 if len(keys) == 0 { 133 return nil, nameErr(b, "empty environ") 134 } 135 k := keys[0] 136 v, _, err := recv.Delete(k) 137 if err != nil { 138 return nil, nameErr(b, err) // environ is frozen 139 } 140 return starlark.Tuple{k, v}, nil 141 } 142 143 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·setdefault 144 func environ_setdefault(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 145 var key, dflt starlark.Value = nil, starlark.None 146 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &key, &dflt); err != nil { 147 return nil, err 148 } 149 environ := b.Receiver().(Environ) 150 if v, ok, err := environ.Get(key); err != nil { 151 return nil, nameErr(b, err) 152 } else if ok { 153 return v, nil 154 } else if err := environ.SetKey(key, dflt); err != nil { 155 return nil, nameErr(b, err) 156 } else { 157 return dflt, nil 158 } 159 } 160 161 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·update 162 func environ_update(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 163 if len(args) > 1 { 164 return nil, fmt.Errorf("update: got %d arguments, want at most 1", len(args)) 165 } 166 if err := updateEnviron(b.Receiver().(Environ), args, kwargs); err != nil { 167 return nil, fmt.Errorf("update: %v", err) 168 } 169 return starlark.None, nil 170 } 171 172 // Common implementation of builtin dict function and dict.update method. 173 // Precondition: len(updates) == 0 or 1. 174 func updateEnviron(dict Environ, updates starlark.Tuple, kwargs []starlark.Tuple) error { 175 if len(updates) == 1 { 176 switch updates := updates[0].(type) { 177 case starlark.IterableMapping: 178 // Iterate over dict's key/value pairs, not just keys. 179 for _, item := range updates.Items() { 180 if err := dict.SetKey(item[0], item[1]); err != nil { 181 return err // dict is frozen 182 } 183 } 184 default: 185 // all other sequences 186 iter := starlark.Iterate(updates) 187 if iter == nil { 188 return fmt.Errorf("got %s, want iterable", updates.Type()) 189 } 190 defer iter.Done() 191 var pair starlark.Value 192 for i := 0; iter.Next(&pair); i++ { 193 iter2 := starlark.Iterate(pair) 194 if iter2 == nil { 195 return fmt.Errorf("dictionary update sequence element #%d is not iterable (%s)", i, pair.Type()) 196 197 } 198 defer iter2.Done() 199 len := starlark.Len(pair) 200 if len < 0 { 201 return fmt.Errorf("dictionary update sequence element #%d has unknown length (%s)", i, pair.Type()) 202 } else if len != 2 { 203 return fmt.Errorf("dictionary update sequence element #%d has length %d, want 2", i, len) 204 } 205 var k, v starlark.Value 206 iter2.Next(&k) 207 iter2.Next(&v) 208 if err := dict.SetKey(k, v); err != nil { 209 return err 210 } 211 } 212 } 213 } 214 215 // Then add the kwargs. 216 before := dict.Len() 217 for _, pair := range kwargs { 218 if err := dict.SetKey(pair[0], pair[1]); err != nil { 219 return err // dict is frozen 220 } 221 } 222 // In the common case, each kwarg will add another dict entry. 223 // If that's not so, check whether it is because there was a duplicate kwarg. 224 if dict.Len() < before+len(kwargs) { 225 keys := make(map[starlark.String]bool, len(kwargs)) 226 for _, kv := range kwargs { 227 k := kv[0].(starlark.String) 228 if keys[k] { 229 return fmt.Errorf("duplicate keyword arg: %v", k) 230 } 231 keys[k] = true 232 } 233 } 234 235 return nil 236 } 237 238 // https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·update 239 func environ_values(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 240 if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil { 241 return nil, err 242 } 243 items := b.Receiver().(Environ).Items() 244 res := make([]starlark.Value, len(items)) 245 for i, item := range items { 246 res[i] = item[1] 247 } 248 return starlark.NewList(res), nil 249 } 250 251 func builtinAttr(recv starlark.Value, name string, methods map[string]builtinMethod) (starlark.Value, error) { 252 method := methods[name] 253 if method == nil { 254 return nil, nil // no such method 255 } 256 257 // Allocate a closure over 'method'. 258 impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 259 return method(b, args, kwargs) 260 } 261 return starlark.NewBuiltin(name, impl).BindReceiver(recv), nil 262 } 263 264 func builtinAttrNames(methods map[string]builtinMethod) []string { 265 names := make([]string, 0, len(methods)) 266 for name := range methods { 267 names = append(names, name) 268 } 269 sort.Strings(names) 270 return names 271 } 272 273 // nameErr returns an error message of the form "name: msg" 274 // where name is b.Name() and msg is a string or error. 275 func nameErr(b *starlark.Builtin, msg interface{}) error { 276 return fmt.Errorf("%s: %v", b.Name(), msg) 277 }