github.com/podhmo/reflect-shape@v0.4.3/extractor.go (about)

     1  package reflectshape
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"runtime"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/podhmo/reflect-shape/metadata"
    11  )
    12  
    13  type Extractor struct {
    14  	Config *Config
    15  	Lookup *metadata.Lookup
    16  
    17  	seen     map[ID]*Shape
    18  	packages map[string]*Package
    19  }
    20  
    21  func (e *Extractor) Visited() map[ID]*Shape {
    22  	return e.seen
    23  }
    24  
    25  func (e *Extractor) Extract(ob interface{}) *Shape {
    26  	// TODO: only handling *T
    27  	rt := reflect.TypeOf(ob)
    28  	rv := reflect.ValueOf(ob)
    29  	return e.extract(rt, rv)
    30  }
    31  
    32  func (e *Extractor) extract(rt reflect.Type, rv reflect.Value) *Shape {
    33  	lv := 0
    34  	for rt.Kind() == reflect.Pointer {
    35  		rt = rt.Elem()
    36  		rv = rv.Elem()
    37  		lv++
    38  	}
    39  
    40  	id := ID{rt: rt}
    41  	if rt.Kind() == reflect.Func {
    42  		id.pc = rv.Pointer() // distinguish same signature function
    43  	}
    44  
    45  	shape, ok := e.seen[id]
    46  	if ok {
    47  		if lv == 0 {
    48  			return shape
    49  		}
    50  		copied := *shape
    51  		copied.Lv = lv
    52  		return &copied
    53  	}
    54  
    55  	name := rt.Name()
    56  	pkgPath := rt.PkgPath()
    57  	isMethod := false
    58  
    59  	if id.pc != 0 { // is function?
    60  		fullname := runtime.FuncForPC(id.pc).Name()
    61  		parts := strings.Split(fullname, ".")
    62  
    63  		if strings.HasSuffix(fullname, "-fm") {
    64  			isMethod = true
    65  			// @@ github.com/podhmo/reflect-shape/neo_test.S0.M-fm
    66  			// @@ github.com/podhmo/reflect-shape/neo_test.(*S1).M-fm
    67  			pkgPath = strings.Join(parts[:len(parts)-2], ".")
    68  			name = fmt.Sprintf("%s.%s", strings.Trim(parts[len(parts)-2], "(*)"), strings.TrimSuffix(parts[len(parts)-1], "-fm"))
    69  		} else {
    70  			// @@ github.com/podhmo/reflect-shape/neo_test.F1
    71  			// @@ github.com/podhmo/reflect-shape/neo_test.S0
    72  			pkgPath = strings.Join(parts[:len(parts)-1], ".")
    73  			name = parts[len(parts)-1]
    74  		}
    75  	}
    76  
    77  	pkg, ok := e.packages[pkgPath]
    78  	if !ok {
    79  		parts := strings.Split(pkgPath, "/") // todo fix
    80  		pkgName := parts[len(parts)-1]
    81  		pkg = &Package{
    82  			Name:  pkgName,
    83  			Path:  pkgPath,
    84  			scope: &Scope{shapes: map[string]*Shape{}},
    85  		}
    86  		e.packages[pkgPath] = pkg
    87  	}
    88  
    89  	shape = &Shape{
    90  		Name:         name,
    91  		Kind:         rt.Kind(),
    92  		ID:           id,
    93  		Type:         rt,
    94  		DefaultValue: rv,
    95  		Number:       len(e.seen),
    96  		IsMethod:     isMethod,
    97  		Package:      pkg,
    98  		e:            e,
    99  	}
   100  	e.seen[id] = shape
   101  	pkg.scope.shapes[name] = shape
   102  
   103  	if lv == 0 {
   104  		return shape
   105  	}
   106  	copied := *shape
   107  	copied.Lv = lv
   108  	return &copied
   109  }
   110  
   111  type Package struct {
   112  	Name string
   113  	Path string
   114  
   115  	scope *Scope
   116  }
   117  
   118  func (p *Package) Scope() *Scope {
   119  	return p.scope
   120  }
   121  
   122  type Scope struct {
   123  	shapes map[string]*Shape
   124  }
   125  
   126  func (s *Scope) Names() []string {
   127  	return s.names(false)
   128  }
   129  
   130  func (s *Scope) NamesWithMethod() []string {
   131  	return s.names(true)
   132  }
   133  
   134  func (s *Scope) names(withMethod bool) []string {
   135  	// anonymous function is not supported yet
   136  	r := make([]string, 0, len(s.shapes))
   137  	for name, s := range s.shapes {
   138  		if !withMethod && s.IsMethod {
   139  			continue
   140  		}
   141  		r = append(r, name)
   142  	}
   143  	sort.Strings(r)
   144  	return r
   145  }