Refactor to improve later; fix comments in single line

master
Paulo Simão 2020-11-04 13:14:18 -03:00
parent 71e7c2f72f
commit 77cadb7a0d
7 changed files with 238 additions and 138 deletions

3
.apigen.yaml 100644
View File

@ -0,0 +1,3 @@
gofname: test/goapi/api.go
goimpldir: test/goapi
tsfname: test/tscli/api.ts

68
comments.go 100644
View File

@ -0,0 +1,68 @@
package main
import (
"go/ast"
"regexp"
"strings"
)
func manageComments(a *ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if a == nil {
return ret
}
var myExp = regexp.MustCompile(`\/?\*?@(?P<k>.*)\s*:\s*(?P<v>.*)\*?\/?`)
var myExp2 = regexp.MustCompile(`\/?\*?@(?P<k>.*?)\*?\/?$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
return ret
}
func manageCommentsGroups(as []*ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if as == nil {
return ret
}
for _, a := range as {
var myExp = regexp.MustCompile(`\/?\*?@(?P<k>.*)\s*:\s*(?P<v>.*)\*?\/?`)
var myExp2 = regexp.MustCompile(`\/?\*?@(?P<k>.*?)\*?\/?$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
}
return ret
}

24
config.go 100644
View File

@ -0,0 +1,24 @@
package main
import (
"flag"
"gopkg.in/yaml.v2"
"io/ioutil"
)
type Config struct {
Gofname string `yaml:"gofname"`
Goimpldir string `yaml:"goimpldir"`
Tsfname string `json:"tsfname"`
}
var config Config
func loadConfig() {
fname := flag.String("f", ".apigen.yaml", "File with config to load - defaults to '.apigen'")
flag.Parse()
bs, err := ioutil.ReadFile(*fname)
Err(err)
err = yaml.Unmarshal(bs, &config)
Err(err)
}

196
main.go
View File

