further developing program, completing layout

This commit is contained in:
Jan Halfar 2016-05-29 12:07:39 +02:00
parent 6e5aa7a265
commit fd22d49b56
14 changed files with 630 additions and 80 deletions

87
cmd/gotsrpc/gotsrpc.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"go/format"
"io/ioutil"
"os"
"path"
"strings"
"github.com/foomo/gotsrpc"
)
func jsonDump(v interface{}) {
jsonBytes, err := json.MarshalIndent(v, "", " ")
fmt.Println(err, string(jsonBytes))
}
func usage() {
fmt.Println("Usage")
fmt.Println(os.Args[0], " --ts-module MyTS.Module.Name my.server/my/package ServiceA [ ServiceB, ... ]")
flag.PrintDefaults()
}
func main() {
flagTsModule := flag.String("ts-module", "", "TypeScript target module")
flag.Parse()
if len(*flagTsModule) == 0 {
fmt.Println("missing ts module")
}
args := flag.Args()
if len(args) < 2 {
usage()
os.Exit(1)
}
gotsrpc.ReaderTrace = true
goPath := os.Getenv("GOPATH")
if len(goPath) == 0 {
fmt.Println("GOPATH not set")
os.Exit(1)
}
longPackageName := args[0]
longPackageNameParts := strings.Split(longPackageName, "/")
goFilename := path.Join(goPath, "src", longPackageName, "gotsrpc.go")
packageName := longPackageNameParts[len(longPackageNameParts)-1]
services, structs, err := gotsrpc.Read(goPath, longPackageName, args[1:])
if err != nil {
fmt.Println("an error occured", err)
os.Exit(2)
}
jsonDump(services)
jsonDump(structs)
ts, err := gotsrpc.RenderTypeScript(services, structs, *flagTsModule)
if err != nil {
fmt.Println("could not generate ts code", err)
os.Exit(3)
}
fmt.Println(ts)
gocode, goerr := gotsrpc.RenderGo(services, packageName)
if goerr != nil {
fmt.Println("could not generate go code", goerr)
os.Exit(4)
}
formattedGoBytes, formattingError := format.Source([]byte(gocode))
if formattingError == nil {
gocode = string(formattedGoBytes)
} else {
fmt.Println("could not format go code", formattingError)
}
writeErr := ioutil.WriteFile(goFilename, []byte(gocode), 0644)
if writeErr != nil {
fmt.Println("could not write go source to file", writeErr)
os.Exit(5)
}
fmt.Println(goFilename, gocode)
//gotsrpc.ReadFile("/Users/jan/go/src/github.com/foomo/gotsrpc/demo/demo.go", []string{"Service"})
}

View File

@ -1,35 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/foomo/gotsrpc"
)
func jsonDump(v interface{}) {
jsonBytes, err := json.MarshalIndent(v, "", " ")
fmt.Println(err, string(jsonBytes))
}
func main() {
//fmt.Println("hello", os.Args[1])
gotsrpc.ReaderTrace = true
//gotsrpc.Read(os.Args[1], os.Args[2:])
//gotsrpc.ReadFile("/Users/jan/go/src/github.com/foomo/gotsrpc/demo/demo.go", []string{"Service"})
goPath := os.Getenv("GOPATH")
if len(goPath) == 0 {
fmt.Println("GOPATH not set")
os.Exit(1)
}
jsonDump(os.Args[2:])
services, err := gotsrpc.ReadServicesInPackage(goPath, os.Args[1], os.Args[2:])
if err != nil {
fmt.Println("an error occured", err)
os.Exit(2)
}
jsonDump(services)
//gotsrpc.ReadFile("/Users/jan/go/src/github.com/foomo/gotsrpc/demo/demo.go", []string{"Service"})
}

41
code.go Normal file
View File

@ -0,0 +1,41 @@
package gotsrpc
import "strings"
type code struct {
line string
lines []string
indent int
}
func newCode() *code {
return &code{
line: "",
lines: []string{},
indent: 0,
}
}
func (c *code) ind(inc int) *code {
c.indent += inc
return c
}
func (c *code) nl() *code {
c.lines = append(c.lines, strings.Repeat(" ", c.indent)+c.line)
c.line = ""
return c
}
func (c *code) l(line string) *code {
c.app(line).nl()
return c
}
func (c *code) app(str string) *code {
c.line += str
return c
}
func (c *code) string() string {
return strings.Join(c.lines, "\n")
}

View File

