github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/common/metrics/gendoc/options.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package gendoc
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"path"
    13  	"reflect"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/hechain20/hechain/common/metrics"
    18  	"golang.org/x/tools/go/packages"
    19  )
    20  
    21  // Options scans the provided list of packages for options structs used when
    22  // creating metrics and returns instances that are recreated from the source
    23  // tree.
    24  func Options(pkgs []*packages.Package) ([]interface{}, error) {
    25  	var options []interface{}
    26  	for _, p := range pkgs {
    27  		for _, f := range p.Syntax {
    28  			opts, err := FileOptions(f)
    29  			if err != nil {
    30  				return nil, err
    31  			}
    32  			options = append(options, opts...)
    33  		}
    34  	}
    35  	return options, nil
    36  }
    37  
    38  // FileOptions walks the specified ast.File for options structs used when
    39  // creating metrics and returns instances that are recreated from the source.
    40  func FileOptions(f *ast.File) ([]interface{}, error) {
    41  	imports := walkImports(f)
    42  	var options []interface{}
    43  	var errors []error
    44  
    45  	// If the file contains a gendoc:ignore directive, ignore the file
    46  	for _, c := range f.Comments {
    47  		for _, c := range c.List {
    48  			if strings.HasPrefix(c.Text, "//gendoc:ignore") {
    49  				return nil, nil
    50  			}
    51  		}
    52  	}
    53  
    54  	// Iterate over declarations
    55  	for i := range f.Decls {
    56  		ast.Inspect(f.Decls[i], func(x ast.Node) bool {
    57  			node, ok := x.(*ast.ValueSpec)
    58  			if !ok {
    59  				return true
    60  			}
    61  
    62  			for _, v := range node.Values {
    63  				value, ok := v.(*ast.CompositeLit)
    64  				if !ok {
    65  					continue
    66  				}
    67  				literalType, ok := value.Type.(*ast.SelectorExpr)
    68  				if !ok {
    69  					continue
    70  				}
    71  				ident, ok := literalType.X.(*ast.Ident)
    72  				if !ok {
    73  					continue
    74  				}
    75  				if imports[ident.Name] != "github.com/hechain20/hechain/common/metrics" {
    76  					continue
    77  				}
    78  				option, err := createOption(literalType)
    79  				if err != nil {
    80  					errors = append(errors, err)
    81  					break
    82  				}
    83  				option, err = populateOption(value, option)
    84  				if err != nil {
    85  					errors = append(errors, err)
    86  					break
    87  				}
    88  				options = append(options, option)
    89  			}
    90  			return false
    91  		})
    92  	}
    93  
    94  	if len(errors) != 0 {
    95  		return nil, errors[0]
    96  	}
    97  
    98  	return options, nil
    99  }
   100  
   101  func walkImports(f *ast.File) map[string]string {
   102  	imports := map[string]string{}
   103  
   104  	for i := range f.Imports {
   105  		ast.Inspect(f.Imports[i], func(x ast.Node) bool {
   106  			switch node := x.(type) {
   107  			case *ast.ImportSpec:
   108  				importPath, err := strconv.Unquote(node.Path.Value)
   109  				if err != nil {
   110  					panic(err)
   111  				}
   112  				importName := path.Base(importPath)
   113  				if node.Name != nil {
   114  					importName = node.Name.Name
   115  				}
   116  				imports[importName] = importPath
   117  				return false
   118  
   119  			default:
   120  				return true
   121  			}
   122  		})
   123  	}
   124  
   125  	return imports
   126  }
   127  
   128  func createOption(lit *ast.SelectorExpr) (interface{}, error) {
   129  	optionName := lit.Sel.Name
   130  	switch optionName {
   131  	case "CounterOpts":
   132  		return &metrics.CounterOpts{}, nil
   133  	case "GaugeOpts":
   134  		return &metrics.GaugeOpts{}, nil
   135  	case "HistogramOpts":
   136  		return &metrics.HistogramOpts{}, nil
   137  	default:
   138  		return nil, fmt.Errorf("unknown object type: %s", optionName)
   139  	}
   140  }
   141  
   142  func populateOption(lit *ast.CompositeLit, target interface{}) (interface{}, error) {
   143  	val := reflect.ValueOf(target).Elem()
   144  	for _, elem := range lit.Elts {
   145  		if kv, ok := elem.(*ast.KeyValueExpr); ok {
   146  			name := kv.Key.(*ast.Ident).Name
   147  			field := val.FieldByName(name)
   148  
   149  			switch name {
   150  			// ignored
   151  			case "Buckets":
   152  
   153  			// map[string]string
   154  			case "LabelHelp":
   155  				labelHelp, err := asStringMap(kv.Value.(*ast.CompositeLit))
   156  				if err != nil {
   157  					return nil, err
   158  				}
   159  				labelHelpValue := reflect.ValueOf(labelHelp)
   160  				field.Set(labelHelpValue)
   161  
   162  			// slice of strings
   163  			case "LabelNames":
   164  				labelNames, err := stringSlice(kv.Value.(*ast.CompositeLit))
   165  				if err != nil {
   166  					return nil, err
   167  				}
   168  				labelNamesValue := reflect.ValueOf(labelNames)
   169  				field.Set(labelNamesValue)
   170  
   171  			// simple strings
   172  			case "Namespace", "Subsystem", "Name", "Help", "StatsdFormat":
   173  				basicVal := kv.Value.(*ast.BasicLit)
   174  				val, err := strconv.Unquote(basicVal.Value)
   175  				if err != nil {
   176  					return nil, err
   177  				}
   178  				field.SetString(val)
   179  
   180  			default:
   181  				return nil, fmt.Errorf("unknown field name: %s", name)
   182  			}
   183  		}
   184  	}
   185  	return val.Interface(), nil
   186  }
   187  
   188  func stringSlice(lit *ast.CompositeLit) ([]string, error) {
   189  	var slice []string
   190  
   191  	for _, elem := range lit.Elts {
   192  		val, err := strconv.Unquote(elem.(*ast.BasicLit).Value)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  		slice = append(slice, val)
   197  	}
   198  
   199  	return slice, nil
   200  }
   201  
   202  func asStringMap(lit *ast.CompositeLit) (map[string]string, error) {
   203  	m := map[string]string{}
   204  
   205  	for _, elem := range lit.Elts {
   206  		elem := elem.(*ast.KeyValueExpr)
   207  		key, err := strconv.Unquote(elem.Key.(*ast.BasicLit).Value)
   208  		if err != nil {
   209  			return nil, err
   210  		}
   211  		value, err := strconv.Unquote(elem.Value.(*ast.BasicLit).Value)
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  		m[key] = value
   216  	}
   217  
   218  	return m, nil
   219  }