├── goon.go ├── go.capnp ├── .gitignore ├── exists.go ├── under.go ├── pp.go ├── verbose.go ├── keyw.go ├── gettype.go ├── skip_test.go ├── diff_test.go ├── Makefile ├── under_test.go ├── order_test.go ├── compile_test.go ├── capidtag_test.go ├── diff.go ├── rw2.go.txt ├── singleslice_test.go ├── listlist_test.go ├── rw2_test.go ├── rw3_test.go ├── LICENSE ├── rw_test.go ├── ignorespaces_test.go ├── sort.go ├── go2cpType_test.go ├── tdir.go ├── cpfile_test.go ├── rw.go.txt ├── types_test.go ├── rw3.go.txt ├── list_test.go ├── doubleslice_test.go ├── struct1_test.go ├── marshal_test.go ├── annot_test.go ├── nested_test.go ├── allprim_test.go ├── utilities_for_test.go ├── cpfile.go ├── save_test.go ├── main.go ├── ptr_test.go ├── ignorespaces.go ├── README.md ├── slice_test.go └── bam.go /goon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //import goon "github.com/glycerine/go-goon" 4 | import goon "github.com/shurcooL/go-goon" 5 | 6 | // require that we download the go-goon package above, which is used by the tests 7 | var _ = goon.Dump 8 | -------------------------------------------------------------------------------- /go.capnp: -------------------------------------------------------------------------------- 1 | @0xd12a1c51fedd6c88; 2 | annotation package(file) :Text; 3 | annotation import(file) :Text; 4 | annotation doc(struct, field, enum) :Text; 5 | annotation tag(enumerant) : Text; 6 | annotation notag(enumerant) : Void; 7 | annotation customtype(field) : Text; 8 | $package("capn"); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | *~ 27 | bambam 28 | gitcommit.go 29 | -------------------------------------------------------------------------------- /exists.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func FileExists(name string) bool { 8 | fi, err := os.Stat(name) 9 | if err != nil { 10 | return false 11 | } 12 | if fi.IsDir() { 13 | return false 14 | } 15 | return true 16 | } 17 | 18 | func DirExists(name string) bool { 19 | fi, err := os.Stat(name) 20 | if err != nil { 21 | return false 22 | } 23 | if fi.IsDir() { 24 | return true 25 | } 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /under.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "unicode" 4 | 5 | func underToCamelCase(s string) string { 6 | ru := []rune(s) 7 | n := len(ru) 8 | last := n - 1 9 | for i := 0; i < n; i++ { 10 | if ru[i] == '_' && i < last { 11 | if unicode.IsLower(ru[i+1]) { 12 | ru[i+1] = unicode.ToUpper(ru[i+1]) 13 | } 14 | copy(ru[i:], ru[i+1:]) 15 | ru = ru[:last] 16 | 17 | last-- 18 | n-- 19 | i-- 20 | } 21 | } 22 | return string(ru) 23 | } 24 | -------------------------------------------------------------------------------- /pp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/ast" 5 | "go/printer" 6 | "go/token" 7 | "os" 8 | ) 9 | 10 | // PrettyPrint out the go source file we read in. 11 | func (x *Extractor) PrettyPrint(fileSet *token.FileSet, astfile *ast.File, fn string) error { 12 | f, err := os.Create(fn) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | printConfig := &printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 4} 18 | 19 | err = printConfig.Fprint(f, fileSet, astfile) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /verbose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // for debug output 9 | var Verbose bool 10 | 11 | // get timestamp for logging purposes 12 | func ts() string { 13 | return time.Now().Format("2006-01-02 15:04:05.999 -0700 MST") 14 | } 15 | 16 | // time-stamped printf 17 | func TSPrintf(format string, a ...interface{}) { 18 | fmt.Printf("%s ", ts()) 19 | fmt.Printf(format, a...) 20 | } 21 | 22 | // print debug/status conditionally on having Verbose on 23 | func VPrintf(format string, a ...interface{}) { 24 | if Verbose { 25 | TSPrintf(format, a...) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /keyw.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var capnpKeywords map[string]bool = map[string]bool{ 4 | "Void": true, "Bool": true, "Int8": true, "Int16": true, "Int32": true, "Int64": true, "UInt8": true, "UInt16": true, "UInt32": true, "UInt64": true, "Float32": true, "Float64": true, "Text": true, "Data": true, "List": true, "struct": true, "union": true, "group": true, "enum": true, "AnyPointer": true, "interface": true, "extends": true, "const": true, "using": true, "import": true, "annotation": true} 5 | 6 | func isCapnpKeyword(w string) bool { 7 | return capnpKeywords[w] // not found will return false, the zero value for bool. 8 | } 9 | -------------------------------------------------------------------------------- /gettype.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "go/ast" 4 | 5 | // recursively extract the go type as a string 6 | func GetTypeAsString(ty ast.Expr, sofar string, goTypeSeq []string) (string, string, []string) { 7 | switch ty.(type) { 8 | 9 | case (*ast.StarExpr): 10 | return GetTypeAsString(ty.(*ast.StarExpr).X, sofar+"*", append(goTypeSeq, "*")) 11 | 12 | case (*ast.Ident): 13 | return sofar, ty.(*ast.Ident).Name, append(goTypeSeq, ty.(*ast.Ident).Name) 14 | 15 | case (*ast.ArrayType): 16 | // slice or array 17 | return GetTypeAsString(ty.(*ast.ArrayType).Elt, sofar+"[]", append(goTypeSeq, "[]")) 18 | } 19 | 20 | return sofar, "", goTypeSeq 21 | } 22 | -------------------------------------------------------------------------------- /skip_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestCapidSkip(t *testing.T) { 10 | 11 | cv.Convey("Given go: type Hi struct { A int `capid:\"skip\"`; B int } ", t, func() { 12 | cv.Convey("then bambam should skip the A field and any negative numbered capid tags", func() { 13 | 14 | in1 := " type Hi struct { A int `capid:\"skip\"`; B int; C int `capid:\"-1\"`; D int `capid:\"-2\"` } " 15 | 16 | expect1 := ` 17 | struct HiCapn { b @0: Int64; } 18 | ` 19 | 20 | cv.So(ExtractString2String(in1), ShouldStartWithModuloWhiteSpace, expect1) 21 | }) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /diff_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestDiffB(t *testing.T) { 10 | 11 | cv.Convey("Given two different (possibly long, differing in whitespace) strings a and b", t, func() { 12 | cv.Convey("then DiffB(a, b) should return the output of diff -b to consisely summarize their differences", func() { 13 | 14 | a := ` 15 | 1 16 | 2 17 | 3 18 | 4 19 | 5 20 | type s1 struct { 21 | MyInts []int 22 | }` 23 | b := ` 24 | 1 25 | 2 26 | 3 27 | 4 28 | type s1 struct { 29 | MyInts []int 30 | }` 31 | cv.So(string(Diffb(a, b)), cv.ShouldEqual, `6d5 32 | < 5 33 | `) 34 | 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | # version gets its data here: 4 | rm -f gitcommit.go 5 | /bin/echo "package main" > gitcommit.go 6 | /bin/echo "var LASTGITCOMMITHASH string" >> gitcommit.go 7 | /bin/echo "func init() { LASTGITCOMMITHASH = \"$(shell git rev-parse HEAD)\" }" >> gitcommit.go 8 | go build 9 | go install 10 | 11 | full: 12 | rm -f translateCapn.go schema.capnp.go && go test -v && go build && go install 13 | 14 | test: 15 | ./bambam testpkg/t.go 16 | capnp compile -ogo schema.capnp 17 | mv schema.capnp* testpkg 18 | perl -pi -e 's/main/testpkg/' translateCapn.go 19 | mv translateCapn.go testpkg/ 20 | cd testpkg; go build 21 | 22 | clean: 23 | rm -rf testdir_* ; rm -f *~; rm -rf diffdir_* 24 | 25 | testbuild: 26 | go test -c -gcflags "-N -l" -v 27 | -------------------------------------------------------------------------------- /under_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestUnderScoreFieldNamesRenamed(t *testing.T) { 10 | 11 | cv.Convey("underToCamelCase should remove underscores and leave camelCase", t, func() { 12 | cv.So(underToCamelCase(`one_two`), cv.ShouldEqual, `oneTwo`) 13 | cv.So(underToCamelCase(`one__two`), cv.ShouldEqual, `oneTwo`) 14 | }) 15 | 16 | cv.Convey("Given a struct that contains fields with underscores", t, func() { 17 | cv.Convey("then these field names should be camel cased", func() { 18 | 19 | ex0 := ` 20 | type s1 struct { 21 | Hello_world int 22 | }` 23 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct S1Capn { helloWorld @0: Int64; } `) 24 | }) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /order_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestOrderOfDeclIrrel(t *testing.T) { 10 | 11 | cv.Convey("Given a golang file with a struct In1 that refers to struct In2 that is defined *later* in the file", t, func() { 12 | cv.Convey("then the first capnp definition for In1Capn consistently refer to the In2Capn defined later", func() { 13 | 14 | ex0 := ` 15 | type In1 struct { 16 | In2 In2 17 | } 18 | type In2 struct {} 19 | ` 20 | expect0 := `struct In1Capn { in2 @0: In2Capn; } struct In2Capn {}` 21 | act0 := ExtractString2String(ex0) 22 | //fmt.Printf("\n\n act0 = %#v\n expected = %#v\n", act0, expect0) 23 | 24 | cv.So(act0, 25 | ShouldStartWithModuloWhiteSpace, expect0) 26 | }) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compile_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestCapnpWillCompileOurOutput(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file with a simple struct", t, func() { 12 | cv.Convey("then when we generate capnp code, it should compile", func() { 13 | 14 | in1 := ` 15 | type in1 struct { 16 | Str string 17 | N int 18 | D float64 19 | }` 20 | 21 | s1, err := ExtractFromString(in1) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | //expect1 := `struct In1 { str @0: Text; n @1: Int64; d @2: Float64; } ` 27 | //cv.So(string(s1), cv.ShouldEqual, expect1) 28 | 29 | // no news on compile is good news 30 | _, err, x := CapnpCompileFragment(s1) 31 | cv.So(err, cv.ShouldEqual, nil) 32 | 33 | x.Cleanup() 34 | }) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /capidtag_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestCapidTagOrderAssignment(t *testing.T) { 10 | 11 | cv.Convey("Given capid tag requests for field numbering", t, func() { 12 | cv.Convey("then when assigning field numbers in the capnproto struct schema, assign capids first, then proceed in order of field appearance", func() { 13 | 14 | ex0 := "type Z struct { A int `capid:\"2\"`; B int `capid:\"0\"`; C int `capid:\"1\"` }" 15 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct ZCapn { b @0: Int64; c @1: Int64; a @2: Int64; } `) 16 | 17 | ex1 := "type Z struct { A int `capid:\"1\"`; B int; C int `capid:\"0\"` }" 18 | cv.So(ExtractString2String(ex1), ShouldStartWithModuloWhiteSpace, `struct ZCapn { c @0: Int64; a @1: Int64; b @2: Int64; } `) 19 | 20 | }) 21 | 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /diff.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func Diffb(a string, b string) []byte { 11 | 12 | dirpath := NewSimpleTempDir("diffdir_") 13 | defer os.RemoveAll(dirpath) 14 | 15 | fa := SimpleTempFile(dirpath) 16 | fmt.Fprintf(fa, "%s\n", a) 17 | fa.Close() 18 | 19 | fb := SimpleTempFile(dirpath) 20 | fmt.Fprintf(fb, "%s\n", b) 21 | fb.Close() 22 | 23 | co, err := exec.Command("diff", "-b", fa.Name(), fb.Name()).CombinedOutput() 24 | if err != nil { 25 | // don't panic, diff returns 2 on differences 26 | } 27 | return co 28 | } 29 | 30 | func NewSimpleTempDir(prefix string) string { 31 | dirpath, err := ioutil.TempDir(".", prefix) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return dirpath 36 | } 37 | 38 | func SimpleTempFile(dirpath string) *os.File { 39 | 40 | f, err := ioutil.TempFile(dirpath, "diff_file_") 41 | if err != nil { 42 | panic(err) 43 | } 44 | return f 45 | } 46 | -------------------------------------------------------------------------------- /rw2.go.txt: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "bytes" 8 | "github.com/glycerine/go-goon" 9 | ) 10 | 11 | // used in rw2_test.go for round-trip testing write/read 12 | 13 | type Dude struct { 14 | Name string 15 | Age int 16 | Addr Address 17 | } 18 | 19 | type Address struct { 20 | Street string 21 | Zip string 22 | } 23 | 24 | func main() { 25 | 26 | rw := Dude{ 27 | Name: "Hank", 28 | Age: 33, 29 | Addr: Address{Street: "123 Main Street", Zip: "11111"}, 30 | } 31 | 32 | var o bytes.Buffer 33 | rw.Save(&o) 34 | 35 | rw2 := &Dude{} 36 | rw2.Load(&o) 37 | 38 | if !reflect.DeepEqual(&rw, rw2) { 39 | fmt.Printf("rw and rw2 were not equal!\n") 40 | 41 | fmt.Printf("\n\n ============= rw: ====\n") 42 | goon.Dump(rw) 43 | fmt.Printf("\n\n ============= rw2: ====\n") 44 | goon.Dump(rw2) 45 | fmt.Printf("\n\n ================\n") 46 | 47 | os.Exit(1) 48 | } 49 | 50 | fmt.Printf("Load() data matched Saved() data.\n") 51 | } 52 | -------------------------------------------------------------------------------- /singleslice_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test004SingleSlice(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file with type Vector struct { M []int }", t, func() { 12 | cv.Convey("then we should generate translation utility methods for List(Int64) <-> []int in the capnp output", func() { 13 | 14 | ex0 := ` 15 | type Vector struct { 16 | M []int 17 | }` 18 | cv.So(ExtractString2String(ex0), ShouldContainModuloWhiteSpace, ` 19 | 20 | func Int64ListToSliceInt(p capn.Int64List) []int { 21 | v := make([]int, p.Len()) 22 | for i := range v { 23 | v[i] = int(p.At(i)) 24 | } 25 | return v 26 | } 27 | `) 28 | 29 | cv.So(ExtractString2String(ex0), ShouldContainModuloWhiteSpace, ` 30 | func SliceIntToInt64List(seg *capn.Segment, m []int) capn.Int64List { 31 | lst := seg.NewInt64List(len(m)) 32 | for i := range m { 33 | lst.Set(i, int64(m[i])) 34 | } 35 | return lst 36 | } 37 | 38 | `) 39 | }) 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /listlist_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test010ListListStruct(t *testing.T) { 10 | 11 | cv.Convey("Given a go [][]struct that maps to List(List(Struct)) in capnproto", t, func() { 12 | cv.Convey("then out list of list of struct serialization code should work", func() { 13 | 14 | ex0 := ` 15 | type Nester1 struct { 16 | Strs []string 17 | } 18 | type RWTest struct { 19 | NestMatrix [][]Nester1 20 | } 21 | ` 22 | cv.So(ExtractString2String(ex0), ShouldContainModuloWhiteSpace, ` 23 | 24 | func RWTestGoToCapn(seg *capn.Segment, src *RWTest) RWTestCapn { 25 | dest := AutoNewRWTestCapn(seg) 26 | 27 | // NestMatrix -> Nester1Capn (go slice to capn list) 28 | if len(src.NestMatrix) > 0 { 29 | plist := seg.NewPointerList(len(src.NestMatrix)) 30 | i := 0 31 | for _, ele := range src.NestMatrix { 32 | plist.Set(i, capn.Object(SliceNester1ToNester1CapnList(seg, ele))) 33 | i++ 34 | } 35 | dest.SetNestMatrix(plist) 36 | } 37 | 38 | return dest 39 | } 40 | `) 41 | 42 | }) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /rw2_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "testing" 7 | 8 | cv "github.com/glycerine/goconvey/convey" 9 | ) 10 | 11 | func Test015WriteRead_StructWithinStruct(t *testing.T) { 12 | 13 | tdir := NewTempDir() 14 | // comment the defer out to debug any rw test failures. 15 | defer tdir.Cleanup() 16 | 17 | err := exec.Command("cp", "rw2.go.txt", tdir.DirPath+"/rw2.go").Run() 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | MainArgs([]string{os.Args[0], "-o", tdir.DirPath, "rw2.go.txt"}) 23 | 24 | cv.Convey("Given bambam generated go bindings: with a struct within a struct", t, func() { 25 | cv.Convey("then we should be able to write to disk, and read back the same structure", func() { 26 | cv.So(err, cv.ShouldEqual, nil) 27 | 28 | tdir.MoveTo() 29 | 30 | err = exec.Command("capnpc", "-ogo", "schema.capnp").Run() 31 | cv.So(err, cv.ShouldEqual, nil) 32 | 33 | err = exec.Command("go", "build").Run() 34 | cv.So(err, cv.ShouldEqual, nil) 35 | 36 | // run it 37 | err = exec.Command("./" + tdir.DirPath).Run() 38 | cv.So(err, cv.ShouldEqual, nil) 39 | 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /rw3_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "testing" 7 | 8 | cv "github.com/glycerine/goconvey/convey" 9 | ) 10 | 11 | func Test017WriteRead_StructPointerWithinStruct(t *testing.T) { 12 | 13 | tdir := NewTempDir() 14 | // comment the defer out to debug any rw test failures. 15 | defer tdir.Cleanup() 16 | 17 | err := exec.Command("cp", "rw3.go.txt", tdir.DirPath+"/rw3.go").Run() 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | MainArgs([]string{os.Args[0], "-o", tdir.DirPath, "rw3.go.txt"}) 23 | 24 | cv.Convey("Given bambam generated go bindings: with a struct pointer within a struct", t, func() { 25 | cv.Convey("then we should be able to write to disk, and read back the same structure", func() { 26 | cv.So(err, cv.ShouldEqual, nil) 27 | 28 | tdir.MoveTo() 29 | 30 | err = exec.Command("capnpc", "-ogo", "schema.capnp").Run() 31 | cv.So(err, cv.ShouldEqual, nil) 32 | 33 | err = exec.Command("go", "build").Run() 34 | cv.So(err, cv.ShouldEqual, nil) 35 | 36 | // run it 37 | err = exec.Command("./" + tdir.DirPath).Run() 38 | cv.So(err, cv.ShouldEqual, nil) 39 | 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jason E. Aten, Ph.D. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rw_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "testing" 8 | 9 | cv "github.com/glycerine/goconvey/convey" 10 | ) 11 | 12 | func Test003WriteReadThroughGeneratedTranslationCode(t *testing.T) { 13 | 14 | tdir := NewTempDir() 15 | // comment the defer out to debug any rw test failures. 16 | defer tdir.Cleanup() 17 | 18 | err := exec.Command("cp", "rw.go.txt", tdir.DirPath+"/rw.go").Run() 19 | if err != nil { 20 | fmt.Printf("cp rw.go.txt %s/rw.go failed: '%s'\n", tdir.DirPath, err) 21 | panic(err) 22 | } 23 | 24 | MainArgs([]string{os.Args[0], "-o", tdir.DirPath, "rw.go.txt"}) 25 | 26 | cv.Convey("Given bambam generated go bindings, \n"+ 27 | " then we should be able to write to disk, and read back the same structure", t, func() { 28 | 29 | cv.So(err, cv.ShouldEqual, nil) 30 | 31 | tdir.MoveTo() 32 | 33 | err = exec.Command("capnpc", "-ogo", "schema.capnp").Run() 34 | cv.So(err, cv.ShouldEqual, nil) 35 | 36 | err = exec.Command("go", "build").Run() 37 | cv.So(err, cv.ShouldEqual, nil) 38 | 39 | // run it 40 | err = exec.Command("./" + tdir.DirPath).Run() 41 | cv.So(err, cv.ShouldEqual, nil) 42 | 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /ignorespaces_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestShouldStartWithModuloWhiteSpace(t *testing.T) { 6 | pass(t, so("", ShouldStartWithModuloWhiteSpace, "")) 7 | pass(t, so("a", ShouldStartWithModuloWhiteSpace, "a")) 8 | pass(t, so(" a ", ShouldStartWithModuloWhiteSpace, "a")) 9 | pass(t, so(" a ", ShouldStartWithModuloWhiteSpace, "a")) 10 | pass(t, so("a", ShouldStartWithModuloWhiteSpace, " a ")) 11 | pass(t, so("a", ShouldStartWithModuloWhiteSpace, " a ")) 12 | pass(t, so("a b c de fgh ij", ShouldStartWithModuloWhiteSpace, " abcdefghi j ")) 13 | 14 | fail(t, so("j ", ShouldMatchModuloSpaces, "j k"), "Expected expected string 'j k' and actual string 'j ' to match (ignoring {' '}) (but they did not!; first diff at '', pos 2); and Full diff -b: 1c1 < j --- > j k ") 15 | 16 | fail(t, so("asdf", ShouldStartWithModuloWhiteSpace), "This assertion requires exactly 1 comparison values (you provided 0).") 17 | fail(t, so("asdf", ShouldStartWithModuloWhiteSpace, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 18 | 19 | fail(t, so(123, ShouldStartWithModuloWhiteSpace, 23), "Both arguments to this assertion must be strings (you provided int and int).") 20 | 21 | } 22 | -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ByFinalOrder []*Field 4 | 5 | func (s ByFinalOrder) Len() int { 6 | return len(s) 7 | } 8 | func (s ByFinalOrder) Swap(i, j int) { 9 | s[i], s[j] = s[j], s[i] 10 | } 11 | func (s ByFinalOrder) Less(i, j int) bool { 12 | return s[i].finalOrder < s[j].finalOrder 13 | } 14 | 15 | type ByOrderOfAppearance []*Field 16 | 17 | func (s ByOrderOfAppearance) Len() int { 18 | return len(s) 19 | } 20 | func (s ByOrderOfAppearance) Swap(i, j int) { 21 | s[i], s[j] = s[j], s[i] 22 | } 23 | func (s ByOrderOfAppearance) Less(i, j int) bool { 24 | return s[i].orderOfAppearance < s[j].orderOfAppearance 25 | } 26 | 27 | type ByGoName []*Struct 28 | 29 | func (s ByGoName) Len() int { 30 | return len(s) 31 | } 32 | func (s ByGoName) Swap(i, j int) { 33 | s[i], s[j] = s[j], s[i] 34 | } 35 | func (s ByGoName) Less(i, j int) bool { 36 | return s[i].goName < s[j].goName 37 | } 38 | 39 | type AlphaHelper struct { 40 | Name string 41 | Code []byte 42 | } 43 | 44 | type AlphaHelperSlice []AlphaHelper 45 | 46 | func (s AlphaHelperSlice) Len() int { 47 | return len(s) 48 | } 49 | func (s AlphaHelperSlice) Swap(i, j int) { 50 | s[i], s[j] = s[j], s[i] 51 | } 52 | func (s AlphaHelperSlice) Less(i, j int) bool { 53 | return s[i].Name < s[j].Name 54 | } 55 | -------------------------------------------------------------------------------- /go2cpType_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | cv "github.com/glycerine/goconvey/convey" 8 | ) 9 | 10 | func Test001GoToCapTypeConversion(t *testing.T) { 11 | 12 | cv.Convey("Given a go structs: type s1 struct { Ptrs []*int }", t, func() { 13 | cv.Convey(`then the goTypeSeq should be {"[]", "*", "int"} and the capTypeSeq should be {"List", "*", "Int64"} `, func() { 14 | 15 | src := ` 16 | type s1 struct { 17 | Ptrs []*int 18 | } 19 | ` 20 | 21 | x := NewExtractor() 22 | defer x.Cleanup() 23 | _, err := ExtractStructs("", "package main; "+src, x) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | x.GenerateTranslators() 29 | 30 | capTypeSeq := []string{} 31 | goTypeSeq := []string{"[]", "*", "int"} 32 | var capTypeDisplay string 33 | capTypeSeq, capTypeDisplay = x.GoTypeToCapnpType(nil, goTypeSeq) 34 | 35 | fmt.Printf("capTypeSeq = '%v', goTypeSeq = '%v', capTypeDispaly = '%v'\n", capTypeSeq, goTypeSeq, capTypeDisplay) 36 | 37 | cv.So(len(capTypeSeq), cv.ShouldEqual, 3) 38 | cv.So(capTypeSeq[0], cv.ShouldEqual, "List") 39 | cv.So(capTypeSeq[1], cv.ShouldEqual, "*") 40 | cv.So(capTypeSeq[2], cv.ShouldEqual, "Int64") 41 | 42 | cv.So(capTypeDisplay, cv.ShouldEqual, "List(Int64)") 43 | }) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tdir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | type TempDir struct { 10 | OrigDir string 11 | DirPath string 12 | Files map[string]*os.File 13 | } 14 | 15 | func NewTempDir() *TempDir { 16 | dirname, err := ioutil.TempDir(".", "testdir_") 17 | if err != nil { 18 | panic(err) 19 | } 20 | origdir, err := os.Getwd() 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | // add files needed for capnpc -ogo compilation 26 | exec.Command("/bin/cp", "go.capnp", dirname).Run() 27 | 28 | return &TempDir{ 29 | OrigDir: origdir, 30 | DirPath: dirname, 31 | Files: make(map[string]*os.File), 32 | } 33 | } 34 | 35 | func (d *TempDir) MoveTo() { 36 | err := os.Chdir(d.DirPath) 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | 42 | func (d *TempDir) Close() { 43 | for _, f := range d.Files { 44 | f.Close() 45 | } 46 | } 47 | 48 | func (d *TempDir) Cleanup() { 49 | d.Close() 50 | err := os.RemoveAll(d.DirPath) 51 | if err != nil { 52 | panic(err) 53 | } 54 | err = os.Chdir(d.OrigDir) 55 | if err != nil { 56 | panic(err) 57 | } 58 | } 59 | 60 | func (d *TempDir) TempFile() *os.File { 61 | 62 | f, err := ioutil.TempFile(d.DirPath, "testfile.") 63 | if err != nil { 64 | panic(err) 65 | } 66 | d.Files[f.Name()] = f 67 | return f 68 | } 69 | -------------------------------------------------------------------------------- /cpfile_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | 9 | cv "github.com/glycerine/goconvey/convey" 10 | ) 11 | 12 | func TestCpCopiesFilesIntoDirHier(t *testing.T) { 13 | 14 | cv.Convey("Cp() function should create directories in destinationPath if need be", t, func() { 15 | 16 | origdir, tmpdir := MakeAndMoveToTempDir() 17 | defer TempDirCleanup(origdir, tmpdir) 18 | hier := fmt.Sprintf("a%cb%cc%c", os.PathSeparator, os.PathSeparator, os.PathSeparator) 19 | goal := hier + "cpfile.go" 20 | err := Cp(origdir+string(os.PathSeparator)+"cpfile.go", tmpdir+string(os.PathSeparator)+hier) 21 | cv.So(err, cv.ShouldEqual, nil) 22 | cv.So(FileExists(goal), cv.ShouldEqual, true) 23 | }) 24 | } 25 | 26 | func MakeAndMoveToTempDir() (origdir string, tmpdir string) { 27 | 28 | var err error 29 | origdir, err = os.Getwd() 30 | if err != nil { 31 | panic(err) 32 | } 33 | tmpdir, err = ioutil.TempDir(origdir, "temptestdir") 34 | if err != nil { 35 | panic(err) 36 | } 37 | err = os.Chdir(tmpdir) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | return origdir, tmpdir 43 | } 44 | 45 | func TempDirCleanup(origdir string, tmpdir string) { 46 | // cleanup 47 | os.Chdir(origdir) 48 | err := os.RemoveAll(tmpdir) 49 | if err != nil { 50 | panic(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rw.go.txt: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "bytes" 8 | "github.com/glycerine/go-goon" 9 | ) 10 | 11 | // used in rw_test.go for round-trip testing write/read 12 | 13 | type Nester1 struct { 14 | Strs []string 15 | } 16 | 17 | type Nester2 struct { 18 | N1 []Nester1 19 | } 20 | 21 | type Ptr1 struct { 22 | Strs []string 23 | } 24 | 25 | type Ptr2 struct { 26 | P1 []*Ptr1 27 | } 28 | 29 | type RWTest struct { 30 | Hello []string 31 | World []int 32 | SixFour []int64 33 | Floaters []float64 34 | 35 | N2 []Nester2 36 | P2 []*Ptr2 37 | 38 | Matrix [][]int 39 | NestMatrix [][]Nester1 40 | } 41 | 42 | func main() { 43 | 44 | thing1 := "thing1" 45 | thing2 := "thing2" 46 | 47 | rw := RWTest{ 48 | Hello: []string{"one", "two", "three"}, 49 | World: []int{1, 2, 3}, 50 | SixFour: []int64{6, 5, 4}, 51 | Floaters: []float64{1.5, 4.5}, 52 | N2: []Nester2{Nester2{N1: []Nester1{Nester1{Strs: []string{"hey", "joe"}}}}}, 53 | P2: []*Ptr2{&Ptr2{P1: []*Ptr1{&Ptr1{Strs: []string{thing1, thing2}}}}}, 54 | Matrix: [][]int{[]int{1,2},[]int{3,4}}, 55 | NestMatrix: [][]Nester1{[]Nester1{Nester1{Strs:[]string{"z","w"}},Nester1{Strs:[]string{"q","r"}}},[]Nester1{Nester1{Strs:[]string{"zebra","wally"}},Nester1{Strs:[]string{"qubert","rocks"}}}}, 56 | } 57 | 58 | var o bytes.Buffer 59 | rw.Save(&o) 60 | 61 | rw2 := &RWTest{} 62 | rw2.Load(&o) 63 | 64 | if !reflect.DeepEqual(&rw, rw2) { 65 | fmt.Printf("rw and rw2 were not equal!\n") 66 | 67 | fmt.Printf("\n\n ============= rw: ====\n") 68 | goon.Dump(rw) 69 | fmt.Printf("\n\n ============= rw2: ====\n") 70 | goon.Dump(rw2) 71 | fmt.Printf("\n\n ================\n") 72 | 73 | os.Exit(1) 74 | } 75 | 76 | fmt.Printf("Load() data matched Saved() data.\n") 77 | } 78 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestBasicTypesInStruct(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file with a simple struct", t, func() { 12 | cv.Convey("then we can extract the struct and convert the basic types to capnp", func() { 13 | 14 | in1 := ` 15 | type In1 struct { 16 | Str string 17 | N int 18 | D float64 19 | }` 20 | expect1 := `struct In1Capn { str @0: Text; n @1: Int64; d @2: Float64; } ` 21 | // List(Float64) next 22 | 23 | cv.So(ExtractString2String(in1), ShouldStartWithModuloWhiteSpace, expect1) 24 | }) 25 | }) 26 | } 27 | 28 | func TestCaseConversion(t *testing.T) { 29 | 30 | cv.Convey("Given a parsable golang source file with a simple struct", t, func() { 31 | cv.Convey("then the capnp generated struct has uppercase struct name and lowercase field name, as per capnp requirements.", func() { 32 | 33 | in1 := ` 34 | type in1 struct { 35 | Str string 36 | N int 37 | D float64 38 | }` 39 | expect1 := `struct In1Capn { str @0: Text; n @1: Int64; d @2: Float64;}` 40 | // List(Float64) next 41 | 42 | cv.So(ExtractString2String(in1), ShouldStartWithModuloWhiteSpace, expect1) 43 | }) 44 | }) 45 | } 46 | 47 | /* unclear if we really want this or not 48 | func TestNonStructTypeDefsAreIgnored(t *testing.T) { 49 | 50 | cv.Convey("Given a go type definition 'type SyncMsg int32' that isn't a struct definition,", t, func() { 51 | cv.Convey("then bambam should not define a new SyncMsgCapn for it, but should generate a capnp Int32 field in the capnp struct", func() { 52 | 53 | in1 := ` 54 | type S struct { 55 | A SyncMsg 56 | } 57 | type SyncMsg int32 58 | ` 59 | expect1 := `struct SCapn { a @0: Int32; } ` 60 | 61 | cv.So(ExtractString2String(in1), ShouldStartWithModuloWhiteSpace, expect1) 62 | }) 63 | }) 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /rw3.go.txt: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "bytes" 8 | "github.com/glycerine/go-goon" 9 | ) 10 | 11 | // used in rw3_test.go for round-trip testing write/read 12 | 13 | // demonstrate anonymous (embedded) structs, 14 | // as well as pointers to structs and 15 | // also just nested structs. 16 | 17 | type Anon struct { 18 | SerialNum int 19 | } 20 | 21 | type Dude struct { 22 | Anon 23 | Name string 24 | Age int 25 | Addr *Address 26 | Fro Hair 27 | Allprim AllPrim 28 | } 29 | 30 | type Address struct { 31 | Street string 32 | Zip string 33 | } 34 | 35 | type Hair struct { 36 | Desc string 37 | } 38 | 39 | type AllPrim struct { 40 | S string 41 | I int 42 | B bool 43 | I8 int8 44 | I16 int16 45 | I32 int32 46 | I64 int64 47 | 48 | Ui8 uint8 49 | Ui16 uint16 50 | Ui32 uint32 51 | Ui64 uint64 52 | 53 | F32 float32 54 | F64 float64 55 | By byte 56 | } 57 | 58 | 59 | func main() { 60 | 61 | rw := Dude{ 62 | Name: "Hank", 63 | Age: 33, 64 | Addr: &Address{Street: "123 Main Street", Zip: "11111"}, 65 | Anon: Anon{SerialNum: 52}, 66 | Fro: Hair{ Desc: "awesome"}, 67 | Allprim: AllPrim{ 68 | S: "mystring", 69 | I: 43, 70 | B: true, 71 | I8: -7, 72 | I16: -16, 73 | I32: -32, 74 | I64: -64, 75 | Ui8: 9, 76 | Ui16: 17, 77 | Ui32: 33, 78 | Ui64: 65, 79 | F32: 2.7, 80 | F64: 3.14, 81 | By: 'a', 82 | }, 83 | } 84 | 85 | var o bytes.Buffer 86 | rw.Save(&o) 87 | 88 | rw2 := &Dude{} 89 | rw2.Load(&o) 90 | 91 | if !reflect.DeepEqual(&rw, rw2) { 92 | fmt.Printf("rw and rw2 were not equal!\n") 93 | 94 | fmt.Printf("\n\n ============= rw: ====\n") 95 | goon.Dump(rw) 96 | fmt.Printf("\n\n ============= rw2: ====\n") 97 | goon.Dump(rw2) 98 | fmt.Printf("\n\n ================\n") 99 | 100 | os.Exit(1) 101 | } 102 | 103 | fmt.Printf("Load() data matched Saved() data.\n") 104 | } 105 | -------------------------------------------------------------------------------- /list_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test005SliceOfStringToListOfText(t *testing.T) { 10 | 11 | cv.Convey("Given a struct that contains a slice of string", t, func() { 12 | cv.Convey("then the capnp schema should contain a list of string and list translating code should be generated", func() { 13 | 14 | ex0 := ` 15 | type s1 struct { 16 | Names []string 17 | }` 18 | 19 | expected0 := ` 20 | struct S1Capn { names @0: List(Text); } 21 | 22 | func (s *s1) Save(w io.Writer) error { 23 | seg := capn.NewBuffer(nil) 24 | s1GoToCapn(seg, s) 25 | _, err := seg.WriteTo(w) 26 | return err 27 | } 28 | 29 | func (s *s1) Load(r io.Reader) error { 30 | capMsg, err := capn.ReadFromStream(r, nil) 31 | if err != nil { 32 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 33 | return err 34 | } 35 | z := ReadRootS1Capn(capMsg) 36 | S1CapnToGo(z, s) 37 | return nil 38 | } 39 | 40 | func S1CapnToGo(src S1Capn, dest *s1) *s1 { 41 | if dest == nil { 42 | dest = &s1{} 43 | } 44 | 45 | dest.Names = src.Names().ToArray() 46 | 47 | return dest 48 | } 49 | 50 | func s1GoToCapn(seg *capn.Segment, src *s1) S1Capn { 51 | dest := AutoNewS1Capn(seg) 52 | 53 | mylist1 := seg.NewTextList(len(src.Names)) 54 | for i := range src.Names { 55 | mylist1.Set(i, string(src.Names[i])) 56 | } 57 | dest.SetNames(mylist1) 58 | 59 | return dest 60 | } 61 | 62 | func SliceStringToTextList(seg *capn.Segment, m []string) capn.TextList { 63 | lst := seg.NewTextList(len(m)) 64 | for i := range m { 65 | lst.Set(i, string(m[i])) 66 | } 67 | return lst 68 | } 69 | 70 | 71 | 72 | func TextListToSliceString(p capn.TextList) []string { 73 | v := make([]string, p.Len()) 74 | for i := range v { 75 | v[i] = string(p.At(i)) 76 | } 77 | return v 78 | } 79 | 80 | ` 81 | 82 | cv.So(ExtractString2String(ex0), ShouldMatchModuloWhiteSpace, expected0) 83 | //cv.So(expected0, ShouldStartWithModuloWhiteSpace, ExtractString2String(ex0)) 84 | 85 | }) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /doubleslice_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test002SlistSliceIntToListListInt64(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file with type Matrix struct { M [][]int }", t, func() { 12 | cv.Convey("then the slice should be converted to a List(List(Int64)) in the capnp output", func() { 13 | 14 | ex0 := ` 15 | type Matrix struct { 16 | M [][]int 17 | }` 18 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, ` 19 | struct MatrixCapn { 20 | m @0: List(List(Int64)); 21 | } 22 | 23 | func (s *Matrix) Save(w io.Writer) error { 24 | seg := capn.NewBuffer(nil) 25 | MatrixGoToCapn(seg, s) 26 | _, err := seg.WriteTo(w) 27 | return err 28 | } 29 | 30 | func (s *Matrix) Load(r io.Reader) error { 31 | capMsg, err := capn.ReadFromStream(r, nil) 32 | if err != nil { 33 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 34 | return err 35 | } 36 | z := ReadRootMatrixCapn(capMsg) 37 | MatrixCapnToGo(z, s) 38 | return nil 39 | } 40 | 41 | func MatrixCapnToGo(src MatrixCapn, dest *Matrix) *Matrix { 42 | if dest == nil { 43 | dest = &Matrix{} 44 | } 45 | 46 | var n int 47 | 48 | // M 49 | n = src.M().Len() 50 | dest.M = make([][]int, n) 51 | for i := 0; i < n; i++ { 52 | dest.M[i] = Int64ListToSliceInt(capn.Int64List(src.M().At(i))) 53 | } 54 | 55 | return dest 56 | } 57 | 58 | func MatrixGoToCapn(seg *capn.Segment, src *Matrix) MatrixCapn { 59 | dest := AutoNewMatrixCapn(seg) 60 | 61 | mylist1 := seg.NewPointerList(len(src.M)) 62 | for i := range src.M { 63 | mylist1.Set(i, capn.Object(SliceIntToInt64List(seg, src.M[i]))) 64 | } 65 | dest.SetM(mylist1) 66 | 67 | return dest 68 | } 69 | 70 | func SliceIntToInt64List(seg *capn.Segment, m []int) capn.Int64List { 71 | lst := seg.NewInt64List(len(m)) 72 | for i := range m { 73 | lst.Set(i, int64(m[i])) 74 | } 75 | return lst 76 | } 77 | 78 | func Int64ListToSliceInt(p capn.Int64List) []int { 79 | v := make([]int, p.Len()) 80 | for i := range v { 81 | v[i] = int(p.At(i)) 82 | } 83 | return v 84 | } 85 | `) 86 | }) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /struct1_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | type EmptyStruct struct { 10 | } 11 | type OneStruct struct { 12 | One int 13 | } 14 | type HalfPrivStruct struct { 15 | One int 16 | two int 17 | } 18 | 19 | type NestingStruct struct { 20 | One int 21 | Nester OneStruct 22 | } 23 | 24 | type EmbeddingStruct struct { 25 | OneStruct 26 | } 27 | 28 | // An example struct with a comment 29 | // second line of comment. 30 | /* 31 | C comments attached too 32 | */ 33 | type ExampleStruct1 struct { 34 | Str string 35 | N int 36 | } 37 | 38 | func TestSimpleStructExtraction(t *testing.T) { 39 | 40 | cv.Convey("Given a parsable golang source file", t, func() { 41 | cv.Convey("then we can extract an empty struct", func() { 42 | 43 | ex0 := ` 44 | type Empty1 struct { 45 | }` 46 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct Empty1Capn { } `) 47 | 48 | }) 49 | cv.Convey("then we can extract simple structs, without recursion or embedding", func() { 50 | 51 | ex1 := ` 52 | type ExampleStruct1 struct { 53 | Str string 54 | N int 55 | }` 56 | cv.So(ExtractString2String(ex1), ShouldStartWithModuloWhiteSpace, `struct ExampleStruct1Capn { str @0: Text; n @1: Int64; } `) 57 | }) 58 | cv.Convey("then we can extract structs that have other named structs inside", func() { 59 | 60 | exNest := ` 61 | type OneStruct struct { 62 | One int 63 | } 64 | type NestingStruct struct { 65 | One int 66 | Nester OneStruct 67 | }` 68 | cv.So(ExtractString2String(exNest), ShouldStartWithModuloWhiteSpace, `struct NestingStructCapn { one @0: Int64; nester @1: OneStructCapn; } struct OneStructCapn { one @0: Int64; } `) 69 | 70 | }) 71 | 72 | }) 73 | } 74 | 75 | func TestEmbedded(t *testing.T) { 76 | 77 | cv.Convey("Given a parsable golang source file", t, func() { 78 | cv.Convey("then we can extract structs with anonymous (embedded) structs inside", func() { 79 | 80 | exEmbed := ` 81 | type OneStruct struct { 82 | One int 83 | } 84 | type EmbedsOne struct { 85 | OneStruct 86 | }` 87 | 88 | cv.So(ExtractString2String(exEmbed), ShouldStartWithModuloWhiteSpace, `struct EmbedsOneCapn { oneStruct @0: OneStructCapn; } struct OneStructCapn { one @0: Int64; } `) 89 | 90 | }) 91 | 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /marshal_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestCapnpStructNaming(t *testing.T) { 10 | 11 | cv.Convey("Given go structs that we will marshal into capnp structs", t, func() { 12 | cv.Convey("then the names of the two types in go code should be distinct: Cpn suffix attached to the capnp structs", func() { 13 | 14 | ex0 := ` 15 | type Extra struct { 16 | A int 17 | }` 18 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct ExtraCapn { a @0: Int64; } `) 19 | }) 20 | }) 21 | } 22 | 23 | func TestMarshal(t *testing.T) { 24 | 25 | cv.Convey("Given go struct Extra", t, func() { 26 | cv.Convey("then the generated ExtraCapntoGo() code should copy content from an ExtraCapn to an Extra struct.", func() { 27 | cv.Convey("and should handle int fields", func() { 28 | ex0 := ` 29 | type Extra struct { 30 | A int 31 | B int 32 | }` 33 | toGoCode := ExtractCapnToGoCode(ex0, "Extra") 34 | cv.So(toGoCode, ShouldMatchModuloWhiteSpace, ` 35 | func ExtraCapnToGo(src ExtraCapn, dest *Extra) *Extra { 36 | if dest == nil { 37 | dest = &Extra{} 38 | } 39 | dest.A = int(src.A()) 40 | dest.B = int(src.B()) 41 | return dest } 42 | `) 43 | 44 | toCapnCode := ExtractGoToCapnCode(ex0, "Extra") 45 | cv.So(toCapnCode, ShouldMatchModuloWhiteSpace, ` 46 | func ExtraGoToCapn(seg *capn.Segment, src *Extra) ExtraCapn { 47 | dest := AutoNewExtraCapn(seg) 48 | dest.SetA(int64(src.A)) 49 | dest.SetB(int64(src.B)) 50 | return dest } 51 | `) 52 | 53 | }) 54 | cv.Convey("and should handle uint fields", func() { 55 | ex0 := ` 56 | type Extra struct { 57 | A uint64 58 | }` 59 | toGoCode := ExtractCapnToGoCode(ex0, "Extra") 60 | cv.So(toGoCode, ShouldMatchModuloWhiteSpace, ` 61 | func ExtraCapnToGo(src ExtraCapn, dest *Extra) *Extra { 62 | if dest == nil { 63 | dest = &Extra{} 64 | } 65 | dest.A = src.A() 66 | return dest } 67 | `) 68 | 69 | toCapnCode := ExtractGoToCapnCode(ex0, "Extra") 70 | cv.So(toCapnCode, ShouldMatchModuloWhiteSpace, ` 71 | func ExtraGoToCapn(seg *capn.Segment, src *Extra) ExtraCapn { 72 | dest := AutoNewExtraCapn(seg) 73 | dest.SetA(src.A) 74 | return dest } 75 | `) 76 | }) 77 | }) 78 | }) 79 | } 80 | 81 | func TestUnMarshal(t *testing.T) { 82 | 83 | cv.Convey("Given go structs", t, func() { 84 | cv.Convey("then the generated Unmarshal() code should copy from the capnp into the go structs", func() { 85 | 86 | }) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /annot_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestCommentAnnotationWorks(t *testing.T) { 10 | 11 | cv.Convey(`Given a golang struct named Data with an attached comment: // capname:"MyData"`, t, func() { 12 | cv.Convey("then we should use the capname MyData for the struct.", func() { 13 | ex0 := ` 14 | // capname:"MyData" 15 | type Data struct { 16 | }` 17 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct MyData { } `) 18 | 19 | ex1 := ` 20 | // capname: "MyData" 21 | type Data struct { 22 | }` 23 | cv.So(ExtractString2String(ex1), ShouldStartWithModuloWhiteSpace, `struct MyData { } `) 24 | 25 | }) 26 | 27 | }) 28 | } 29 | 30 | func TestTagAnnotationWorks(t *testing.T) { 31 | 32 | cv.Convey("Given a golang struct with an invalid capnp field name 'Union', but with a field tag: `capname:\"MyNewFieldName\"`", t, func() { 33 | cv.Convey("then we should use the capname MyNewFieldName for the field, and not stop the run.", func() { 34 | ex0 := "type S struct { Union string `capname:\"MyNewFieldName\"` \n}" 35 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct SCapn { MyNewFieldName @0: Text; } `) 36 | ex1 := "type S struct { Union string `capname: \t \t \"MyNewFieldName\"` \n}" 37 | cv.So(ExtractString2String(ex1), ShouldStartWithModuloWhiteSpace, `struct SCapn { MyNewFieldName @0: Text; } `) 38 | }) 39 | 40 | }) 41 | } 42 | 43 | func TestTagCapidWorks(t *testing.T) { 44 | 45 | cv.Convey("Given the desire to preserve the field numbering in the generated Capnproto schema,", t, func() { 46 | cv.Convey("when we add the tag: capid:\"1\", then the field should be numbered @1.", func() { 47 | cv.Convey("and if there are fewer than 2 (numbered 0, 1) fields then we error out.", func() { 48 | ex0 := "type S struct { A string `capid:\"1\"`; B string \n}" 49 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct SCapn { b @0: Text; a @1: Text; } `) 50 | }) 51 | }) 52 | 53 | }) 54 | } 55 | 56 | func TestLowercaseGoFieldsIgnoredByDefault(t *testing.T) { 57 | 58 | cv.Convey("Given the traditional golang std lib behvaior of not serializing lowercase fields,", t, func() { 59 | cv.Convey("then by default we should ignore lower case fields in writing the capnproto schema.", func() { 60 | ex0 := "type S struct { a string; B string \n}" 61 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct SCapn { b @0: Text; } `) 62 | }) 63 | }) 64 | 65 | } 66 | -------------------------------------------------------------------------------- /nested_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestNestedStructs016(t *testing.T) { 10 | 11 | cv.Convey("Given go structs that contain structs \n"+ 12 | " then these should marshal and unmarshal their inner struct members", t, func() { 13 | 14 | ex0 := ` 15 | type Inner struct { 16 | C int 17 | } 18 | type Outer struct { 19 | A int 20 | B Inner 21 | } 22 | ` 23 | cv.So(ExtractString2String(ex0), ShouldMatchModuloWhiteSpace, ` 24 | struct InnerCapn { 25 | c @0: Int64; 26 | } 27 | 28 | struct OuterCapn { 29 | a @0: Int64; b @1: InnerCapn; 30 | } 31 | 32 | func (s *Inner) Save(w io.Writer) error { 33 | seg := capn.NewBuffer(nil) 34 | InnerGoToCapn(seg, s) 35 | _, err := seg.WriteTo(w) 36 | return err 37 | } 38 | 39 | 40 | 41 | func (s *Inner) Load(r io.Reader) error { 42 | capMsg, err := capn.ReadFromStream(r, nil) 43 | if err != nil { 44 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 45 | return err 46 | } 47 | z := ReadRootInnerCapn(capMsg) 48 | InnerCapnToGo(z, s) 49 | return nil 50 | } 51 | 52 | 53 | 54 | func InnerCapnToGo(src InnerCapn, dest *Inner) *Inner { 55 | if dest == nil { 56 | dest = &Inner{} 57 | } 58 | dest.C = int(src.C()) 59 | 60 | return dest 61 | } 62 | 63 | 64 | 65 | func InnerGoToCapn(seg *capn.Segment, src *Inner) InnerCapn { 66 | dest := AutoNewInnerCapn(seg) 67 | dest.SetC(int64(src.C)) 68 | 69 | return dest 70 | } 71 | 72 | 73 | 74 | func (s *Outer) Save(w io.Writer) error { 75 | seg := capn.NewBuffer(nil) 76 | OuterGoToCapn(seg, s) 77 | _, err := seg.WriteTo(w) 78 | return err 79 | } 80 | 81 | 82 | 83 | func (s *Outer) Load(r io.Reader) error { 84 | capMsg, err := capn.ReadFromStream(r, nil) 85 | if err != nil { 86 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 87 | return err 88 | } 89 | z := ReadRootOuterCapn(capMsg) 90 | OuterCapnToGo(z, s) 91 | return nil 92 | } 93 | 94 | 95 | 96 | func OuterCapnToGo(src OuterCapn, dest *Outer) *Outer { 97 | if dest == nil { 98 | dest = &Outer{} 99 | } 100 | dest.A = int(src.A()) 101 | dest.B = *InnerCapnToGo(src.B(), nil) 102 | 103 | return dest 104 | } 105 | 106 | 107 | 108 | func OuterGoToCapn(seg *capn.Segment, src *Outer) OuterCapn { 109 | dest := AutoNewOuterCapn(seg) 110 | dest.SetA(int64(src.A)) 111 | dest.SetB(InnerGoToCapn(seg, &src.B)) 112 | 113 | return dest 114 | } 115 | `) 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /allprim_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test012AllPrimFields(t *testing.T) { 10 | 11 | cv.Convey("Given a struct that contains all primitive fields", t, func() { 12 | cv.Convey("then the capnp schema should contain the fields and we should generate translation code for the all field types", func() { 13 | 14 | ex0 := ` 15 | type s1 struct { 16 | S string 17 | I int 18 | B bool 19 | I8 int8 20 | I16 int16 21 | I32 int32 22 | I64 int64 23 | 24 | Ui8 uint8 25 | Ui16 uint16 26 | Ui32 uint32 27 | Ui64 uint64 28 | 29 | F32 float32 30 | F64 float64 31 | By byte 32 | } 33 | ` 34 | 35 | expected0 := ` 36 | struct S1Capn { 37 | s @0: Text; 38 | i @1: Int64; 39 | b @2: Bool; 40 | i8 @3: Int8; 41 | i16 @4: Int16; 42 | i32 @5: Int32; 43 | i64 @6: Int64; 44 | ui8 @7: UInt8; 45 | ui16 @8: UInt16; 46 | ui32 @9: UInt32; 47 | ui64 @10: UInt64; 48 | f32 @11: Float32; 49 | f64 @12: Float64; 50 | by @13: UInt8; 51 | } 52 | 53 | func (s *s1) Save(w io.Writer) error { 54 | seg := capn.NewBuffer(nil) 55 | s1GoToCapn(seg, s) 56 | _, err := seg.WriteTo(w) 57 | return err 58 | } 59 | 60 | func (s *s1) Load(r io.Reader) error { 61 | capMsg, err := capn.ReadFromStream(r, nil) 62 | if err != nil { 63 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 64 | return err 65 | } 66 | z := ReadRootS1Capn(capMsg) 67 | S1CapnToGo(z, s) 68 | return nil 69 | } 70 | 71 | func S1CapnToGo(src S1Capn, dest *s1) *s1 { 72 | if dest == nil { 73 | dest = &s1{} 74 | } 75 | dest.S = src.S() 76 | dest.I = int(src.I()) 77 | dest.B = src.B() 78 | dest.I8 = src.I8() 79 | dest.I16 = src.I16() 80 | dest.I32 = src.I32() 81 | dest.I64 = src.I64() 82 | dest.Ui8 = src.Ui8() 83 | dest.Ui16 = src.Ui16() 84 | dest.Ui32 = src.Ui32() 85 | dest.Ui64 = src.Ui64() 86 | dest.F32 = src.F32() 87 | dest.F64 = src.F64() 88 | dest.By = src.By() 89 | 90 | return dest 91 | } 92 | 93 | func s1GoToCapn(seg *capn.Segment, src *s1) S1Capn { 94 | dest := AutoNewS1Capn(seg) 95 | dest.SetS(src.S) 96 | dest.SetI(int64(src.I)) 97 | dest.SetB(src.B) 98 | dest.SetI8(src.I8) 99 | dest.SetI16(src.I16) 100 | dest.SetI32(src.I32) 101 | dest.SetI64(src.I64) 102 | dest.SetUi8(src.Ui8) 103 | dest.SetUi16(src.Ui16) 104 | dest.SetUi32(src.Ui32) 105 | dest.SetUi64(src.Ui64) 106 | dest.SetF32(src.F32) 107 | dest.SetF64(src.F64) 108 | dest.SetBy(src.By) 109 | 110 | return dest 111 | } 112 | ` 113 | 114 | cv.So(ExtractString2String(ex0), ShouldMatchModuloWhiteSpace, expected0) 115 | //cv.So(expected0, ShouldStartWithModuloWhiteSpace, ExtractString2String(ex0)) 116 | 117 | }) 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /utilities_for_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Copyright (c) 2014 SmartyStreets, LLC 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | NOTE: Various optional and subordinate components carry their own licensing requirements and restrictions. Use of those components is subject to the terms and conditions outlined the respective license of each component. 13 | */ 14 | 15 | import ( 16 | "fmt" 17 | "path" 18 | "runtime" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func pass(t *testing.T, result string) { 24 | if result != success { 25 | _, file, line, _ := runtime.Caller(1) 26 | base := path.Base(file) 27 | t.Errorf("Expectation should have passed but failed (see %s: line %d): '%s'", base, line, result) 28 | } 29 | } 30 | 31 | func fail(t *testing.T, actual string, expected string) { 32 | actual = format(actual) 33 | expected = format(expected) 34 | 35 | if actual != expected { 36 | if actual == "" { 37 | actual = "(empty)" 38 | } 39 | _, file, line, _ := runtime.Caller(1) 40 | base := path.Base(file) 41 | t.Errorf("Expectation should have failed but passed (see %s: line %d). \nExpected: %s\nActual: %s\n", 42 | base, line, expected, actual) 43 | } 44 | } 45 | func format(message string) string { 46 | message = strings.Replace(message, "\n", " ", -1) 47 | for strings.Contains(message, " ") { 48 | message = strings.Replace(message, " ", " ", -1) 49 | } 50 | return message 51 | } 52 | 53 | func so(actual interface{}, assert func(interface{}, ...interface{}) string, expected ...interface{}) string { 54 | return assert(actual, expected...) 55 | } 56 | 57 | type Thing1 struct { 58 | a string 59 | } 60 | type Thing2 struct { 61 | a string 62 | } 63 | 64 | type Thinger interface { 65 | Hi() 66 | } 67 | 68 | type Thing struct{} 69 | 70 | func (self *Thing) Hi() {} 71 | 72 | type IntAlias int 73 | type StringAlias string 74 | type StringSliceAlias []string 75 | type StringStringMapAlias map[string]string 76 | 77 | /******** FakeSerialzier ********/ 78 | 79 | type fakeSerializer struct{} 80 | 81 | func (self *fakeSerializer) serialize(expected, actual interface{}, message string) string { 82 | return fmt.Sprintf("%v|%v|%s", expected, actual, message) 83 | } 84 | 85 | func (self *fakeSerializer) serializeDetailed(expected, actual interface{}, message string) string { 86 | return fmt.Sprintf("%v|%v|%s", expected, actual, message) 87 | } 88 | 89 | func newFakeSerializer() *fakeSerializer { 90 | return new(fakeSerializer) 91 | } 92 | -------------------------------------------------------------------------------- /cpfile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path" 8 | ) 9 | 10 | // Cp implements a portable version of /bin/cp for use in golang 11 | // code that wants to work on many platforms. If the destinationPath 12 | // directory hierarchy does not exist, we will attempt to make it, 13 | // like mkdir -p would. 14 | func Cp(originPath, destinationPath string) (err error) { 15 | if !FileExists(originPath) { 16 | return fmt.Errorf("Cp error: source path '%s' does not exist", originPath) 17 | } 18 | 19 | var dirname, target string 20 | 21 | ostat, err := os.Stat(originPath) 22 | if err != nil { 23 | return err 24 | } 25 | if !ostat.Mode().IsRegular() { 26 | return fmt.Errorf("Cp: can only copy regular files, origin is '%s' of mode: '%s'", ostat.Name(), ostat.Mode().String()) 27 | } 28 | 29 | if FileExists(destinationPath) { 30 | // okay, good to go 31 | //fmt.Printf("\n okay, destinationPath '%s' exists as a file\n", destinationPath) 32 | dirname = path.Dir(destinationPath) 33 | target = path.Base(destinationPath) 34 | } else { 35 | // no such file yet 36 | //fmt.Printf("\n okay, destinationPath '%s': no such file yet.\n", destinationPath) 37 | 38 | // set dirname and target 39 | 40 | // do we end in "/" or "\"? 41 | if IsDirPath(destinationPath) { 42 | dirname = destinationPath 43 | target = path.Base(originPath) 44 | } else { 45 | // the final path component is taken to be the target file name, unless 46 | // it names an existing directory. 47 | if DirExists(destinationPath) { 48 | //fmt.Printf("\n okay, destinationPath '%s' exists as a directory\n", destinationPath) 49 | dirname = path.Dir(destinationPath) 50 | target = path.Base(originPath) 51 | 52 | } else { 53 | dirname = path.Dir(destinationPath) 54 | target = path.Base(destinationPath) 55 | } 56 | } 57 | 58 | //fmt.Printf("\n dirname = '%s' target = '%s'\n", dirname, target) 59 | 60 | // is it a directory that exists? 61 | if DirExists(dirname) { 62 | //fmt.Printf("\n okay, dirname '%s' exists as a directory\n", dirname) 63 | } else { 64 | //fmt.Printf("\n okay, dirname '%s' is not an existing directory, try to make needed directories\n", dirname) 65 | // dirname is not an existing directory, try to mkdir -p make the needed dirs. 66 | 67 | err = os.MkdirAll(dirname, 0755) 68 | if err != nil { 69 | return fmt.Errorf("could not create destination directory path(s) with MkdirAll on '%s', error: '%s'", dirname, err) 70 | } else { 71 | //fmt.Printf("\n made dirname: '%s'\n", dirname) 72 | } 73 | } 74 | 75 | } 76 | // INVAR: we should be good to copy to dirname 77 | //fmt.Printf("\n INVAR: we should be good to copy to dirname: '%s'\n", dirname) 78 | 79 | fullpath := appendFilename(dirname, target) 80 | 81 | dstat, err := os.Stat(fullpath) 82 | if err != nil { 83 | if !os.IsNotExist(err) { 84 | return err 85 | } 86 | } else { 87 | // we have an existing file, don't bother writing atop ourselves. 88 | if os.SameFile(ostat, dstat) { 89 | return nil // don't error out here, the copy is actually already done. 90 | } 91 | } 92 | 93 | // proceed to copy 94 | //fmt.Printf("os.Open with originPath = '%s'\n", originPath) 95 | src, err := os.Open(originPath) 96 | if err != nil { 97 | return err 98 | } 99 | defer src.Close() 100 | 101 | //fmt.Printf("os.Create with fullpath = '%s'\n", fullpath) 102 | dest, err := os.Create(fullpath) 103 | if err != nil { 104 | //fmt.Printf("returning early after attempt os.Create with fullpath = '%s'\n", fullpath) 105 | return err 106 | } 107 | defer func() { 108 | closeError := dest.Close() 109 | // only substitute closeError if there was 110 | // no previous earlier error 111 | if err == nil { 112 | err = closeError 113 | } 114 | }() 115 | _, err = io.Copy(dest, src) 116 | if err != nil { 117 | return err 118 | } 119 | return nil 120 | } 121 | 122 | func appendFilename(dir, fn string) string { 123 | // but don't double // or \\ it 124 | if IsDirPath(dir) { 125 | return dir + fn 126 | } 127 | return dir + string(os.PathSeparator) + fn 128 | } 129 | 130 | func IsDirPath(dir string) bool { 131 | n := len(dir) 132 | if n == 0 { 133 | return false 134 | } 135 | if dir[n-1] == os.PathSeparator { 136 | return true 137 | } 138 | return false 139 | } 140 | -------------------------------------------------------------------------------- /save_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func Test006Save1(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file", t, func() { 12 | cv.Convey("then structs with public fields get a Save() method to serialize them, and a Load() method to restore them.", func() { 13 | 14 | exEmbed := ` 15 | type RWTest struct { 16 | Hello []string 17 | } 18 | ` 19 | cv.So(ExtractString2String(exEmbed), ShouldMatchModuloWhiteSpace, ` 20 | struct RWTestCapn { 21 | hello @0: List(Text); 22 | } 23 | 24 | func (s *RWTest) Save(w io.Writer) error { 25 | seg := capn.NewBuffer(nil) 26 | RWTestGoToCapn(seg, s) 27 | _, err := seg.WriteTo(w) 28 | return err 29 | } 30 | 31 | func (s *RWTest) Load(r io.Reader) error { 32 | capMsg, err := capn.ReadFromStream(r, nil) 33 | if err != nil { 34 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 35 | return err 36 | } 37 | z := ReadRootRWTestCapn(capMsg) 38 | RWTestCapnToGo(z, s) 39 | return nil 40 | } 41 | 42 | func RWTestCapnToGo(src RWTestCapn, dest *RWTest) *RWTest { 43 | if dest == nil { 44 | dest = &RWTest{} 45 | } 46 | dest.Hello = src.Hello().ToArray() 47 | 48 | return dest 49 | } 50 | 51 | func RWTestGoToCapn(seg *capn.Segment, src *RWTest) RWTestCapn { 52 | dest := AutoNewRWTestCapn(seg) 53 | 54 | mylist1 := seg.NewTextList(len(src.Hello)) 55 | for i := range src.Hello { 56 | mylist1.Set(i, string(src.Hello[i])) 57 | } 58 | dest.SetHello(mylist1) 59 | 60 | return dest 61 | } 62 | 63 | func SliceStringToTextList(seg *capn.Segment, m []string) capn.TextList { 64 | lst := seg.NewTextList(len(m)) 65 | for i := range m { 66 | lst.Set(i, string(m[i])) 67 | } 68 | return lst 69 | } 70 | 71 | 72 | 73 | func TextListToSliceString(p capn.TextList) []string { 74 | v := make([]string, p.Len()) 75 | for i := range v { 76 | v[i] = string(p.At(i)) 77 | } 78 | return v 79 | } 80 | 81 | `) 82 | 83 | }) 84 | 85 | }) 86 | } 87 | 88 | func Test007Save2(t *testing.T) { 89 | 90 | cv.Convey("Given a parsable golang source file", t, func() { 91 | cv.Convey("then structs with public fields get a save() method to serialize them, and a load() method to restore them.", func() { 92 | 93 | exEmbed := ` 94 | type RWTest struct { 95 | Hello []string 96 | World []int 97 | } 98 | ` 99 | cv.So(ExtractString2String(exEmbed), ShouldMatchModuloWhiteSpace, ` 100 | struct RWTestCapn { 101 | hello @0: List(Text); 102 | world @1: List(Int64); 103 | } 104 | 105 | func (s *RWTest) Save(w io.Writer) error { 106 | seg := capn.NewBuffer(nil) 107 | RWTestGoToCapn(seg, s) 108 | _, err := seg.WriteTo(w) 109 | return err 110 | } 111 | 112 | func (s *RWTest) Load(r io.Reader) error { 113 | capMsg, err := capn.ReadFromStream(r, nil) 114 | if err != nil { 115 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 116 | return err 117 | } 118 | z := ReadRootRWTestCapn(capMsg) 119 | RWTestCapnToGo(z, s) 120 | return nil 121 | } 122 | 123 | 124 | func RWTestCapnToGo(src RWTestCapn, dest *RWTest) *RWTest { 125 | if dest == nil { 126 | dest = &RWTest{} 127 | } 128 | dest.Hello = src.Hello().ToArray() 129 | 130 | var n int 131 | 132 | // World 133 | n = src.World().Len() 134 | dest.World = make([]int, n) 135 | for i := 0; i < n; i++ { 136 | dest.World[i] = int(src.World().At(i)) 137 | } 138 | 139 | 140 | return dest 141 | } 142 | 143 | func RWTestGoToCapn(seg *capn.Segment, src *RWTest) RWTestCapn { 144 | dest := AutoNewRWTestCapn(seg) 145 | 146 | mylist1 := seg.NewTextList(len(src.Hello)) 147 | for i := range src.Hello { 148 | mylist1.Set(i, string(src.Hello[i])) 149 | } 150 | dest.SetHello(mylist1) 151 | 152 | mylist2 := seg.NewInt64List(len(src.World)) 153 | for i:= range src.World { 154 | mylist2.Set(i, int64(src.World[i])) 155 | } 156 | dest.SetWorld(mylist2) 157 | 158 | return dest 159 | } 160 | 161 | 162 | 163 | func SliceIntToInt64List(seg *capn.Segment, m []int) capn.Int64List { 164 | lst := seg.NewInt64List(len(m)) 165 | for i := range m { 166 | lst.Set(i, int64(m[i])) 167 | } 168 | return lst 169 | } 170 | 171 | 172 | 173 | func Int64ListToSliceInt(p capn.Int64List) []int { 174 | v := make([]int, p.Len()) 175 | for i := range v { 176 | v[i] = int(p.At(i)) 177 | } 178 | return v 179 | } 180 | 181 | 182 | 183 | func SliceStringToTextList(seg *capn.Segment, m []string) capn.TextList { 184 | lst := seg.NewTextList(len(m)) 185 | for i := range m { 186 | lst.Set(i, string(m[i])) 187 | } 188 | return lst 189 | } 190 | 191 | 192 | 193 | func TextListToSliceString(p capn.TextList) []string { 194 | v := make([]string, p.Len()) 195 | for i := range v { 196 | v[i] = string(p.At(i)) 197 | } 198 | return v 199 | } 200 | 201 | `) 202 | 203 | }) 204 | 205 | }) 206 | } 207 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | func use() { 12 | fmt.Fprintf(os.Stderr, "\nuse: bambam -o outdir -p package myGoSourceFile.go myGoSourceFile2.go ...\n") 13 | fmt.Fprintf(os.Stderr, " # Bambam makes it easy to use Capnproto serialization[1] from Go.\n") 14 | fmt.Fprintf(os.Stderr, " # Bambam reads .go files and writes a .capnp schema and Go bindings.\n") 15 | fmt.Fprintf(os.Stderr, " # options:\n") 16 | fmt.Fprintf(os.Stderr, " # -o=\"odir\" specifies the directory to write to (created if need be).\n") 17 | fmt.Fprintf(os.Stderr, " # -p=\"main\" specifies the package header to write (e.g. main, mypkg).\n") 18 | fmt.Fprintf(os.Stderr, " # -X exports private fields of Go structs. Default only maps public fields.\n") 19 | fmt.Fprintf(os.Stderr, " # -version shows build version with git commit hash.\n") 20 | fmt.Fprintf(os.Stderr, " # -debug print lots of debug info as we process.\n") 21 | fmt.Fprintf(os.Stderr, " # -OVERWRITE modify .go files in-place, adding capid tags (write to -o dir by default).\n") 22 | fmt.Fprintf(os.Stderr, " # required: at least one .go source file for struct definitions. Must be last, after options.\n") 23 | fmt.Fprintf(os.Stderr, " #\n") 24 | fmt.Fprintf(os.Stderr, " # [1] https://github.com/glycerine/go-capnproto \n") 25 | fmt.Fprintf(os.Stderr, "\n") 26 | os.Exit(1) 27 | } 28 | 29 | func main() { 30 | MainArgs(os.Args) 31 | } 32 | 33 | // allow invocation from test 34 | func MainArgs(args []string) { 35 | //fmt.Println(os.Args) 36 | os.Args = args 37 | 38 | flag.Usage = use 39 | if len(os.Args) < 2 { 40 | use() 41 | } 42 | 43 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 44 | debug := flag.Bool("debug", false, "print lots of debug info as we process.") 45 | verrequest := flag.Bool("version", false, "request git commit hash used to build this bambam") 46 | outdir := flag.String("o", "odir", "specify output directory") 47 | pkg := flag.String("p", "main", "specify package for generated code") 48 | privs := flag.Bool("X", false, "export private as well as public struct fields") 49 | overwrite := flag.Bool("OVERWRITE", false, "replace named .go files with capid tagged versions.") 50 | flag.Parse() 51 | 52 | if debug != nil { 53 | Verbose = *debug 54 | } 55 | 56 | if verrequest != nil && *verrequest { 57 | fmt.Printf("%s\n", LASTGITCOMMITHASH) 58 | os.Exit(0) 59 | } 60 | 61 | if outdir == nil || *outdir == "" { 62 | fmt.Fprintf(os.Stderr, "required -o option missing. Use bambam -o myfile.go # to specify the output directory.\n") 63 | use() 64 | } 65 | 66 | if !DirExists(*outdir) { 67 | err := os.MkdirAll(*outdir, 0755) 68 | if err != nil { 69 | panic(err) 70 | } 71 | } 72 | 73 | if pkg == nil || *pkg == "" { 74 | fmt.Fprintf(os.Stderr, "required -p option missing. Specify a package name for the generated go code with -p \n") 75 | use() 76 | } 77 | 78 | // all the rest are input .go files 79 | inputFiles := flag.Args() 80 | 81 | if len(inputFiles) == 0 { 82 | fmt.Fprintf(os.Stderr, "bambam needs at least one .go golang source file to process specified on the command line.\n") 83 | os.Exit(1) 84 | } 85 | 86 | for _, fn := range inputFiles { 87 | if !strings.HasSuffix(fn, ".go") && !strings.HasSuffix(fn, ".go.txt") { 88 | fmt.Fprintf(os.Stderr, "error: bambam input file '%s' did not end in '.go' or '.go.txt'.\n", fn) 89 | os.Exit(1) 90 | } 91 | } 92 | 93 | x := NewExtractor() 94 | x.fieldPrefix = " " 95 | x.fieldSuffix = "\n" 96 | x.outDir = *outdir 97 | if privs != nil { 98 | x.extractPrivate = *privs 99 | } 100 | if overwrite != nil { 101 | x.overwrite = *overwrite 102 | } 103 | 104 | for _, inFile := range inputFiles { 105 | _, err := x.ExtractStructsFromOneFile(nil, inFile) 106 | if err != nil { 107 | panic(err) 108 | } 109 | } 110 | // get rid of default tmp dir 111 | x.compileDir.Cleanup() 112 | 113 | x.compileDir.DirPath = *outdir 114 | x.pkgName = *pkg 115 | 116 | schemaFN := x.compileDir.DirPath + "/schema.capnp" 117 | schemaFile, err := os.Create(schemaFN) 118 | if err != nil { 119 | panic(err) 120 | } 121 | defer schemaFile.Close() 122 | 123 | by := x.GenCapnpHeader() 124 | schemaFile.Write(by.Bytes()) 125 | 126 | _, err = x.WriteToSchema(schemaFile) 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | fmt.Fprintf(schemaFile, "\n") 132 | fmt.Fprintf(schemaFile, "##compile with:\n\n##\n##\n## capnp compile -ogo %s\n\n", schemaFN) 133 | 134 | // translator library of go functions is separate from the schema 135 | 136 | translateFn := x.compileDir.DirPath + "/translateCapn.go" 137 | translatorFile, err := os.Create(translateFn) 138 | if err != nil { 139 | panic(err) 140 | } 141 | defer translatorFile.Close() 142 | fmt.Fprintf(translatorFile, `package %s 143 | 144 | import ( 145 | capn "github.com/glycerine/go-capnproto" 146 | "io" 147 | ) 148 | 149 | `, x.pkgName) 150 | 151 | _, err = x.WriteToTranslators(translatorFile) 152 | if err != nil { 153 | panic(err) 154 | } 155 | 156 | err = x.CopySourceFilesAddCapidTag() 157 | if err != nil { 158 | panic(err) 159 | } 160 | 161 | exec.Command("cp", "-p", "go.capnp", x.compileDir.DirPath).Run() 162 | fmt.Printf("generated files in '%s'\n", x.compileDir.DirPath) 163 | } 164 | -------------------------------------------------------------------------------- /ptr_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestPointerInStruct(t *testing.T) { 10 | 11 | cv.Convey("Given a struct that contains a pointer to a struct", t, func() { 12 | cv.Convey("then the pointer element should be de-pointerized", func() { 13 | 14 | ex0 := ` 15 | type big struct {} 16 | type s1 struct { 17 | MyBig *big 18 | }` 19 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct BigCapn { } struct S1Capn { myBig @0: BigCapn; } `) 20 | 21 | }) 22 | }) 23 | } 24 | 25 | func TestPointerInSliceInStruct(t *testing.T) { 26 | 27 | cv.Convey("Given a struct that contains a slice of pointers", t, func() { 28 | cv.Convey("then the translation routines should still work", func() { 29 | 30 | in0 := ` 31 | type Big struct { 32 | A int 33 | } 34 | type s1 struct { 35 | Ptrs []*Big 36 | } 37 | ` 38 | 39 | expect0 := ` 40 | struct BigCapn { a @0: Int64; } 41 | struct S1Capn { ptrs @0: List(BigCapn); } 42 | 43 | func (s *Big) Save(w io.Writer) error { 44 | seg := capn.NewBuffer(nil) 45 | BigGoToCapn(seg, s) 46 | _, err := seg.WriteTo(w) 47 | return err 48 | } 49 | 50 | func (s *Big) Load(r io.Reader) error { 51 | capMsg, err := capn.ReadFromStream(r, nil) 52 | if err != nil { 53 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 54 | return err 55 | } 56 | z := ReadRootBigCapn(capMsg) 57 | BigCapnToGo(z, s) 58 | return nil 59 | } 60 | 61 | func BigCapnToGo(src BigCapn, dest *Big) *Big { 62 | if dest == nil { 63 | dest = &Big{} 64 | } 65 | dest.A = int(src.A()) 66 | 67 | return dest 68 | } 69 | 70 | func BigGoToCapn(seg *capn.Segment, src *Big) BigCapn { 71 | dest := AutoNewBigCapn(seg) 72 | dest.SetA(int64(src.A)) 73 | 74 | return dest 75 | } 76 | 77 | 78 | func (s *s1) Save(w io.Writer) error { 79 | seg := capn.NewBuffer(nil) 80 | s1GoToCapn(seg, s) 81 | _, err := seg.WriteTo(w) 82 | return err 83 | } 84 | 85 | 86 | 87 | func (s *s1) Load(r io.Reader) error { 88 | capMsg, err := capn.ReadFromStream(r, nil) 89 | if err != nil { 90 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 91 | return err 92 | } 93 | z := ReadRootS1Capn(capMsg) 94 | S1CapnToGo(z, s) 95 | return nil 96 | } 97 | 98 | func S1CapnToGo(src S1Capn, dest *s1) *s1 { 99 | if dest == nil { 100 | dest = &s1{} 101 | } 102 | 103 | var n int 104 | 105 | // Ptrs 106 | n = src.Ptrs().Len() 107 | dest.Ptrs = make([]*Big, n) 108 | for i := 0; i < n; i++ { 109 | dest.Ptrs[i] = BigCapnToGo(src.Ptrs().At(i), nil) 110 | } 111 | 112 | return dest 113 | } 114 | 115 | func s1GoToCapn(seg *capn.Segment, src *s1) S1Capn { 116 | dest := AutoNewS1Capn(seg) 117 | 118 | // Ptrs -> BigCapn (go slice to capn list) 119 | if len(src.Ptrs) > 0 { 120 | typedList := NewBigCapnList(seg, len(src.Ptrs)) 121 | plist := capn.PointerList(typedList) 122 | i := 0 123 | for _, ele := range src.Ptrs { 124 | plist.Set(i, capn.Object(BigGoToCapn(seg, ele))) 125 | i++ 126 | } 127 | dest.SetPtrs(typedList) 128 | } 129 | 130 | return dest 131 | } 132 | ` 133 | cv.So(ExtractString2String(in0), ShouldStartWithModuloWhiteSpace, expect0) 134 | }) 135 | }) 136 | } 137 | 138 | func TestPointerAndStraightInSliceInStruct(t *testing.T) { 139 | 140 | cv.Convey("Given a struct that contains a slice of pointers and a slice of struct", t, func() { 141 | cv.Convey("then the translation routines should still work", func() { 142 | 143 | in0 := ` 144 | type Big struct { 145 | A int 146 | B string 147 | C []string 148 | } 149 | type s1 struct { 150 | Ptrs []*Big 151 | Straight []Big 152 | } 153 | ` 154 | expect0 := ` 155 | struct BigCapn { a @0: Int64; b @1: Text; c @2: List(Text); } 156 | struct S1Capn { ptrs @0: List(BigCapn); straight @1: List(BigCapn); } 157 | 158 | 159 | func (s *Big) Save(w io.Writer) error { 160 | seg := capn.NewBuffer(nil) 161 | BigGoToCapn(seg, s) 162 | _, err := seg.WriteTo(w) 163 | return err 164 | } 165 | 166 | 167 | 168 | func (s *Big) Load(r io.Reader) error { 169 | capMsg, err := capn.ReadFromStream(r, nil) 170 | if err != nil { 171 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 172 | return err 173 | } 174 | z := ReadRootBigCapn(capMsg) 175 | BigCapnToGo(z, s) 176 | return nil 177 | } 178 | 179 | func BigCapnToGo(src BigCapn, dest *Big) *Big { 180 | if dest == nil { 181 | dest = &Big{} 182 | } 183 | dest.A = int(src.A()) 184 | dest.B = src.B() 185 | dest.C = src.C().ToArray() 186 | 187 | return dest 188 | } 189 | 190 | func BigGoToCapn(seg *capn.Segment, src *Big) BigCapn { 191 | dest := AutoNewBigCapn(seg) 192 | dest.SetA(int64(src.A)) 193 | dest.SetB(src.B) 194 | 195 | mylist1 := seg.NewTextList(len(src.C)) 196 | for i := range src.C { 197 | mylist1.Set(i, string(src.C[i])) 198 | } 199 | dest.SetC(mylist1) 200 | 201 | return dest 202 | } 203 | 204 | 205 | func (s *s1) Save(w io.Writer) error { 206 | seg := capn.NewBuffer(nil) 207 | s1GoToCapn(seg, s) 208 | _, err := seg.WriteTo(w) 209 | return err 210 | } 211 | 212 | 213 | 214 | func (s *s1) Load(r io.Reader) error { 215 | capMsg, err := capn.ReadFromStream(r, nil) 216 | if err != nil { 217 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 218 | return err 219 | } 220 | z := ReadRootS1Capn(capMsg) 221 | S1CapnToGo(z, s) 222 | return nil 223 | } 224 | 225 | func S1CapnToGo(src S1Capn, dest *s1) *s1 { 226 | if dest == nil { 227 | dest = &s1{} 228 | } 229 | 230 | var n int 231 | 232 | // Ptrs 233 | n = src.Ptrs().Len() 234 | dest.Ptrs = make([]*Big, n) 235 | for i := 0; i < n; i++ { 236 | dest.Ptrs[i] = BigCapnToGo(src.Ptrs().At(i), nil) 237 | } 238 | 239 | // Straight 240 | n = src.Straight().Len() 241 | dest.Straight = make([]Big, n) 242 | for i := 0; i < n; i++ { 243 | dest.Straight[i] = *BigCapnToGo(src.Straight().At(i), nil) 244 | } 245 | 246 | return dest 247 | } 248 | 249 | func s1GoToCapn(seg *capn.Segment, src *s1) S1Capn { 250 | dest := AutoNewS1Capn(seg) 251 | 252 | // Ptrs -> BigCapn (go slice to capn list) 253 | if len(src.Ptrs) > 0 { 254 | typedList := NewBigCapnList(seg, len(src.Ptrs)) 255 | plist := capn.PointerList(typedList) 256 | i := 0 257 | for _, ele := range src.Ptrs { 258 | plist.Set(i, capn.Object(BigGoToCapn(seg, ele))) 259 | i++ 260 | } 261 | dest.SetPtrs(typedList) 262 | } 263 | 264 | // Straight -> BigCapn (go slice to capn list) 265 | if len(src.Straight) > 0 { 266 | typedList := NewBigCapnList(seg, len(src.Straight)) 267 | plist := capn.PointerList(typedList) 268 | i := 0 269 | for _, ele := range src.Straight { 270 | plist.Set(i, capn.Object(BigGoToCapn(seg, &ele))) 271 | i++ 272 | } 273 | dest.SetStraight(typedList) 274 | } 275 | 276 | return dest 277 | } 278 | ` 279 | cv.So(ExtractString2String(in0), ShouldStartWithModuloWhiteSpace, expect0) 280 | }) 281 | }) 282 | } 283 | -------------------------------------------------------------------------------- /ignorespaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | success = "" 11 | needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." 12 | shouldMatchModulo = "Expected expected string '%s'\n and actual string '%s'\n to match (ignoring %s)\n (but they did not!; first diff at '%s', pos %d); and \nFull diff -b:\n%s\n" 13 | shouldStartWithModulo = "Expected expected PREFIX string '%s'\n to be found at the start of actual string '%s'\n to (ignoring %s)\n (but they did not!; first diff at '%s', pos %d); and \nFull diff -b:\n%s\n" 14 | shouldContainModuloWS = "Expected expected string '%s'\n to contain string '%s'\n (ignoring whitespace)\n (but it did not!)" 15 | shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)." 16 | ) 17 | 18 | // ShouldMatchModulo receives exactly two string parameters and an ignore map. It ensures that the order 19 | // of not-ignored characters in the two strings is identical. Runes specified in the ignore map 20 | // are ignored for the purposes of this string comparison, and each should map to true. 21 | // ShouldMatchModulo thus allows you to do whitespace insensitive comparison, which is very useful 22 | // in lexer/parser work. 23 | // 24 | func ShouldMatchModulo(ignoring map[rune]bool, actual interface{}, expected ...interface{}) string { 25 | if fail := need(1, expected); fail != success { 26 | return fail 27 | } 28 | 29 | value, valueIsString := actual.(string) 30 | expec, expecIsString := expected[0].(string) 31 | 32 | if !valueIsString || !expecIsString { 33 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 34 | } 35 | 36 | equal, vpos, _ := stringsEqualIgnoring(value, expec, ignoring) 37 | if equal { 38 | return success 39 | } else { 40 | // extract the string fragment at the differnce point to make it easier to diagnose 41 | diffpoint := "" 42 | const diffMax = 20 43 | vrune := []rune(value) 44 | n := len(vrune) - vpos 45 | if n > diffMax { 46 | n = diffMax 47 | } 48 | if vpos == 0 { 49 | vpos = 1 50 | } 51 | diffpoint = string(vrune[vpos-1 : (vpos - 1 + n)]) 52 | 53 | diff := Diffb(value, expec) 54 | 55 | ignored := "{" 56 | switch len(ignoring) { 57 | case 0: 58 | return fmt.Sprintf(shouldMatchModulo, expec, value, "nothing", diffpoint, vpos-1, diff) 59 | case 1: 60 | for k := range ignoring { 61 | ignored = ignored + fmt.Sprintf("'%c'", k) 62 | } 63 | ignored = ignored + "}" 64 | return fmt.Sprintf(shouldMatchModulo, expec, value, ignored, diffpoint, vpos-1, diff) 65 | 66 | default: 67 | for k := range ignoring { 68 | ignored = ignored + fmt.Sprintf("'%c', ", k) 69 | } 70 | ignored = ignored + "}" 71 | return fmt.Sprintf(shouldMatchModulo, expec, value, ignored, diffpoint, vpos-1, diff) 72 | } 73 | } 74 | } 75 | 76 | // ShouldMatchModuloSpaces compares two strings but ignores ' ' spaces. 77 | // Serves as an example of use of ShouldMatchModulo. 78 | // 79 | func ShouldMatchModuloSpaces(actual interface{}, expected ...interface{}) string { 80 | if fail := need(1, expected); fail != success { 81 | return fail 82 | } 83 | return ShouldMatchModulo(map[rune]bool{' ': true}, actual, expected[0]) 84 | } 85 | 86 | func ShouldMatchModuloWhiteSpace(actual interface{}, expected ...interface{}) string { 87 | if fail := need(1, expected); fail != success { 88 | return fail 89 | } 90 | return ShouldMatchModulo(map[rune]bool{' ': true, '\n': true, '\t': true}, actual, expected[0]) 91 | } 92 | 93 | func ShouldStartWithModuloWhiteSpace(actual interface{}, expectedPrefix ...interface{}) string { 94 | if fail := need(1, expectedPrefix); fail != success { 95 | return fail 96 | } 97 | 98 | ignoring := map[rune]bool{' ': true, '\n': true, '\t': true} 99 | 100 | value, valueIsString := actual.(string) 101 | expecPrefix, expecIsString := expectedPrefix[0].(string) 102 | 103 | if !valueIsString || !expecIsString { 104 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expectedPrefix[0])) 105 | } 106 | 107 | equal, vpos, _ := hasPrefixEqualIgnoring(value, expecPrefix, ignoring) 108 | if equal { 109 | return success 110 | } else { 111 | diffpoint := "" 112 | const diffMax = 20 113 | vrune := []rune(value) 114 | n := len(vrune) - vpos + 1 115 | if n > diffMax { 116 | n = diffMax 117 | } 118 | beg := vpos - 1 119 | if beg < 0 { 120 | beg = 0 121 | } 122 | diffpoint = string(vrune[beg:(vpos - 1 + n)]) 123 | 124 | diff := Diffb(value, expecPrefix) 125 | 126 | ignored := "{" 127 | switch len(ignoring) { 128 | case 0: 129 | return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, "nothing", diffpoint, vpos-1, diff) 130 | case 1: 131 | for k := range ignoring { 132 | ignored = ignored + fmt.Sprintf("'%c'", k) 133 | } 134 | ignored = ignored + "}" 135 | return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, ignored, diffpoint, vpos-1, diff) 136 | 137 | default: 138 | for k := range ignoring { 139 | ignored = ignored + fmt.Sprintf("'%c', ", k) 140 | } 141 | ignored = ignored + "}" 142 | return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, ignored, diffpoint, vpos-1, diff) 143 | } 144 | } 145 | } 146 | 147 | // returns if equal, and if not then rpos and spos hold the position of first mismatch 148 | func stringsEqualIgnoring(a, b string, ignoring map[rune]bool) (equal bool, rpos int, spos int) { 149 | r := []rune(a) 150 | s := []rune(b) 151 | 152 | nextr := 0 153 | nexts := 0 154 | 155 | for { 156 | // skip past spaces in both r and s 157 | for nextr < len(r) { 158 | if ignoring[r[nextr]] { 159 | nextr++ 160 | } else { 161 | break 162 | } 163 | } 164 | 165 | for nexts < len(s) { 166 | if ignoring[s[nexts]] { 167 | nexts++ 168 | } else { 169 | break 170 | } 171 | } 172 | 173 | if nextr >= len(r) && nexts >= len(s) { 174 | return true, -1, -1 // full match 175 | } 176 | 177 | if nextr >= len(r) { 178 | return false, nextr, nexts 179 | } 180 | if nexts >= len(s) { 181 | return false, nextr, nexts 182 | } 183 | 184 | if r[nextr] != s[nexts] { 185 | return false, nextr, nexts 186 | } 187 | nextr++ 188 | nexts++ 189 | } 190 | 191 | return false, nextr, nexts 192 | } 193 | 194 | // returns if equal, and if not then rpos and spos hold the position of first mismatch 195 | func hasPrefixEqualIgnoring(str, prefix string, ignoring map[rune]bool) (equal bool, spos int, rpos int) { 196 | s := []rune(str) 197 | r := []rune(prefix) 198 | 199 | nextr := 0 200 | nexts := 0 201 | 202 | for { 203 | // skip past spaces in both r and s 204 | for nextr < len(r) { 205 | if ignoring[r[nextr]] { 206 | nextr++ 207 | } else { 208 | break 209 | } 210 | } 211 | 212 | for nexts < len(s) { 213 | if ignoring[s[nexts]] { 214 | nexts++ 215 | } else { 216 | break 217 | } 218 | } 219 | 220 | if nextr >= len(r) && nexts >= len(s) { 221 | return true, -1, -1 // full match 222 | } 223 | 224 | if nextr >= len(r) { 225 | return true, nexts, nextr // for prefix testing 226 | } 227 | if nexts >= len(s) { 228 | return false, nexts, nextr 229 | } 230 | 231 | if r[nextr] != s[nexts] { 232 | return false, nexts, nextr 233 | } 234 | nextr++ 235 | nexts++ 236 | } 237 | 238 | return false, nexts, nextr 239 | } 240 | 241 | func need(needed int, expected []interface{}) string { 242 | if len(expected) != needed { 243 | return fmt.Sprintf(needExactValues, needed, len(expected)) 244 | } 245 | return success 246 | } 247 | 248 | func ShouldContainModuloWhiteSpace(haystack interface{}, expectedNeedle ...interface{}) string { 249 | if fail := need(1, expectedNeedle); fail != success { 250 | return fail 251 | } 252 | 253 | value, valueIsString := haystack.(string) 254 | expecNeedle, expecIsString := expectedNeedle[0].(string) 255 | 256 | if !valueIsString || !expecIsString { 257 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(haystack), reflect.TypeOf(expectedNeedle[0])) 258 | } 259 | 260 | elimWs := func(r rune) rune { 261 | if r == ' ' || r == '\t' || r == '\n' { 262 | return -1 // drop the rune 263 | } 264 | return r 265 | } 266 | 267 | h := strings.Map(elimWs, value) 268 | n := strings.Map(elimWs, expecNeedle) 269 | 270 | if strings.Contains(h, n) { 271 | return success 272 | } 273 | 274 | return fmt.Sprintf(shouldContainModuloWS, value, expecNeedle) 275 | } 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bambam: auto-generate capnproto schema from your golang source files. 2 | ====== 3 | 4 | Adding [capnproto serialization](https://github.com/glycerine/go-capnproto) to an existing Go project used to mean writing a lot of boilerplate. 5 | 6 | Not anymore. 7 | 8 | Given a set of golang (Go) source files, bambam will generate a [capnproto](http://kentonv.github.io/capnproto/) schema. Even better: bambam will also generate translation functions to readily convert between your golang structs and the new capnproto structs. 9 | 10 | prereqs 11 | ------- 12 | 13 | You'll need a recent (up-to-date) version of go-capnproto. If you installed go-capnproto before, you'll want to update it [>= f9f239fc7f5ad9611cf4e88b10080a4b47c3951d / 16 Nov 2014]. 14 | 15 | [Capnproto](http://kentonv.github.io/capnproto/) and [go-capnproto](https://github.com/glycerine/go-capnproto) should both be installed and on your PATH. 16 | 17 | to install: *run make*. This lets us record the git commit in LASTGITCOMMITHASH to provide accurate version info. Otherwise you'll get an 'undefined: LASTGITCOMMITHASH' failure. 18 | -------- 19 | ~~~ 20 | # be sure go-capnproto and capnpc are installed first. 21 | 22 | $ go get -t github.com/glycerine/bambam # the -t pulls in the test dependencies. 23 | 24 | # ignore the initial compile error about 'undefined: LASTGITCOMMITHASH'. `make` will fix that. 25 | $ cd $GOPATH/src/github.com/glycerine/bambam 26 | $ make # runs tests, build if all successful 27 | $ go install 28 | ~~~ 29 | 30 | 31 | use 32 | --------- 33 | 34 | ~~~ 35 | use: bambam -o outdir -p package myGoSourceFile.go myGoSourceFile2.go ... 36 | # Bambam makes it easy to use Capnproto serialization[1] from Go. 37 | # Bambam reads .go files and writes a .capnp schema and Go bindings. 38 | # options: 39 | # -o="odir" specifies the directory to write to (created if need be). 40 | # -p="main" specifies the package header to write (e.g. main, mypkg). 41 | # -X exports private fields of Go structs. Default only maps public fields. 42 | # -version shows build version with git commit hash 43 | # -OVERWRITE modify .go files in-place, adding capid tags (write to -o dir by default). 44 | # required: at least one .go source file for struct definitions. Must be last, after options. 45 | # 46 | # [1] https://github.com/glycerine/go-capnproto 47 | ~~~ 48 | 49 | demo 50 | ----- 51 | 52 | See rw.go.txt. To see all the files compiled together in one project: (a) comment out the defer in the rw_test.go file; (b) run `go test`; (c) then `cd testdir_*` and look at the sample project files there. (d). run `go build` in the testdir_ to rebuild the binary. Notice that you will need all three .go files to successfully build. The two .capnp files should be kept so you can read your data from any capnp-supported language. Here's what is what in that example directory: 53 | 54 | ~~~ 55 | rw.go # your original go source file (in this test) 56 | translateCapn.go # generated by bambam after reading rw.go 57 | schema.capnp # generated by bambam after reading rw.go 58 | schema.capnp.go # generated by `capnpc -ogo schema.capnp` <- you have to do this yourself or in your Makefile. 59 | go.capnp # always necessary boilerplate to let capnpc work, just copy it from bambam/go.capnp to your build dir. 60 | ~~~ 61 | 62 | example: 63 | 64 | ~~~ 65 | jaten@c03:~/go/src/github.com/glycerine/bambam:master$ cd testdir_884497362/ 66 | jaten@c03:~/go/src/github.com/glycerine/bambam/testdir_884497362:master$ go build 67 | jaten@c03:~/go/src/github.com/glycerine/bambam/testdir_884497362:master$ ls 68 | go.capnp rw.go rw.go.txt schema.capnp schema.capnp.go testdir_884497362 translateCapn.go 69 | jaten@c03:~/go/src/github.com/glycerine/bambam/testdir_884497362:master$ ./testdir_884497362 70 | Load() data matched Saved() data. 71 | jaten@c03:~/go/src/github.com/glycerine/bambam/testdir_884497362:master$ # run was successful 72 | ~~~ 73 | 74 | Here is what it looks like to use the Save()/Load() methods. You end up with a Save() and Load() function for each of your structs. Simple. 75 | 76 | ~~~ 77 | package main 78 | 79 | import ( 80 | "bytes" 81 | ) 82 | 83 | // 84 | // By default bambam will add the `capid` tags 85 | // to a copy of your source in the output directory. 86 | // Use bambam -OVERWRITE to modify files directly in-place. 87 | // The capid tags control the @0, @1, field numbering 88 | // in the generated capnproto schema. If you change 89 | // your go structs, the capid tags let your schema 90 | // stay backwards compatible with prior serializations. 91 | // 92 | type MyStruct struct { 93 | Hello []string `capid:"0"` 94 | World []int `capid:"1"` 95 | } 96 | 97 | func main() { 98 | 99 | rw := MyStruct{ 100 | Hello: []string{"one", "two", "three"}, 101 | World: []int{1, 2, 3}, 102 | } 103 | 104 | // any io.ReadWriter will work here (os.File, etc) 105 | var o bytes.Buffer 106 | 107 | rw.Save(&o) 108 | // now we have saved! 109 | 110 | 111 | rw2 := &MyStruct{} 112 | rw2.Load(&o) 113 | // now we have restored! 114 | 115 | } 116 | 117 | ~~~ 118 | 119 | what Go types does bambam recognize? 120 | ---------------------------------------- 121 | 122 | Supported: structs, slices, and primitive/scalar types are supported. Structs that contain structs are supported. You have both slices of scalars (e.g. `[]int`) and slices of structs (e.g. `[]MyStruct`) available. 123 | 124 | We handle `[][]T`, but not `[][][]T`, where `T` is a struct or primitive type. The need for triply nested slices is expected to be rare. Interpose a struct after two slices if you need to go deeper. 125 | 126 | Currently unsupported (pull requests welcome): Go maps. 127 | 128 | Also: pointers to structs to be serialized work, but pointers in the inner-most struct do not. This is not a big limitation, as it is rarely meaningful to pass a pointer value to a different process. 129 | 130 | 131 | capid tags on go structs 132 | -------------------------- 133 | 134 | When you run `bambam`, it will generate a modified copy of your go source files in the output directory. 135 | 136 | These new versions include capid tags on all public fields of structs. You should inspect the copy of the source file in the output directory, and then replace your original source with the tagged version. You can also manually add capid tags to fields, if you need to manually specify a field number (e.g. you are matching an pre-existing capnproto definition). 137 | 138 | If you are feeling especially bold, `bambam -OVERWRITE my.go` will replace my.go with the capid tagged version. For safety, only do this on backed-up and version controlled source files. 139 | 140 | By default only public fields (with a Capital first letter in their name) are tagged. The -X flag ignores the public/private distinction, and tags all fields. 141 | 142 | The capid tags allow the capnproto schema evolution to function properly as you add new fields to structs. If you don't include the capid tags, your serialization code won't be backwards compatible as you change your structs. 143 | 144 | Deleting fields from your go structs isn't (currently) particularly well-supported. We could potentially allow fields to be // commented out in the go source and yet still parse the comments and use that parse to keep the schema correct, but that's not a trivial bit of work. 145 | 146 | example of capid annotion use 147 | ~~~ 148 | type Job struct { 149 | C int `capid:"2"` // we added C later, thus it is numbered higher. 150 | A int `capid:"0"` 151 | B int `capid:"1"` 152 | } 153 | ~~~ 154 | 155 | other tags 156 | ---------- 157 | 158 | Also available tags: `capid:"skip"` or `capid:"-1"` (any negative number): this field will be skipped and not serialized or written to the schema. 159 | 160 | ~~~ 161 | // capname:"Counter" 162 | type number struct { 163 | A int 164 | } 165 | ~~~ 166 | 167 | The above struct will be mapped into capnproto as: 168 | 169 | ~~~ 170 | struct Counter { 171 | a @0: Int64; 172 | } 173 | ~~~ 174 | 175 | Without the `// capname:"Counter"` comment, you would get: 176 | 177 | ~~~ 178 | struct NumberCapn { 179 | a @0: Int64; 180 | } 181 | ~~~ 182 | 183 | Explanation: Using a `// capname:"newName"` comment on the line right before a struct definition will cause `bambam` to use 'newName' as the name for the corresponding struct in the capnproto schema. Otherwise the corresponding struct will simply uppercase the first letter of the orignal Go struct, and append "Capn". For example: a Go struct called `number` would induce a parallel generated capnp struct called `NumberCapn`. 184 | 185 | windows build script 186 | --------------------------- 187 | see `build.cmd`. Thanks to Klaus Post (http://klauspost.com) for contributing this. 188 | 189 | ----- 190 | ----- 191 | 192 | Copyright (c) 2015, Jason E. Aten, Ph.D. 193 | 194 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | cv "github.com/glycerine/goconvey/convey" 7 | ) 8 | 9 | func TestSliceToList(t *testing.T) { 10 | 11 | cv.Convey("Given a parsable golang source file with struct containing a slice", t, func() { 12 | cv.Convey("then the slice should be converted to a List() in the capnp output", func() { 13 | 14 | ex0 := ` 15 | type s1 struct { 16 | MyInts []int 17 | }` 18 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct S1Capn { myInts @0: List(Int64); } `) 19 | 20 | }) 21 | }) 22 | } 23 | 24 | func TestSliceOfStructToList(t *testing.T) { 25 | 26 | cv.Convey("Given a parsable golang source file with struct containing a slice of struct bbb", t, func() { 27 | cv.Convey("then the slice should be converted to a List(Bbb) in the capnp output", func() { 28 | 29 | ex0 := ` 30 | type bbb struct {} 31 | type s1 struct { 32 | MyBees []bbb 33 | }` 34 | out0 := ExtractString2String(ex0) 35 | 36 | VPrintf("out0: '%s'\n", out0) 37 | 38 | cv.So(out0, ShouldStartWithModuloWhiteSpace, `struct BbbCapn { } struct S1Capn { myBees @0: List(BbbCapn); } `) 39 | 40 | }) 41 | }) 42 | } 43 | 44 | func TestSliceOfPointerToList(t *testing.T) { 45 | 46 | cv.Convey("Given a parsable golang source file with struct containing a slice of pointers to struct big", t, func() { 47 | cv.Convey("then the slice should be converted to a List(Big) in the capnp output", func() { 48 | 49 | ex0 := ` 50 | type big struct {} 51 | type s1 struct { 52 | MyBigs []*big 53 | }` 54 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct BigCapn { } struct S1Capn { myBigs @0: List(BigCapn); } `) 55 | 56 | }) 57 | }) 58 | } 59 | 60 | func TestSliceOfByteBecomesData(t *testing.T) { 61 | 62 | cv.Convey("Given golang src with []byte", t, func() { 63 | cv.Convey("then the slice should be converted to Data, not List(Byte), in the capnp output", func() { 64 | 65 | ex0 := ` 66 | type s1 struct { 67 | MyData []byte 68 | }` 69 | cv.So(ExtractString2String(ex0), ShouldStartWithModuloWhiteSpace, `struct S1Capn { myData @0: List(UInt8); } `) 70 | 71 | }) 72 | }) 73 | } 74 | 75 | func TestStructWithSliceOfOtherStructs(t *testing.T) { 76 | 77 | cv.Convey("Given a go struct containing MyBigs []Big, where Big is another struct", t, func() { 78 | cv.Convey("then then the CapnToGo() translation code should call the CapnToGo translation function over each slice member during translation", func() { 79 | 80 | in0 := ` 81 | type Big struct {} 82 | type s1 struct { 83 | MyBigs []Big 84 | }` 85 | 86 | expect0 := ` 87 | struct BigCapn { } 88 | struct S1Capn { myBigs @0: List(BigCapn); } 89 | 90 | 91 | func (s *Big) Save(w io.Writer) error { 92 | seg := capn.NewBuffer(nil) 93 | BigGoToCapn(seg, s) 94 | _, err := seg.WriteTo(w) 95 | return err 96 | } 97 | 98 | 99 | 100 | func (s *Big) Load(r io.Reader) error { 101 | capMsg, err := capn.ReadFromStream(r, nil) 102 | if err != nil { 103 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 104 | return err 105 | } 106 | z := ReadRootBigCapn(capMsg) 107 | BigCapnToGo(z, s) 108 | return nil 109 | } 110 | 111 | func BigCapnToGo(src BigCapn, dest *Big) *Big { 112 | if dest == nil { 113 | dest = &Big{} 114 | } 115 | return dest 116 | } 117 | func BigGoToCapn(seg *capn.Segment, src *Big) BigCapn { 118 | dest := AutoNewBigCapn(seg) 119 | return dest 120 | } 121 | 122 | 123 | func (s *s1) Save(w io.Writer) error { 124 | seg := capn.NewBuffer(nil) 125 | s1GoToCapn(seg, s) 126 | _, err := seg.WriteTo(w) 127 | return err 128 | } 129 | 130 | 131 | 132 | func (s *s1) Load(r io.Reader) error { 133 | capMsg, err := capn.ReadFromStream(r, nil) 134 | if err != nil { 135 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 136 | return err 137 | } 138 | z := ReadRootS1Capn(capMsg) 139 | S1CapnToGo(z, s) 140 | return nil 141 | } 142 | 143 | func S1CapnToGo(src S1Capn, dest *s1) *s1 { 144 | if dest == nil { 145 | dest = &s1{} 146 | } 147 | var n int 148 | 149 | // MyBigs 150 | n = src.MyBigs().Len() 151 | dest.MyBigs = make([]Big, n) 152 | for i := 0; i < n; i++ { 153 | dest.MyBigs[i] = *BigCapnToGo(src.MyBigs().At(i), nil) 154 | } 155 | 156 | ` 157 | cv.So(ExtractString2String(in0), ShouldStartWithModuloWhiteSpace, expect0) 158 | 159 | }) 160 | }) 161 | } 162 | 163 | // ========================================== 164 | // ========================================== 165 | 166 | func Test008SliceOfSliceOfStruct(t *testing.T) { 167 | 168 | cv.Convey("Given a go struct a slice of slice of int: type Cooper struct { Formation [][]Mini } ", t, func() { 169 | cv.Convey("then then List(List(Mini)) should be generated in the capnp schema", func() { 170 | 171 | in0 := ` 172 | type Mini struct { 173 | A int64 174 | } 175 | type Cooper struct { 176 | Downey []Mini 177 | Formation [][]Mini 178 | }` 179 | 180 | expect0 := ` 181 | struct CooperCapn { 182 | downey @0: List(MiniCapn); 183 | formation @1: List(List(MiniCapn)); 184 | } 185 | 186 | struct MiniCapn { 187 | a @0: Int64; 188 | } 189 | 190 | 191 | func (s *Cooper) Save(w io.Writer) error { 192 | seg := capn.NewBuffer(nil) 193 | CooperGoToCapn(seg, s) 194 | _, err := seg.WriteTo(w) 195 | return err 196 | } 197 | 198 | 199 | 200 | func (s *Cooper) Load(r io.Reader) error { 201 | capMsg, err := capn.ReadFromStream(r, nil) 202 | if err != nil { 203 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 204 | return err 205 | } 206 | z := ReadRootCooperCapn(capMsg) 207 | CooperCapnToGo(z, s) 208 | return nil 209 | } 210 | 211 | 212 | 213 | func CooperCapnToGo(src CooperCapn, dest *Cooper) *Cooper { 214 | if dest == nil { 215 | dest = &Cooper{} 216 | } 217 | 218 | var n int 219 | 220 | // Downey 221 | n = src.Downey().Len() 222 | dest.Downey = make([]Mini, n) 223 | for i := 0; i < n; i++ { 224 | dest.Downey[i] = *MiniCapnToGo(src.Downey().At(i), nil) 225 | } 226 | 227 | 228 | // Formation 229 | n = src.Formation().Len() 230 | dest.Formation = make([][]Mini, n) 231 | for i := 0; i < n; i++ { 232 | dest.Formation[i] = MiniCapnListToSliceMini(MiniCapn_List(src.Formation().At(i))) 233 | } 234 | 235 | return dest 236 | } 237 | 238 | 239 | 240 | func CooperGoToCapn(seg *capn.Segment, src *Cooper) CooperCapn { 241 | dest := AutoNewCooperCapn(seg) 242 | 243 | // Downey -> MiniCapn (go slice to capn list) 244 | if len(src.Downey) > 0 { 245 | typedList := NewMiniCapnList(seg, len(src.Downey)) 246 | plist := capn.PointerList(typedList) 247 | i := 0 248 | for _, ele := range src.Downey { 249 | plist.Set(i, capn.Object(MiniGoToCapn(seg, &ele))) 250 | i++ 251 | } 252 | dest.SetDowney(typedList) 253 | } 254 | 255 | // Formation -> MiniCapn (go slice to capn list) 256 | if len(src.Formation) > 0 { 257 | plist := seg.NewPointerList(len(src.Formation)) 258 | i := 0 259 | for _, ele := range src.Formation { 260 | plist.Set(i, capn.Object(SliceMiniToMiniCapnList(seg, ele))) 261 | i++ 262 | } 263 | dest.SetFormation(plist) 264 | } 265 | 266 | return dest 267 | } 268 | 269 | 270 | 271 | func (s *Mini) Save(w io.Writer) error { 272 | seg := capn.NewBuffer(nil) 273 | MiniGoToCapn(seg, s) 274 | _, err := seg.WriteTo(w) 275 | return err 276 | } 277 | 278 | 279 | 280 | func (s *Mini) Load(r io.Reader) error { 281 | capMsg, err := capn.ReadFromStream(r, nil) 282 | if err != nil { 283 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 284 | return err 285 | } 286 | z := ReadRootMiniCapn(capMsg) 287 | MiniCapnToGo(z, s) 288 | return nil 289 | } 290 | 291 | 292 | 293 | func MiniCapnToGo(src MiniCapn, dest *Mini) *Mini { 294 | if dest == nil { 295 | dest = &Mini{} 296 | } 297 | dest.A = src.A() 298 | 299 | return dest 300 | } 301 | 302 | 303 | 304 | func MiniGoToCapn(seg *capn.Segment, src *Mini) MiniCapn { 305 | dest := AutoNewMiniCapn(seg) 306 | dest.SetA(src.A) 307 | 308 | return dest 309 | } 310 | 311 | 312 | func SliceMiniToMiniCapnList(seg *capn.Segment, m []Mini) MiniCapn_List { 313 | lst := NewMiniCapnList(seg, len(m)) 314 | for i := range m { 315 | lst.Set(i, MiniGoToCapn(seg, &m[i])) 316 | } 317 | return lst 318 | } 319 | 320 | 321 | 322 | func MiniCapnListToSliceMini(p MiniCapn_List) []Mini { 323 | v := make([]Mini, p.Len()) 324 | for i := range v { 325 | MiniCapnToGo(p.At(i), &v[i]) 326 | } 327 | return v 328 | } 329 | 330 | ` 331 | cv.So(ExtractString2String(in0), ShouldMatchModuloWhiteSpace, expect0) 332 | 333 | }) 334 | }) 335 | } 336 | 337 | // ========================================== 338 | // ========================================== 339 | 340 | func Test009SliceOfSliceOfInt(t *testing.T) { 341 | 342 | cv.Convey("Given a go struct a slice of slice: type Cooper struct { A [][]int } ", t, func() { 343 | cv.Convey("then then List(List(Int64)) should be generated in the capnp schema", func() { 344 | 345 | in0 := ` 346 | type Cooper struct { 347 | A [][]int 348 | }` 349 | 350 | expect0 := ` 351 | struct CooperCapn { 352 | a @0: List(List(Int64)); 353 | } 354 | 355 | func (s *Cooper) Save(w io.Writer) error { 356 | seg := capn.NewBuffer(nil) 357 | CooperGoToCapn(seg, s) 358 | _, err := seg.WriteTo(w) 359 | return err 360 | } 361 | 362 | func (s *Cooper) Load(r io.Reader) error { 363 | capMsg, err := capn.ReadFromStream(r, nil) 364 | if err != nil { 365 | //panic(fmt.Errorf("capn.ReadFromStream error: %s", err)) 366 | return err 367 | } 368 | z := ReadRootCooperCapn(capMsg) 369 | CooperCapnToGo(z, s) 370 | return nil 371 | } 372 | 373 | func CooperCapnToGo(src CooperCapn, dest *Cooper) *Cooper { 374 | if dest == nil { 375 | dest = &Cooper{} 376 | } 377 | 378 | var n int 379 | 380 | // A 381 | n = src.A().Len() 382 | dest.A = make([][]int, n) 383 | for i := 0; i < n; i++ { 384 | dest.A[i] = Int64ListToSliceInt(capn.Int64List(src.A().At(i))) 385 | } 386 | 387 | return dest 388 | } 389 | 390 | func CooperGoToCapn(seg *capn.Segment, src *Cooper) CooperCapn { 391 | dest := AutoNewCooperCapn(seg) 392 | 393 | mylist1 := seg.NewPointerList(len(src.A)) 394 | for i := range src.A { 395 | mylist1.Set(i, capn.Object(SliceIntToInt64List(seg, src.A[i]))) 396 | } 397 | dest.SetA(mylist1) 398 | 399 | return dest 400 | } 401 | 402 | func SliceIntToInt64List(seg *capn.Segment, m []int) capn.Int64List { 403 | lst := seg.NewInt64List(len(m)) 404 | for i := range m { 405 | lst.Set(i, int64(m[i])) 406 | } 407 | return lst 408 | } 409 | 410 | func Int64ListToSliceInt(p capn.Int64List) []int { 411 | v := make([]int, p.Len()) 412 | for i := range v { 413 | v[i] = int(p.At(i)) 414 | } 415 | return v 416 | } 417 | ` 418 | 419 | cv.So(ExtractString2String(in0), ShouldStartWithModuloWhiteSpace, expect0) 420 | 421 | }) 422 | }) 423 | } 424 | -------------------------------------------------------------------------------- /bam.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/parser" 8 | "go/token" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "os/exec" 13 | "regexp" 14 | "sort" 15 | "strconv" 16 | "strings" 17 | "unicode" 18 | ) 19 | 20 | type Extractor struct { 21 | fieldCount int 22 | out bytes.Buffer 23 | pkgName string 24 | importDecl string 25 | fieldPrefix string 26 | fieldSuffix string 27 | 28 | curStruct *Struct 29 | heldComment string 30 | extractPrivate bool 31 | 32 | // map structs' goName <-> capName 33 | goType2capTypeCache map[string]string 34 | capType2goType map[string]string 35 | 36 | // key is goName 37 | srs map[string]*Struct 38 | ToGoCode map[string][]byte 39 | ToCapnCode map[string][]byte 40 | SaveCode map[string][]byte 41 | LoadCode map[string][]byte 42 | 43 | // key is CanonGoType(goTypeSeq) 44 | SliceToListCode map[string][]byte 45 | ListToSliceCode map[string][]byte 46 | 47 | compileDir *TempDir 48 | outDir string 49 | srcFiles []*SrcFile 50 | overwrite bool 51 | 52 | // fields for testing capid tagging 53 | PubABC int `capid:"1"` 54 | PubYXZ int `capid:"0"` 55 | PubDEF int 56 | } 57 | 58 | func NewExtractor() *Extractor { 59 | return &Extractor{ 60 | pkgName: "testpkg", 61 | importDecl: "testpkg", 62 | goType2capTypeCache: make(map[string]string), 63 | capType2goType: make(map[string]string), 64 | 65 | // key is goTypeName 66 | ToGoCode: make(map[string][]byte), 67 | ToCapnCode: make(map[string][]byte), 68 | SaveCode: make(map[string][]byte), 69 | LoadCode: make(map[string][]byte), 70 | srs: make(map[string]*Struct), 71 | compileDir: NewTempDir(), 72 | srcFiles: make([]*SrcFile, 0), 73 | SliceToListCode: make(map[string][]byte), 74 | ListToSliceCode: make(map[string][]byte), 75 | } 76 | } 77 | 78 | func (x *Extractor) Cleanup() { 79 | if x.compileDir != nil { 80 | x.compileDir.Cleanup() 81 | } 82 | } 83 | 84 | type Field struct { 85 | capname string 86 | goCapGoName string // Uppercased-first-letter of capname, as generated in go bindings. 87 | goCapGoType string // int64 when goType is int, because capType is Int64. 88 | capType string 89 | goName string 90 | goType string 91 | goTypePrefix string 92 | goToCapFunc string 93 | 94 | goTypeSeq []string 95 | capTypeSeq []string 96 | goCapGoTypeSeq []string 97 | 98 | tagValue string 99 | isList bool 100 | capIdFromTag int 101 | orderOfAppearance int 102 | finalOrder int 103 | embedded bool 104 | astField *ast.Field 105 | canonGoType string // key into SliceToListCode and ListToSliceCode 106 | canonGoTypeListToSliceFunc string 107 | canonGoTypeSliceToListFunc string 108 | singleCapListType string 109 | baseIsIntrinsic bool 110 | newListExpression string 111 | } 112 | 113 | type Struct struct { 114 | capName string 115 | goName string 116 | fld []*Field 117 | longestField int 118 | comment string 119 | capIdMap map[int]*Field 120 | firstNonTextListSeen bool 121 | listNum int 122 | } 123 | 124 | type SrcFile struct { 125 | filename string 126 | fset *token.FileSet 127 | astFile *ast.File 128 | } 129 | 130 | func (s *Struct) computeFinalOrder() { 131 | 132 | // assign Field.finalOrder to all values in s.fld, from 0..(len(s.fld)-1) 133 | max := len(s.fld) - 1 134 | // check for out of bounds requests 135 | for _, f := range s.fld { 136 | if f.capIdFromTag > max { 137 | err := fmt.Errorf(`problem in capid tag '%s' on field '%s' in struct '%s': number '%d' is beyond the count of fields we have, largest available is %d`, f.tagValue, f.goName, s.goName, f.capIdFromTag, max) 138 | panic(err) 139 | } 140 | } 141 | 142 | // only one field? already done 143 | if len(s.fld) == 1 { 144 | s.fld[0].finalOrder = 0 145 | return 146 | } 147 | // INVAR: no duplicate requests, and all are in bounds. 148 | 149 | // wipe slate clean 150 | for _, f := range s.fld { 151 | f.finalOrder = -1 152 | } 153 | 154 | final := make([]*Field, len(s.fld)) 155 | 156 | // assign from map 157 | for _, v := range s.capIdMap { 158 | v.finalOrder = v.capIdFromTag 159 | final[v.capIdFromTag] = v 160 | } 161 | 162 | appear := make([]*Field, len(s.fld)) 163 | copy(appear, s.fld) 164 | 165 | sort.Sort(ByOrderOfAppearance(appear)) 166 | 167 | //find next available slot, and fill in, in order of appearance 168 | write := 0 169 | 170 | for read := 0; read <= max; read++ { 171 | if appear[read].finalOrder != -1 { 172 | continue 173 | } 174 | // appear[read] needs an assignment 175 | 176 | done := advanceWrite(&write, final) 177 | if !done { 178 | // final[write] has a slot for an assignment 179 | final[write] = appear[read] 180 | final[write].finalOrder = write 181 | } 182 | 183 | } 184 | } 185 | 186 | // returns true if done. if false, then final[*pw] is available for writing. 187 | func advanceWrite(pw *int, final []*Field) bool { 188 | for n := len(final); *pw < n; (*pw)++ { 189 | if final[*pw] == nil { // .finalOrder == -1 { 190 | return false 191 | } 192 | } 193 | return true 194 | } 195 | 196 | func NewStruct(capName, goName string) *Struct { 197 | return &Struct{ 198 | capName: capName, 199 | goName: goName, 200 | fld: []*Field{}, 201 | capIdMap: map[int]*Field{}, 202 | } 203 | } 204 | 205 | func (x *Extractor) GenerateTranslators() { 206 | 207 | for _, s := range x.srs { 208 | 209 | x.SaveCode[s.goName] = []byte(fmt.Sprintf(` 210 | func (s *%s) Save(w io.Writer) error { 211 | seg := capn.NewBuffer(nil) 212 | %sGoToCapn(seg, s) 213 | _, err := seg.WriteTo(w) 214 | return err 215 | } 216 | `, s.goName, s.goName)) 217 | 218 | x.LoadCode[s.goName] = []byte(fmt.Sprintf(` 219 | func (s *%s) Load(r io.Reader) error { 220 | capMsg, err := capn.ReadFromStream(r, nil) 221 | if err != nil { 222 | //panic(fmt.Errorf("capn.ReadFromStream error: %%s", err)) 223 | return err 224 | } 225 | z := ReadRoot%s(capMsg) 226 | %sToGo(z, s) 227 | return nil 228 | } 229 | `, s.goName, s.capName, s.capName)) 230 | 231 | x.ToGoCode[s.goName] = []byte(fmt.Sprintf(` 232 | func %sToGo(src %s, dest *%s) *%s { 233 | if dest == nil { 234 | dest = &%s{} 235 | } 236 | %s 237 | return dest 238 | } 239 | `, s.capName, s.capName, s.goName, s.goName, s.goName, x.SettersToGo(s.goName))) 240 | 241 | x.ToCapnCode[s.goName] = []byte(fmt.Sprintf(` 242 | func %sGoToCapn(seg *capn.Segment, src *%s) %s { 243 | dest := AutoNew%s(seg) 244 | %s 245 | return dest 246 | } 247 | `, s.goName, s.goName, s.capName, s.capName, x.SettersToCapn(s.goName))) 248 | 249 | } 250 | } 251 | 252 | func (x *Extractor) packageDot() string { 253 | if x.pkgName == "" || x.pkgName == "main" { 254 | return "" 255 | } 256 | return x.pkgName + "." 257 | } 258 | 259 | func (x *Extractor) SettersToGo(goName string) string { 260 | var buf bytes.Buffer 261 | myStruct := x.srs[goName] 262 | if myStruct == nil { 263 | panic(fmt.Sprintf("bad goName '%s'", goName)) 264 | } 265 | VPrintf("\n\n SettersToGo running on myStruct = %#v\n", myStruct) 266 | i := 0 267 | for _, f := range myStruct.fld { 268 | VPrintf("\n\n SettersToGo running on myStruct.fld[%d] = %#v\n", i, f) 269 | 270 | n := len(f.goTypeSeq) 271 | 272 | if n >= 2 && f.goTypeSeq[0] == "[]" { 273 | x.SettersToGoListHelper(&buf, myStruct, f) 274 | } else { 275 | 276 | var isCapType bool = false 277 | if _, ok := x.goType2capTypeCache[f.goTypeSeq[0]]; !ok { 278 | if len(f.goTypeSeq) > 1 { 279 | if _, ok := x.goType2capTypeCache[f.goTypeSeq[1]]; ok { 280 | isCapType = true 281 | } 282 | } 283 | } else { 284 | isCapType = true 285 | } 286 | 287 | if isCapType { 288 | if f.goTypeSeq[0] == "*" { 289 | fmt.Fprintf(&buf, " dest.%s = %sCapnToGo(src.%s(), nil)\n", 290 | f.goName, f.goType, f.goCapGoName) 291 | } else { 292 | fmt.Fprintf(&buf, " dest.%s = *%sCapnToGo(src.%s(), nil)\n", 293 | f.goName, f.goType, f.goCapGoName) 294 | } 295 | 296 | continue 297 | } 298 | 299 | switch f.goType { 300 | case "int": 301 | fmt.Fprintf(&buf, " dest.%s = int(src.%s())\n", f.goName, f.goCapGoName) 302 | 303 | default: 304 | fmt.Fprintf(&buf, " dest.%s = src.%s()\n", f.goName, f.goCapGoName) 305 | } 306 | } 307 | i++ 308 | } 309 | return string(buf.Bytes()) 310 | } 311 | 312 | func (x *Extractor) SettersToGoListHelper(buf io.Writer, myStruct *Struct, f *Field) { 313 | 314 | VPrintf("\n in SettersToGoListHelper(): debug: field f = %#v\n", f) 315 | VPrintf("\n in SettersToGoListHelper(): debug: myStruct = %#v\n", myStruct) 316 | 317 | // special case Text / string slices 318 | if f.capType == "List(Text)" { 319 | fmt.Fprintf(buf, " dest.%s = src.%s().ToArray()\n", f.goName, f.goCapGoName) 320 | return 321 | } 322 | if !myStruct.firstNonTextListSeen { 323 | fmt.Fprintf(buf, "\n var n int\n") 324 | myStruct.firstNonTextListSeen = true 325 | } 326 | // add a dereference (*) in from of the ToGo() invocation for go types that aren't pointers. 327 | addStar := "*" 328 | if isPointerType(f.goTypePrefix) { 329 | addStar = "" 330 | } 331 | 332 | fmt.Fprintf(buf, ` 333 | // %s 334 | n = src.%s().Len() 335 | dest.%s = make(%s%s, n) 336 | for i := 0; i < n; i++ { 337 | dest.%s[i] = %s 338 | } 339 | 340 | `, f.goName, f.goCapGoName, f.goName, f.goTypePrefix, f.goType, f.goName, x.ElemStarCapToGo(addStar, f)) 341 | 342 | } 343 | 344 | func (x *Extractor) ElemStarCapToGo(addStar string, f *Field) string { 345 | f.goToCapFunc = x.goToCapTypeFunction(f.capTypeSeq) 346 | 347 | VPrintf("\n\n f = %#v addStar = '%v' f.goToCapFunc = '%s'\n", f, addStar, f.goToCapFunc) 348 | 349 | // list of list special handling, try to generalize it, as it needs 350 | // to work for intrinsics and structs 351 | if f.goTypePrefix == "[][]" { 352 | VPrintf("\n slice of slice / ListList detected.\n") 353 | return fmt.Sprintf("%s(%s(src.%s().At(i)))", f.canonGoTypeListToSliceFunc, f.singleCapListType, f.goName) 354 | } 355 | 356 | if IsIntrinsicGoType(f.goType) { 357 | VPrintf("\n intrinsic detected.\n") 358 | return fmt.Sprintf("%s(src.%s().At(i))", f.goType, f.goName) 359 | } else { 360 | VPrintf("\n non-intrinsic detected. f.goType = '%v'\n", f.goType) 361 | return fmt.Sprintf("%s%sToGo(src.%s().At(i), nil)", addStar, f.goToCapFunc, f.goName) 362 | } 363 | } 364 | 365 | func (x *Extractor) goToCapTypeFunction(capTypeSeq []string) string { 366 | var r string 367 | for _, s := range capTypeSeq { 368 | if s != "*" && s != "List" { 369 | r = r + s 370 | } 371 | } 372 | return r 373 | } 374 | 375 | func isPointerType(goTypePrefix string) bool { 376 | if len(goTypePrefix) == 0 { 377 | return false 378 | } 379 | prefix := []rune(goTypePrefix) 380 | if prefix[len(prefix)-1] == '*' { 381 | return true 382 | } 383 | return false 384 | } 385 | 386 | func last(slc []string) string { 387 | n := len(slc) 388 | if n == 0 { 389 | panic("last of empty slice undefined") 390 | } 391 | return slc[n-1] 392 | } 393 | 394 | func IsDoubleList(f *Field) bool { 395 | if len(f.capTypeSeq) > 2 { 396 | if f.capTypeSeq[0] == f.capTypeSeq[1] && f.capTypeSeq[1] == "List" { 397 | return true 398 | } 399 | } 400 | return false 401 | } 402 | 403 | func (x *Extractor) SettersToCapn(goName string) string { 404 | var buf bytes.Buffer 405 | t := x.srs[goName] 406 | if t == nil { 407 | panic(fmt.Sprintf("bad goName '%s'", goName)) 408 | } 409 | VPrintf("\n\n SettersToCapn running on myStruct = %#v\n", t) 410 | for i, f := range t.fld { 411 | VPrintf("\n\n SettersToCapn running on t.fld[%d] = %#v\n", i, f) 412 | 413 | if f.isList { 414 | t.listNum++ 415 | if IsIntrinsicGoType(f.goType) { 416 | VPrintf("\n intrinsic detected in SettersToCapn.\n") 417 | 418 | if IsDoubleList(f) { 419 | VPrintf("\n yes IsDoubleList(f: '%s') in SettersToCapn.\n", f.goName) 420 | 421 | fmt.Fprintf(&buf, ` 422 | 423 | mylist%d := seg.NewPointerList(len(src.%s)) 424 | for i := range src.%s { 425 | mylist%d.Set(i, capn.Object(%s(seg, src.%s[i]))) 426 | } 427 | dest.Set%s(mylist%d) 428 | `, t.listNum, f.goName, f.goName, t.listNum, f.canonGoTypeSliceToListFunc, f.goName, f.goCapGoName, t.listNum) 429 | 430 | } else { 431 | VPrintf("\n\n at non-List(List()), yes intrinsic list in SettersToCap(): f = %#v\n", f) 432 | fmt.Fprintf(&buf, ` 433 | 434 | mylist%d := seg.New%sList(len(src.%s)) 435 | for i := range src.%s { 436 | mylist%d.Set(i, %s(src.%s[i])) 437 | } 438 | dest.Set%s(mylist%d) 439 | `, t.listNum, last(f.capTypeSeq), f.goName, f.goName, t.listNum, last(f.goCapGoTypeSeq), f.goName, f.goCapGoName, t.listNum) 440 | } 441 | } else { 442 | // handle list of struct 443 | VPrintf("\n\n at struct list in SettersToCap(): f = %#v\n", f) 444 | addAmpersand := "&" 445 | if isPointerType(f.goTypePrefix) { 446 | addAmpersand = "" 447 | } 448 | 449 | // list of list needs special casing 450 | if isListList(f.goTypePrefix) { 451 | fmt.Fprintf(&buf, ` 452 | // %s -> %s (go slice to capn list) 453 | if len(src.%s) > 0 { 454 | plist := seg.NewPointerList(len(src.%s)) 455 | i := 0 456 | for _, ele := range src.%s { 457 | plist.Set(i, capn.Object(%s(seg, ele))) 458 | i++ 459 | } 460 | dest.Set%s(plist) 461 | } 462 | `, f.goName, f.goToCapFunc, f.goName, f.goName, f.goName, f.canonGoTypeSliceToListFunc, f.goCapGoName) 463 | } else { 464 | fmt.Fprintf(&buf, ` 465 | // %s -> %s (go slice to capn list) 466 | if len(src.%s) > 0 { 467 | typedList := New%sList(seg, len(src.%s)) 468 | plist := capn.PointerList(typedList) 469 | i := 0 470 | for _, ele := range src.%s { 471 | plist.Set(i, capn.Object(%sGoToCapn(seg, %sele))) 472 | i++ 473 | } 474 | dest.Set%s(typedList) 475 | } 476 | `, f.goName, f.goToCapFunc, f.goName, f.goToCapFunc, f.goName, f.goName, f.goType, addAmpersand, f.goCapGoName) 477 | } 478 | } // end switch f.goType 479 | 480 | } else { 481 | 482 | var isCapType bool = false 483 | if _, ok := x.goType2capTypeCache[f.goTypeSeq[0]]; !ok { 484 | if len(f.goTypeSeq) > 1 { 485 | if _, ok := x.goType2capTypeCache[f.goTypeSeq[1]]; ok { 486 | isCapType = true 487 | } 488 | } 489 | } else { 490 | isCapType = true 491 | } 492 | 493 | if isCapType { 494 | if f.goTypeSeq[0] == "*" { 495 | fmt.Fprintf(&buf, " dest.Set%s(%sGoToCapn(seg, src.%s))\n", 496 | f.goName, f.goType, f.goName) 497 | } else { 498 | fmt.Fprintf(&buf, " dest.Set%s(%sGoToCapn(seg, &src.%s))\n", 499 | f.goName, f.goType, f.goName) 500 | } 501 | 502 | continue 503 | } 504 | 505 | switch f.goType { 506 | case "int": 507 | fmt.Fprintf(&buf, " dest.Set%s(int64(src.%s))\n", f.goCapGoName, f.goName) 508 | default: 509 | fmt.Fprintf(&buf, " dest.Set%s(src.%s)\n", f.goCapGoName, f.goName) 510 | } 511 | } 512 | } 513 | return string(buf.Bytes()) 514 | } 515 | 516 | func isListList(goTypePrefix string) bool { 517 | return strings.HasPrefix(goTypePrefix, "[][]") 518 | } 519 | 520 | func (x *Extractor) ToGoCodeFor(goName string) []byte { 521 | return x.ToGoCode[goName] 522 | } 523 | 524 | func (x *Extractor) ToCapnCodeFor(goStructName string) []byte { 525 | return x.ToCapnCode[goStructName] 526 | } 527 | 528 | func (x *Extractor) WriteToSchema(w io.Writer) (n int64, err error) { 529 | 530 | var m int 531 | var spaces string 532 | 533 | // sort structs alphabetically to get a stable (testable) ordering. 534 | sortedStructs := ByGoName(make([]*Struct, 0, len(x.srs))) 535 | for _, strct := range x.srs { 536 | sortedStructs = append(sortedStructs, strct) 537 | } 538 | sort.Sort(ByGoName(sortedStructs)) 539 | 540 | for _, s := range sortedStructs { 541 | 542 | m, err = fmt.Fprintf(w, "%sstruct %s { %s", x.fieldSuffix, s.capName, x.fieldSuffix) 543 | n += int64(m) 544 | if err != nil { 545 | return 546 | } 547 | 548 | s.computeFinalOrder() 549 | 550 | sort.Sort(ByFinalOrder(s.fld)) 551 | 552 | for i, fld := range s.fld { 553 | 554 | VPrintf("\n\n debug in WriteToSchema(), fld = %#v\n", fld) 555 | 556 | SetSpaces(&spaces, s.longestField, len(fld.capname)) 557 | 558 | capType, already := x.goType2capTypeCache[fld.goType] 559 | if !already { 560 | VPrintf("\n\n debug: setting capType = '%s', instead of '%s', already = false\n", fld.capType, capType) 561 | capType = fld.capType 562 | } else { 563 | VPrintf("\n\n debug: already = true, capType = '%s' fld.capType = %v\n", capType, fld.capType) 564 | } 565 | 566 | m, err = fmt.Fprintf(w, "%s%s %s@%d: %s%s; %s", x.fieldPrefix, fld.capname, spaces, fld.finalOrder, ExtraSpaces(i), fld.capType, x.fieldSuffix) 567 | //m, err = fmt.Fprintf(w, "%s%s %s@%d: %s%s; %s", x.fieldPrefix, fld.capname, spaces, fld.finalOrder, ExtraSpaces(i), capType, x.fieldSuffix) 568 | n += int64(m) 569 | if err != nil { 570 | return 571 | } 572 | 573 | } // end field loop 574 | 575 | m, err = fmt.Fprintf(w, "} %s", x.fieldSuffix) 576 | n += int64(m) 577 | if err != nil { 578 | return 579 | } 580 | 581 | } // end loop over structs 582 | 583 | return 584 | } 585 | 586 | func (x *Extractor) GenCapidTag(f *Field) string { 587 | if f.astField == nil { 588 | f.astField = &ast.Field{} 589 | } 590 | if f.astField.Tag == nil { 591 | f.astField.Tag = &ast.BasicLit{} 592 | } 593 | 594 | curTag := f.astField.Tag.Value 595 | 596 | if hasCapidTag(curTag) { 597 | return curTag 598 | } 599 | // else add one 600 | addme := fmt.Sprintf(`capid:"%d"`, f.finalOrder) 601 | if curTag == "" || curTag == "``" { 602 | return fmt.Sprintf("`%s`", addme) 603 | } 604 | return fmt.Sprintf("`%s %s`", stripBackticks(curTag), addme) 605 | } 606 | 607 | func hasCapidTag(s string) bool { 608 | return strings.Contains(s, "capid") 609 | } 610 | 611 | func stripBackticks(s string) string { 612 | if len(s) == 0 { 613 | return s 614 | } 615 | 616 | r := []rune(s) 617 | if r[0] == '`' { 618 | r = r[1:] 619 | } 620 | if len(r) > 0 && r[len(r)-1] == '`' { 621 | r = r[:len(r)-1] 622 | } 623 | return string(r) 624 | } 625 | 626 | func (x *Extractor) CopySourceFilesAddCapidTag() error { 627 | 628 | // run through struct fields, adding tags 629 | for _, s := range x.srs { 630 | for _, f := range s.fld { 631 | 632 | VPrintf("\n\n\n ********** before f.astField.Tag = %#v\n", f.astField.Tag) 633 | f.astField.Tag.Value = x.GenCapidTag(f) 634 | VPrintf("\n\n\n ********** AFTER: f.astField.Tag = %#v\n", f.astField.Tag) 635 | } 636 | } 637 | 638 | // run through files, printing 639 | for _, s := range x.srcFiles { 640 | if s.filename != "" { 641 | err := x.PrettyPrint(s.fset, s.astFile, x.compileDir.DirPath+string(os.PathSeparator)+s.filename) 642 | if err != nil { 643 | return err 644 | } 645 | } 646 | } 647 | 648 | if x.overwrite { 649 | bk := fmt.Sprintf("%s%cbk%c", x.compileDir.DirPath, os.PathSeparator, os.PathSeparator) 650 | err := os.MkdirAll(bk, 0755) 651 | if err != nil { 652 | panic(err) 653 | } 654 | for _, s := range x.srcFiles { 655 | if s.filename != "" { 656 | // make a backup 657 | err := Cp(s.filename, bk+s.filename) 658 | if err != nil { 659 | panic(err) 660 | } 661 | // overwrite 662 | err = Cp(x.compileDir.DirPath+string(os.PathSeparator)+s.filename, s.filename) 663 | if err != nil { 664 | panic(err) 665 | } 666 | } 667 | } 668 | 669 | } 670 | 671 | return nil 672 | } 673 | 674 | func (x *Extractor) WriteToTranslators(w io.Writer) (n int64, err error) { 675 | 676 | var m int 677 | 678 | x.GenerateTranslators() 679 | 680 | // sort structs alphabetically to get a stable (testable) ordering. 681 | sortedStructs := ByGoName(make([]*Struct, 0, len(x.srs))) 682 | for _, strct := range x.srs { 683 | sortedStructs = append(sortedStructs, strct) 684 | } 685 | sort.Sort(ByGoName(sortedStructs)) 686 | 687 | // now print the translating methods, in a second pass over structures, to accomodate 688 | // our test structure 689 | for _, s := range sortedStructs { 690 | 691 | m, err = fmt.Fprintf(w, "\n\n") 692 | n += int64(m) 693 | if err != nil { 694 | return 695 | } 696 | 697 | m, err = w.Write(x.SaveCode[s.goName]) 698 | n += int64(m) 699 | if err != nil { 700 | return 701 | } 702 | 703 | m, err = fmt.Fprintf(w, "\n\n") 704 | n += int64(m) 705 | if err != nil { 706 | return 707 | } 708 | 709 | m, err = w.Write(x.LoadCode[s.goName]) 710 | n += int64(m) 711 | if err != nil { 712 | return 713 | } 714 | 715 | m, err = fmt.Fprintf(w, "\n\n") 716 | n += int64(m) 717 | if err != nil { 718 | return 719 | } 720 | 721 | m, err = w.Write(x.ToGoCodeFor(s.goName)) 722 | n += int64(m) 723 | if err != nil { 724 | return 725 | } 726 | 727 | m, err = fmt.Fprintf(w, "\n\n") 728 | n += int64(m) 729 | if err != nil { 730 | return 731 | } 732 | 733 | m, err = w.Write(x.ToCapnCodeFor(s.goName)) 734 | n += int64(m) 735 | if err != nil { 736 | return 737 | } 738 | 739 | } // end second loop over structs for translating methods. 740 | 741 | // print the helpers made from x.GenerateListHelpers(capListTypeSeq, goTypeSeq) 742 | // sort helper functions to get consistent (testable) order. 743 | a := make([]AlphaHelper, len(x.SliceToListCode)+len(x.ListToSliceCode)) 744 | i := 0 745 | for k, v := range x.SliceToListCode { 746 | a[i].Name = k 747 | a[i].Code = v 748 | i++ 749 | } 750 | for k, v := range x.ListToSliceCode { 751 | a[i].Name = k 752 | a[i].Code = v 753 | i++ 754 | } 755 | 756 | sort.Sort(AlphaHelperSlice(a)) 757 | 758 | for _, help := range a { 759 | 760 | m, err = fmt.Fprintf(w, "\n\n") 761 | n += int64(m) 762 | if err != nil { 763 | return 764 | } 765 | 766 | m, err = w.Write(help.Code) 767 | n += int64(m) 768 | if err != nil { 769 | return 770 | } 771 | 772 | } 773 | 774 | return 775 | } 776 | 777 | func ExtractFromString(src string) ([]byte, error) { 778 | return ExtractStructs("", "package main; "+src, nil) 779 | } 780 | 781 | func ExtractString2String(src string) string { 782 | 783 | x := NewExtractor() 784 | defer x.Cleanup() 785 | _, err := ExtractStructs("", "package main; "+src, x) 786 | if err != nil { 787 | panic(err) 788 | } 789 | 790 | //goon.Dump(x.srs) 791 | 792 | // final write, this time accounting for capid tag ordering 793 | var buf bytes.Buffer 794 | _, err = x.WriteToSchema(&buf) 795 | if err != nil { 796 | panic(err) 797 | } 798 | _, err = x.WriteToTranslators(&buf) 799 | if err != nil { 800 | panic(err) 801 | } 802 | 803 | return string(buf.Bytes()) 804 | } 805 | 806 | func ExtractCapnToGoCode(src string, goName string) string { 807 | 808 | x := NewExtractor() 809 | defer x.Cleanup() 810 | _, err := ExtractStructs("", "package main; "+src, x) 811 | if err != nil { 812 | panic(err) 813 | } 814 | x.GenerateTranslators() 815 | return string(x.ToGoCodeFor(goName)) 816 | } 817 | 818 | func ExtractGoToCapnCode(src string, goName string) string { 819 | 820 | x := NewExtractor() 821 | defer x.Cleanup() 822 | _, err := ExtractStructs("", "package main; "+src, x) 823 | if err != nil { 824 | panic(err) 825 | } 826 | x.GenerateTranslators() 827 | return string(x.ToCapnCodeFor(goName)) 828 | } 829 | 830 | // ExtractStructs pulls out the struct definitions from a golang source file. 831 | // 832 | // src has to be string, []byte, or io.Reader, as in parser.ParseFile(). src 833 | // can be nil if fname is provided. See http://golang.org/pkg/go/parser/#ParseFile 834 | // 835 | func ExtractStructs(fname string, src interface{}, x *Extractor) ([]byte, error) { 836 | if x == nil { 837 | x = NewExtractor() 838 | defer x.Cleanup() 839 | } 840 | 841 | return x.ExtractStructsFromOneFile(src, fname) 842 | } 843 | 844 | func (x *Extractor) ExtractStructsFromOneFile(src interface{}, fname string) ([]byte, error) { 845 | 846 | fset := token.NewFileSet() // positions are relative to fset 847 | 848 | f, err := parser.ParseFile(fset, fname, src, parser.ParseComments) 849 | if err != nil { 850 | panic(err) 851 | } 852 | 853 | if fname != "" { 854 | x.srcFiles = append(x.srcFiles, &SrcFile{filename: fname, fset: fset, astFile: f}) 855 | } 856 | 857 | // VPrintf("parsed output f.Decls is:\n") 858 | //VPrintf("len(f.Decls) = %d\n", len(f.Decls)) 859 | 860 | for _, v := range f.Decls { 861 | switch v.(type) { 862 | case *ast.GenDecl: 863 | d := v.(*ast.GenDecl) 864 | //VPrintf("dump of d, an %#v = \n", ty) 865 | //goon.Dump(d) 866 | 867 | //VPrintf("\n\n\n detail dump of d.Specs\n") 868 | for _, spe := range d.Specs { 869 | switch spe.(type) { 870 | case (*ast.TypeSpec): 871 | 872 | // go back and print the comments 873 | if d.Doc != nil && d.Doc.List != nil && len(d.Doc.List) > 0 { 874 | for _, com := range d.Doc.List { 875 | x.GenerateComment(com.Text) 876 | } 877 | } 878 | 879 | typeSpec := spe.(*ast.TypeSpec) 880 | //VPrintf("\n\n *ast.TypeSpec spe = \n") 881 | 882 | if typeSpec.Name.Obj.Kind == ast.Typ { 883 | 884 | switch typeSpec.Name.Obj.Decl.(type) { 885 | case (*ast.TypeSpec): 886 | //curStructName := typeSpec.Name.String() 887 | curStructName := typeSpec.Name.Name 888 | ts2 := typeSpec.Name.Obj.Decl.(*ast.TypeSpec) 889 | 890 | //VPrintf("\n\n in ts2 = %#v\n", ts2) 891 | //goon.Dump(ts2) 892 | 893 | switch ty := ts2.Type.(type) { 894 | default: 895 | // *ast.InterfaceType and *ast.Ident end up here. 896 | //VPrintf("\n\n unrecog type ty = %#v\n", ty) 897 | case (*ast.Ident): 898 | goNewTypeName := ts2.Name.Obj.Name 899 | goTargetTypeName := ty.Name 900 | x.NoteTypedef(goNewTypeName, goTargetTypeName) 901 | 902 | case (*ast.StructType): 903 | stru := ts2.Type.(*ast.StructType) 904 | 905 | err = x.StartStruct(curStructName) 906 | if err != nil { 907 | return []byte{}, err 908 | } 909 | //VPrintf("\n\n stru = %#v\n", stru) 910 | //goon.Dump(stru) 911 | 912 | if stru.Fields != nil { 913 | for _, fld := range stru.Fields.List { 914 | if fld != nil { 915 | //VPrintf("\n\n fld.Names = %#v\n", fld.Names) // looking for 916 | //goon.Dump(fld.Names) 917 | 918 | if len(fld.Names) == 0 { 919 | // field without name: embedded/anonymous struct 920 | var typeName string 921 | switch nmmm := fld.Type.(type) { 922 | case *ast.StarExpr: 923 | typeName = nmmm.X.(*ast.Ident).Name 924 | err = x.GenerateStructField(typeName, "*", typeName, fld, NotList, fld.Tag, YesEmbedded, []string{typeName}) 925 | if err != nil { 926 | return []byte{}, err 927 | } 928 | case *ast.Ident: 929 | typeName = nmmm.Name 930 | err = x.GenerateStructField(typeName, "", typeName, fld, NotList, fld.Tag, YesEmbedded, []string{typeName}) 931 | if err != nil { 932 | return []byte{}, err 933 | } 934 | } 935 | 936 | } else { 937 | // field with name 938 | for _, ident := range fld.Names { 939 | 940 | switch ident.Obj.Decl.(type) { 941 | case (*ast.Field): 942 | // named field 943 | fld2 := ident.Obj.Decl.(*ast.Field) 944 | 945 | //VPrintf("\n\n fld2 = %#v\n", fld2) 946 | //goon.Dump(fld2) 947 | 948 | typeNamePrefix, ident4, gotypeseq := GetTypeAsString(fld2.Type, "", []string{}) 949 | //VPrintf("\n\n tnas = %#v, ident4 = %s\n", typeNamePrefix, ident4) 950 | 951 | err = x.GenerateStructField(ident.Name, typeNamePrefix, ident4, fld2, IsSlice(typeNamePrefix), fld2.Tag, NotEmbedded, gotypeseq) 952 | if err != nil { 953 | return []byte{}, err 954 | } 955 | } 956 | } 957 | } 958 | 959 | } 960 | } 961 | } 962 | 963 | //VPrintf("} // end of %s \n\n", typeSpec.Name) // prod 964 | x.EndStruct() 965 | 966 | //goon.Dump(stru) 967 | //VPrintf("\n =========== end stru =======\n\n\n") 968 | } 969 | } 970 | } 971 | 972 | } 973 | } 974 | } 975 | } 976 | 977 | return x.out.Bytes(), err 978 | } 979 | 980 | func IsSlice(tnas string) bool { 981 | return strings.HasPrefix(tnas, "[]") 982 | } 983 | 984 | func (x *Extractor) NoteTypedef(goNewTypeName string, goTargetTypeName string) { 985 | // we just want to preserve the mapping, without adding Capn suffix 986 | //VPrintf("\n\n noting typedef: goNewTypeName: '%s', goTargetTypeName: '%s'\n", goNewTypeName, goTargetTypeName) 987 | var capTypeSeq []string 988 | capTypeSeq, x.goType2capTypeCache[goNewTypeName] = x.GoTypeToCapnpType(nil, []string{goTargetTypeName}) 989 | VPrintf("\n\n 888 noting typedef: goNewTypeName: '%s', goTargetTypeName: '%s' x.goType2capTypeCache[goNewTypeName]: '%v' capTypeSeq: '%v'\n", goNewTypeName, goTargetTypeName, x.goType2capTypeCache[goNewTypeName], capTypeSeq) 990 | } 991 | 992 | var regexCapname = regexp.MustCompile(`capname:[ \t]*\"([^\"]+)\"`) 993 | 994 | var regexCapid = regexp.MustCompile(`capid:[ \t]*\"([^\"]+)\"`) 995 | 996 | func GoType2CapnType(gotypeName string) string { 997 | return UppercaseFirstLetter(gotypeName) + "Capn" 998 | } 999 | 1000 | func (x *Extractor) StartStruct(goName string) error { 1001 | x.fieldCount = 0 1002 | 1003 | capname := GoType2CapnType(goName) 1004 | x.goType2capTypeCache[goName] = capname 1005 | 1006 | VPrintf("\n\n debug 777 setting x.goType2capTypeCache['%s'] = '%s'\n", goName, capname) 1007 | 1008 | x.capType2goType[capname] = goName 1009 | 1010 | // check for rename comment, capname:"newCapName" 1011 | if x.heldComment != "" { 1012 | 1013 | match := regexCapname.FindStringSubmatch(x.heldComment) 1014 | if match != nil { 1015 | if len(match) == 2 { 1016 | capname = match[1] 1017 | } 1018 | } 1019 | } 1020 | 1021 | if isCapnpKeyword(capname) { 1022 | err := fmt.Errorf(`after uppercasing the first letter, struct '%s' becomes '%s' but this is a reserved capnp word, so please write a comment annotation just before the struct definition in go (e.g. // capname:"capName") to rename it.`, goName, capname) 1023 | panic(err) 1024 | return err 1025 | } 1026 | 1027 | fmt.Fprintf(&x.out, "struct %s { %s", capname, x.fieldSuffix) 1028 | 1029 | x.curStruct = NewStruct(capname, goName) 1030 | x.curStruct.comment = x.heldComment 1031 | x.heldComment = "" 1032 | x.srs[goName] = x.curStruct 1033 | 1034 | return nil 1035 | } 1036 | func (x *Extractor) EndStruct() { 1037 | fmt.Fprintf(&x.out, "} %s", x.fieldSuffix) 1038 | } 1039 | 1040 | func (x *Extractor) GenerateComment(c string) { 1041 | x.heldComment = x.heldComment + c + "\n" 1042 | } 1043 | 1044 | func UppercaseFirstLetter(name string) string { 1045 | if len(name) == 0 { 1046 | return name 1047 | } 1048 | 1049 | // gotta upercase the first letter of type (struct) names 1050 | runes := []rune(name) 1051 | runes[0] = unicode.ToUpper(runes[0]) 1052 | return string(runes) 1053 | 1054 | } 1055 | 1056 | func LowercaseCapnpFieldName(name string) string { 1057 | if len(name) == 0 { 1058 | return name 1059 | } 1060 | 1061 | // gotta lowercase the first letter of field names 1062 | runes := []rune(name) 1063 | runes[0] = unicode.ToLower(runes[0]) 1064 | return string(runes) 1065 | } 1066 | 1067 | const YesIsList = true 1068 | const NotList = false 1069 | 1070 | const NotEmbedded = false 1071 | const YesEmbedded = true 1072 | 1073 | func (x *Extractor) GenerateStructField(goFieldName string, goFieldTypePrefix string, goFieldTypeName string, astfld *ast.Field, isList bool, tag *ast.BasicLit, IsEmbedded bool, goTypeSeq []string) error { 1074 | 1075 | if goFieldTypeName == "" { 1076 | return nil 1077 | } 1078 | 1079 | // skip protobuf side effects 1080 | if goFieldTypeName == "XXX_unrecognized" { 1081 | fmt.Printf("detected XXX_unrecognized\n") 1082 | return nil 1083 | } 1084 | 1085 | VPrintf("\n\n\n GenerateStructField called with goFieldName = '%s', goFieldTypeName = '%s', astfld = %#v, tag = %#v\n\n", goFieldName, goFieldTypeName, astfld, tag) 1086 | 1087 | // if we are ignoring private (lowercase first letter) fields, then stop here. 1088 | if !IsEmbedded { 1089 | if len(goFieldName) > 0 && unicode.IsLower([]rune(goFieldName)[0]) && !x.extractPrivate { 1090 | return nil 1091 | } 1092 | } 1093 | 1094 | curField := &Field{orderOfAppearance: x.fieldCount, embedded: IsEmbedded, astField: astfld, goTypeSeq: goTypeSeq, capTypeSeq: []string{}} 1095 | 1096 | var tagValue string 1097 | loweredName := underToCamelCase(LowercaseCapnpFieldName(goFieldName)) 1098 | 1099 | if tag != nil { 1100 | VPrintf("tag = %#v\n", tag) 1101 | 1102 | if tag.Value != "" { 1103 | 1104 | // capname tag 1105 | match := regexCapname.FindStringSubmatch(tag.Value) 1106 | if match != nil { 1107 | if len(match) == 2 { 1108 | VPrintf("matched, using '%s' instead of '%s'\n", match[1], goFieldName) 1109 | loweredName = match[1] 1110 | tagValue = tag.Value 1111 | 1112 | if isCapnpKeyword(loweredName) { 1113 | err := fmt.Errorf(`problem detected after applying the capname tag '%s' found on field '%s': '%s' is a reserved capnp word, so please use a *different* struct field tag (e.g. capname:"capnpName") to rename it`, tag.Value, goFieldName, loweredName) 1114 | return err 1115 | } 1116 | 1117 | } 1118 | } 1119 | 1120 | // capid tag 1121 | match2 := regexCapid.FindStringSubmatch(tag.Value) 1122 | if match2 != nil { 1123 | if len(match2) == 2 { 1124 | if match2[1] == "skip" { 1125 | VPrintf("skipping field '%s' marked with capid:\"skip\"", loweredName) 1126 | return nil 1127 | } 1128 | VPrintf("matched, applying capid tag '%s' for field '%s'\n", match2[1], loweredName) 1129 | n, err := strconv.Atoi(match2[1]) 1130 | if err != nil { 1131 | err := fmt.Errorf(`problem in capid tag '%s' on field '%s' in struct '%s': could not convert to number, error: '%s'`, match2[1], goFieldName, x.curStruct.goName, err) 1132 | panic(err) 1133 | return err 1134 | } 1135 | if n < 0 { 1136 | VPrintf("skipping field '%s' marked with negative capid:\"%d\"", loweredName, n) 1137 | return nil 1138 | } 1139 | fld, already := x.curStruct.capIdMap[n] 1140 | if already { 1141 | err := fmt.Errorf(`problem in capid tag '%s' on field '%s' in struct '%s': number '%d' is already taken by field '%s'`, match2[1], goFieldName, x.curStruct.goName, n, fld.goName) 1142 | panic(err) 1143 | return err 1144 | 1145 | } else { 1146 | x.curStruct.capIdMap[n] = curField 1147 | curField.capIdFromTag = n 1148 | } 1149 | } 1150 | } 1151 | } 1152 | 1153 | } 1154 | 1155 | VPrintf("\n\n\n GenerateStructField: goFieldName:'%s' -> loweredName:'%s'\n\n", goFieldName, loweredName) 1156 | 1157 | if isCapnpKeyword(loweredName) { 1158 | err := fmt.Errorf(`after lowercasing the first letter, field '%s' becomes '%s' but this is a reserved capnp word, so please use a struct field tag (e.g. capname:"capnpName") to rename it`, goFieldName, loweredName) 1159 | return err 1160 | } 1161 | 1162 | var capnTypeDisplayed string 1163 | curField.capTypeSeq, capnTypeDisplayed = x.GoTypeToCapnpType(curField, goTypeSeq) 1164 | 1165 | VPrintf("\n\n\n DEBUG: '%s' '%s' @%d: %s; %s\n\n", x.fieldPrefix, loweredName, x.fieldCount, capnTypeDisplayed, x.fieldSuffix) 1166 | 1167 | sz := len(loweredName) 1168 | if sz > x.curStruct.longestField { 1169 | x.curStruct.longestField = sz 1170 | } 1171 | 1172 | curField.capname = loweredName 1173 | 1174 | curField.goCapGoName = UppercaseFirstLetter(loweredName) 1175 | 1176 | curField.goCapGoTypeSeq, curField.goCapGoType = x.CapnTypeToGoType(curField.capTypeSeq) 1177 | 1178 | curField.capType = capnTypeDisplayed 1179 | curField.goName = goFieldName 1180 | curField.goType = goFieldTypeName 1181 | if len(curField.capTypeSeq) > 0 && curField.capTypeSeq[0] == "List" { 1182 | curField.isList = true 1183 | } 1184 | curField.tagValue = tagValue 1185 | curField.goTypePrefix = goFieldTypePrefix 1186 | 1187 | x.curStruct.fld = append(x.curStruct.fld, curField) 1188 | x.fieldCount++ 1189 | 1190 | VPrintf("\n\n curField = %#v\n", curField) 1191 | 1192 | return nil 1193 | } 1194 | 1195 | func (x *Extractor) CapnTypeToGoType(capTypeSeq []string) (goTypeSeq []string, displayGoCapGoType string) { 1196 | 1197 | for _, c := range capTypeSeq { 1198 | goType, special := x.c2g(c) 1199 | 1200 | if special { 1201 | if goType == "Data" { 1202 | goTypeSeq = append(goTypeSeq, "[]", "byte") 1203 | continue 1204 | } 1205 | 1206 | if x.capType2goType[c] != "" { 1207 | goType = x.capType2goType[c] 1208 | } 1209 | } 1210 | goTypeSeq = append(goTypeSeq, goType) 1211 | } 1212 | return goTypeSeq, x.assembleGoType(goTypeSeq) 1213 | } 1214 | 1215 | func (x *Extractor) assembleGoType(goTypeSeq []string) string { 1216 | // make a legitimate go type 1217 | return strings.Join(goTypeSeq, "") 1218 | } 1219 | 1220 | // special flags Data <-> []byte, and types we couldn't convert 1221 | func (x *Extractor) c2g(capType string) (goType string, special bool) { 1222 | 1223 | switch capType { 1224 | default: 1225 | return capType, true 1226 | case "Data": 1227 | return "Data", true 1228 | case "List": 1229 | return "[]", false 1230 | case "Text": 1231 | return "string", false 1232 | case "Bool": 1233 | return "bool", false 1234 | case "Int8": 1235 | return "int8", false 1236 | case "Int16": 1237 | return "int16", false 1238 | case "Int32": 1239 | return "int32", false 1240 | case "Int64": 1241 | return "int64", false 1242 | case "UInt8": 1243 | return "uint8", false 1244 | case "UInt16": 1245 | return "uint16", false 1246 | case "UInt32": 1247 | return "uint32", false 1248 | case "UInt64": 1249 | return "uint64", false 1250 | case "Float32": 1251 | return "float32", false 1252 | case "Float64": 1253 | return "float64", false 1254 | } 1255 | } 1256 | 1257 | func (x *Extractor) GoTypeToCapnpType(curField *Field, goTypeSeq []string) (capTypeSeq []string, capnTypeDisplayed string) { 1258 | 1259 | VPrintf("\n\n In GoTypeToCapnpType() : goTypeSeq=%#v)\n", goTypeSeq) 1260 | 1261 | capTypeSeq = make([]string, len(goTypeSeq)) 1262 | for i, t := range goTypeSeq { 1263 | capTypeSeq[i] = x.g2c(t) 1264 | } 1265 | 1266 | // now that the capTypeSeq is completely generated, check for lists 1267 | // currently only do List(primitive or struct type); no List(List(prim)) or List(List(struct)) 1268 | n := len(capTypeSeq) 1269 | for i, ty := range capTypeSeq { 1270 | if ty == "List" && i == n-2 { 1271 | VPrintf("\n\n generating List helpers at i=%d, capTypeSeq = '%#v\n", i, capTypeSeq) 1272 | x.GenerateListHelpers(curField, capTypeSeq[i:], goTypeSeq[i:]) 1273 | } 1274 | } 1275 | 1276 | return capTypeSeq, x.assembleCapType(capTypeSeq) 1277 | } 1278 | 1279 | func (x *Extractor) assembleCapType(capTypeSeq []string) string { 1280 | // make a legitimate capnp type 1281 | switch capTypeSeq[0] { 1282 | case "List": 1283 | return "List(" + x.assembleCapType(capTypeSeq[1:]) + ")" 1284 | case "*": 1285 | return x.assembleCapType(capTypeSeq[1:]) 1286 | default: 1287 | return capTypeSeq[0] 1288 | } 1289 | } 1290 | 1291 | func (x *Extractor) g2c(goFieldTypeName string) string { 1292 | 1293 | switch goFieldTypeName { 1294 | case "[]": 1295 | return "List" 1296 | case "*": 1297 | return "*" 1298 | case "string": 1299 | return "Text" 1300 | case "int": 1301 | return "Int64" 1302 | case "bool": 1303 | return "Bool" 1304 | case "int8": 1305 | return "Int8" 1306 | case "int16": 1307 | return "Int16" 1308 | case "int32": 1309 | return "Int32" 1310 | case "int64": 1311 | return "Int64" 1312 | case "uint8": 1313 | return "UInt8" 1314 | case "uint16": 1315 | return "UInt16" 1316 | case "uint32": 1317 | return "UInt32" 1318 | case "uint64": 1319 | return "UInt64" 1320 | case "float32": 1321 | return "Float32" 1322 | case "float64": 1323 | return "Float64" 1324 | case "byte": 1325 | return "UInt8" 1326 | } 1327 | 1328 | var capnTypeDisplayed string 1329 | alreadyKnownCapnType := x.goType2capTypeCache[goFieldTypeName] 1330 | if alreadyKnownCapnType != "" { 1331 | VPrintf("\n\n debug: x.goType2capTypeCache[goFieldTypeName='%s'] -> '%s'\n", goFieldTypeName, alreadyKnownCapnType) 1332 | capnTypeDisplayed = alreadyKnownCapnType 1333 | } else { 1334 | capnTypeDisplayed = GoType2CapnType(goFieldTypeName) 1335 | VPrintf("\n\n 999 debug: adding to x.goType2capTypeCache[goFieldTypeName='%s'] = '%s'\n", goFieldTypeName, capnTypeDisplayed) 1336 | x.goType2capTypeCache[goFieldTypeName] = capnTypeDisplayed 1337 | } 1338 | 1339 | return capnTypeDisplayed 1340 | } 1341 | 1342 | func (x *Extractor) GenerateEmbedded(typeName string) { 1343 | fmt.Fprintf(&x.out, "%s; ", typeName) // prod 1344 | } 1345 | 1346 | func getNewCapnpId() string { 1347 | id, err := exec.Command("capnp", "id").CombinedOutput() 1348 | if err != nil { 1349 | panic(err) 1350 | } 1351 | n := len(id) 1352 | if n > 0 { 1353 | id = id[:n-1] 1354 | } 1355 | 1356 | return string(id) 1357 | } 1358 | 1359 | func (x *Extractor) GenCapnpHeader() *bytes.Buffer { 1360 | var by bytes.Buffer 1361 | 1362 | id := getNewCapnpId() 1363 | 1364 | fmt.Fprintf(&by, `%s; 1365 | using Go = import "go.capnp"; 1366 | $Go.package("%s"); 1367 | $Go.import("%s"); 1368 | %s`, id, x.pkgName, x.importDecl, x.fieldSuffix) 1369 | 1370 | return &by 1371 | } 1372 | 1373 | func (x *Extractor) AssembleCapnpFile(in []byte) *bytes.Buffer { 1374 | by := x.GenCapnpHeader() 1375 | 1376 | by.Write(in) 1377 | fmt.Fprintf(by, "\n") 1378 | 1379 | return by 1380 | } 1381 | 1382 | func CapnpCompileFragment(in []byte) ([]byte, error, *Extractor) { 1383 | x := NewExtractor() 1384 | out, err := x.CapnpCompileFragment(in) 1385 | return out, err, x 1386 | } 1387 | 1388 | func (x *Extractor) CapnpCompileFragment(in []byte) ([]byte, error) { 1389 | 1390 | if x.compileDir != nil { 1391 | x.compileDir.Cleanup() 1392 | } 1393 | x.compileDir = NewTempDir() 1394 | 1395 | f := x.compileDir.TempFile() 1396 | 1397 | by := x.AssembleCapnpFile(in) 1398 | debug := string(by.Bytes()) 1399 | 1400 | f.Write(by.Bytes()) 1401 | f.Close() 1402 | 1403 | compiled, combinedOut, err := CapnpCompilePath(f.Name()) 1404 | if err != nil { 1405 | errmsg := fmt.Sprintf("error compiling the generated capnp code: '%s'; error: '%s'\n", debug, err) + string(combinedOut) 1406 | return []byte(errmsg), fmt.Errorf(errmsg) 1407 | } 1408 | 1409 | return compiled, nil 1410 | } 1411 | 1412 | func CapnpCompilePath(fname string) (generatedGoFile []byte, comboOut []byte, err error) { 1413 | goOutFn := fname + ".go" 1414 | 1415 | by, err := exec.Command("capnp", "compile", "-ogo", fname).CombinedOutput() 1416 | if err != nil { 1417 | return []byte{}, by, err 1418 | } 1419 | 1420 | generatedGoFile, err = ioutil.ReadFile(goOutFn) 1421 | 1422 | return generatedGoFile, by, err 1423 | } 1424 | 1425 | func SetSpaces(spaces *string, Max int, Len int) { 1426 | if Len >= Max { 1427 | *spaces = "" 1428 | return 1429 | } 1430 | *spaces = strings.Repeat(" ", Max-Len) 1431 | } 1432 | 1433 | func ExtraSpaces(fieldNum int) string { 1434 | if fieldNum < 10 { 1435 | return " " 1436 | } 1437 | if fieldNum < 100 { 1438 | return " " 1439 | } 1440 | return "" 1441 | } 1442 | 1443 | func IsIntrinsicGoType(goFieldTypeName string) bool { 1444 | VPrintf("\n IsIntrinsic called with '%s'\n", goFieldTypeName) 1445 | 1446 | switch goFieldTypeName { 1447 | case "string": 1448 | return true 1449 | case "int": 1450 | return true 1451 | case "bool": 1452 | return true 1453 | case "int8": 1454 | return true 1455 | case "int16": 1456 | return true 1457 | case "int32": 1458 | return true 1459 | case "int64": 1460 | return true 1461 | case "uint8": 1462 | return true 1463 | case "uint16": 1464 | return true 1465 | case "uint32": 1466 | return true 1467 | case "uint64": 1468 | return true 1469 | case "float32": 1470 | return true 1471 | case "float64": 1472 | return true 1473 | case "byte": 1474 | return true 1475 | default: 1476 | return false 1477 | } 1478 | return false 1479 | } 1480 | 1481 | func CanonGoType(goTypeSeq []string) string { 1482 | var r string 1483 | for _, s := range goTypeSeq { 1484 | if s == "[]" { 1485 | r += "Slice" 1486 | } else { 1487 | r += UppercaseFirstLetter(s) 1488 | } 1489 | } 1490 | return r 1491 | } 1492 | 1493 | func CanonCapType(capTypeSeq []string) string { 1494 | var r string 1495 | for _, s := range capTypeSeq { 1496 | r += s 1497 | } 1498 | return r 1499 | } 1500 | 1501 | func (x *Extractor) GenerateListHelpers(f *Field, capListTypeSeq []string, goTypeSeq []string) { 1502 | 1503 | canonGoType := CanonGoType(goTypeSeq) 1504 | //canonCapType := CanonCapType(capListTypeSeq) 1505 | 1506 | // already done before? but maybe f was nil and so needs re-doing! 1507 | // so don't return early even if partially set before! 1508 | 1509 | VPrintf("\n\n debug GenerateListHelper: called with capListTypeSeq = '%#v'\n", capListTypeSeq) 1510 | 1511 | n := len(capListTypeSeq) 1512 | capBaseType := capListTypeSeq[n-1] 1513 | capTypeThenList := strings.Join(capListTypeSeq, "") 1514 | if n > 1 { 1515 | capTypeThenList = capBaseType + strings.Join(capListTypeSeq[:n-1], "") 1516 | } 1517 | 1518 | if IsIntrinsicGoType(last(goTypeSeq)) { 1519 | f.baseIsIntrinsic = true 1520 | capTypeThenList = capBaseType + "List" 1521 | f.singleCapListType = fmt.Sprintf("capn.%sList", capBaseType) 1522 | f.newListExpression = fmt.Sprintf("seg.New%sList(len(m))", capBaseType) 1523 | } else { 1524 | capTypeThenList = capBaseType + strings.Join(capListTypeSeq[:n-1], "") 1525 | f.singleCapListType = fmt.Sprintf("%s_List", capBaseType) 1526 | f.newListExpression = fmt.Sprintf("New%s(seg, len(m))", capTypeThenList) 1527 | } 1528 | VPrintf("\n capTypeThenList is set to : '%s'\n\n", capTypeThenList) 1529 | 1530 | collapGoType := strings.Join(goTypeSeq, "") 1531 | m := len(goTypeSeq) 1532 | goBaseType := goTypeSeq[m-1] 1533 | 1534 | c2g, _ := x.c2g(capBaseType) 1535 | 1536 | f.canonGoType = canonGoType 1537 | f.canonGoTypeListToSliceFunc = fmt.Sprintf("%sTo%s", capTypeThenList, canonGoType) 1538 | f.canonGoTypeSliceToListFunc = fmt.Sprintf("%sTo%s", canonGoType, capTypeThenList) 1539 | 1540 | x.SliceToListCode[canonGoType] = []byte(fmt.Sprintf(` 1541 | func %sTo%s(seg *capn.Segment, m %s) %s { 1542 | lst := %s 1543 | for i := range m { 1544 | lst.Set(i, %s) 1545 | } 1546 | return lst 1547 | } 1548 | `, canonGoType, capTypeThenList, collapGoType, f.singleCapListType, f.newListExpression, x.SliceToListSetRHS(f.baseIsIntrinsic, goBaseType, c2g))) 1549 | 1550 | x.ListToSliceCode[canonGoType] = []byte(fmt.Sprintf(` 1551 | func %sTo%s(p %s) %s { 1552 | v := make(%s, p.Len()) 1553 | for i := range v { 1554 | %s 1555 | } 1556 | return v 1557 | } 1558 | `, capTypeThenList, canonGoType, f.singleCapListType, collapGoType, collapGoType, x.ListToSliceSetLHS_RHS(f.baseIsIntrinsic, capBaseType, goBaseType))) 1559 | 1560 | VPrintf("\n\n GenerateListHelpers done for field '%#v'\n\n", f) 1561 | } 1562 | 1563 | func (x *Extractor) SliceToListSetRHS(baseIsIntrinsic bool, goName string, c2g string) string { 1564 | if baseIsIntrinsic { 1565 | return fmt.Sprintf("%s(m[i])", c2g) 1566 | } else { 1567 | return fmt.Sprintf("%sGoToCapn(seg, &m[i])", goName) 1568 | } 1569 | } 1570 | 1571 | func (x *Extractor) ListToSliceSetLHS_RHS(baseIsIntrinsic bool, capName string, goBaseType string) string { 1572 | if baseIsIntrinsic { 1573 | return fmt.Sprintf("v[i] = %s(p.At(i))", goBaseType) // e.g. int64 1574 | } else { 1575 | return fmt.Sprintf("%sToGo(p.At(i), &v[i])", capName) 1576 | } 1577 | } 1578 | --------------------------------------------------------------------------------