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 }