gregoryv/draw

View on GitHub
design/vrecord.go

Summary

Maintainability
A
0 mins
Test Coverage
package design

import (
    "bytes"
    "fmt"
    "reflect"
    "strings"

    "github.com/gregoryv/draw/shape"
)

func NewVRecord(v interface{}) *VRecord {
    return newVRecord(v, false)
}

// NewDetailedVRecord returns a VRecord including method argument and
// return types.
func NewDetailedVRecord(v interface{}) *VRecord {
    return newVRecord(v, true)
}

func newVRecord(v interface{}, detailed bool) *VRecord {
    t := reflect.TypeOf(v)
    title := fmt.Sprintf("%s %s", t, t.Kind())
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        if t.Kind() == reflect.Interface {
            title = fmt.Sprintf("%s %s", t, t.Kind())
        }
    }
    rec := shape.NewRecord(title)

    switch {
    case t.Kind() == reflect.Struct:
        addFields(rec, t)
        // always use pointer as the language works this way
        addMethods(rec, reflect.PtrTo(t), detailed)

    case t.Kind() == reflect.Interface:
        addMethods(rec, t, detailed)

    case t.Kind() == reflect.Slice:
        addMethods(rec, t, detailed)
    }
    return &VRecord{
        Record: rec,
        t:      t,
    }
}

func addFields(r *shape.Record, t reflect.Type) {
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        if isPublic(field.Name) {
            r.Fields = append(r.Fields, field.Name)
        }
    }
}

func addMethods(r *shape.Record, t reflect.Type, detailed bool) {
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        // todo include full method if wanted
        r.Methods = append(r.Methods, methodSignature(m, detailed))
    }
}

func methodSignature(m reflect.Method, detailed bool) (sign string) {
    var sb strings.Builder
    sb.WriteString(m.Name)
    sb.WriteString("(")
    if !detailed {
        sb.WriteString(")")
        return sb.String()
    }

    if m.Func.Kind() == reflect.Invalid {
        sb.WriteString(")")
        return sb.String()
    }
    t := m.Func.Type()
    for i := 1; i < t.NumIn(); i++ {
        arg := t.In(i)
        if i > 1 {
            sb.WriteString(", ")
        }
        sb.WriteString(arg.String())
    }
    sb.WriteString(") ")
    for i := 0; i < t.NumOut(); i++ {
        arg := t.Out(i)
        if i > 0 {
            sb.WriteString(", ")
        }
        sb.WriteString(arg.String())
    }
    return sb.String()
}

// todo here so we can toggle manually added private methods
func isPublic(name string) bool {
    up := bytes.ToUpper([]byte(name))
    return []byte(name)[0] == up[0]
}

// VRecord represents a type struct or interface as a record shape.
type VRecord struct {
    *shape.Record
    t reflect.Type
}

// TitleOnly hides fields and methods.
func (vr *VRecord) TitleOnly() {
    vr.HideFields()
    vr.HideMethods()
}

func (vr *VRecord) Implements(iface *VRecord) bool {
    return reflect.PtrTo(vr.t).Implements(iface.t)
}

// todo shouldn't composedOf be the inverse of aggregates
func (vr *VRecord) ComposedOf(d *VRecord) bool {
    if vr.t.Kind() == reflect.Slice {
        return vr.t.ConvertibleTo(reflect.SliceOf(d.t))
    }
    for i := 0; i < vr.t.NumField(); i++ {
        field := vr.t.Field(i)
        if field.Type == d.t || field.Type == reflect.SliceOf(d.t) {
            return true
        }
    }
    return false
}

func (vr *VRecord) Aggregates(d *VRecord) bool {
    return aggregates(vr.t, d.t)
}

// aggregates returns true if type a aggregates type b
func aggregates(a, b reflect.Type) bool {
    defer func() { _ = recover() }()

    for i := 0; i < a.NumField(); i++ {
        t := a.Field(i).Type
        switch {
        case t == reflect.PtrTo(b):
        case t == reflect.SliceOf(reflect.PtrTo(b)):
        case t == reflect.SliceOf(b):
        case t == b && isAggregateKind(t.Kind()):
        default:
            continue
        }
        return true
    }
    return false
}

func isAggregateKind(k reflect.Kind) bool {
    _, found := destAggregates[k]
    return found
}

var destAggregates = map[reflect.Kind]struct{}{
    reflect.Interface: struct{}{},
    reflect.Map:       struct{}{},
    reflect.Chan:      struct{}{},
    reflect.Func:      struct{}{},
}