Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
# [bel](https://en.wikipedia.org/wiki/Bel_(mythology))
Generate TypeScript interfaces from Go structs/interfaces - useful for JSON RPC

[![Go Report Card](https://goreportcard.com/badge/github.com/32leaves/bel)](https://goreportcard.com/report/github.com/32leaves/bel)
[![GoDoc](https://godoc.org/github.com/32leaves/bel?status.svg)](https://godoc.org/github.com/32leaves/bel)
[![gocover.run](https://gocover.run/github.com/32leaves/bel.svg?style=flat&tag=1.10)](https://gocover.run?tag=1.10&repo=github.com%2F32leaves%2Fbel)
[![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)
**This is a fork of https://github.com/csweichel/bel. Thanks go to him for most of the work.**

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io#github.com/32leaves/bel)

`bel` is used in production in https://gitpod.io.
`bel` is used in production in https://conclude.io.

## Getting started
`bel` is easy to use. There are two steps involved: extract the Typescript information, and generate the Typescript code.
```Go
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

type Demo struct {
Expand Down Expand Up @@ -61,7 +56,7 @@ package main

import (
"os"
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

type DemoService interface {
Expand Down Expand Up @@ -90,7 +85,7 @@ export interface DemoService {
```

## Advanced Usage
You can try all the examples mentioned below in [Gitpod](https://gitpod.io#github.com/32leaves/bel).
You can try all the examples mentioned below in [Gitpod](https://gitpod.io#github.com/laknoll/bel).

### FollowStructs
Follow structs enable the transitive generation of types. See [examples/embed-structs.go](examples/follow-structs.go).
Expand Down Expand Up @@ -137,4 +132,4 @@ You can configure the `io.Writer` that _bel_ uses using `bel.GenerateOutputTo`.
# Contributing
All contributions/PR/issue/beer are welcome ❤️.

It's easiest to work with _bel_ using Gitpod: [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io#github.com/32leaves/bel)
It's easiest to work with _bel_ using Gitpod: [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io#github.com/laknoll/bel)
4 changes: 3 additions & 1 deletion doc_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bel

import (
"sort"
"testing"

"github.com/go-test/deep"
Expand All @@ -19,7 +20,7 @@ type DoSomethingReq struct {
}

func TestParsedSourceDocHandler(t *testing.T) {
handler, err := NewParsedSourceDocHandler(".", "github.com/32leaves/")
handler, err := NewParsedSourceDocHandler(".", "github.com/laknoll/")
if err != nil {
t.Error(err)
return
Expand All @@ -30,6 +31,7 @@ func TestParsedSourceDocHandler(t *testing.T) {
t.Error(err)
return
}
sort.Slice(extract, func(ia, ib int) bool { return extract[ia].Name < extract[ib].Name })

expectation := []TypescriptType{
{
Expand Down
83 changes: 79 additions & 4 deletions enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type StructWithEnum struct {
Baz string
}

type StructWithEnumMap struct {
EnumMap map[MyEnum]string
}

func TestParseStringEnum(t *testing.T) {
handler, err := NewParsedSourceEnumHandler(".")
if err != nil {
Expand Down Expand Up @@ -171,8 +175,9 @@ func TestExtractIntEnum(t *testing.T) {
TypedElement: TypedElement{
Name: "Bar",
Type: TypescriptType{
Name: "MyOtherEnum",
Kind: TypescriptKind("simple"),
Name: "MyOtherEnum",
Kind: TypescriptKind("simple"),
IsEnum: true,
},
},
},
Expand All @@ -189,14 +194,84 @@ func TestExtractIntEnum(t *testing.T) {
TypedElement: TypedElement{
Name: "Foo",
Type: TypescriptType{
Name: "MyEnum",
Kind: TypescriptKind("simple"),
Name: "MyEnum",
Kind: TypescriptKind("simple"),
IsEnum: true,
},
},
},
},
},
}
diff := deep.Equal(expectation, extract)
for _, d := range diff {
t.Error(d)
}
}

func TestEnumInMap(t *testing.T) {
handler, err := NewParsedSourceEnumHandler(".")
if err != nil {
t.Error(err)
return
}

extract, err := Extract(StructWithEnumMap{}, WithEnumerations(handler))
if err != nil {
t.Error(err)
return
}
sort.Slice(extract, func(ia, ib int) bool { return extract[ia].Name < extract[ib].Name })
for i := range extract {
sort.Slice(extract[i].Members, func(ia, ib int) bool { return extract[i].Members[ia].Name < extract[i].Members[ib].Name })
sort.Slice(extract[i].EnumMembers, func(ia, ib int) bool { return extract[i].EnumMembers[ia].Value < extract[i].EnumMembers[ib].Value })
}

expectation := []TypescriptType{
{
Name: "MyEnum",
Kind: TypescriptKind("enum"),
EnumMembers: []TypescriptEnumMember{
{
Name: "MemberOne",
Value: "\"member-one\"",
},
{
Name: "MemberThree",
Value: "\"member-three\"",
},
{
Name: "MemberTwo",
Value: "\"member-two\"",
},
},
},
{
Name: "StructWithEnumMap",
Kind: TypescriptKind("iface"),
Members: []TypescriptMember{
{
TypedElement: TypedElement{
Name: "EnumMap",
Type: TypescriptType{
Kind: TypescriptKind("map"),
Params: []TypescriptType{
{
Name: "MyEnum",
Kind: TypescriptKind("simple"),
IsEnum: true,
}, {
Name: "string",
Kind: "simple",
},
},
},
},
},
},
},
}

diff := deep.Equal(expectation, extract)
for _, d := range diff {
t.Error(d)
Expand Down
2 changes: 1 addition & 1 deletion examples/code-generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"os"

"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// HelloWorld is a simple example struct
Expand Down
2 changes: 1 addition & 1 deletion examples/custom-namer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"reflect"

"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// ThisStructsName is an empty struct
Expand Down
2 changes: 1 addition & 1 deletion examples/embed-structs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// ReferencedStruct is a struct referenced by HasAReference
Expand Down
2 changes: 1 addition & 1 deletion examples/enums.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// StringEnum is an enumeration with string values
Expand Down
2 changes: 1 addition & 1 deletion examples/follow-structs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// User is a struct describing users
Expand Down
2 changes: 1 addition & 1 deletion examples/name-anon-structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"reflect"

"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// NestedStuff contains a nested anonymous enum
Expand Down
2 changes: 1 addition & 1 deletion examples/sort-alphabetically.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// StructB is a structure with non-alphabetically sorted fields
Expand Down
4 changes: 2 additions & 2 deletions examples/with-documentation.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package main

import (
"github.com/32leaves/bel"
"github.com/laknoll/bel"
)

// WithDocumentation demonstrates how to extract documentation
func WithDocumentation() {
handler, err := bel.NewParsedSourceDocHandler(".", "github.com/32leaves")
handler, err := bel.NewParsedSourceDocHandler(".", "github.com/laknoll")
if err != nil {
panic(err)
}
Expand Down
19 changes: 18 additions & 1 deletion extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type extractor struct {
sorter func(a, b interface{}) bool
anonStructNamer AnonStructNamer
typeNamer TypeNamer
primitiveNamer TypeNamer
enumHandler EnumHandler
docHandler DocHandler

Expand Down Expand Up @@ -66,6 +67,16 @@ func CustomNamer(namer TypeNamer) ExtractOption {
}
}

// PrimitiveNamer sets a custom function for translating Golang naming convention
// to Typescript naming convention for primitive types. This function does not have to translate
// the type names, just the way they are written.
// Returning an empty string will cause bel to use the underlying primitive type
func PrimitiveNamer(namer TypeNamer) ExtractOption {
return func(e *extractor) {
e.primitiveNamer = namer
}
}

// WithEnumerations configures an enum handler which detects and extracts enums from
// types and constants.
func WithEnumerations(handler EnumHandler) ExtractOption {
Expand Down Expand Up @@ -353,7 +364,7 @@ func (e *extractor) getType(ttype reflect.Type, t *reflect.StructField) (*Typesc
EnumMembers: em,
}
e.addResult(enum)
tstype = &TypescriptType{Name: e.typeNamer(ttype), Kind: TypescriptSimpleKind}
tstype = &TypescriptType{Name: e.typeNamer(ttype), Kind: TypescriptSimpleKind, IsEnum: true}
} else {
res, err := e.getPrimitiveType(ttype)
if err != nil {
Expand All @@ -373,6 +384,12 @@ func (e *extractor) getPrimitiveType(t reflect.Type) (*TypescriptType, error) {
}
}

if e.primitiveNamer != nil {
if name := e.primitiveNamer(t); name != "" {
return mktype(name), nil
}
}

kind := t.Kind()
switch kind {
case reflect.Bool:
Expand Down
21 changes: 18 additions & 3 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ const interfaceTemplate = `
{
{{ range .Members -}}
{{- template "comment" . -}}
{{ .Name }}{{ if .IsOptional }}?{{ end }}{{ if .IsFunction }}({{ template "args" . }}){{ end }}: {{ subt .Type | default "void" }}
{{ validTypeScriptName .Name }}{{ if .IsOptional }}?{{ end }}{{ if .IsFunction }}({{ template "args" . }}){{ end }}: {{ subt .Type | default "void" }}
{{ end }}
}
{{ end -}}
{{- define "args" }}{{ range $idx, $val := .Args }}{{ if eq $idx 0 }}{{ else }}, {{ end }}{{ .Name }}: {{ subt .Type }}{{ end }}{{ end -}}
{{- define "simple" }}{{ .Name }}{{ end -}}
{{- define "map" }}{ [key: {{ subt (mapKeyType .) }}]: {{ subt (mapValType .) }} }{{ end -}}
{{- define "map" }}{ [{{ if ( isEnumKey . ) }}key in{{ else }}key:{{ end }} {{ subt (mapKeyType .) }}]: {{ subt (mapValType .) }} }{{ end -}}
{{- define "array" }}{{ subt (arrType .) }}[]{{ end -}}
{{- define "root-enum" }}{{- template "comment" . -}}export enum {{ .Name }} {
{{ range .EnumMembers }}{{ .Name }} = {{ .Value }},
Expand Down Expand Up @@ -96,7 +96,7 @@ func GeneratePreamble(preamble string) GenerateOption {
func Render(types []TypescriptType, cfg ...GenerateOption) error {
opts := generateOptions{
out: os.Stdout,
Preamble: fmt.Sprintf("// generated using github.com/32leaves/bel on %s\n// DO NOT MODIFY\n", time.Now()),
Preamble: fmt.Sprintf("// generated using github.com/laknoll/bel on %s\n// DO NOT MODIFY\n", time.Now()),
}
for _, c := range cfg {
c(&opts)
Expand Down Expand Up @@ -132,7 +132,16 @@ func Render(types []TypescriptType, cfg ...GenerateOption) error {
}
}

isEnumKey := func(t TypescriptType) bool {
if len(t.Params) < 2 {
return false
}
keyType := t.Params[0]
return keyType.IsEnum
}

funcs := template.FuncMap{
"isEnumKey": isEnumKey,
"mapKeyType": getParam("map", 0, 2),
"mapValType": getParam("map", 1, 2),
"arrType": getParam("array", 0, 1),
Expand All @@ -144,6 +153,12 @@ func Render(types []TypescriptType, cfg ...GenerateOption) error {

return "root-" + string(t.Kind)
}),
"validTypeScriptName": func(name string) string {
if strings.Contains(name, "-") {
name = "\"" + name + "\""
}
return name
},
"default": func(def, val string) string {
if val == "" {
return def
Expand Down
28 changes: 28 additions & 0 deletions generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,31 @@ func TestGenerateStuff(t *testing.T) {
return
}
}

type TestStruct struct {
WithMinus int `json:"with-minus"`
NormalField string
}

func TestGenerateValidTypeScriptNames(t *testing.T) {
handler, err := NewParsedSourceEnumHandler(".")
if err != nil {
t.Error(err)
return
}

extract, err := Extract(TestStruct{},
WithEnumerations(handler),
FollowStructs,
)
if err != nil {
t.Error(err)
return
}

err = Render(extract)
if err != nil {
t.Error(err)
return
}
}
Loading