diff --git a/.gitignore b/.gitignore index d467825..ac9b12a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -*.txt -*.pprof -cmap2/ -cache/ -debug +/*.txt +/*.pprof +/cmap2/ +/cache/ +/debug +/testdata/testmodel1/s2ts_gen_*.go /helpers.js diff --git a/README.md b/README.md index 164b088..3f8cc48 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,18 @@ Args: ``` +## Advanced + +### Custom output per model + +```golang +type CustomTypescript interface { + RenderCustomTypescript(w io.Writer) (err error) +} +``` +If your model implements a ```RenderCustomTypescript(w io.Writer) (err error)``` function it will inject what ever you +write to the writer at the end of the model. struct2ts will handle the first level of indenting for you. + ## TODO * Use [xast](https://github.com/OneOfOne/xast) to skip reflection. diff --git a/go.sum b/go.sum index 3d9fc8e..c7f906a 100644 --- a/go.sum +++ b/go.sum @@ -2,9 +2,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/tools v0.0.0-20190213135902-6bedcd10978a h1:ncPOGSo3avrTTUKHvDmwoS5E5of95qqNwftSXoxX+Wk= golang.org/x/tools v0.0.0-20190213135902-6bedcd10978a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/struct.go b/struct.go index e93114e..3ba1922 100644 --- a/struct.go +++ b/struct.go @@ -1,6 +1,7 @@ package struct2ts import ( + "errors" "fmt" "io" "reflect" @@ -57,10 +58,52 @@ func (s *Struct) RenderTo(opts *Options, w io.Writer) (err error) { return } + if err = s.RenderCustom(opts, w); err != nil { + return + } + _, err = fmt.Fprint(w, "}") return } +type CustomTypescript interface { + RenderCustomTypescript(w io.Writer) (err error) +} + +func (s *Struct) RenderCustom(opts *Options, w io.Writer) (err error) { + ww := newTabScanner(w, opts.indents[1]) + ctit := reflect.TypeOf((*CustomTypescript)(nil)).Elem() + var implementingType reflect.Type = nil + if s.t.Implements(ctit) { + implementingType = ctit + } + if reflect.PtrTo(s.t).Implements(ctit) { + implementingType = reflect.PtrTo(s.t) + } + if implementingType != nil { + m, ok := implementingType.MethodByName("RenderCustomTypescript") + if !ok { + return errors.New("couldn't get method RenderCustomTypescript") + } + _, err = fmt.Fprintf(ww, "\n") + o := reflect.New(s.t) + if implementingType.Kind() != reflect.Ptr { + o = o.Elem() + } + wv := reflect.ValueOf(ww) + r := m.Func.Call([]reflect.Value{o, wv}) + if len(r) > 0 && !r[0].IsNil() { + switch r0t := r[0].Interface().(type) { + case error: + return r0t + } + } + _, err = fmt.Fprintf(w, "\n") + } + + return +} + func (s *Struct) RenderFields(opts *Options, w io.Writer) (err error) { for _, f := range s.Fields { if err = f.RenderTopLevel(w, opts); err != nil { diff --git a/tabscanner.go b/tabscanner.go new file mode 100644 index 0000000..8affaea --- /dev/null +++ b/tabscanner.go @@ -0,0 +1,37 @@ +package struct2ts + +import "io" + +type tabScanner struct { + output io.Writer + tabs []byte +} + +func (ts *tabScanner) Write(bs []byte) (n int, err error) { + p := 0 + var rn int + for pos, b := range bs { + switch b { + case '\n': + rn, err = ts.output.Write(bs[p : pos+1]) + n += rn + if err != nil { + return + } + _, err = ts.output.Write(ts.tabs) + if err != nil { + return + } + p = pos + 1 + } + } + rn, err = ts.output.Write(bs[p:]) + return n + rn, err +} + +func newTabScanner(w io.Writer, tabs string) io.Writer { + return &tabScanner{ + output: w, + tabs: []byte(tabs), + } +} diff --git a/tabscanner_test.go b/tabscanner_test.go new file mode 100644 index 0000000..f838e7e --- /dev/null +++ b/tabscanner_test.go @@ -0,0 +1,95 @@ +package struct2ts + +import ( + "bytes" + "testing" +) + +func TestTabScanner_Write(t *testing.T) { + tests := []struct { + Name string + Input struct { + Tabs string + Buffers []string + } + Expected struct { + Output string + n *int + } + }{ + { + "One new line mid string - one input, 3 spaces", + struct { + Tabs string + Buffers []string + }{Tabs: " ", Buffers: []string{"Hello how are you?\nI'm fine thanks!"}}, + struct { + Output string + n *int + }{Output: "Hello how are you?\n I'm fine thanks!"}, + }, + { + "One new line end string - one input, 3 spaces", + struct { + Tabs string + Buffers []string + }{Tabs: " ", Buffers: []string{"Hello how are you?\n"}}, + struct { + Output string + n *int + }{Output: "Hello how are you?\n "}, + }, + { + "One new line start string - one input, 3 spaces", + struct { + Tabs string + Buffers []string + }{Tabs: " ", Buffers: []string{"\nHello how are you?"}}, + struct { + Output string + n *int + }{Output: "\n Hello how are you?"}, + }, + { + "empty string", + struct { + Tabs string + Buffers []string + }{Tabs: " ", Buffers: []string{""}}, + struct { + Output string + n *int + }{Output: ""}, + }, + { + "Just a new line", + struct { + Tabs string + Buffers []string + }{Tabs: " ", Buffers: []string{"\n"}}, + struct { + Output string + n *int + }{Output: "\n "}, + }, + } + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + outputBuffer := bytes.NewBuffer(nil) + ts := newTabScanner(outputBuffer, test.Input.Tabs) + for _, s := range test.Input.Buffers { + _, err := ts.Write([]byte(s)) + if err != nil { + t.Error("Buffer write error", err) + } + } + if outputBuffer.String() != test.Expected.Output { + t.Log("Output buffer doesn't match. Got ") + t.Log(outputBuffer.String()) + t.Log("expected") + t.Log(test.Expected.Output) + t.Fail() + } + }) + } +} diff --git a/testdata/testmodel1/testmodel1.go b/testdata/testmodel1/testmodel1.go new file mode 100644 index 0000000..d35e4a9 --- /dev/null +++ b/testdata/testmodel1/testmodel1.go @@ -0,0 +1,28 @@ +package testmodel1 + +import ( + "fmt" + "io" +) + +type Struct1 struct { + HasNoCustomInterface int +} + +type Struct2 struct { + TotallyDoesHaveOneAsAPointer int +} + +func (s *Struct2) RenderCustomTypescript(w io.Writer) (err error) { + fmt.Fprint(w, "// Custom Output!!!") + return nil +} + +type Struct3 struct { + TotallyDoesHaveOne int +} + +func (s Struct3) RenderCustomTypescript(w io.Writer) (err error) { + fmt.Fprint(w, "// Custom Output!!!") + return nil +} diff --git a/testdata/testmodel1/testmodel1.sh b/testdata/testmodel1/testmodel1.sh new file mode 100644 index 0000000..a221ec1 --- /dev/null +++ b/testdata/testmodel1/testmodel1.sh @@ -0,0 +1,5 @@ +#!/bin/bash -Xe + +go run github.com/OneOfOne/struct2ts/cmd/struct2ts github.com/OneOfOne/struct2ts/testdata/testmodel1.Struct1 +go run github.com/OneOfOne/struct2ts/cmd/struct2ts github.com/OneOfOne/struct2ts/testdata/testmodel1.Struct2 +go run github.com/OneOfOne/struct2ts/cmd/struct2ts github.com/OneOfOne/struct2ts/testdata/testmodel1.Struct3