github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/config/instance.go (about)

     1  // Copyright 2019 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package config
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"runtime"
    11  	"sync"
    12  )
    13  
    14  // typedConfigure is a type-erased version of func(*Constructor[T]).
    15  type typedConfigure struct {
    16  	configure func(*Constructor[any])
    17  	typ       reflect.Type
    18  }
    19  
    20  var (
    21  	globalsMu sync.Mutex
    22  	globals   = make(map[string]typedConfigure)
    23  	defaults  = make(map[string]string)
    24  )
    25  
    26  // Register registers a constructor and later invokes the provided
    27  // function whenever a new profile instance is created. Register
    28  // panics if multiple constructors are registered with the same name.
    29  // Constructors should typically be registered in package init
    30  // functions, and the configure function must define at least
    31  // Constructor.New. For example, the following configures a
    32  // constructor with a single parameter, n, which simply returns its
    33  // value.
    34  //
    35  //	config.Register("config/test", func(constr *config.Constructor[int]) {
    36  //		n := constr.Int("n", 32, "the number configured")
    37  //		constr.New = func() (int, error) {
    38  //			return *n, nil
    39  //		}
    40  //		constr.Doc = "a customizable integer"
    41  //	})
    42  func Register[T any](name string, configure func(*Constructor[T])) {
    43  	globalsMu.Lock()
    44  	defer globalsMu.Unlock()
    45  	if _, found := globals[name]; found {
    46  		panic("config.Register: instance with name " + name + " has already been registered")
    47  	}
    48  	globals[name] = typedConfigure{
    49  		func(untyped *Constructor[any]) {
    50  			typed := Constructor[T]{params: untyped.params}
    51  			configure(&typed)
    52  			untyped.Doc = typed.Doc
    53  			untyped.New = func() (any, error) { return typed.New() }
    54  		},
    55  		reflect.TypeOf(new(T)).Elem(),
    56  	}
    57  }
    58  
    59  // Default declares a new derived instance. It is a convenience
    60  // function used to provide a default implementation among multiple
    61  // choices, and is equivalent to the the profile directive
    62  //
    63  //	instance name instance
    64  //
    65  // Default panics if name is already the name of an instance, or if
    66  // the specified parent instance does not exist.
    67  func Default(name, instance string) {
    68  	globalsMu.Lock()
    69  	defer globalsMu.Unlock()
    70  	if _, found := globals[name]; found {
    71  		panic("config.Default: default " + name + " has same name as a global")
    72  	}
    73  	if _, found := globals[instance]; !found {
    74  		if _, found = defaults[instance]; !found {
    75  			panic("config.Default: instance " + instance + " does not exist")
    76  		}
    77  	}
    78  	defaults[name] = instance
    79  }
    80  
    81  type (
    82  	// Constructor defines a constructor, as configured by Register.
    83  	// Typically a constructor registers a set of parameters through the
    84  	// flags-like methods provided by Constructor. The value returned by
    85  	// New is configured by these parameters.
    86  	Constructor[T any] struct {
    87  		New    func() (T, error)
    88  		Doc    string
    89  		params map[string]*param
    90  	}
    91  	// Nil is an interface type with no implementations. Constructor[Nil]
    92  	// indicates an instance is created just for its side effects.
    93  	Nil interface{ neverImplemented() }
    94  )
    95  
    96  func newConstructor() *Constructor[any] {
    97  	return &Constructor[any]{
    98  		params: make(map[string]*param),
    99  	}
   100  }
   101  
   102  // InstanceVar registers a parameter that is satisfied by another
   103  // instance; the method panics if ptr is not a pointer. The default
   104  // value is always an indirection; if it is left empty it is taken as
   105  // the nil value: it remains uninitialized by default.
   106  func (c *Constructor[_]) InstanceVar(ptr interface{}, name string, value string, help string) {
   107  	ptrTyp := reflect.TypeOf(ptr)
   108  	if ptrTyp.Kind() != reflect.Ptr {
   109  		panic(fmt.Sprintf(
   110  			"Instance.InterfaceVar: passed ptr %s is not a pointer",
   111  			ptrTyp,
   112  		))
   113  	}
   114  	param := c.define(name, paramInterface, help)
   115  	param.ifaceptr = ptr
   116  	if value == "nil" {
   117  		value = ""
   118  	}
   119  	if value == "" && !isNilAssignable(ptrTyp.Elem()) {
   120  		// TODO: Consider allowing empty values to mean zero values for types
   121  		// that are not nil-assignable.  We currently do not allow the empty
   122  		// string to be consistent with parsing, as there is no way to set a
   123  		// parameter to an empty value, as we require an identifier.
   124  		panic(fmt.Sprintf(
   125  			"Instance.InterfaceVar: ptr element %s cannot have nil/empty value",
   126  			ptrTyp.Elem(),
   127  		))
   128  	}
   129  	param.ifaceindir = indirect(value)
   130  }
   131  
   132  // Int registers an integer parameter with a default value. The returned
   133  // pointer points to its value.
   134  func (c *Constructor[_]) Int(name string, value int, help string) *int {
   135  	p := new(int)
   136  	c.IntVar(p, name, value, help)
   137  	return p
   138  }
   139  
   140  // IntVar registers an integer parameter with a default value. The parameter's
   141  // value written to the location pointed to by ptr.
   142  func (c *Constructor[_]) IntVar(ptr *int, name string, value int, help string) {
   143  	*ptr = value
   144  	c.define(name, paramInt, help).intptr = ptr
   145  }
   146  
   147  // Float registers floating point parameter with a default value. The returned
   148  // pointer points to its value.
   149  func (c *Constructor[_]) Float(name string, value float64, help string) *float64 {
   150  	p := new(float64)
   151  	c.FloatVar(p, name, value, help)
   152  	return p
   153  }
   154  
   155  // FloatVar register a floating point parameter with a default value. The parameter's
   156  // value is written to the provided pointer.
   157  func (c *Constructor[_]) FloatVar(ptr *float64, name string, value float64, help string) {
   158  	*ptr = value
   159  	c.define(name, paramFloat, help).floatptr = ptr
   160  }
   161  
   162  // String registers a string parameter with a default value. The returned pointer
   163  // points to its value.
   164  func (c *Constructor[_]) String(name string, value string, help string) *string {
   165  	p := new(string)
   166  	c.StringVar(p, name, value, help)
   167  	return p
   168  }
   169  
   170  // StringVar registers a string parameter with a default value. The parameter's
   171  // value written to the location pointed to by ptr.
   172  func (c *Constructor[_]) StringVar(ptr *string, name string, value string, help string) {
   173  	*ptr = value
   174  	c.define(name, paramString, help).strptr = ptr
   175  }
   176  
   177  // Bool registers a boolean parameter with a default value. The returned pointer
   178  // points to its value.
   179  func (c *Constructor[_]) Bool(name string, value bool, help string) *bool {
   180  	p := new(bool)
   181  	c.BoolVar(p, name, value, help)
   182  	return p
   183  }
   184  
   185  // BoolVar registers a boolean parameter with a default value. The parameter's
   186  // value written to the location pointed to by ptr.
   187  func (c *Constructor[_]) BoolVar(ptr *bool, name string, value bool, help string) {
   188  	*ptr = value
   189  	c.define(name, paramBool, help).boolptr = ptr
   190  }
   191  
   192  func (c *Constructor[_]) define(name string, kind int, help string) *param {
   193  	if c.params[name] != nil {
   194  		panic("config: parameter " + name + " already defined")
   195  	}
   196  	p := &param{kind: kind, help: help}
   197  	_, p.file, p.line, _ = runtime.Caller(2)
   198  	c.params[name] = p
   199  	return c.params[name]
   200  }
   201  
   202  const (
   203  	paramInterface = iota
   204  	paramInt
   205  	paramFloat
   206  	paramString
   207  	paramBool
   208  )
   209  
   210  type param struct {
   211  	kind int
   212  	help string
   213  
   214  	file string
   215  	line int
   216  
   217  	intptr     *int
   218  	floatptr   *float64
   219  	ifaceptr   interface{}
   220  	ifaceindir indirect
   221  	strptr     *string
   222  	boolptr    *bool
   223  }
   224  
   225  func (p *param) Interface() interface{} {
   226  	switch p.kind {
   227  	case paramInterface:
   228  		return reflect.ValueOf(p.ifaceptr).Elem().Interface()
   229  	case paramInt:
   230  		return *p.intptr
   231  	case paramFloat:
   232  		return *p.floatptr
   233  	case paramString:
   234  		return *p.strptr
   235  	case paramBool:
   236  		return *p.boolptr
   237  	default:
   238  		panic(p.kind)
   239  	}
   240  }
   241  
   242  func isNilAssignable(typ reflect.Type) bool {
   243  	switch typ.Kind() {
   244  	case reflect.Chan:
   245  	case reflect.Func:
   246  	case reflect.Interface:
   247  	case reflect.Map:
   248  	case reflect.Ptr:
   249  	case reflect.Slice:
   250  	case reflect.UnsafePointer:
   251  	default:
   252  		return false
   253  	}
   254  	return true
   255  }