github.com/alecthomas/kong@v0.9.1-0.20240410131203-2ab5733f1179/model.go (about) 1 package kong 2 3 import ( 4 "fmt" 5 "math" 6 "os" 7 "reflect" 8 "strconv" 9 "strings" 10 ) 11 12 // A Visitable component in the model. 13 type Visitable interface { 14 node() 15 } 16 17 // Application is the root of the Kong model. 18 type Application struct { 19 *Node 20 // Help flag, if the NoDefaultHelp() option is not specified. 21 HelpFlag *Flag 22 } 23 24 // Argument represents a branching positional argument. 25 type Argument = Node 26 27 // Command represents a command in the CLI. 28 type Command = Node 29 30 // NodeType is an enum representing the type of a Node. 31 type NodeType int 32 33 // Node type enumerations. 34 const ( 35 ApplicationNode NodeType = iota 36 CommandNode 37 ArgumentNode 38 ) 39 40 // Node is a branch in the CLI. ie. a command or positional argument. 41 type Node struct { 42 Type NodeType 43 Parent *Node 44 Name string 45 Help string // Short help displayed in summaries. 46 Detail string // Detailed help displayed when describing command/arg alone. 47 Group *Group 48 Hidden bool 49 Flags []*Flag 50 Positional []*Positional 51 Children []*Node 52 DefaultCmd *Node 53 Target reflect.Value // Pointer to the value in the grammar that this Node is associated with. 54 Tag *Tag 55 Aliases []string 56 Passthrough bool // Set to true to stop flag parsing when encountered. 57 Active bool // Denotes the node is part of an active branch in the CLI. 58 59 Argument *Value // Populated when Type is ArgumentNode. 60 } 61 62 func (*Node) node() {} 63 64 // Leaf returns true if this Node is a leaf node. 65 func (n *Node) Leaf() bool { 66 return len(n.Children) == 0 67 } 68 69 // Find a command/argument/flag by pointer to its field. 70 // 71 // Returns nil if not found. Panics if ptr is not a pointer. 72 func (n *Node) Find(ptr interface{}) *Node { 73 key := reflect.ValueOf(ptr) 74 if key.Kind() != reflect.Ptr { 75 panic("expected a pointer") 76 } 77 return n.findNode(key) 78 } 79 80 func (n *Node) findNode(key reflect.Value) *Node { 81 if n.Target == key { 82 return n 83 } 84 for _, child := range n.Children { 85 if found := child.findNode(key); found != nil { 86 return found 87 } 88 } 89 return nil 90 } 91 92 // AllFlags returns flags from all ancestor branches encountered. 93 // 94 // If "hide" is true hidden flags will be omitted. 95 func (n *Node) AllFlags(hide bool) (out [][]*Flag) { 96 if n.Parent != nil { 97 out = append(out, n.Parent.AllFlags(hide)...) 98 } 99 group := []*Flag{} 100 for _, flag := range n.Flags { 101 if !hide || !flag.Hidden { 102 flag.Active = true 103 group = append(group, flag) 104 } 105 } 106 if len(group) > 0 { 107 out = append(out, group) 108 } 109 return 110 } 111 112 // Leaves returns the leaf commands/arguments under Node. 113 // 114 // If "hidden" is true hidden leaves will be omitted. 115 func (n *Node) Leaves(hide bool) (out []*Node) { 116 _ = Visit(n, func(nd Visitable, next Next) error { 117 if nd == n { 118 return next(nil) 119 } 120 if node, ok := nd.(*Node); ok { 121 if hide && node.Hidden { 122 return nil 123 } 124 if len(node.Children) == 0 && node.Type != ApplicationNode { 125 out = append(out, node) 126 } 127 } 128 return next(nil) 129 }) 130 return 131 } 132 133 // Depth of the command from the application root. 134 func (n *Node) Depth() int { 135 depth := 0 136 p := n.Parent 137 for p != nil && p.Type != ApplicationNode { 138 depth++ 139 p = p.Parent 140 } 141 return depth 142 } 143 144 // Summary help string for the node (not including application name). 145 func (n *Node) Summary() string { 146 summary := n.Path() 147 if flags := n.FlagSummary(true); flags != "" { 148 summary += " " + flags 149 } 150 args := []string{} 151 optional := 0 152 for _, arg := range n.Positional { 153 argSummary := arg.Summary() 154 if arg.Tag.Optional { 155 optional++ 156 argSummary = strings.TrimRight(argSummary, "]") 157 } 158 args = append(args, argSummary) 159 } 160 if len(args) != 0 { 161 summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional) 162 } else if len(n.Children) > 0 { 163 summary += " <command>" 164 } 165 allFlags := n.Flags 166 if n.Parent != nil { 167 allFlags = append(allFlags, n.Parent.Flags...) 168 } 169 for _, flag := range allFlags { 170 if !flag.Required { 171 summary += " [flags]" 172 break 173 } 174 } 175 return summary 176 } 177 178 // FlagSummary for the node. 179 func (n *Node) FlagSummary(hide bool) string { 180 required := []string{} 181 count := 0 182 for _, group := range n.AllFlags(hide) { 183 for _, flag := range group { 184 count++ 185 if flag.Required { 186 required = append(required, flag.Summary()) 187 } 188 } 189 } 190 return strings.Join(required, " ") 191 } 192 193 // FullPath is like Path() but includes the Application root node. 194 func (n *Node) FullPath() string { 195 root := n 196 for root.Parent != nil { 197 root = root.Parent 198 } 199 return strings.TrimSpace(root.Name + " " + n.Path()) 200 } 201 202 // Vars returns the combined Vars defined by all ancestors of this Node. 203 func (n *Node) Vars() Vars { 204 if n == nil { 205 return Vars{} 206 } 207 return n.Parent.Vars().CloneWith(n.Tag.Vars) 208 } 209 210 // Path through ancestors to this Node. 211 func (n *Node) Path() (out string) { 212 if n.Parent != nil { 213 out += " " + n.Parent.Path() 214 } 215 switch n.Type { 216 case CommandNode: 217 out += " " + n.Name 218 if len(n.Aliases) > 0 { 219 out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ",")) 220 } 221 case ArgumentNode: 222 out += " " + "<" + n.Name + ">" 223 default: 224 } 225 return strings.TrimSpace(out) 226 } 227 228 // ClosestGroup finds the first non-nil group in this node and its ancestors. 229 func (n *Node) ClosestGroup() *Group { 230 switch { 231 case n.Group != nil: 232 return n.Group 233 case n.Parent != nil: 234 return n.Parent.ClosestGroup() 235 default: 236 return nil 237 } 238 } 239 240 // A Value is either a flag or a variable positional argument. 241 type Value struct { 242 Flag *Flag // Nil if positional argument. 243 Name string 244 Help string 245 OrigHelp string // Original help string, without interpolated variables. 246 HasDefault bool 247 Default string 248 DefaultValue reflect.Value 249 Enum string 250 Mapper Mapper 251 Tag *Tag 252 Target reflect.Value 253 Required bool 254 Set bool // Set to true when this value is set through some mechanism. 255 Format string // Formatting directive, if applicable. 256 Position int // Position (for positional arguments). 257 Passthrough bool // Set to true to stop flag parsing when encountered. 258 Active bool // Denotes the value is part of an active branch in the CLI. 259 } 260 261 // EnumMap returns a map of the enums in this value. 262 func (v *Value) EnumMap() map[string]bool { 263 parts := strings.Split(v.Enum, ",") 264 out := make(map[string]bool, len(parts)) 265 for _, part := range parts { 266 out[strings.TrimSpace(part)] = true 267 } 268 return out 269 } 270 271 // EnumSlice returns a slice of the enums in this value. 272 func (v *Value) EnumSlice() []string { 273 parts := strings.Split(v.Enum, ",") 274 out := make([]string, len(parts)) 275 for i, part := range parts { 276 out[i] = strings.TrimSpace(part) 277 } 278 return out 279 } 280 281 // ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults. 282 func (v *Value) ShortSummary() string { 283 if v.Flag != nil { 284 return fmt.Sprintf("--%s", v.Name) 285 } 286 argText := "<" + v.Name + ">" 287 if v.IsCumulative() { 288 argText += " ..." 289 } 290 if !v.Required { 291 argText = "[" + argText + "]" 292 } 293 return argText 294 } 295 296 // Summary returns a human-readable summary of the value. 297 func (v *Value) Summary() string { 298 if v.Flag != nil { 299 if v.IsBool() { 300 return fmt.Sprintf("--%s", v.Name) 301 } 302 return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder()) 303 } 304 argText := "<" + v.Name + ">" 305 if v.IsCumulative() { 306 argText += " ..." 307 } 308 if !v.Required { 309 argText = "[" + argText + "]" 310 } 311 return argText 312 } 313 314 // IsCumulative returns true if the type can be accumulated into. 315 func (v *Value) IsCumulative() bool { 316 return v.IsSlice() || v.IsMap() 317 } 318 319 // IsSlice returns true if the value is a slice. 320 func (v *Value) IsSlice() bool { 321 return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice 322 } 323 324 // IsMap returns true if the value is a map. 325 func (v *Value) IsMap() bool { 326 return v.Target.Kind() == reflect.Map 327 } 328 329 // IsBool returns true if the underlying value is a boolean. 330 func (v *Value) IsBool() bool { 331 if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) { 332 return true 333 } 334 if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() { 335 return true 336 } 337 return v.Target.Kind() == reflect.Bool 338 } 339 340 // IsCounter returns true if the value is a counter. 341 func (v *Value) IsCounter() bool { 342 return v.Tag.Type == "counter" 343 } 344 345 // Parse tokens into value, parse, and validate, but do not write to the field. 346 func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { 347 if target.Kind() == reflect.Ptr && target.IsNil() { 348 target.Set(reflect.New(target.Type().Elem())) 349 } 350 err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) 351 if err != nil { 352 return fmt.Errorf("%s: %w", v.ShortSummary(), err) 353 } 354 v.Set = true 355 return nil 356 } 357 358 // Apply value to field. 359 func (v *Value) Apply(value reflect.Value) { 360 v.Target.Set(value) 361 v.Set = true 362 } 363 364 // ApplyDefault value to field if it is not already set. 365 func (v *Value) ApplyDefault() error { 366 if reflectValueIsZero(v.Target) { 367 return v.Reset() 368 } 369 v.Set = true 370 return nil 371 } 372 373 // Reset this value to its default, either the zero value or the parsed result of its envar, 374 // or its "default" tag. 375 // 376 // Does not include resolvers. 377 func (v *Value) Reset() error { 378 v.Target.Set(reflect.Zero(v.Target.Type())) 379 if len(v.Tag.Envs) != 0 { 380 for _, env := range v.Tag.Envs { 381 envar, ok := os.LookupEnv(env) 382 // Parse the first non-empty ENV in the list 383 if ok { 384 err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target) 385 if err != nil { 386 return fmt.Errorf("%s (from envar %s=%q)", err, env, envar) 387 } 388 return nil 389 } 390 } 391 } 392 if v.HasDefault { 393 return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target) 394 } 395 return nil 396 } 397 398 func (*Value) node() {} 399 400 // A Positional represents a non-branching command-line positional argument. 401 type Positional = Value 402 403 // A Flag represents a command-line flag. 404 type Flag struct { 405 *Value 406 Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically. 407 Xor []string 408 PlaceHolder string 409 Envs []string 410 Aliases []string 411 Short rune 412 Hidden bool 413 Negated bool 414 } 415 416 func (f *Flag) String() string { 417 out := "--" + f.Name 418 if f.Short != 0 { 419 out = fmt.Sprintf("-%c, %s", f.Short, out) 420 } 421 if !f.IsBool() && !f.IsCounter() { 422 out += "=" + f.FormatPlaceHolder() 423 } 424 return out 425 } 426 427 // FormatPlaceHolder formats the placeholder string for a Flag. 428 func (f *Flag) FormatPlaceHolder() string { 429 placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider) 430 if ok { 431 return placeholderHelper.PlaceHolder(f) 432 } 433 tail := "" 434 if f.Value.IsSlice() && f.Value.Tag.Sep != -1 { 435 tail += string(f.Value.Tag.Sep) + "..." 436 } 437 if f.PlaceHolder != "" { 438 return f.PlaceHolder + tail 439 } 440 if f.HasDefault { 441 if f.Value.Target.Kind() == reflect.String { 442 return strconv.Quote(f.Default) + tail 443 } 444 return f.Default + tail 445 } 446 if f.Value.IsMap() { 447 if f.Value.Tag.MapSep != -1 { 448 tail = string(f.Value.Tag.MapSep) + "..." 449 } 450 return "KEY=VALUE" + tail 451 } 452 if f.Tag != nil && f.Tag.TypeName != "" { 453 return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail 454 } 455 return strings.ToUpper(f.Name) + tail 456 } 457 458 // Group holds metadata about a command or flag group used when printing help. 459 type Group struct { 460 // Key is the `group` field tag value used to identify this group. 461 Key string 462 // Title is displayed above the grouped items. 463 Title string 464 // Description is optional and displayed under the Title when non empty. 465 // It can be used to introduce the group's purpose to the user. 466 Description string 467 } 468 469 // This is directly from the Go 1.13 source code. 470 func reflectValueIsZero(v reflect.Value) bool { 471 switch v.Kind() { 472 case reflect.Bool: 473 return !v.Bool() 474 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 475 return v.Int() == 0 476 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 477 return v.Uint() == 0 478 case reflect.Float32, reflect.Float64: 479 return math.Float64bits(v.Float()) == 0 480 case reflect.Complex64, reflect.Complex128: 481 c := v.Complex() 482 return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 483 case reflect.Array: 484 for i := 0; i < v.Len(); i++ { 485 if !reflectValueIsZero(v.Index(i)) { 486 return false 487 } 488 } 489 return true 490 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 491 return v.IsNil() 492 case reflect.String: 493 return v.Len() == 0 494 case reflect.Struct: 495 for i := 0; i < v.NumField(); i++ { 496 if !reflectValueIsZero(v.Field(i)) { 497 return false 498 } 499 } 500 return true 501 default: 502 // This should never happens, but will act as a safeguard for 503 // later, as a default value doesn't makes sense here. 504 panic(&reflect.ValueError{ 505 Method: "reflect.Value.IsZero", 506 Kind: v.Kind(), 507 }) 508 } 509 }