github.com/grailbio/base@v0.0.11/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 := ¶m{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 }