From 926d746df51b229a930d87596ea4c84588f3fc6a Mon Sep 17 00:00:00 2001 From: stevenzack Date: Fri, 14 Aug 2020 09:02:32 +0800 Subject: [PATCH] Add goctl kotlin support --- go.mod | 1 + go.sum | 2 + tools/goctl/api/ktgen/cmd.go | 36 +++++++ tools/goctl/api/ktgen/funcs.go | 67 +++++++++++++ tools/goctl/api/ktgen/gen.go | 172 +++++++++++++++++++++++++++++++++ tools/goctl/goctl.go | 20 ++++ 6 files changed, 298 insertions(+) create mode 100644 tools/goctl/api/ktgen/cmd.go create mode 100644 tools/goctl/api/ktgen/funcs.go create mode 100644 tools/goctl/api/ktgen/gen.go diff --git a/go.mod b/go.mod index 27e58583..22c3fa4f 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/google/uuid v1.1.1 github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect + github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/justinas/alice v1.2.0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect diff --git a/go.sum b/go.sum index 2fc6e4e9..047533ab 100644 --- a/go.sum +++ b/go.sum @@ -134,6 +134,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtg github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= +github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= diff --git a/tools/goctl/api/ktgen/cmd.go b/tools/goctl/api/ktgen/cmd.go new file mode 100644 index 00000000..b8e382e8 --- /dev/null +++ b/tools/goctl/api/ktgen/cmd.go @@ -0,0 +1,36 @@ +package ktgen + +import ( + "errors" + "github.com/tal-tech/go-zero/core/lang" + "github.com/tal-tech/go-zero/tools/goctl/api/parser" + "github.com/urfave/cli" +) + +func KtCommand(c *cli.Context) error { + apiFile := c.String("api") + if apiFile == "" { + return errors.New("missing -api") + } + dir := c.String("dir") + if dir == "" { + return errors.New("missing -dir") + } + pkg := c.String("pkg") + if pkg == "" { + return errors.New("missing -pkg") + } + + p, e := parser.NewParser(apiFile) + if e != nil { + return e + } + api,e:=p.Parse() + if e!=nil { + return e + } + + lang.Must(genBase(dir,pkg,api)) + lang.Must(genApi(dir,pkg, api)) + return nil +} diff --git a/tools/goctl/api/ktgen/funcs.go b/tools/goctl/api/ktgen/funcs.go new file mode 100644 index 00000000..9a301c66 --- /dev/null +++ b/tools/goctl/api/ktgen/funcs.go @@ -0,0 +1,67 @@ +package ktgen + +import ( + "github.com/tal-tech/go-zero/tools/goctl/api/util" + "log" + "strings" + "text/template" +) +var funcsMap=template.FuncMap{ + "lowCamelCase":lowCamelCase, + "pathToFuncName":pathToFuncName, + "parseType":parseType, + "add":add, +} +func lowCamelCase(s string) string { + if len(s) < 1 { + return "" + } + s = util.ToCamelCase(util.ToSnakeCase(s)) + return util.ToLower(s[:1]) + s[1:] +} + +func pathToFuncName(path string) string { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + path = strings.Replace(path, "/", "_", -1) + path = strings.Replace(path, "-", "_", -1) + + camel := util.ToCamelCase(path) + return util.ToLower(camel[:1]) + camel[1:] +} +func parseType(t string) string { + t=strings.Replace(t,"*","",-1) + if strings.HasPrefix(t,"[]"){ + return "List<"+parseType(t[2:])+ ">" + } + + if strings.HasPrefix(t,"map"){ + tys,e:=util.DecomposeType(t) + if e!=nil{ + log.Fatal(e) + } + if len(tys)!=2{ + log.Fatal("Map type number !=2") + } + return "Map" + } + + switch t { + case "string": + return "String" + case "int", "int32", "int64": + return "Int" + case "float", "float32", "float64": + return "Double" + case "bool": + return "Boolean" + default: + return t + } +} + +func add(a,i int)int{ + return a+i +} \ No newline at end of file diff --git a/tools/goctl/api/ktgen/gen.go b/tools/goctl/api/ktgen/gen.go new file mode 100644 index 00000000..fd9923f4 --- /dev/null +++ b/tools/goctl/api/ktgen/gen.go @@ -0,0 +1,172 @@ +package ktgen + +import ( + "github.com/tal-tech/go-zero/core/logx" + "github.com/tal-tech/go-zero/tools/goctl/api/spec" + "log" + "os" + "path/filepath" + "text/template" + "github.com/iancoleman/strcase" +) + +const ( + apiBaseTemplate = `package {{.}} + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.net.URL + +const val SERVER = "http://localhost:8080" + +suspend fun apiPost( + uri: String, + body: Any, + onOk: ((String) -> Unit)? = null, + onFail: ((String) -> Unit)? = null, + eventually: (() -> Unit)? = null +) = withContext(Dispatchers.IO) { + val url = URL(SERVER + uri) + with(url.openConnection() as HttpURLConnection) { + requestMethod = "POST" + headerFields["Content-Type"] = listOf("Application/json") + + val data = when (body) { + is String -> { + body + } + else -> { + Gson().toJson(body) + } + } + val wr = OutputStreamWriter(outputStream) + wr.write(data) + wr.flush() + + //response + BufferedReader(InputStreamReader(inputStream)).use { + val response = it.readText() + if (responseCode == 200) { + onOk?.invoke(response) + } else { + onFail?.invoke(response) + } + } + } + eventually?.invoke() +} + +suspend fun apiGet( + uri: String, + onOk: ((String) -> Unit)? = null, + onFail: ((String) -> Unit)? = null, + eventually: (() -> Unit)? = null +) = withContext(Dispatchers.IO) { + val url = URL(SERVER + uri) + with(url.openConnection() as HttpURLConnection) { + requestMethod = "POST" + headerFields["Content-Type"] = listOf("Application/json") + + val wr = OutputStreamWriter(outputStream) + wr.flush() + + //response + BufferedReader(InputStreamReader(inputStream)).use { + val response = it.readText() + if (responseCode == 200) { + onOk?.invoke(response) + } else { + onFail?.invoke(response) + } + } + } + eventually?.invoke() +} +` + apiTemplate = `package {{with .Info}}{{.Title}}{{end}} + +import com.google.gson.Gson + +object Api{ + {{range .Types}} + data class {{.Name}}({{$length := (len .Members)}}{{range $i,$item := .Members}} + val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}} + ){{end}} + {{with .Service}} + {{range .Routes}}suspend fun {{pathToFuncName .Path}}({{if ne .Method "get"}} + req:{{with .RequestType}}{{.Name}},{{end}}{{end}} + onOk: (({{with .ResponseType}}{{.Name}}{{end}}) -> Unit)? = null, + onFail: ((String) -> Unit)? = null, + eventually: (() -> Unit)? = null + ){ + api{{if eq .Method "get"}}Get{{else}}Post{{end}}("{{.Path}}",{{if ne .Method "get"}}req,{{end}} onOk = { + onOk?.invoke(Gson().fromJson(it,{{with .ResponseType}}{{.Name}}{{end}}::class.java)) + }, onFail = onFail, eventually =eventually) + } + {{end}}{{end}} +}` +) + +func genBase(dir, pkg string, api *spec.ApiSpec) error { + e := os.MkdirAll(dir, 0755) + if e != nil { + logx.Error(e) + return e + } + path := filepath.Join(dir, "BaseApi.kt") + if _, e := os.Stat(path); e == nil { + return nil + } + + file, e := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if e != nil { + logx.Error(e) + return e + } + defer file.Close() + + t, e := template.New("n").Parse(apiBaseTemplate) + if e != nil { + logx.Error(e) + return e + } + e = t.Execute(file, pkg) + if e != nil { + logx.Error(e) + return e + } + return nil +} + +func genApi(dir, pkg string, api *spec.ApiSpec) error { + path := filepath.Join(dir, strcase.ToCamel(api.Info.Title+"Api")+".kt") + api.Info.Title= pkg + + e:=os.MkdirAll(dir,0755) + if e!=nil { + logx.Error(e) + return e + } + + file,e:=os.OpenFile(path,os.O_WRONLY|os.O_TRUNC|os.O_CREATE,0644) + if e!=nil { + logx.Error(e) + return e + } + defer file.Close() + + t,e:=template.New("api").Funcs(funcsMap).Parse(apiTemplate) + if e!=nil{ + log.Fatal(e) + } + e=t.Execute(file,api) + if e!=nil{ + log.Fatal(e) + } + return nil +} diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 9a7aba99..1e6b357d 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/tal-tech/go-zero/tools/goctl/api/ktgen" "os" "github.com/tal-tech/go-zero/core/logx" @@ -150,6 +151,25 @@ var ( }, Action: dartgen.DartCommand, }, + { + Name: "kt", + Usage: "generate kotlin code for provided api file", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "dir", + Usage: "the target directory", + }, + cli.StringFlag{ + Name: "api", + Usage: "the api file", + }, + cli.StringFlag{ + Name: "pkg", + Usage: "define package name for kotlin file", + }, + }, + Action: ktgen.KtCommand, + }, }, }, {