348 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"text/template"
 | 
						|
 | 
						|
	"golang.org/x/xerrors"
 | 
						|
)
 | 
						|
 | 
						|
type methodMeta struct {
 | 
						|
	node  ast.Node
 | 
						|
	ftype *ast.FuncType
 | 
						|
}
 | 
						|
 | 
						|
type Visitor struct {
 | 
						|
	Methods map[string]map[string]*methodMeta
 | 
						|
	Include map[string][]string
 | 
						|
}
 | 
						|
 | 
						|
func (v *Visitor) Visit(node ast.Node) ast.Visitor {
 | 
						|
	st, ok := node.(*ast.TypeSpec)
 | 
						|
	if !ok {
 | 
						|
		return v
 | 
						|
	}
 | 
						|
 | 
						|
	iface, ok := st.Type.(*ast.InterfaceType)
 | 
						|
	if !ok {
 | 
						|
		return v
 | 
						|
	}
 | 
						|
	if v.Methods[st.Name.Name] == nil {
 | 
						|
		v.Methods[st.Name.Name] = map[string]*methodMeta{}
 | 
						|
	}
 | 
						|
	for _, m := range iface.Methods.List {
 | 
						|
		switch ft := m.Type.(type) {
 | 
						|
		case *ast.Ident:
 | 
						|
			v.Include[st.Name.Name] = append(v.Include[st.Name.Name], ft.Name)
 | 
						|
		case *ast.FuncType:
 | 
						|
			v.Methods[st.Name.Name][m.Names[0].Name] = &methodMeta{
 | 
						|
				node:  m,
 | 
						|
				ftype: ft,
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	// latest (v1)
 | 
						|
	if err := generate("./api", "api", "api", "./api/proxy_gen.go"); err != nil {
 | 
						|
		fmt.Println("error: ", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// v0
 | 
						|
	if err := generate("./api/v0api", "v0api", "v0api", "./api/v0api/proxy_gen.go"); err != nil {
 | 
						|
		fmt.Println("error: ", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func typeName(e ast.Expr, pkg string) (string, error) {
 | 
						|
	switch t := e.(type) {
 | 
						|
	case *ast.SelectorExpr:
 | 
						|
		return t.X.(*ast.Ident).Name + "." + t.Sel.Name, nil
 | 
						|
	case *ast.Ident:
 | 
						|
		pstr := t.Name
 | 
						|
		return pstr, nil
 | 
						|
	case *ast.ArrayType:
 | 
						|
		subt, err := typeName(t.Elt, pkg)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		return "[]" + subt, nil
 | 
						|
	case *ast.StarExpr:
 | 
						|
		subt, err := typeName(t.X, pkg)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		return "*" + subt, nil
 | 
						|
	case *ast.MapType:
 | 
						|
		k, err := typeName(t.Key, pkg)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		v, err := typeName(t.Value, pkg)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		return "map[" + k + "]" + v, nil
 | 
						|
	case *ast.StructType:
 | 
						|
		if len(t.Fields.List) != 0 {
 | 
						|
			return "", xerrors.Errorf("can't struct")
 | 
						|
		}
 | 
						|
		return "struct{}", nil
 | 
						|
	case *ast.InterfaceType:
 | 
						|
		if len(t.Methods.List) != 0 {
 | 
						|
			return "", xerrors.Errorf("can't interface")
 | 
						|
		}
 | 
						|
		return "interface{}", nil
 | 
						|
	case *ast.ChanType:
 | 
						|
		subt, err := typeName(t.Value, pkg)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		if t.Dir == ast.SEND {
 | 
						|
			subt = "->chan " + subt
 | 
						|
		} else {
 | 
						|
			subt = "<-chan " + subt
 | 
						|
		}
 | 
						|
		return subt, nil
 | 
						|
	default:
 | 
						|
		return "", xerrors.Errorf("unknown type")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func generate(path, pkg, outpkg, outfile string) error {
 | 
						|
	fset := token.NewFileSet()
 | 
						|
	apiDir, err := filepath.Abs(path)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	outfile, err = filepath.Abs(outfile)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	pkgs, err := parser.ParseDir(fset, apiDir, nil, parser.AllErrors|parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ap := pkgs[pkg]
 | 
						|
 | 
						|
	v := &Visitor{make(map[string]map[string]*methodMeta), map[string][]string{}}
 | 
						|
	ast.Walk(v, ap)
 | 
						|
 | 
						|
	type methodInfo struct {
 | 
						|
		Name                                     string
 | 
						|
		node                                     ast.Node
 | 
						|
		Tags                                     map[string][]string
 | 
						|
		NamedParams, ParamNames, Results, DefRes string
 | 
						|
	}
 | 
						|
 | 
						|
	type strinfo struct {
 | 
						|
		Name    string
 | 
						|
		Methods map[string]*methodInfo
 | 
						|
		Include []string
 | 
						|
	}
 | 
						|
 | 
						|
	type meta struct {
 | 
						|
		Infos   map[string]*strinfo
 | 
						|
		Imports map[string]string
 | 
						|
		OutPkg  string
 | 
						|
	}
 | 
						|
 | 
						|
	m := &meta{
 | 
						|
		OutPkg:  outpkg,
 | 
						|
		Infos:   map[string]*strinfo{},
 | 
						|
		Imports: map[string]string{},
 | 
						|
	}
 | 
						|
 | 
						|
	for fn, f := range ap.Files {
 | 
						|
		if strings.HasSuffix(fn, "gen.go") {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		//fmt.Println("F:", fn)
 | 
						|
		cmap := ast.NewCommentMap(fset, f, f.Comments)
 | 
						|
 | 
						|
		for _, im := range f.Imports {
 | 
						|
			m.Imports[im.Path.Value] = im.Path.Value
 | 
						|
			if im.Name != nil {
 | 
						|
				m.Imports[im.Path.Value] = im.Name.Name + " " + m.Imports[im.Path.Value]
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for ifname, methods := range v.Methods {
 | 
						|
			if _, ok := m.Infos[ifname]; !ok {
 | 
						|
				m.Infos[ifname] = &strinfo{
 | 
						|
					Name:    ifname,
 | 
						|
					Methods: map[string]*methodInfo{},
 | 
						|
					Include: v.Include[ifname],
 | 
						|
				}
 | 
						|
			}
 | 
						|
			info := m.Infos[ifname]
 | 
						|
			for mname, node := range methods {
 | 
						|
				filteredComments := cmap.Filter(node.node).Comments()
 | 
						|
 | 
						|
				if _, ok := info.Methods[mname]; !ok {
 | 
						|
					var params, pnames []string
 | 
						|
					for _, param := range node.ftype.Params.List {
 | 
						|
						pstr, err := typeName(param.Type, outpkg)
 | 
						|
						if err != nil {
 | 
						|
							return err
 | 
						|
						}
 | 
						|
 | 
						|
						c := len(param.Names)
 | 
						|
						if c == 0 {
 | 
						|
							c = 1
 | 
						|
						}
 | 
						|
 | 
						|
						for i := 0; i < c; i++ {
 | 
						|
							pname := fmt.Sprintf("p%d", len(params))
 | 
						|
							pnames = append(pnames, pname)
 | 
						|
							params = append(params, pname+" "+pstr)
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					results := []string{}
 | 
						|
					for _, result := range node.ftype.Results.List {
 | 
						|
						rs, err := typeName(result.Type, outpkg)
 | 
						|
						if err != nil {
 | 
						|
							return err
 | 
						|
						}
 | 
						|
						results = append(results, rs)
 | 
						|
					}
 | 
						|
 | 
						|
					defRes := ""
 | 
						|
					if len(results) > 1 {
 | 
						|
						defRes = results[0]
 | 
						|
						switch {
 | 
						|
						case defRes[0] == '*' || defRes[0] == '<', defRes == "interface{}":
 | 
						|
							defRes = "nil"
 | 
						|
						case defRes == "bool":
 | 
						|
							defRes = "false"
 | 
						|
						case defRes == "string":
 | 
						|
							defRes = `""`
 | 
						|
						case defRes == "int", defRes == "int64", defRes == "uint64", defRes == "uint":
 | 
						|
							defRes = "0"
 | 
						|
						default:
 | 
						|
							defRes = "*new(" + defRes + ")"
 | 
						|
						}
 | 
						|
						defRes += ", "
 | 
						|
					}
 | 
						|
 | 
						|
					info.Methods[mname] = &methodInfo{
 | 
						|
						Name:        mname,
 | 
						|
						node:        node.node,
 | 
						|
						Tags:        map[string][]string{},
 | 
						|
						NamedParams: strings.Join(params, ", "),
 | 
						|
						ParamNames:  strings.Join(pnames, ", "),
 | 
						|
						Results:     strings.Join(results, ", "),
 | 
						|
						DefRes:      defRes,
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// try to parse tag info
 | 
						|
				if len(filteredComments) > 0 {
 | 
						|
					tagstr := filteredComments[len(filteredComments)-1].List[0].Text
 | 
						|
					tagstr = strings.TrimPrefix(tagstr, "//")
 | 
						|
					tl := strings.Split(strings.TrimSpace(tagstr), " ")
 | 
						|
					for _, ts := range tl {
 | 
						|
						tf := strings.Split(ts, ":")
 | 
						|
						if len(tf) != 2 {
 | 
						|
							continue
 | 
						|
						}
 | 
						|
						if tf[0] != "perm" { // todo: allow more tag types
 | 
						|
							continue
 | 
						|
						}
 | 
						|
						info.Methods[mname].Tags[tf[0]] = tf
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*jb, err := json.MarshalIndent(Infos, "", "  ")
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	fmt.Println(string(jb))*/
 | 
						|
 | 
						|
	w, err := os.OpenFile(outfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = doTemplate(w, m, `// Code generated by github.com/filecoin-project/lotus/gen/api. DO NOT EDIT.
 | 
						|
 | 
						|
package {{.OutPkg}}
 | 
						|
 | 
						|
import (
 | 
						|
{{range .Imports}}	{{.}}
 | 
						|
{{end}}
 | 
						|
)
 | 
						|
`)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = doTemplate(w, m, `
 | 
						|
 | 
						|
var ErrNotSupported = xerrors.New("method not supported")
 | 
						|
 | 
						|
{{range .Infos}}
 | 
						|
type {{.Name}}Struct struct {
 | 
						|
{{range .Include}}
 | 
						|
	{{.}}Struct
 | 
						|
{{end}}
 | 
						|
	Internal struct {
 | 
						|
{{range .Methods}}
 | 
						|
		{{.Name}} func({{.NamedParams}}) ({{.Results}}) `+"`"+`{{range .Tags}}{{index . 0}}:"{{index . 1}}"{{end}}`+"`"+`
 | 
						|
{{end}}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type {{.Name}}Stub struct {
 | 
						|
{{range .Include}}
 | 
						|
	{{.}}Stub
 | 
						|
{{end}}
 | 
						|
}
 | 
						|
{{end}}
 | 
						|
 | 
						|
{{range .Infos}}
 | 
						|
{{$name := .Name}}
 | 
						|
{{range .Methods}}
 | 
						|
func (s *{{$name}}Struct) {{.Name}}({{.NamedParams}}) ({{.Results}}) {
 | 
						|
	if s.Internal.{{.Name}} == nil {
 | 
						|
		return {{.DefRes}}ErrNotSupported
 | 
						|
	}
 | 
						|
	return s.Internal.{{.Name}}({{.ParamNames}})
 | 
						|
}
 | 
						|
 | 
						|
func (s *{{$name}}Stub) {{.Name}}({{.NamedParams}}) ({{.Results}}) {
 | 
						|
	return {{.DefRes}}ErrNotSupported
 | 
						|
}
 | 
						|
{{end}}
 | 
						|
{{end}}
 | 
						|
 | 
						|
{{range .Infos}}var _ {{.Name}} = new({{.Name}}Struct)
 | 
						|
{{end}}
 | 
						|
 | 
						|
`)
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func doTemplate(w io.Writer, info interface{}, templ string) error {
 | 
						|
	t := template.Must(template.New("").
 | 
						|
		Funcs(template.FuncMap{}).Parse(templ))
 | 
						|
 | 
						|
	return t.Execute(w, info)
 | 
						|
}
 |