github.com/alecthomas/kong@v0.9.1-0.20240410131203-2ab5733f1179/options.go (about) 1 package kong 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "os/user" 9 "path/filepath" 10 "reflect" 11 "regexp" 12 "strings" 13 ) 14 15 // An Option applies optional changes to the Kong application. 16 type Option interface { 17 Apply(k *Kong) error 18 } 19 20 // OptionFunc is function that adheres to the Option interface. 21 type OptionFunc func(k *Kong) error 22 23 func (o OptionFunc) Apply(k *Kong) error { return o(k) } //nolint: revive 24 25 // Vars sets the variables to use for interpolation into help strings and default values. 26 // 27 // See README for details. 28 type Vars map[string]string 29 30 // Apply lets Vars act as an Option. 31 func (v Vars) Apply(k *Kong) error { 32 for key, value := range v { 33 k.vars[key] = value 34 } 35 return nil 36 } 37 38 // CloneWith clones the current Vars and merges "vars" onto the clone. 39 func (v Vars) CloneWith(vars Vars) Vars { 40 out := make(Vars, len(v)+len(vars)) 41 for key, value := range v { 42 out[key] = value 43 } 44 for key, value := range vars { 45 out[key] = value 46 } 47 return out 48 } 49 50 // Exit overrides the function used to terminate. This is useful for testing or interactive use. 51 func Exit(exit func(int)) Option { 52 return OptionFunc(func(k *Kong) error { 53 k.Exit = exit 54 return nil 55 }) 56 } 57 58 type embedded struct { 59 strct any 60 tags []string 61 } 62 63 // Embed a struct into the root of the CLI. 64 // 65 // "strct" must be a pointer to a structure. 66 func Embed(strct any, tags ...string) Option { 67 t := reflect.TypeOf(strct) 68 if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { 69 panic("kong: Embed() must be called with a pointer to a struct") 70 } 71 return OptionFunc(func(k *Kong) error { 72 k.embedded = append(k.embedded, embedded{strct, tags}) 73 return nil 74 }) 75 } 76 77 type dynamicCommand struct { 78 name string 79 help string 80 group string 81 tags []string 82 cmd interface{} 83 } 84 85 // DynamicCommand registers a dynamically constructed command with the root of the CLI. 86 // 87 // This is useful for command-line structures that are extensible via user-provided plugins. 88 // 89 // "tags" is a list of extra tag strings to parse, in the form <key>:"<value>". 90 func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option { 91 return OptionFunc(func(k *Kong) error { 92 k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{ 93 name: name, 94 help: help, 95 group: group, 96 cmd: cmd, 97 tags: tags, 98 }) 99 return nil 100 }) 101 } 102 103 // NoDefaultHelp disables the default help flags. 104 func NoDefaultHelp() Option { 105 return OptionFunc(func(k *Kong) error { 106 k.noDefaultHelp = true 107 return nil 108 }) 109 } 110 111 // PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before 112 // parsing occurs. 113 // 114 // This is useful for, e.g., adding short options to flags, updating help, etc. 115 func PostBuild(fn func(*Kong) error) Option { 116 return OptionFunc(func(k *Kong) error { 117 k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn)) 118 return nil 119 }) 120 } 121 122 // Name overrides the application name. 123 func Name(name string) Option { 124 return PostBuild(func(k *Kong) error { 125 k.Model.Name = name 126 return nil 127 }) 128 } 129 130 // Description sets the application description. 131 func Description(description string) Option { 132 return PostBuild(func(k *Kong) error { 133 k.Model.Help = description 134 return nil 135 }) 136 } 137 138 // TypeMapper registers a mapper to a type. 139 func TypeMapper(typ reflect.Type, mapper Mapper) Option { 140 return OptionFunc(func(k *Kong) error { 141 k.registry.RegisterType(typ, mapper) 142 return nil 143 }) 144 } 145 146 // KindMapper registers a mapper to a kind. 147 func KindMapper(kind reflect.Kind, mapper Mapper) Option { 148 return OptionFunc(func(k *Kong) error { 149 k.registry.RegisterKind(kind, mapper) 150 return nil 151 }) 152 } 153 154 // ValueMapper registers a mapper to a field value. 155 func ValueMapper(ptr interface{}, mapper Mapper) Option { 156 return OptionFunc(func(k *Kong) error { 157 k.registry.RegisterValue(ptr, mapper) 158 return nil 159 }) 160 } 161 162 // NamedMapper registers a mapper to a name. 163 func NamedMapper(name string, mapper Mapper) Option { 164 return OptionFunc(func(k *Kong) error { 165 k.registry.RegisterName(name, mapper) 166 return nil 167 }) 168 } 169 170 // Writers overrides the default writers. Useful for testing or interactive use. 171 func Writers(stdout, stderr io.Writer) Option { 172 return OptionFunc(func(k *Kong) error { 173 k.Stdout = stdout 174 k.Stderr = stderr 175 return nil 176 }) 177 } 178 179 // Bind binds values for hooks and Run() function arguments. 180 // 181 // Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and 182 // the current *Context will also be made available. 183 // 184 // There are two hook points: 185 // 186 // BeforeApply(...) error 187 // AfterApply(...) error 188 // 189 // Called before validation/assignment, and immediately after validation/assignment, respectively. 190 func Bind(args ...interface{}) Option { 191 return OptionFunc(func(k *Kong) error { 192 k.bindings.add(args...) 193 return nil 194 }) 195 } 196 197 // BindTo allows binding of implementations to interfaces. 198 // 199 // BindTo(impl, (*iface)(nil)) 200 func BindTo(impl, iface interface{}) Option { 201 return OptionFunc(func(k *Kong) error { 202 k.bindings.addTo(impl, iface) 203 return nil 204 }) 205 } 206 207 // BindToProvider allows binding of provider functions. 208 // 209 // This is useful when the Run() function of different commands require different values that may 210 // not all be initialisable from the main() function. 211 func BindToProvider(provider interface{}) Option { 212 return OptionFunc(func(k *Kong) error { 213 return k.bindings.addProvider(provider) 214 }) 215 } 216 217 // Help printer to use. 218 func Help(help HelpPrinter) Option { 219 return OptionFunc(func(k *Kong) error { 220 k.help = help 221 return nil 222 }) 223 } 224 225 // ShortHelp configures the short usage message. 226 // 227 // It should be used together with kong.ShortUsageOnError() to display a 228 // custom short usage message on errors. 229 func ShortHelp(shortHelp HelpPrinter) Option { 230 return OptionFunc(func(k *Kong) error { 231 k.shortHelp = shortHelp 232 return nil 233 }) 234 } 235 236 // HelpFormatter configures how the help text is formatted. 237 // 238 // Deprecated: Use ValueFormatter() instead. 239 func HelpFormatter(helpFormatter HelpValueFormatter) Option { 240 return OptionFunc(func(k *Kong) error { 241 k.helpFormatter = helpFormatter 242 return nil 243 }) 244 } 245 246 // ValueFormatter configures how the help text is formatted. 247 func ValueFormatter(helpFormatter HelpValueFormatter) Option { 248 return OptionFunc(func(k *Kong) error { 249 k.helpFormatter = helpFormatter 250 return nil 251 }) 252 } 253 254 // ConfigureHelp sets the HelpOptions to use for printing help. 255 func ConfigureHelp(options HelpOptions) Option { 256 return OptionFunc(func(k *Kong) error { 257 k.helpOptions = options 258 return nil 259 }) 260 } 261 262 // AutoGroup automatically assigns groups to flags. 263 func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option { 264 return PostBuild(func(kong *Kong) error { 265 parents := []Visitable{kong.Model} 266 return Visit(kong.Model, func(node Visitable, next Next) error { 267 if flag, ok := node.(*Flag); ok && flag.Group == nil { 268 flag.Group = format(parents[len(parents)-1], flag) 269 } 270 parents = append(parents, node) 271 defer func() { parents = parents[:len(parents)-1] }() 272 return next(nil) 273 }) 274 }) 275 } 276 277 // Groups associates `group` field tags with group metadata. 278 // 279 // This option is used to simplify Kong tags while providing 280 // rich group information such as title and optional description. 281 // 282 // Each key in the "groups" map corresponds to the value of a 283 // `group` Kong tag, while the first line of the value will be 284 // the title, and subsequent lines if any will be the description of 285 // the group. 286 // 287 // See also ExplicitGroups for a more structured alternative. 288 type Groups map[string]string 289 290 func (g Groups) Apply(k *Kong) error { //nolint: revive 291 for key, info := range g { 292 lines := strings.Split(info, "\n") 293 title := strings.TrimSpace(lines[0]) 294 description := "" 295 if len(lines) > 1 { 296 description = strings.TrimSpace(strings.Join(lines[1:], "\n")) 297 } 298 k.groups = append(k.groups, Group{ 299 Key: key, 300 Title: title, 301 Description: description, 302 }) 303 } 304 return nil 305 } 306 307 // ExplicitGroups associates `group` field tags with their metadata. 308 // 309 // It can be used to provide a title or header to a command or flag group. 310 func ExplicitGroups(groups []Group) Option { 311 return OptionFunc(func(k *Kong) error { 312 k.groups = groups 313 return nil 314 }) 315 } 316 317 // UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error. 318 func UsageOnError() Option { 319 return OptionFunc(func(k *Kong) error { 320 k.usageOnError = fullUsage 321 return nil 322 }) 323 } 324 325 // ShortUsageOnError configures Kong to display context-sensitive short 326 // usage if FatalIfErrorf is called with an error. The default short 327 // usage message can be overridden with kong.ShortHelp(...). 328 func ShortUsageOnError() Option { 329 return OptionFunc(func(k *Kong) error { 330 k.usageOnError = shortUsage 331 return nil 332 }) 333 } 334 335 // ClearResolvers clears all existing resolvers. 336 func ClearResolvers() Option { 337 return OptionFunc(func(k *Kong) error { 338 k.resolvers = nil 339 return nil 340 }) 341 } 342 343 // Resolvers registers flag resolvers. 344 func Resolvers(resolvers ...Resolver) Option { 345 return OptionFunc(func(k *Kong) error { 346 k.resolvers = append(k.resolvers, resolvers...) 347 return nil 348 }) 349 } 350 351 // IgnoreFields will cause kong.New() to skip field names that match any 352 // of the provided regex patterns. This is useful if you are not able to add a 353 // kong="-" struct tag to a struct/element before the call to New. 354 // 355 // Example: When referencing protoc generated structs, you will likely want to 356 // ignore/skip XXX_* fields. 357 func IgnoreFields(regexes ...string) Option { 358 return OptionFunc(func(k *Kong) error { 359 for _, r := range regexes { 360 if r == "" { 361 return errors.New("regex input cannot be empty") 362 } 363 364 re, err := regexp.Compile(r) 365 if err != nil { 366 return fmt.Errorf("unable to compile regex: %v", err) 367 } 368 369 k.ignoreFields = append(k.ignoreFields, re) 370 } 371 372 return nil 373 }) 374 } 375 376 // ConfigurationLoader is a function that builds a resolver from a file. 377 type ConfigurationLoader func(r io.Reader) (Resolver, error) 378 379 // Configuration provides Kong with support for loading defaults from a set of configuration files. 380 // 381 // Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong. 382 // 383 // Note: The JSON function is a ConfigurationLoader. 384 // 385 // ~ and variable expansion will occur on the provided paths. 386 func Configuration(loader ConfigurationLoader, paths ...string) Option { 387 return OptionFunc(func(k *Kong) error { 388 k.loader = loader 389 for _, path := range paths { 390 f, err := os.Open(ExpandPath(path)) 391 if err != nil { 392 if os.IsNotExist(err) || os.IsPermission(err) { 393 continue 394 } 395 396 return err 397 } 398 f.Close() 399 400 resolver, err := k.LoadConfig(path) 401 if err != nil { 402 return fmt.Errorf("%s: %v", path, err) 403 } 404 if resolver != nil { 405 k.resolvers = append(k.resolvers, resolver) 406 } 407 } 408 return nil 409 }) 410 } 411 412 // ExpandPath is a helper function to expand a relative or home-relative path to an absolute path. 413 // 414 // eg. ~/.someconf -> /home/alec/.someconf 415 func ExpandPath(path string) string { 416 if filepath.IsAbs(path) { 417 return path 418 } 419 if strings.HasPrefix(path, "~/") { 420 user, err := user.Current() 421 if err != nil { 422 return path 423 } 424 return filepath.Join(user.HomeDir, path[2:]) 425 } 426 abspath, err := filepath.Abs(path) 427 if err != nil { 428 return path 429 } 430 return abspath 431 } 432 433 func siftStrings(ss []string, filter func(s string) bool) []string { 434 i := 0 435 ss = append([]string(nil), ss...) 436 for _, s := range ss { 437 if filter(s) { 438 ss[i] = s 439 i++ 440 } 441 } 442 return ss[0:i] 443 } 444 445 // DefaultEnvars option inits environment names for flags. 446 // The name will not generate if tag "env" is "-". 447 // Predefined environment variables are skipped. 448 // 449 // For example: 450 // 451 // --some.value -> PREFIX_SOME_VALUE 452 func DefaultEnvars(prefix string) Option { 453 processFlag := func(flag *Flag) { 454 switch env := flag.Envs; { 455 case flag.Name == "help": 456 return 457 case len(env) == 1 && env[0] == "-": 458 flag.Envs = nil 459 return 460 case len(env) > 0: 461 return 462 } 463 replacer := strings.NewReplacer("-", "_", ".", "_") 464 names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...) 465 names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") }) 466 name := strings.ToUpper(strings.Join(names, "_")) 467 flag.Envs = append(flag.Envs, name) 468 flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name) 469 } 470 471 var processNode func(node *Node) 472 processNode = func(node *Node) { 473 for _, flag := range node.Flags { 474 processFlag(flag) 475 } 476 for _, node := range node.Children { 477 processNode(node) 478 } 479 } 480 481 return PostBuild(func(k *Kong) error { 482 processNode(k.Model.Node) 483 return nil 484 }) 485 } 486 487 // FlagNamer allows you to override the default kebab-case automated flag name generation. 488 func FlagNamer(namer func(fieldName string) string) Option { 489 return OptionFunc(func(k *Kong) error { 490 k.flagNamer = namer 491 return nil 492 }) 493 }