github.com/pvitto98/fabric@v2.1.1+incompatible/common/metrics/gendoc/options.go (about)

     1  /*
     2  Copyright IBM Corp. 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/hyperledger/fabric/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  	var imports = walkImports(f)
    42  	var options []interface{}
    43  	var errors []error
    44  
    45  	// If the file contains gendoc:ignore, ignore the file
    46  	for _, c := range f.Comments {
    47  		if strings.Contains(c.Text(), "gendoc:ignore") {
    48  			return nil, nil
    49  		}
    50  	}
    51  
    52  	// Iterate over declarations
    53  	for i := range f.Decls {
    54  		ast.Inspect(f.Decls[i], func(x ast.Node) bool {
    55  			node, ok := x.(*ast.ValueSpec)
    56  			if !ok {
    57  				return true
    58  			}
    59  
    60  			for _, v := range node.Values {
    61  				value, ok := v.(*ast.CompositeLit)
    62  				if !ok {
    63  					continue
    64  				}
    65  				literalType, ok := value.Type.(*ast.SelectorExpr)
    66  				if !ok {
    67  					continue
    68  				}
    69  				ident, ok := literalType.X.(*ast.Ident)
    70  				if !ok {
    71  					continue
    72  				}
    73  				if imports[ident.Name] != "github.com/hyperledger/fabric/common/metrics" {
    74  					continue
    75  				}
    76  				option, err := createOption(literalType)
    77  				if err != nil {
    78  					errors = append(errors, err)
    79  					break
    80  				}
    81  				option, err = populateOption(value, option)
    82  				if err != nil {
    83  					errors = append(errors, err)
    84  					break
    85  				}
    86  				options = append(options, option)
    87  			}
    88  			return false
    89  		})
    90  	}
    91  
    92  	if len(errors) != 0 {
    93  		return nil, errors[0]
    94  	}
    95  
    96  	return options, nil
    97  }
    98  
    99  func walkImports(f *ast.File) map[string]string {
   100  	imports := map[string]string{}
   101  
   102  	for i := range f.Imports {
   103  		ast.Inspect(f.Imports[i], func(x ast.Node) bool {
   104  			switch node := x.(type) {
   105  			case *ast.ImportSpec:
   106  				importPath, err := strconv.Unquote(node.Path.Value)
   107  				if err != nil {
   108  					panic(err)
   109  				}
   110  				importName := path.Base(importPath)
   111  				if node.Name != nil {
   112  					importName = node.Name.Name
   113  				}
   114  				imports[importName] = importPath
   115  				return false
   116  
   117  			default:
   118  				return true
   119  			}
   120  		})
   121  	}
   122  
   123  	return imports
   124  }
   125  
   126  func createOption(lit *ast.SelectorExpr) (interface{}, error) {
   127  	optionName := lit.Sel.Name
   128  	switch optionName {
   129  	case "CounterOpts":
   130  		return &metrics.CounterOpts{}, nil
   131  	case "GaugeOpts":
   132  		return &metrics.GaugeOpts{}, nil
   133  	case "HistogramOpts":
   134  		return &metrics.HistogramOpts{}, nil
   135  	default:
   136  		return nil, fmt.Errorf("unknown object type: %s", optionName)
   137  	}
   138  }
   139  
   140  func populateOption(lit *ast.CompositeLit, target interface{}) (interface{}, error) {
   141  	val := reflect.ValueOf(target).Elem()
   142  	for _, elem := range lit.Elts {
   143  		if kv, ok := elem.(*ast.KeyValueExpr); ok {
   144  			name := kv.Key.(*ast.Ident).Name
   145  			field := val.FieldByName(name)
   146  
   147  			switch name {
   148  			// ignored
   149  			case "Buckets":
   150  
   151  			// map[string]string
   152  			case "LabelHelp":
   153  				labelHelp, err := asStringMap(kv.Value.(*ast.CompositeLit))
   154  				if err != nil {
   155  					return nil, err
   156  				}
   157  				labelHelpValue := reflect.ValueOf(labelHelp)
   158  				field.Set(labelHelpValue)
   159  
   160  			// slice of strings
   161  			case "LabelNames":
   162  				labelNames, err := stringSlice(kv.Value.(*ast.CompositeLit))
   163  				if err != nil {
   164  					return nil, err
   165  				}
   166  				labelNamesValue := reflect.ValueOf(labelNames)
   167  				field.Set(labelNamesValue)
   168  
   169  			// simple strings
   170  			case "Namespace", "Subsystem", "Name", "Help", "StatsdFormat":
   171  				basicVal := kv.Value.(*ast.BasicLit)
   172  				val, err := strconv.Unquote(basicVal.Value)
   173  				if err != nil {
   174  					return nil, err
   175  				}
   176  				field.SetString(val)
   177  
   178  			default:
   179  				return nil, fmt.Errorf("unknown field name: %s", name)
   180  			}
   181  		}
   182  	}
   183  	return val.Interface(), nil
   184  }
   185  
   186  func stringSlice(lit *ast.CompositeLit) ([]string, error) {
   187  	var slice []string
   188  
   189  	for _, elem := range lit.Elts {
   190  		val, err := strconv.Unquote(elem.(*ast.BasicLit).Value)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		slice = append(slice, val)
   195  	}
   196  
   197  	return slice, nil
   198  }
   199  
   200  func asStringMap(lit *ast.CompositeLit) (map[string]string, error) {
   201  	m := map[string]string{}
   202  
   203  	for _, elem := range lit.Elts {
   204  		elem := elem.(*ast.KeyValueExpr)
   205  		key, err := strconv.Unquote(elem.Key.(*ast.BasicLit).Value)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		value, err := strconv.Unquote(elem.Value.(*ast.BasicLit).Value)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		m[key] = value
   214  	}
   215  
   216  	return m, nil
   217  }