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  }