github.com/grafana/pyroscope@v1.18.0/tools/doc-generator/parse/parser.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/tools/doc-generator/parser.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package parse 7 8 import ( 9 "flag" 10 "fmt" 11 "net/url" 12 "reflect" 13 "strings" 14 "time" 15 "unicode" 16 "unsafe" 17 18 "github.com/go-kit/log" 19 "github.com/grafana/dskit/flagext" 20 dslog "github.com/grafana/dskit/log" 21 "github.com/grafana/regexp" 22 "github.com/pkg/errors" 23 "github.com/prometheus/common/model" 24 "github.com/prometheus/prometheus/model/relabel" 25 ) 26 27 const ( 28 typeURL = "url" 29 typeString = "string" 30 typeDuration = "duration" 31 typeRelabelConfig = "relabel_config..." 32 ) 33 34 var ( 35 yamlFieldNameParser = regexp.MustCompile("^[^,]+") 36 yamlFieldInlineParser = regexp.MustCompile("^[^,]*,inline$") 37 ) 38 39 // ExamplerConfig can be implemented by configs to provide examples. 40 // If string is non-empty, it will be added as comment. 41 // If yaml value is non-empty, it will be marshaled as yaml under the same key as it would appear in config. 42 type ExamplerConfig interface { 43 ExampleDoc() (comment string, yaml interface{}) 44 } 45 46 type FieldExample struct { 47 Comment string 48 Yaml interface{} 49 } 50 51 type ConfigBlock struct { 52 Name string 53 Desc string 54 Entries []*ConfigEntry 55 FlagsPrefix string 56 FlagsPrefixes []string 57 } 58 59 func (b *ConfigBlock) Add(entry *ConfigEntry) { 60 b.Entries = append(b.Entries, entry) 61 } 62 63 type EntryKind string 64 65 const ( 66 KindBlock EntryKind = "block" 67 KindField EntryKind = "field" 68 KindSlice EntryKind = "slice" 69 KindMap EntryKind = "map" 70 ) 71 72 type ConfigEntry struct { 73 Kind EntryKind 74 Name string 75 Required bool 76 77 // In case the Kind is KindBlock 78 Block *ConfigBlock 79 BlockDesc string 80 Root bool 81 82 // In case the Kind is KindField 83 FieldFlag string 84 FieldDesc string 85 FieldType string 86 FieldDefault string 87 FieldExample *FieldExample 88 FieldCategory string 89 90 // In case the Kind is KindMap or KindSlice 91 Element *ConfigBlock 92 } 93 94 func (e ConfigEntry) Description() string { 95 if e.FieldCategory == "" || e.FieldCategory == "basic" { 96 return e.FieldDesc 97 } 98 99 return fmt.Sprintf("(%s) %s", e.FieldCategory, e.FieldDesc) 100 } 101 102 type RootBlock struct { 103 Name string 104 Desc string 105 StructType reflect.Type 106 } 107 108 func Flags(cfg flagext.Registerer, logger log.Logger) map[uintptr]*flag.Flag { 109 fs := flag.NewFlagSet("", flag.PanicOnError) 110 cfg.RegisterFlags(fs) 111 112 flags := map[uintptr]*flag.Flag{} 113 fs.VisitAll(func(f *flag.Flag) { 114 // Skip deprecated flags 115 if f.Value.String() == "deprecated" { 116 return 117 } 118 119 val := reflect.ValueOf(f.Value) 120 if val.Kind() == reflect.Ptr { 121 flags[val.Pointer()] = f 122 } else if val.CanAddr() { 123 flags[val.UnsafeAddr()] = f 124 } else if val.CanInterface() { 125 flags[uintptr(unsafe.Pointer(&f.Value))] = f 126 } else { 127 panic("cannot get pointer to flag value") 128 } 129 }) 130 131 return flags 132 } 133 134 // Config returns a slice of ConfigBlocks. The first ConfigBlock is a recursively expanded cfg. 135 // The remaining entries in the slice are all (root or not) ConfigBlocks. 136 func Config(cfg interface{}, flags map[uintptr]*flag.Flag, rootBlocks []RootBlock) ([]*ConfigBlock, error) { 137 return config(nil, cfg, flags, rootBlocks) 138 } 139 140 func config(block *ConfigBlock, cfg interface{}, flags map[uintptr]*flag.Flag, rootBlocks []RootBlock) ([]*ConfigBlock, error) { 141 blocks := []*ConfigBlock{} 142 143 // If the input block is nil it means we're generating the doc for the top-level block 144 if block == nil { 145 block = &ConfigBlock{} 146 blocks = append(blocks, block) 147 } 148 149 // The input config is expected to be addressable. 150 v := reflect.ValueOf(cfg) 151 for v.Kind() != reflect.Struct { 152 // if pointer is zero recreate object 153 if v.IsZero() { 154 v = reflect.New(reflect.TypeOf(v)) 155 } 156 // The input config is expected to be a pointer to struct. 157 v = v.Elem() 158 } 159 160 t := v.Type() 161 if v.Kind() != reflect.Struct { 162 return nil, fmt.Errorf("%s is a %s while a %s is expected", v, v.Kind(), reflect.Struct) 163 } 164 165 for i := 0; i < t.NumField(); i++ { 166 field := t.Field(i) 167 fieldValue := v.FieldByIndex(field.Index) 168 169 // Skip fields explicitly marked as "hidden" in the doc 170 if isFieldHidden(field) { 171 continue 172 } 173 174 // Skip fields not exported via yaml (unless they're inline) 175 fieldName := getFieldName(field) 176 if fieldName == "" && !isFieldInline(field) { 177 continue 178 } 179 180 // Skip field types which are non configurable 181 if field.Type.Kind() == reflect.Func { 182 continue 183 } 184 185 // Skip deprecated fields we're still keeping for backward compatibility 186 // reasons (by convention we prefix them by UnusedFlag) 187 if strings.HasPrefix(field.Name, "UnusedFlag") { 188 continue 189 } 190 191 // Handle custom fields in vendored libs upon which we have no control. 192 fieldEntry := getCustomFieldEntry(cfg, field, fieldValue, flags) 193 if fieldEntry != nil { 194 block.Add(fieldEntry) 195 continue 196 } 197 198 // Recursively re-iterate if it's a struct and it's not a custom type. 199 if _, custom := getCustomFieldType(field.Type); (field.Type.Kind() == reflect.Struct || field.Type.Kind() == reflect.Ptr) && !custom { 200 // Check whether the sub-block is a root config block 201 rootName, rootDesc, isRoot := isRootBlock(field.Type, rootBlocks) 202 203 // Since we're going to recursively iterate, we need to create a new sub 204 // block and pass it to the doc generation function. 205 var subBlock *ConfigBlock 206 207 if !isFieldInline(field) { 208 var blockName string 209 var blockDesc string 210 211 if isRoot { 212 blockName = rootName 213 214 // Honor the custom description if available. 215 blockDesc = getFieldDescription(cfg, field, rootDesc) 216 } else { 217 blockName = fieldName 218 blockDesc = getFieldDescription(cfg, field, "") 219 } 220 221 subBlock = &ConfigBlock{ 222 Name: blockName, 223 Desc: blockDesc, 224 } 225 226 block.Add(&ConfigEntry{ 227 Kind: KindBlock, 228 Name: fieldName, 229 Required: isFieldRequired(field), 230 Block: subBlock, 231 BlockDesc: blockDesc, 232 Root: isRoot, 233 }) 234 235 if isRoot { 236 blocks = append(blocks, subBlock) 237 } 238 } else { 239 subBlock = block 240 } 241 242 if field.Type.Kind() == reflect.Ptr { 243 // If this is a pointer, it's probably nil, so we initialize it. 244 fieldValue = reflect.New(field.Type.Elem()) 245 } else if field.Type.Kind() == reflect.Struct { 246 fieldValue = fieldValue.Addr() 247 } 248 249 // Recursively generate the doc for the sub-block 250 otherBlocks, err := config(subBlock, fieldValue.Interface(), flags, rootBlocks) 251 if err != nil { 252 return nil, err 253 } 254 255 blocks = append(blocks, otherBlocks...) 256 continue 257 } 258 259 var ( 260 element *ConfigBlock 261 kind = KindField 262 ) 263 { 264 // Add ConfigBlock for slices only if the field isn't a custom type, 265 // which shouldn't be inspected because doesn't have YAML tags, flag registrations, etc. 266 _, isCustomType := getFieldCustomType(field.Type) 267 isSliceOfStructs := field.Type.Kind() == reflect.Slice && (field.Type.Elem().Kind() == reflect.Struct || field.Type.Elem().Kind() == reflect.Ptr) 268 if !isCustomType && isSliceOfStructs { 269 element = &ConfigBlock{ 270 Name: fieldName, 271 Desc: getFieldDescription(cfg, field, ""), 272 } 273 kind = KindSlice 274 275 _, err := config(element, reflect.New(field.Type.Elem()).Interface(), flags, rootBlocks) 276 if err != nil { 277 return nil, errors.Wrapf(err, "couldn't inspect slice, element_type=%s", field.Type.Elem()) 278 } 279 } 280 } 281 282 fieldType, err := getFieldType(field.Type) 283 if err != nil { 284 return nil, errors.Wrapf(err, "config=%s.%s", t.PkgPath(), t.Name()) 285 } 286 287 fieldFlag := getFieldFlag(field, fieldValue, flags) 288 if fieldFlag == nil { 289 block.Add(&ConfigEntry{ 290 Kind: kind, 291 Name: fieldName, 292 Required: isFieldRequired(field), 293 FieldDesc: getFieldDescription(cfg, field, ""), 294 FieldType: fieldType, 295 FieldExample: getFieldExample(fieldName, field.Type), 296 Element: element, 297 }) 298 continue 299 } 300 301 block.Add(&ConfigEntry{ 302 Kind: kind, 303 Name: fieldName, 304 Required: isFieldRequired(field), 305 FieldFlag: fieldFlag.Name, 306 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 307 FieldType: fieldType, 308 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 309 FieldExample: getFieldExample(fieldName, field.Type), 310 Element: element, 311 }) 312 } 313 314 return blocks, nil 315 } 316 317 func getFieldName(field reflect.StructField) string { 318 name := field.Name 319 tag := field.Tag.Get("yaml") 320 321 // If the tag is not specified, then an exported field can be 322 // configured via the field name (lowercase), while an unexported 323 // field can't be configured. 324 if tag == "" { 325 if unicode.IsLower(rune(name[0])) { 326 return "" 327 } 328 329 return strings.ToLower(name) 330 } 331 332 // Parse the field name 333 fieldName := yamlFieldNameParser.FindString(tag) 334 if fieldName == "-" { 335 return "" 336 } 337 338 return fieldName 339 } 340 341 func getFieldCustomType(t reflect.Type) (string, bool) { 342 // Handle custom data types used in the config 343 switch t.String() { 344 case reflect.TypeOf(&url.URL{}).String(): 345 return typeURL, true 346 case reflect.TypeOf(time.Duration(0)).String(): 347 return typeDuration, true 348 case reflect.TypeOf(flagext.StringSliceCSV{}).String(): 349 return typeString, true 350 case reflect.TypeOf(flagext.CIDRSliceCSV{}).String(): 351 return typeString, true 352 case reflect.TypeOf([]*relabel.Config{}).String(): 353 return typeRelabelConfig, true 354 default: 355 return "", false 356 } 357 } 358 359 func getFieldType(t reflect.Type) (string, error) { 360 if typ, isCustom := getFieldCustomType(t); isCustom { 361 return typ, nil 362 } 363 364 // Fallback to auto-detection of built-in data types 365 switch t.Kind() { 366 case reflect.Bool: 367 return "boolean", nil 368 369 case reflect.Int: 370 fallthrough 371 case reflect.Int8: 372 fallthrough 373 case reflect.Int16: 374 fallthrough 375 case reflect.Int32: 376 fallthrough 377 case reflect.Int64: 378 fallthrough 379 case reflect.Uint: 380 fallthrough 381 case reflect.Uint8: 382 fallthrough 383 case reflect.Uint16: 384 fallthrough 385 case reflect.Uint32: 386 fallthrough 387 case reflect.Uint64: 388 return "int", nil 389 390 case reflect.Float32: 391 fallthrough 392 case reflect.Float64: 393 return "float", nil 394 395 case reflect.String: 396 return typeString, nil 397 398 case reflect.Slice: 399 // Get the type of elements 400 elemType, err := getFieldType(t.Elem()) 401 if err != nil { 402 return "", err 403 } 404 405 return "list of " + elemType + "s", nil 406 case reflect.Map: 407 return fmt.Sprintf("map of %s to %s", t.Key(), t.Elem().String()), nil 408 409 case reflect.Struct: 410 return t.Name(), nil 411 case reflect.Ptr: 412 return getFieldType(t.Elem()) 413 414 default: 415 return "", fmt.Errorf("unsupported data type %s", t.Kind()) 416 } 417 } 418 419 func getCustomFieldType(t reflect.Type) (string, bool) { 420 // Handle custom data types used in the config 421 switch t.String() { 422 case reflect.TypeOf(&url.URL{}).String(): 423 return typeURL, true 424 case reflect.TypeOf(time.Duration(0)).String(): 425 return typeDuration, true 426 case reflect.TypeOf(flagext.StringSliceCSV{}).String(): 427 return typeString, true 428 case reflect.TypeOf(flagext.CIDRSliceCSV{}).String(): 429 return typeString, true 430 case reflect.TypeOf([]*relabel.Config{}).String(): 431 return typeRelabelConfig, true 432 default: 433 return "", false 434 } 435 } 436 437 func ReflectType(typ string) reflect.Type { 438 switch typ { 439 case typeString: 440 return reflect.TypeOf("") 441 case typeURL: 442 return reflect.TypeOf(flagext.URLValue{}) 443 case typeDuration: 444 return reflect.TypeOf(time.Duration(0)) 445 case "time": 446 return reflect.TypeOf(&flagext.Time{}) 447 case "boolean": 448 return reflect.TypeOf(false) 449 case "int": 450 return reflect.TypeOf(0) 451 case "float": 452 return reflect.TypeOf(0.0) 453 case "list of strings": 454 return reflect.TypeOf(flagext.StringSliceCSV{}) 455 case "map of string to string": 456 fallthrough 457 case "map of tracker name (string) to matcher (string)": 458 return reflect.TypeOf(map[string]string{}) 459 case typeRelabelConfig: 460 return reflect.TypeOf([]*relabel.Config{}) 461 case "map of string to float64": 462 return reflect.TypeOf(map[string]float64{}) 463 default: 464 panic("unknown field type " + typ) 465 } 466 } 467 468 func getFieldFlag(field reflect.StructField, fieldValue reflect.Value, flags map[uintptr]*flag.Flag) *flag.Flag { 469 if isAbsentInCLI(field) { 470 return nil 471 } 472 fieldPtr := fieldValue.Addr().Pointer() 473 fieldFlag, ok := flags[fieldPtr] 474 if !ok { 475 return nil 476 } 477 478 return fieldFlag 479 } 480 481 func getFieldExample(fieldKey string, fieldType reflect.Type) *FieldExample { 482 ex, ok := reflect.New(fieldType).Interface().(ExamplerConfig) 483 if !ok { 484 return nil 485 } 486 comment, yml := ex.ExampleDoc() 487 return &FieldExample{ 488 Comment: comment, 489 Yaml: map[string]interface{}{fieldKey: yml}, 490 } 491 } 492 493 func getCustomFieldEntry(cfg interface{}, field reflect.StructField, fieldValue reflect.Value, flags map[uintptr]*flag.Flag) *ConfigEntry { 494 if field.Type == reflect.TypeOf(dslog.Level{}) { 495 fieldFlag := getFieldFlag(field, fieldValue, flags) 496 if fieldFlag == nil { 497 return nil 498 } 499 500 return &ConfigEntry{ 501 Kind: KindField, 502 Name: getFieldName(field), 503 Required: isFieldRequired(field), 504 FieldFlag: fieldFlag.Name, 505 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 506 FieldType: typeString, 507 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 508 } 509 } 510 if field.Type == reflect.TypeOf(flagext.URLValue{}) { 511 fieldFlag := getFieldFlag(field, fieldValue, flags) 512 if fieldFlag == nil { 513 return nil 514 } 515 516 return &ConfigEntry{ 517 Kind: KindField, 518 Name: getFieldName(field), 519 Required: isFieldRequired(field), 520 FieldFlag: fieldFlag.Name, 521 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 522 FieldType: typeURL, 523 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 524 } 525 } 526 if field.Type == reflect.TypeOf(flagext.Secret{}) { 527 fieldFlag := getFieldFlag(field, fieldValue, flags) 528 if fieldFlag == nil { 529 return nil 530 } 531 532 return &ConfigEntry{ 533 Kind: KindField, 534 Name: getFieldName(field), 535 Required: isFieldRequired(field), 536 FieldFlag: fieldFlag.Name, 537 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 538 FieldType: "string", 539 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 540 } 541 } 542 if field.Type == reflect.TypeOf(model.Duration(0)) { 543 fieldFlag := getFieldFlag(field, fieldValue, flags) 544 if fieldFlag == nil { 545 return nil 546 } 547 548 return &ConfigEntry{ 549 Kind: KindField, 550 Name: getFieldName(field), 551 Required: isFieldRequired(field), 552 FieldFlag: fieldFlag.Name, 553 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 554 FieldType: "duration", 555 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 556 } 557 } 558 if field.Type == reflect.TypeOf(flagext.Time{}) { 559 fieldFlag := getFieldFlag(field, fieldValue, flags) 560 if fieldFlag == nil { 561 return nil 562 } 563 564 return &ConfigEntry{ 565 Kind: KindField, 566 Name: getFieldName(field), 567 Required: isFieldRequired(field), 568 FieldFlag: fieldFlag.Name, 569 FieldDesc: getFieldDescription(cfg, field, fieldFlag.Usage), 570 FieldType: "time", 571 FieldDefault: getFieldDefault(field, fieldFlag.DefValue), 572 } 573 } 574 575 return nil 576 } 577 578 func getFieldDefault(field reflect.StructField, fallback string) string { 579 if v := getDocTagValue(field, "default"); v != "" { 580 return v 581 } 582 583 return fallback 584 } 585 586 func isFieldHidden(f reflect.StructField) bool { 587 return getDocTagFlag(f, "hidden") 588 } 589 590 func isAbsentInCLI(f reflect.StructField) bool { 591 return getDocTagFlag(f, "nocli") 592 } 593 594 func isFieldRequired(f reflect.StructField) bool { 595 return getDocTagFlag(f, "required") 596 } 597 598 func isFieldInline(f reflect.StructField) bool { 599 return yamlFieldInlineParser.MatchString(f.Tag.Get("yaml")) 600 } 601 602 func getFieldDescription(cfg interface{}, field reflect.StructField, fallback string) string { 603 if desc := getDocTagValue(field, "description"); desc != "" { 604 return desc 605 } 606 607 if methodName := getDocTagValue(field, "description_method"); methodName != "" { 608 structRef := reflect.ValueOf(cfg) 609 610 if method, ok := structRef.Type().MethodByName(methodName); ok { 611 if out := method.Func.Call([]reflect.Value{structRef}); len(out) == 1 { 612 return out[0].String() 613 } 614 } 615 } 616 617 return fallback 618 } 619 620 func isRootBlock(t reflect.Type, rootBlocks []RootBlock) (string, string, bool) { 621 for _, rootBlock := range rootBlocks { 622 if t == rootBlock.StructType { 623 return rootBlock.Name, rootBlock.Desc, true 624 } 625 } 626 627 return "", "", false 628 } 629 630 func getDocTagFlag(f reflect.StructField, name string) bool { 631 cfg := parseDocTag(f) 632 _, ok := cfg[name] 633 return ok 634 } 635 636 func getDocTagValue(f reflect.StructField, name string) string { 637 cfg := parseDocTag(f) 638 return cfg[name] 639 } 640 641 func parseDocTag(f reflect.StructField) map[string]string { 642 cfg := map[string]string{} 643 tag := f.Tag.Get("doc") 644 645 if tag == "" { 646 return cfg 647 } 648 649 for _, entry := range strings.Split(tag, "|") { 650 parts := strings.SplitN(entry, "=", 2) 651 652 switch len(parts) { 653 case 1: 654 cfg[parts[0]] = "" 655 case 2: 656 cfg[parts[0]] = parts[1] 657 } 658 } 659 660 return cfg 661 }