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  }