@ -3,8 +3,9 @@ package demo
import nstd "github.com/foomo/gotsrpc/demo/nested"
type Address struct {
City string `json:"city,omitempty"`
SecretServerCrap bool `json:"-"`
City string `json:"city,omitempty"`
Signs []string `json:"signs,omitempty"`
SecretServerCrap bool `json:"-"`
PeoplePtr []*Person
ArrayOfMaps []map[string]bool
ArrayArrayAddress [][]*Address

View File

@ -18,3 +18,7 @@ func (s *Service) Hello(name string) (reply string, err *Err) {
func Sepp(bar bool) string {
return "ich bin der sepp"
}
func (s *Service) NothingInNothinOut() {
}

View File

@ -1,9 +1,9 @@
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package demo
import (
"net/http"
"github.com/foomo/gotsrpc"
"net/http"
)
type ServiceGoTSRPCProxy struct {
@ -18,23 +18,37 @@ func NewServiceGoTSRPCProxy(service *Service, endpoint string) *ServiceGoTSRPCPr
}
}
func (sp *ServiceGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ServeHTTP exposes your service
func (p *ServiceGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
gotsrpc.ErrorMethodNotAllowed(w)
return
}
switch gotsrpc.GetCalledFunc(r, sp.EndPoint) {
case "Hello":
args := []interface{}{""}
var args []interface{}
switch gotsrpc.GetCalledFunc(r, p.EndPoint) {
case "ExtractAddress":
args = []interface{}{&Person{}}
err := gotsrpc.LoadArgs(args, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
helloReply, helloErr := sp.service.Hello(args[0].(string))
extractAddressRet, extractAddressRet_1 := p.service.ExtractAddress(args[0].(*Person))
gotsrpc.Reply([]interface{}{extractAddressRet, extractAddressRet_1}, w)
return
case "Hello":
args = []interface{}{""}
err := gotsrpc.LoadArgs(args, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
helloReply, helloErr := p.service.Hello(args[0].(string))
gotsrpc.Reply([]interface{}{helloReply, helloErr}, w)
return
default:
gotsrpc.ErrorFuncNotFound(w)
case "NothingInNothinOut":
p.service.NothingInNothinOut()
gotsrpc.Reply([]interface{}{}, w)
return
}
}

40
demo/gotsrpc_test.go Normal file
View File

@ -0,0 +1,40 @@
package demo_test
import (
"net/http"
"github.com/foomo/gotsrpc"
)
type ServiceGoTSRPCProxy struct {
EndPoint string
service *Service
}
func NewServiceGoTSRPCProxy(service *Service, endpoint string) *ServiceGoTSRPCProxy {
return &ServiceGoTSRPCProxy{
EndPoint: endpoint,
service: service,
}
}
func (sp *ServiceGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
gotsrpc.ErrorMethodNotAllowed(w)
return
}
switch gotsrpc.GetCalledFunc(r, sp.EndPoint) {
case "Hello":
args := []interface{}{""}
err := gotsrpc.LoadArgs(args, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
helloReply, helloErr := sp.service.Hello(args[0].(string))
gotsrpc.Reply([]interface{}{helloReply, helloErr}, w)
return
default:
gotsrpc.ErrorFuncNotFound(w)
}
}

20
demo/maps.js Normal file
View File

@ -0,0 +1,20 @@
var foo = {
people: {
bla: {
audi: {
color: "white",
wheels: 1
}
}
},
cars: {
audi: {
color: "white",
wheels: 1
},
blas: {
color: "red",
wheels: 23.1
}
}
};

38
demo/maps.ts Normal file
View File

@ -0,0 +1,38 @@
interface CarMap {
[index:string]:Car;
}
interface Car {
color:string;
wheels:number;
}
interface Complex {
cars:CarMap;
people:{[index:string]:{
[index:string]:Car
}};
}
var foo : Complex = {
people: {
bla:{
audi: {
color: "white",
wheels:1
}
}
},
cars: {
audi: {
color: "white",
wheels:1
},
blas: {
color:"red",
wheels:23.1
}
}
}

168
go.go Normal file
View File

@ -0,0 +1,168 @@
package gotsrpc
import (
"fmt"
"strings"
)
func (v *Value) goType() (t string) {
if v.IsPtr {
t = "*"
}
switch true {
case v.Array != nil:
t += "[]" + v.Array.Value.goType()
case len(v.GoScalarType) > 0:
t += v.GoScalarType
case v.StructType != nil:
t += v.StructType.Name
}
return
}
func (v *Value) emptyLiteral() (e string) {
e = ""
if v.IsPtr {
e += "&"
}
switch true {
case len(v.GoScalarType) > 0:
switch v.GoScalarType {
case "string":
e += "\"\""
case "float":
return "float(0.0)"
case "float32":
return "float32(0.0)"
case "float64":
return "float64(0.0)"
case "int":
return "int(0)"
case "int32":
return "int32(0)"
case "int64":
return "int64(0)"
case "bool":
return "false"
}
case v.Array != nil:
e += "[]" + v.Array.Value.emptyLiteral() + "{}"
case v.StructType != nil:
e += v.StructType.Name + "{}"
}
return
}
func lcfirst(str string) string {
return strfirst(str, strings.ToLower)
}
func ucfirst(str string) string {
return strfirst(str, strings.ToUpper)
}
func strfirst(str string, strfunc func(string) string) string {
if len(str) == 0 {
return ""
} else if len(str) == 1 {
return strfunc(str)
}
return strfunc(str[0:1]) + str[1:]
}
func renderServiceProxies(services []*Service, packageName string, g *code) error {
g.l(`
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package ` + packageName + `
import (
"net/http"
"github.com/foomo/gotsrpc"
)
`)
for _, service := range services {
proxyName := service.Name + "GoTSRPCProxy"
g.l(`
type ` + proxyName + ` struct {
EndPoint string
service *` + service.Name + `
}
func New` + proxyName + `(service *` + service.Name + `, endpoint string) *` + proxyName + ` {
return &` + proxyName + `{
EndPoint: endpoint,
service: service,
}
}
// ServeHTTP exposes your service
func (p *` + proxyName + `) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
gotsrpc.ErrorMethodNotAllowed(w)
return
}
var args []interface{}
switch gotsrpc.GetCalledFunc(r, p.EndPoint) {`)
// indenting into switch cases
g.ind(4)
for _, method := range service.Methods {
// a case for each method
g.l("case \"" + method.Name + "\":")
g.ind(1)
callArgs := []string{}
if len(method.Args) > 0 {
args := []string{}
for argI, arg := range method.Args {
args = append(args, arg.Value.emptyLiteral())
callArgs = append(callArgs, fmt.Sprint("args[", argI, "].("+arg.Value.goType()+")"))
}
g.l("args = []interface{}{" + strings.Join(args, ", ") + "}")
g.l("err := gotsrpc.LoadArgs(args, r)")
g.l("if err != nil {")
g.ind(1)
g.l("gotsrpc.ErrorCouldNotLoadArgs(w)")
g.l("return")
g.ind(-1)
g.l("}")
}
returnValueNames := []string{}
for retI, retField := range method.Return {
retArgName := retField.Name
if len(retArgName) == 0 {
retArgName = "ret"
if retI > 0 {
retArgName += "_" + fmt.Sprint(retI)
}
}
returnValueNames = append(returnValueNames, lcfirst(method.Name)+ucfirst(retArgName))
}
if len(returnValueNames) > 0 {
g.app(strings.Join(returnValueNames, ", ") + " := ")
}
g.app("p.service." + method.Name + "(" + strings.Join(callArgs, ", ") + ")")
g.nl()
g.l("gotsrpc.Reply([]interface{}{" + strings.Join(returnValueNames, ", ") + "}, w)")
g.l("return")
g.ind(-1)
}
g.ind(-1).l("}") // close switch
g.ind(-1).l("}") // close ServeHttp
}
return nil
}
func RenderGo(services []*Service, packageName string) (gocode string, err error) {
g := newCode()
err = renderServiceProxies(services, packageName, g)
if err != nil {
return
}
gocode = g.string()
return
}

View File

@ -22,12 +22,13 @@ type StructType struct {
}
type Value struct {
ScalarType ScalarType `json:",omitempty"`
StructType *StructType `json:",omitempty"`
Struct *Struct `json:",omitempty"`
Map *Map `json:",omitempty"`
Array *Array `json:",omitempty"`
IsPtr bool `json:",omitempty"`
ScalarType ScalarType `json:",omitempty"`
GoScalarType string `json:",omitempty"`
StructType *StructType `json:",omitempty"`
Struct *Struct `json:",omitempty"`
Map *Map `json:",omitempty"`
Array *Array `json:",omitempty"`
IsPtr bool `json:",omitempty"`
}
type Array struct {
@ -58,5 +59,5 @@ type Method struct {
type Struct struct {
Name string
Fields map[string]*Field
Fields []*Field
}

View File

@ -7,7 +7,15 @@ import (
"reflect"
)
func readServiceFile(file *ast.File, services map[string]*Service) error {
func readServiceFile(file *ast.File, services []*Service) error {
findService := func(serviceName string) (service *Service, ok bool) {
for _, service := range services {
if service.Name == serviceName {
return service, true
}
}
return nil, false
}
for _, decl := range file.Decls {
if reflect.ValueOf(decl).Type().String() == "*ast.FuncDecl" {
funcDecl := decl.(*ast.FuncDecl)
@ -21,13 +29,13 @@ func readServiceFile(file *ast.File, services map[string]*Service) error {
ident := starExpr.X.(*ast.Ident)
fmt.Println(" on sth:", ident.Name)
service, ok := services[ident.Name]
service, ok := findService(ident.Name)
if ok {
service.Methods = append(service.Methods, &Method{
Name: funcDecl.Name.Name,
Args: readFields(funcDecl.Type.Params.List),
Return: readFields(funcDecl.Type.Results.List),
Args: readFields(funcDecl.Type.Params),
Return: readFields(funcDecl.Type.Results),
})
}
}
@ -41,9 +49,13 @@ func readServiceFile(file *ast.File, services map[string]*Service) error {
return nil
}
func readFields(astFields []*ast.Field) (fields []*Field) {
func readFields(fieldList *ast.FieldList) (fields []*Field) {
fields = []*Field{}
for _, param := range astFields {
if fieldList == nil {
return
}
for _, param := range fieldList.List {
name, value, _ := readField(param)
fields = append(fields, &Field{
Name: name,
@ -54,13 +66,13 @@ func readFields(astFields []*ast.Field) (fields []*Field) {
}
func readServicesInPackage(pkg *ast.Package, serviceNames []string) (services map[string]*Service, err error) {
services = map[string]*Service{}
func readServicesInPackage(pkg *ast.Package, serviceNames []string) (services []*Service, err error) {
services = []*Service{}
for _, serviceName := range serviceNames {
services[serviceName] = &Service{
services = append(services, &Service{
Name: serviceName,
Methods: []*Method{},
}
})
}
for _, file := range pkg.Files {
err = readServiceFile(file, services)
@ -72,7 +84,7 @@ func readServicesInPackage(pkg *ast.Package, serviceNames []string) (services ma
return
}
func ReadServicesInPackage(goPath string, packageName string, serviceNames []string) (services map[string]*Service, err error) {
func Read(goPath string, packageName string, serviceNames []string) (services []*Service, structs map[string]*Struct, err error) {
if len(serviceNames) == 0 {
err = errors.New("nothing to do service names are empty")
return
@ -81,5 +93,10 @@ func ReadServicesInPackage(goPath string, packageName string, serviceNames []str
if err != nil {
return
}
return readServicesInPackage(pkg, serviceNames)
services, err = readServicesInPackage(pkg, serviceNames)
if err != nil {
return
}
structs, err = readStructs(pkg)
return
}

View File

@ -9,6 +9,18 @@ import (
var ReaderTrace = false
func readStructs(pkg *ast.Package) (structs map[string]*Struct, err error) {
structs = map[string]*Struct{}
for _, file := range pkg.Files {
//readFile(filename, file)
err = extractStructs(file, structs)
if err != nil {
return
}
}
return
}
func trace(args ...interface{}) {
if ReaderTrace {
fmt.Println(args...)
@ -58,15 +70,6 @@ func extractJSONInfo(tag string) *JSONInfo {
}
}
func ReadStructs(pkg *ast.Package, services []string) error {
structs := map[string]*Struct{}
for _, file := range pkg.Files {
//readFile(filename, file)
extractStructs(file, structs)
}
return nil
}
func getScalarFromAstIdent(ident *ast.Ident) ScalarType {
switch ident.Name {
case "string":
@ -92,10 +95,13 @@ func getTypesFromAstType(ident *ast.Ident) (structType string, scalarType Scalar
func readAstType(v *Value, fieldIdent *ast.Ident) {
structType, scalarType := getTypesFromAstType(fieldIdent)
v.ScalarType = scalarType
if len(structType) > 0 {
v.StructType = &StructType{
Name: structType,
}
} else {
v.GoScalarType = fieldIdent.Name
}
}
@ -213,8 +219,8 @@ func readField(astField *ast.Field) (name string, v *Value, jsonInfo *JSONInfo)
return
}
func readFieldList(fieldList []*ast.Field) (fields map[string]*Field) {
fields = map[string]*Field{}
func readFieldList(fieldList []*ast.Field) (fields []*Field) {
fields = []*Field{}
for _, field := range fieldList {
name, value, jsonInfo := readField(field)
@ -223,17 +229,17 @@ func readFieldList(fieldList []*ast.Field) (fields map[string]*Field) {
}
if value != nil {
fields[name] = &Field{
fields = append(fields, &Field{
Name: name,
Value: value,
JSONInfo: jsonInfo,
}
})
}
}
return
}
func extractStructs(file *ast.File, structs map[string]*Struct) {
func extractStructs(file *ast.File, structs map[string]*Struct) error {
for _, imp := range file.Imports {
fmt.Println("import", imp.Name, imp.Path)
}
@ -243,7 +249,7 @@ func extractStructs(file *ast.File, structs map[string]*Struct) {
//ast.StructType
structs[name] = &Struct{
Name: name,
Fields: map[string]*Field{},
Fields: []*Field{},
}
if reflect.ValueOf(obj.Decl).Type().String() == "*ast.TypeSpec" {
typeSpec := obj.Decl.(*ast.TypeSpec)
@ -264,4 +270,5 @@ func extractStructs(file *ast.File, structs map[string]*Struct) {
//fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", obj.Kind, obj)
}
}
return nil
}

147
typescript.go Normal file
View File

@ -0,0 +1,147 @@
package gotsrpc
import (
"fmt"
"strings"
)
func (f *Field) tsName() string {
n := f.Name
if f.JSONInfo != nil && len(f.JSONInfo.Name) > 0 {
n = f.JSONInfo.Name
}
return n
}
func (v *Value) tsType() string {
switch true {
case v.IsPtr:
if v.StructType != nil {
if len(v.StructType.Package) > 0 {
return v.StructType.Package + "." + v.StructType.Name
}
return v.StructType.Name
}
return string(v.ScalarType)
case v.Array != nil:
return v.Array.Value.tsType() + "[]"
case len(v.ScalarType) > 0:
return string(v.ScalarType)
default:
return "any"
}
}
func renderStruct(str *Struct, ts *code) error {
ts.l("export interface " + str.Name + " {").ind(1)
for _, f := range str.Fields {
ts.app(f.tsName())
if f.Value.IsPtr {
ts.app("?")
}
ts.app(":" + f.Value.tsType())
ts.app(";")
ts.nl()
}
ts.ind(-1).l("}")
return nil
}
/*
export class ServiceClient {
static defaultInst = new ServiceClient()
constructor(public endPoint:string = "/service") { }
hello(name:string, success:(reply:string, err:Err) => void, err:(request:XMLHttpRequest) => void) {
GoTSRPC.call(this.endPoint, "Hello", [name], success, err);
}
}
*/
func renderService(service *Service, ts *code) error {
clientName := service.Name + "Client"
ts.l("export class " + clientName + " {").ind(1).
l("static defaultInst = new " + clientName + ";").
l("constructor(public endPoint:string = \"/service\") { }")
for _, method := range service.Methods {
ts.app(method.Name + "(")
// actual args
args := []string{}
callArgs := []string{}
for _, arg := range method.Args {
args = append(args, arg.tsName()+":"+arg.Value.tsType())
callArgs = append(callArgs, arg.Name)
}
ts.app(strings.Join(args, ", "))
// success callback
retArgs := []string{}
for index, retField := range method.Return {
retArgName := retField.tsName()
if len(retArgName) == 0 {
retArgName = "ret"
if index > 0 {
retArgName += "_" + fmt.Sprint(index)
}
}
retArgs = append(retArgs, retArgName+":"+retField.Value.tsType())
}
if len(args) > 0 {
ts.app(", ")
}
ts.app("success(" + strings.Join(retArgs, ", ") + ") => void")
ts.app(", err:(request:XMLHttpRequest) => void) {").nl()
ts.ind(1)
// generic framework call
ts.l("GoTSRPC.call(this.endPoint, \"" + method.Name + "\"), [" + strings.Join(callArgs, ", ") + "], success, err);")
ts.ind(-1)
ts.app("}")
ts.nl()
}
ts.ind(-1)
ts.l("}")
return nil
}
func RenderTypeScript(services []*Service, structs map[string]*Struct, tsModuleName string) (typeScript string, err error) {
ts := newCode()
ts.l(`module GoTSRPC {
export function call(endPoint:string, method:string, args:any[], success:any, err:any) {
var request = new XMLHttpRequest();
request.open('POST', endPoint + "/" + encodeURIComponent(method), true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send(JSON.stringify(args));
request.onload = function() {
if (request.status == 200) {
var data = JSON.parse(request.responseText);
success.apply(null, data);
} else {
err(request)
}
};
request.onerror = function() {
err(request);
};
}
}`)
ts.l("module " + tsModuleName + " {")
ts.ind(1)
for _, str := range structs {
err = renderStruct(str, ts)
if err != nil {
return
}
}
for _, service := range services {
err = renderService(service, ts)
if err != nil {
return
}
}
ts.ind(-1)
ts.l("}")
typeScript = ts.string()
return
}