diff --git a/.apigen.yaml b/.apigen.yaml new file mode 100644 index 0000000..409008e --- /dev/null +++ b/.apigen.yaml @@ -0,0 +1,3 @@ +gofname: test/goapi/api.go +goimpldir: test/goapi +tsfname: test/tscli/api.ts diff --git a/comments.go b/comments.go new file mode 100644 index 0000000..d63c344 --- /dev/null +++ b/comments.go @@ -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.*)\s*:\s*(?P.*)\*?\/?`) + + var myExp2 = regexp.MustCompile(`\/?\*?@(?P.*?)\*?\/?$`) + + 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.*)\s*:\s*(?P.*)\*?\/?`) + + var myExp2 = regexp.MustCompile(`\/?\*?@(?P.*?)\*?\/?$`) + + 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 +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..a69f398 --- /dev/null +++ b/config.go @@ -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) +} diff --git a/main.go b/main.go index 4900999..1d6c641 100644 --- a/main.go +++ b/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.*)\s*:\s*(?P.*)`) - var myExp2 = regexp.MustCompile(`@(?P.*?)$`) - 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.*)\s*:\s*(?P.*)`) - var myExp2 = regexp.MustCompile(`@(?P.*?)$`) - 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 { 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{\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