Refactor to improve later; fix comments in single line
parent
71e7c2f72f
commit
77cadb7a0d
|
@ -0,0 +1,3 @@
|
|||
gofname: test/goapi/api.go
|
||||
goimpldir: test/goapi
|
||||
tsfname: test/tscli/api.ts
|
|
@ -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
|
||||
}
|
|
@ -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
196
main.go
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"dc"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
|
@ -13,105 +12,17 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"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 gofname string
|
||||
var goimplfdir string
|
||||
var tsfname string
|
||||
var httptestdir string
|
||||
//var httptestdir string
|
||||
var tstypemapper map[string]string = make(map[string]string)
|
||||
var knownMethods map[string]bool = make(map[string]bool)
|
||||
var httpMapper map[string]map[string]string = make(map[string]map[string]string)
|
||||
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) {
|
||||
md := manageComments(a.Doc)
|
||||
if md["API"] == "" {
|
||||
|
@ -191,21 +102,22 @@ func addFunction(a *ast.FuncDecl) {
|
|||
if md["API"] == "" {
|
||||
return
|
||||
}
|
||||
reqType := ""
|
||||
resType := ""
|
||||
reqType := &APIParamType{}
|
||||
resType := &APIParamType{}
|
||||
|
||||
if len(a.Type.Params.List) > 1 {
|
||||
switch x := a.Type.Params.List[1].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
reqType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
reqType = y.Name
|
||||
reqType.Typename = y.Name
|
||||
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:
|
||||
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) {
|
||||
case *ast.StarExpr:
|
||||
resType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
resType = y.Name
|
||||
resType.Typename = y.Name
|
||||
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:
|
||||
resType = x.Name
|
||||
resType.Typename = x.Name
|
||||
}
|
||||
}
|
||||
|
||||
if md["RAW"] == "true" {
|
||||
reqType = md["REQ"]
|
||||
resType = md["RES"]
|
||||
reqType.Typename = md["REQ"]
|
||||
resType.Typename = md["RES"]
|
||||
}
|
||||
|
||||
verb := md["VERB"]
|
||||
|
@ -246,11 +159,21 @@ func addFunction(a *ast.FuncDecl) {
|
|||
api.Methods[a.Name.Name] = &fn
|
||||
}
|
||||
|
||||
func processGoServerOutput(f string, api *API) {
|
||||
b := bytes.Buffer{}
|
||||
if f == "" {
|
||||
f = gofname
|
||||
func processGoServerOutput(api *API) {
|
||||
|
||||
APIParamTypeToString := func(t *APIParamType) string {
|
||||
ret := ""
|
||||
if t.Ispointer {
|
||||
ret = ret + "&"
|
||||
}
|
||||
ret = ret + t.Typename
|
||||
return ret
|
||||
}
|
||||
|
||||
b := bytes.Buffer{}
|
||||
|
||||
f := config.Gofname
|
||||
|
||||
os.Remove(f)
|
||||
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) {
|
||||
b := bytes.Buffer{}
|
||||
if f == "" {
|
||||
f = tsfname
|
||||
f = config.Tsfname
|
||||
}
|
||||
|
||||
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}))$/
|
||||
|
||||
type HTMLMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "TRACE"
|
||||
|
||||
async function Invoke(path: string, method: HTMLMethod, body?: any): Promise<Response> {
|
||||
let jbody = undefined
|
||||
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 {
|
||||
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("}\n\n"))
|
||||
//}
|
||||
|
@ -502,27 +427,28 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<b
|
|||
err := ioutil.WriteFile(f, b.Bytes(), 0600)
|
||||
dc.Err(err)
|
||||
}
|
||||
func processHTTPTestOutput(f string, api *API) {
|
||||
|
||||
for k, m := range api.Methods {
|
||||
fname := path.Join(httptestdir, k+".http")
|
||||
if dc.FileExists(fname) {
|
||||
continue
|
||||
}
|
||||
b := bytes.Buffer{}
|
||||
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)
|
||||
dc.Err(err)
|
||||
}
|
||||
}
|
||||
//func processHTTPTestOutput(f string, api *API) {
|
||||
//
|
||||
// for k, m := range api.Methods {
|
||||
// fname := path.Join(httptestdir, k+".http")
|
||||
// if dc.FileExists(fname) {
|
||||
// continue
|
||||
// }
|
||||
// b := bytes.Buffer{}
|
||||
// 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)
|
||||
// dc.Err(err)
|
||||
// }
|
||||
//}
|
||||
|
||||
func mapHttp(api *API) {
|
||||
for k, v := range api.Methods {
|
||||
|
@ -536,10 +462,8 @@ func mapHttp(api *API) {
|
|||
}
|
||||
func process(api *API) {
|
||||
mapHttp(api)
|
||||
processGoServerOutput(gofname, api)
|
||||
//processGoMongoOutput(strings.Replace(gofname, ".go", "_mongo.go", 1), api)
|
||||
processGoServerOutput(api)
|
||||
processTSClientOutput("", api)
|
||||
processHTTPTestOutput("", api)
|
||||
}
|
||||
|
||||
func load() {
|
||||
|
@ -549,7 +473,7 @@ func load() {
|
|||
|
||||
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 {
|
||||
panic(err)
|
||||
|
@ -596,6 +520,8 @@ func load() {
|
|||
|
||||
func main() {
|
||||
|
||||
loadConfig()
|
||||
|
||||
tstypemapper["time.Time"] = "Date"
|
||||
tstypemapper["primitive.ObjectID"] = "string"
|
||||
tstypemapper["time.Duration"] = "Date"
|
||||
|
@ -612,13 +538,7 @@ func main() {
|
|||
tstypemapper["interface{}"] = "any"
|
||||
tstypemapper["bson.M"] = "any"
|
||||
|
||||
flag.StringVar(&gofname, "g", "api.go", "Go API Handle File")
|
||||
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)
|
||||
os.Remove(config.Gofname)
|
||||
load()
|
||||
process(&api)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
Loading…
Reference in New Issue