220 lines
4.8 KiB
Go
220 lines
4.8 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package gendoc
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"path"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hyperledger/fabric/common/metrics"
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
// Options scans the provided list of packages for options structs used when
|
|
// creating metrics and returns instances that are recreated from the source
|
|
// tree.
|
|
func Options(pkgs []*packages.Package) ([]interface{}, error) {
|
|
var options []interface{}
|
|
for _, p := range pkgs {
|
|
for _, f := range p.Syntax {
|
|
opts, err := FileOptions(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
options = append(options, opts...)
|
|
}
|
|
}
|
|
return options, nil
|
|
}
|
|
|
|
// FileOptions walks the specified ast.File for options structs used when
|
|
// creating metrics and returns instances that are recreated from the source.
|
|
func FileOptions(f *ast.File) ([]interface{}, error) {
|
|
imports := walkImports(f)
|
|
var options []interface{}
|
|
var errors []error
|
|
|
|
// If the file contains a gendoc:ignore directive, ignore the file
|
|
for _, c := range f.Comments {
|
|
for _, c := range c.List {
|
|
if strings.HasPrefix(c.Text, "//gendoc:ignore") {
|
|
return nil, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Iterate over declarations
|
|
for i := range f.Decls {
|
|
ast.Inspect(f.Decls[i], func(x ast.Node) bool {
|
|
node, ok := x.(*ast.ValueSpec)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
for _, v := range node.Values {
|
|
value, ok := v.(*ast.CompositeLit)
|
|
if !ok {
|
|
continue
|
|
}
|
|
literalType, ok := value.Type.(*ast.SelectorExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ident, ok := literalType.X.(*ast.Ident)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if imports[ident.Name] != "github.com/hyperledger/fabric/common/metrics" {
|
|
continue
|
|
}
|
|
option, err := createOption(literalType)
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
break
|
|
}
|
|
option, err = populateOption(value, option)
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
break
|
|
}
|
|
options = append(options, option)
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
if len(errors) != 0 {
|
|
return nil, errors[0]
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
func walkImports(f *ast.File) map[string]string {
|
|
imports := map[string]string{}
|
|
|
|
for i := range f.Imports {
|
|
ast.Inspect(f.Imports[i], func(x ast.Node) bool {
|
|
switch node := x.(type) {
|
|
case *ast.ImportSpec:
|
|
importPath, err := strconv.Unquote(node.Path.Value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
importName := path.Base(importPath)
|
|
if node.Name != nil {
|
|
importName = node.Name.Name
|
|
}
|
|
imports[importName] = importPath
|
|
return false
|
|
|
|
default:
|
|
return true
|
|
}
|
|
})
|
|
}
|
|
|
|
return imports
|
|
}
|
|
|
|
func createOption(lit *ast.SelectorExpr) (interface{}, error) {
|
|
optionName := lit.Sel.Name
|
|
switch optionName {
|
|
case "CounterOpts":
|
|
return &metrics.CounterOpts{}, nil
|
|
case "GaugeOpts":
|
|
return &metrics.GaugeOpts{}, nil
|
|
case "HistogramOpts":
|
|
return &metrics.HistogramOpts{}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown object type: %s", optionName)
|
|
}
|
|
}
|
|
|
|
func populateOption(lit *ast.CompositeLit, target interface{}) (interface{}, error) {
|
|
val := reflect.ValueOf(target).Elem()
|
|
for _, elem := range lit.Elts {
|
|
if kv, ok := elem.(*ast.KeyValueExpr); ok {
|
|
name := kv.Key.(*ast.Ident).Name
|
|
field := val.FieldByName(name)
|
|
|
|
switch name {
|
|
// ignored
|
|
case "Buckets":
|
|
|
|
// map[string]string
|
|
case "LabelHelp":
|
|
labelHelp, err := asStringMap(kv.Value.(*ast.CompositeLit))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
labelHelpValue := reflect.ValueOf(labelHelp)
|
|
field.Set(labelHelpValue)
|
|
|
|
// slice of strings
|
|
case "LabelNames":
|
|
labelNames, err := stringSlice(kv.Value.(*ast.CompositeLit))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
labelNamesValue := reflect.ValueOf(labelNames)
|
|
field.Set(labelNamesValue)
|
|
|
|
// simple strings
|
|
case "Namespace", "Subsystem", "Name", "Help", "StatsdFormat":
|
|
basicVal := kv.Value.(*ast.BasicLit)
|
|
val, err := strconv.Unquote(basicVal.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
field.SetString(val)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown field name: %s", name)
|
|
}
|
|
}
|
|
}
|
|
return val.Interface(), nil
|
|
}
|
|
|
|
func stringSlice(lit *ast.CompositeLit) ([]string, error) {
|
|
var slice []string
|
|
|
|
for _, elem := range lit.Elts {
|
|
val, err := strconv.Unquote(elem.(*ast.BasicLit).Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
slice = append(slice, val)
|
|
}
|
|
|
|
return slice, nil
|
|
}
|
|
|
|
func asStringMap(lit *ast.CompositeLit) (map[string]string, error) {
|
|
m := map[string]string{}
|
|
|
|
for _, elem := range lit.Elts {
|
|
elem := elem.(*ast.KeyValueExpr)
|
|
key, err := strconv.Unquote(elem.Key.(*ast.BasicLit).Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value, err := strconv.Unquote(elem.Value.(*ast.BasicLit).Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m[key] = value
|
|
}
|
|
|
|
return m, nil
|
|
}
|