@ -3,7 +3,6 @@ package main
import ( import (
"bytes" "bytes"
"dc" "dc"
"flag"
"fmt" "fmt"
"go/ast" "go/ast"
"go/parser" "go/parser"
@ -13,105 +12,17 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path"
"regexp"
"strings" "strings"
) )
type API struct {
BasePath string `yaml:"basepath"`
Host string `yaml:"host"`
Types map[string]*APIType `yaml:"types"`
Methods map[string]*APIMethod `yaml:"methods"`
Namespace string
}
type APIField struct {
Type string `yaml:"type"`
Array bool `yaml:"array"`
Desc string `yaml:"desc"`
Map bool `yaml:"map"`
Mapkey string `yaml:"mapkey"`
Mapval string `yaml:"mapval"`
}
type APIType struct {
Name string
Desc string `yaml:"desc"`
Fields map[string]*APIField `yaml:"fields"`
Col string `yaml:"col"`
}
type APIMethod struct {
Desc string `yaml:"desc"`
Verb string `yaml:"verb"`
Path string `yaml:"path"`
Perm string `yaml:perm`
ReqType string `yaml:"reqtype"`
ResType string `yaml:"restype"`
Raw bool `yaml:"raw"`
}
var api API var api API
var gofname string //var httptestdir string
var goimplfdir string
var tsfname string
var httptestdir string
var tstypemapper map[string]string = make(map[string]string) var tstypemapper map[string]string = make(map[string]string)
var knownMethods map[string]bool = make(map[string]bool) var knownMethods map[string]bool = make(map[string]bool)
var httpMapper map[string]map[string]string = make(map[string]map[string]string) var httpMapper map[string]map[string]string = make(map[string]map[string]string)
var packageName string = "main" var packageName string = "main"
func manageComments(a *ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if a == nil {
return ret
}
var myExp = regexp.MustCompile(`@(?P<k>.*)\s*:\s*(?P<v>.*)`)
var myExp2 = regexp.MustCompile(`@(?P<k>.*?)$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
return ret
}
func manageCommentsGroups(as []*ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if as == nil {
return ret
}
for _, a := range as {
var myExp = regexp.MustCompile(`@(?P<k>.*)\s*:\s*(?P<v>.*)`)
var myExp2 = regexp.MustCompile(`@(?P<k>.*?)$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
}
return ret
}
func addStruct(a *ast.GenDecl) { func addStruct(a *ast.GenDecl) {
md := manageComments(a.Doc) md := manageComments(a.Doc)
if md["API"] == "" { if md["API"] == "" {
@ -191,21 +102,22 @@ func addFunction(a *ast.FuncDecl) {
if md["API"] == "" { if md["API"] == "" {
return return
} }
reqType := "" reqType := &APIParamType{}
resType := "" resType := &APIParamType{}
if len(a.Type.Params.List) > 1 { if len(a.Type.Params.List) > 1 {
switch x := a.Type.Params.List[1].Type.(type) { switch x := a.Type.Params.List[1].Type.(type) {
case *ast.StarExpr: case *ast.StarExpr:
reqType.Ispointer = true
switch y := x.X.(type) { switch y := x.X.(type) {
case *ast.Ident: case *ast.Ident:
reqType = y.Name reqType.Typename = y.Name
case *ast.SelectorExpr: case *ast.SelectorExpr:
reqType = y.X.(*ast.Ident).Name + "." + y.Sel.Name reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
} }
case *ast.Ident: case *ast.Ident:
reqType = x.Name reqType.Typename = x.Name
} }
} }
@ -213,21 +125,22 @@ func addFunction(a *ast.FuncDecl) {
switch x := a.Type.Results.List[0].Type.(type) { switch x := a.Type.Results.List[0].Type.(type) {
case *ast.StarExpr: case *ast.StarExpr:
resType.Ispointer = true
switch y := x.X.(type) { switch y := x.X.(type) {
case *ast.Ident: case *ast.Ident:
resType = y.Name resType.Typename = y.Name
case *ast.SelectorExpr: case *ast.SelectorExpr:
resType = y.X.(*ast.Ident).Name + "." + y.Sel.Name resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
} }
case *ast.Ident: case *ast.Ident:
resType = x.Name resType.Typename = x.Name
} }
} }
if md["RAW"] == "true" { if md["RAW"] == "true" {
reqType = md["REQ"] reqType.Typename = md["REQ"]
resType = md["RES"] resType.Typename = md["RES"]
} }
verb := md["VERB"] verb := md["VERB"]
@ -246,11 +159,21 @@ func addFunction(a *ast.FuncDecl) {
api.Methods[a.Name.Name] = &fn api.Methods[a.Name.Name] = &fn
} }
func processGoServerOutput(f string, api *API) { func processGoServerOutput(api *API) {
b := bytes.Buffer{}
if f == "" { APIParamTypeToString := func(t *APIParamType) string {
f = gofname ret := ""
if t.Ispointer {
ret = ret + "&"
}
ret = ret + t.Typename
return ret
} }
b := bytes.Buffer{}
f := config.Gofname
os.Remove(f) os.Remove(f)
b.WriteString(fmt.Sprintf(`package %s b.WriteString(fmt.Sprintf(`package %s
@ -345,7 +268,7 @@ ret.Perms["%s_%s"]="%s"
} }
} }
`, m.ReqType, k)) `, APIParamTypeToString(m.ReqType), k))
} }
} }
@ -359,7 +282,7 @@ ret.Perms["%s_%s"]="%s"
func processTSClientOutput(f string, api *API) { func processTSClientOutput(f string, api *API) {
b := bytes.Buffer{} b := bytes.Buffer{}
if f == "" { if f == "" {
f = tsfname f = config.Tsfname
} }
b.WriteString("//#region Base\n") b.WriteString("//#region Base\n")
@ -377,6 +300,8 @@ export function GetAPIBase(): string{
let REGEX_DATE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/ let REGEX_DATE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/
type HTMLMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "TRACE"
async function Invoke(path: string, method: HTMLMethod, body?: any): Promise<Response> { async function Invoke(path: string, method: HTMLMethod, body?: any): Promise<Response> {
let jbody = undefined let jbody = undefined
let init = {method: method, mode: "cors", credentials: "include", withCredentials: true} let init = {method: method, mode: "cors", credentials: "include", withCredentials: true}
@ -491,7 +416,7 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<b
// } // }
// //
//} else { //} else {
b.WriteString(fmt.Sprintf("export async function %s(req:%s):Promise<%s>{\n", k, m.ReqType, m.ResType)) b.WriteString(fmt.Sprintf("export async function %s(req:%s):Promise<%s>{\n", k, m.ReqType.Typename, m.ResType.Typename))
b.WriteString(fmt.Sprintf("\treturn InvokeJSON(\"%s\",\"%s\",req)\n", m.Path, m.Verb)) b.WriteString(fmt.Sprintf("\treturn InvokeJSON(\"%s\",\"%s\",req)\n", m.Path, m.Verb))
b.WriteString(fmt.Sprintf("}\n\n")) b.WriteString(fmt.Sprintf("}\n\n"))
//} //}
@ -502,27 +427,28 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<b
err := ioutil.WriteFile(f, b.Bytes(), 0600) err := ioutil.WriteFile(f, b.Bytes(), 0600)
dc.Err(err) dc.Err(err)
} }
func processHTTPTestOutput(f string, api *API) {
for k, m := range api.Methods { //func processHTTPTestOutput(f string, api *API) {
fname := path.Join(httptestdir, k+".http") //
if dc.FileExists(fname) { // for k, m := range api.Methods {
continue // fname := path.Join(httptestdir, k+".http")
} // if dc.FileExists(fname) {
b := bytes.Buffer{} // continue
b.WriteString(fmt.Sprintf(`%s{{BASEURL}}%s // }
Content-Type: application/json // b := bytes.Buffer{}
Cookie: dc={{COOKIE}} // b.WriteString(fmt.Sprintf(`%s{{BASEURL}}%s
//Content-Type: application/json
{} //Cookie: dc={{COOKIE}}
//
### //{}
`, m.Verb, api.BasePath+m.Path)) //
//###
err := ioutil.WriteFile(fname, b.Bytes(), 0600) //`, m.Verb, api.BasePath+m.Path))
dc.Err(err) //
} // err := ioutil.WriteFile(fname, b.Bytes(), 0600)
} // dc.Err(err)
// }
//}
func mapHttp(api *API) { func mapHttp(api *API) {
for k, v := range api.Methods { for k, v := range api.Methods {
@ -536,10 +462,8 @@ func mapHttp(api *API) {
} }
func process(api *API) { func process(api *API) {
mapHttp(api) mapHttp(api)
processGoServerOutput(gofname, api) processGoServerOutput(api)
//processGoMongoOutput(strings.Replace(gofname, ".go", "_mongo.go", 1), api)
processTSClientOutput("", api) processTSClientOutput("", api)
processHTTPTestOutput("", api)
} }
func load() { func load() {
@ -549,7 +473,7 @@ func load() {
fset := token.NewFileSet() // positions are relative to fset fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseDir(fset, goimplfdir, nil, parser.ParseComments) f, err := parser.ParseDir(fset, config.Goimpldir, nil, parser.ParseComments)
if err != nil { if err != nil {
panic(err) panic(err)
@ -596,6 +520,8 @@ func load() {
func main() { func main() {
loadConfig()
tstypemapper["time.Time"] = "Date" tstypemapper["time.Time"] = "Date"
tstypemapper["primitive.ObjectID"] = "string" tstypemapper["primitive.ObjectID"] = "string"
tstypemapper["time.Duration"] = "Date" tstypemapper["time.Duration"] = "Date"
@ -612,13 +538,7 @@ func main() {
tstypemapper["interface{}"] = "any" tstypemapper["interface{}"] = "any"
tstypemapper["bson.M"] = "any" tstypemapper["bson.M"] = "any"
flag.StringVar(&gofname, "g", "api.go", "Go API Handle File") os.Remove(config.Gofname)
flag.StringVar(&goimplfdir, "I", "src", "Go API Impl Dir")
flag.StringVar(&tsfname, "t", "api.ts", "TS File")
flag.StringVar(&httptestdir, "h", "test.http", "HTTP Test Dir")
flag.Parse()
os.Remove(gofname)
load() load()
process(&api) process(&api)
} }

30
test/goapi/test.go 100644
View File

@ -0,0 +1,30 @@
package goapi
import (
"context"
"time"
)
/*@API*/
type ARequestStruct struct {
A *string
B int64
C time.Time
D *string
}
/*@API*/
type AResponseStruct struct {
A string
B int64
C time.Time
D *string
}
/*
@API
PATH: /someapi
*/
func SomeAPI(ctx context.Context, a *ARequestStruct) (*AResponseStruct, error) {
return nil, nil
}

38
types.go 100644
View File

@ -0,0 +1,38 @@
package main
type API struct {
BasePath string `yaml:"basepath"`
Host string `yaml:"host"`
Types map[string]*APIType `yaml:"types"`
Methods map[string]*APIMethod `yaml:"methods"`
Namespace string
}
type APIField struct {
Type string `yaml:"type"`
Array bool `yaml:"array"`
Desc string `yaml:"desc"`
Map bool `yaml:"map"`
Mapkey string `yaml:"mapkey"`
Mapval string `yaml:"mapval"`
}
type APIType struct {
Name string
Desc string `yaml:"desc"`
Fields map[string]*APIField `yaml:"fields"`
Col string `yaml:"col"`
}
type APIParamType struct {
Typename string
Ispointer bool
}
type APIMethod struct {
Desc string `yaml:"desc"`
Verb string `yaml:"verb"`
Path string `yaml:"path"`
Perm string `yaml:perm`
ReqType *APIParamType
ResType *APIParamType
Raw bool `yaml:"raw"`
}

17
util.go 100644
View File

@ -0,0 +1,17 @@
package main
import "log"
func Err(e error) {
if e != nil {
panic(e)
}
}
func Debug(s string, p ...interface{}) {
log.Printf("DEBUG: "+s, p...)
}
func Log(s string, p ...interface{}) {
log.Printf("LOG: "+s, p...)
}