├── .gitignore ├── go.mod ├── .github ├── dependabot.yml └── workflows │ └── unit-test.yml ├── go.sum ├── defer_test.go ├── package.go ├── package_test.go ├── defer.go ├── return.go ├── return_test.go ├── utils.go ├── import_test.go ├── type.go ├── type_test.go ├── interface_test.go ├── struct_test.go ├── value.go ├── utils_test.go ├── node.go ├── logic_test.go ├── var.go ├── Makefile ├── const.go ├── call_test.go ├── struct.go ├── field.go ├── call.go ├── gen.go ├── var_test.go ├── const_test.go ├── import.go ├── CHANGELOG.md ├── interface.go ├── string_test.go ├── logic.go ├── function.go ├── README.md ├── function_test.go ├── string.go ├── group_test.go ├── group.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.* 2 | 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Xuanwo/gg 2 | 3 | go 1.15 4 | 5 | require github.com/Xuanwo/go-bufferpool v0.2.0 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 1 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Xuanwo/go-bufferpool v0.2.0 h1:DXzqJD9lJufXbT/03GrcEvYOs4gXYUj9/g5yi6Q9rUw= 2 | github.com/Xuanwo/go-bufferpool v0.2.0/go.mod h1:Mle++9GGouhOwGj52i9PJLNAPmW2nb8PWBP7JJzNCzk= 3 | -------------------------------------------------------------------------------- /defer_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestDefer(t *testing.T) { 6 | buf := pool.Get() 7 | defer buf.Free() 8 | 9 | expected := "defer hello()" 10 | 11 | Defer("hello()").render(buf) 12 | 13 | compareAST(t, expected, buf.String()) 14 | } 15 | -------------------------------------------------------------------------------- /package.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type ipackage struct { 6 | name string 7 | } 8 | 9 | func (i *ipackage) render(w io.Writer) { 10 | writeStringF(w, "package %s\n", i.name) 11 | } 12 | 13 | func Package(name string) *ipackage { 14 | return &ipackage{name: name} 15 | } 16 | -------------------------------------------------------------------------------- /package_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPackage(t *testing.T) { 8 | buf := pool.Get() 9 | defer buf.Free() 10 | 11 | expected := "package test\n" 12 | 13 | Package("test").render(buf) 14 | 15 | compareAST(t, expected, buf.String()) 16 | } 17 | -------------------------------------------------------------------------------- /defer.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type idefer struct { 6 | body Node 7 | } 8 | 9 | func (i *idefer) render(w io.Writer) { 10 | writeString(w, "defer ") 11 | i.body.render(w) 12 | } 13 | 14 | func Defer(body interface{}) Node { 15 | // Add extra space here. 16 | return &idefer{parseNode(body)} 17 | } 18 | -------------------------------------------------------------------------------- /return.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type ireturn struct { 6 | items *group 7 | } 8 | 9 | func Return(node ...interface{}) *ireturn { 10 | i := &ireturn{ 11 | items: newGroup("", "", ", "), 12 | } 13 | i.items.append(node...) 14 | return i 15 | } 16 | 17 | func (i *ireturn) render(w io.Writer) { 18 | writeString(w, "return ") 19 | i.items.render(w) 20 | } 21 | -------------------------------------------------------------------------------- /return_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestReturn(t *testing.T) { 6 | buf := pool.Get() 7 | defer buf.Free() 8 | 9 | expected := `return a, b,123,Test{Abc:123}` 10 | 11 | ir := Return( 12 | String("a"), 13 | String("b"), 14 | Lit(123), 15 | Value("Test").AddField("Abc", Lit(123)), 16 | ) 17 | ir.render(buf) 18 | 19 | compareAST(t, expected, buf.String()) 20 | } 21 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func writeString(w io.Writer, s ...string) { 9 | for _, v := range s { 10 | _, err := w.Write([]byte(v)) 11 | if err != nil { 12 | panic(fmt.Errorf("write string: %v", err)) 13 | } 14 | } 15 | } 16 | 17 | func writeStringF(w io.Writer, format string, args ...interface{}) { 18 | s := fmt.Sprintf(format, args...) 19 | _, err := w.Write([]byte(s)) 20 | if err != nil { 21 | panic(fmt.Errorf("write string: %v", err)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /import_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestImports(t *testing.T) { 8 | buf := pool.Get() 9 | defer buf.Free() 10 | 11 | expected := `import ( 12 | // test 13 | "context" 14 | . "time" 15 | _ "math" 16 | 17 | test "testing" 18 | ) 19 | ` 20 | Import(). 21 | AddLineComment("test"). 22 | AddPath("context"). 23 | AddDot("time"). 24 | AddBlank("math"). 25 | AddLine(). 26 | AddAlias("testing", "test"). 27 | render(buf) 28 | 29 | compareAST(t, expected, buf.String()) 30 | } 31 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type itype struct { 6 | name string 7 | item Node 8 | sep string 9 | } 10 | 11 | func Type(name string, typ interface{}) *itype { 12 | return &itype{ 13 | name: name, 14 | item: parseNode(typ), 15 | } 16 | } 17 | 18 | func TypeAlias(name string, typ interface{}) *itype { 19 | return &itype{ 20 | name: name, 21 | item: parseNode(typ), 22 | sep: "=", 23 | } 24 | } 25 | 26 | func (i *itype) render(w io.Writer) { 27 | writeStringF(w, "type %s %s", i.name, i.sep) 28 | i.item.render(w) 29 | } 30 | -------------------------------------------------------------------------------- /type_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestType(t *testing.T) { 6 | t.Run("type def", func(t *testing.T) { 7 | buf := pool.Get() 8 | defer buf.Free() 9 | 10 | expected := "type xstring string" 11 | 12 | Type("xstring", "string").render(buf) 13 | 14 | compareAST(t, expected, buf.String()) 15 | }) 16 | t.Run("type alias", func(t *testing.T) { 17 | buf := pool.Get() 18 | defer buf.Free() 19 | 20 | expected := "type xstring = string" 21 | 22 | TypeAlias("xstring", "string").render(buf) 23 | 24 | compareAST(t, expected, buf.String()) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /interface_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestInterface(t *testing.T) { 6 | buf := pool.Get() 7 | defer buf.Free() 8 | 9 | expected := `type Tester interface { 10 | // TestA is a test 11 | TestA(a int64, b int) 12 | 13 | TestB() (err error) 14 | } 15 | ` 16 | in := Interface("Tester") 17 | in.AddLineComment("TestA is a test") 18 | in.NewFunction("TestA"). 19 | AddParameter("a", "int64"). 20 | AddParameter("b", "int") 21 | in.AddLine() 22 | in.NewFunction("TestB"). 23 | AddResult("err", "error") 24 | 25 | in.render(buf) 26 | 27 | compareAST(t, expected, buf.String()) 28 | } 29 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestStruct(t *testing.T) { 6 | t.Run("empty", func(t *testing.T) { 7 | buf := pool.Get() 8 | defer buf.Free() 9 | 10 | expected := "type Test struct{}" 11 | 12 | Struct("Test").render(buf) 13 | 14 | compareAST(t, expected, buf.String()) 15 | }) 16 | 17 | t.Run("fields", func(t *testing.T) { 18 | buf := pool.Get() 19 | defer buf.Free() 20 | 21 | expected := `type Test struct{ 22 | A int64 23 | b string 24 | }` 25 | 26 | Struct("Test"). 27 | AddField("A", "int64"). 28 | AddField("b", "string"). 29 | render(buf) 30 | 31 | compareAST(t, expected, buf.String()) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: "Unit Test" 2 | 3 | on: [ push,pull_request ] 4 | 5 | jobs: 6 | unit_test: 7 | name: Unit Test 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | matrix: 12 | os: [ ubuntu-latest, macos-latest, windows-latest ] 13 | go: [ "1.15", "1.16", "1.17" ] 14 | 15 | steps: 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ${{ matrix.go }} 20 | 21 | - name: Checkout repository 22 | uses: actions/checkout@v2 23 | 24 | - name: Build 25 | run: make build 26 | 27 | - name: Test 28 | run: make test 29 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type ivalue struct { 6 | typ string 7 | items *group 8 | } 9 | 10 | func Value(typ string) *ivalue { 11 | // FIXME: we need to support builtin type like map and slide. 12 | return &ivalue{ 13 | typ: typ, 14 | items: newGroup("{", "}", ","), 15 | } 16 | } 17 | 18 | func (v *ivalue) render(w io.Writer) { 19 | writeString(w, v.typ) 20 | v.items.render(w) 21 | } 22 | 23 | func (v *ivalue) String() string { 24 | buf := pool.Get() 25 | defer buf.Free() 26 | 27 | v.render(buf) 28 | return buf.String() 29 | } 30 | 31 | func (v *ivalue) AddField(name, value interface{}) *ivalue { 32 | v.items.append(field(name, value, ":")) 33 | return v 34 | } 35 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | // cleanAST will remove all space and newline from code. 9 | // We know it will break the AST semantic, but golang doesn't support parse 10 | // partial code source, we have to do like this. 11 | // Maybe we can find a better way to compare the AST in go. 12 | func cleanAST(a string) string { 13 | a = strings.ReplaceAll(a, " ", "") 14 | a = strings.ReplaceAll(a, "\n", "") 15 | a = strings.ReplaceAll(a, "\t", "") 16 | return a 17 | } 18 | 19 | func compareAST(t *testing.T, a, b string) { 20 | na, nb := cleanAST(a), cleanAST(b) 21 | if na == nb { 22 | return 23 | } 24 | t.Error("AST is not the same.") 25 | t.Errorf("left:\n%s\ncleaned:\n%s", a, na) 26 | t.Errorf("right:\n%s\ncleaned:\n%s", b, nb) 27 | } 28 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type Node interface { 9 | render(w io.Writer) 10 | } 11 | 12 | // Embed accept a close clause to build a node. 13 | func Embed(fn func() Node) Node { 14 | return fn() 15 | } 16 | 17 | // parseNodes will parse valid input into node slices. 18 | func parseNodes(in []interface{}) []Node { 19 | ns := make([]Node, 0, len(in)) 20 | for _, v := range in { 21 | ns = append(ns, parseNode(v)) 22 | } 23 | return ns 24 | } 25 | 26 | // parseNode will parse a valid input into a node. 27 | // For now, we only support two types: 28 | // - Native Node 29 | // - golang string 30 | func parseNode(in interface{}) Node { 31 | switch v := in.(type) { 32 | case Node: 33 | return v 34 | case string: 35 | return String(v) 36 | default: 37 | panic(fmt.Errorf("invalid input: %s", v)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /logic_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestIf(t *testing.T) { 6 | buf := pool.Get() 7 | defer buf.Free() 8 | 9 | expected := ` 10 | if ok { 11 | println("Hello, World!") 12 | } 13 | ` 14 | 15 | If(String("ok")).AddBody(String(`println("Hello, World!")`)). 16 | render(buf) 17 | 18 | compareAST(t, expected, buf.String()) 19 | } 20 | 21 | func TestSwitch(t *testing.T) { 22 | buf := pool.Get() 23 | defer buf.Free() 24 | 25 | expected := ` 26 | switch x { 27 | case 1: 28 | print("1") 29 | case 2: 30 | print("2") 31 | default: 32 | print("default") 33 | } 34 | ` 35 | is := Switch(String("x")) 36 | is.NewCase(String("1")).AddBody(String(`print("1")`)) 37 | is.NewCase(String("2")).AddBody(String(`print("2")`)) 38 | is.NewDefault().AddBody(String(`print("default")`)) 39 | is.render(buf) 40 | 41 | compareAST(t, expected, buf.String()) 42 | } 43 | -------------------------------------------------------------------------------- /var.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type ivar struct { 6 | items *group 7 | } 8 | 9 | func Var() *ivar { 10 | i := &ivar{ 11 | items: newGroup("(", ")", "\n"), 12 | } 13 | i.items.omitWrapIf = func() bool { 14 | // We only need to omit wrap while length == 1. 15 | // NewIf length == 0, we need to keep it, or it will be invalid expr. 16 | return i.items.length() == 1 17 | } 18 | return i 19 | } 20 | 21 | func (i *ivar) render(w io.Writer) { 22 | writeString(w, "var ") 23 | i.items.render(w) 24 | } 25 | 26 | func (i *ivar) AddField(name, value interface{}) *ivar { 27 | i.items.append(field(name, value, "=")) 28 | return i 29 | } 30 | 31 | func (i *ivar) AddTypedField(name, typ, value interface{}) *ivar { 32 | i.items.append(typedField(name, typ, value, "=")) 33 | return i 34 | } 35 | 36 | func (i *ivar) AddDecl(name, value interface{}) *ivar { 37 | i.items.append(field(name, value, " ")) 38 | return i 39 | } 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: all check format vet build test generate tidy 4 | 5 | help: 6 | @echo "Please use \`make \` where is one of" 7 | @echo " check to do static check" 8 | @echo " build to create bin directory and build" 9 | @echo " generate to generate code" 10 | @echo " test to run test" 11 | @echo " integration_test to run integration test" 12 | 13 | check: vet 14 | 15 | format: 16 | @echo "gofmt" 17 | @gofmt -w -l . 18 | @echo "ok" 19 | 20 | vet: 21 | @echo "go vet" 22 | @go vet ./... 23 | @echo "ok" 24 | 25 | build: tidy format check 26 | @echo "build storage" 27 | @go build -tags tools ./... 28 | @echo "ok" 29 | 30 | test: 31 | @echo "run test" 32 | @go test -race -coverprofile=coverage.txt -covermode=atomic -v ./... 33 | @go tool cover -html="coverage.txt" -o "coverage.html" 34 | @echo "ok" 35 | 36 | tidy: 37 | @go mod tidy 38 | @go mod verify 39 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type iconst struct { 6 | items *group 7 | } 8 | 9 | func Const() *iconst { 10 | i := &iconst{ 11 | items: newGroup("(", ")", "\n"), 12 | } 13 | i.items.omitWrapIf = func() bool { 14 | // We only need to omit wrap while length == 1. 15 | // NewIf length == 0, we need to keep it, or it will be invalid expr. 16 | return i.items.length() == 1 17 | } 18 | return i 19 | } 20 | func (i *iconst) render(w io.Writer) { 21 | writeString(w, "const ") 22 | i.items.render(w) 23 | } 24 | 25 | func (i *iconst) AddField(name, value interface{}) *iconst { 26 | i.items.append(field(name, value, "=")) 27 | return i 28 | } 29 | func (i *iconst) AddTypedField(name, typ, value interface{}) *iconst { 30 | i.items.append(typedField(name, typ, value, "=")) 31 | return i 32 | } 33 | 34 | func (i *iconst) AddLineComment(content string, args ...interface{}) *iconst { 35 | i.items.append(LineComment(content, args...)) 36 | return i 37 | } 38 | -------------------------------------------------------------------------------- /call_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestCalls(t *testing.T) { 6 | t.Run("no owner", func(t *testing.T) { 7 | buf := pool.Get() 8 | defer buf.Free() 9 | 10 | expected := "List()" 11 | 12 | Call("List").render(buf) 13 | 14 | compareAST(t, expected, buf.String()) 15 | }) 16 | 17 | t.Run("witch owner", func(t *testing.T) { 18 | buf := pool.Get() 19 | defer buf.Free() 20 | 21 | expected := "x.List(src)" 22 | 23 | Call("List"). 24 | WithOwner("x"). 25 | AddParameter("src"). 26 | render(buf) 27 | 28 | compareAST(t, expected, buf.String()) 29 | }) 30 | 31 | t.Run("panic while owner is not nil", func(t *testing.T) { 32 | 33 | }) 34 | 35 | t.Run("call list", func(t *testing.T) { 36 | buf := pool.Get() 37 | defer buf.Free() 38 | 39 | expected := "x.List().Next(src,dst)" 40 | 41 | Call("List"). 42 | WithOwner("x"). 43 | AddCall("Next", "src", "dst"). 44 | render(buf) 45 | 46 | compareAST(t, expected, buf.String()) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type istruct struct { 6 | name string 7 | items *group 8 | } 9 | 10 | // Struct will insert a new struct. 11 | func Struct(name string) *istruct { 12 | return &istruct{ 13 | name: name, 14 | // We will insert new line before closing the struct to avoid being affect 15 | // by line comments. 16 | items: newGroup("{", "\n}", "\n"), 17 | } 18 | } 19 | 20 | func (i *istruct) render(w io.Writer) { 21 | writeStringF(w, "type %s struct", i.name) 22 | i.items.render(w) 23 | } 24 | 25 | // AddLine will insert an empty line. 26 | func (i *istruct) AddLine() *istruct { 27 | i.items.append(Line()) 28 | return i 29 | } 30 | 31 | // AddLineComment will insert a new line comment. 32 | func (i *istruct) AddLineComment(content string, args ...interface{}) *istruct { 33 | i.items.append(LineComment(content, args...)) 34 | return i 35 | } 36 | 37 | func (i *istruct) AddField(name, typ interface{}) *istruct { 38 | i.items.append(field(name, typ, " ")) 39 | return i 40 | } 41 | -------------------------------------------------------------------------------- /field.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // ifield is used to represent a key-value pair. 8 | // 9 | // It could be used in: 10 | // - struct decl 11 | // - struct value 12 | // - method receiver 13 | // - function parameter 14 | // - function result 15 | // - ... 16 | type ifield struct { 17 | name Node 18 | typ Node 19 | value Node 20 | separator string 21 | } 22 | 23 | func field(name, value interface{}, sep string) *ifield { 24 | return &ifield{ 25 | name: parseNode(name), 26 | value: parseNode(value), 27 | separator: sep, 28 | } 29 | } 30 | 31 | func typedField(name, typ, value interface{}, sep string) *ifield { 32 | return &ifield{ 33 | name: parseNode(name), 34 | typ: parseNode(typ), 35 | value: parseNode(value), 36 | separator: sep, 37 | } 38 | } 39 | 40 | func (f *ifield) render(w io.Writer) { 41 | f.name.render(w) 42 | if f.typ != nil { 43 | writeString(w, " ") 44 | f.typ.render(w) 45 | } 46 | writeString(w, f.separator) 47 | f.value.render(w) 48 | } 49 | -------------------------------------------------------------------------------- /call.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type icall struct { 8 | owner Node 9 | name string 10 | items *group 11 | calls *group 12 | } 13 | 14 | // Call is used to generate a function call. 15 | func Call(name string) *icall { 16 | ic := &icall{ 17 | name: name, 18 | items: newGroup("(", ")", ","), 19 | calls: newGroup("", "", "."), 20 | } 21 | return ic 22 | } 23 | 24 | func (i *icall) render(w io.Writer) { 25 | if i.owner != nil { 26 | i.owner.render(w) 27 | writeString(w, ".") 28 | } 29 | writeString(w, i.name) 30 | i.items.render(w) 31 | if i.calls.length() != 0 { 32 | writeString(w, ".") 33 | i.calls.render(w) 34 | } 35 | } 36 | 37 | func (i *icall) WithOwner(name string) *icall { 38 | i.owner = String(name) 39 | return i 40 | } 41 | 42 | func (i *icall) AddParameter(value ...interface{}) *icall { 43 | i.items.append(value...) 44 | return i 45 | } 46 | 47 | func (i *icall) AddCall(name string, params ...interface{}) *icall { 48 | i.calls.append(Call(name).AddParameter(params...)) 49 | return i 50 | } 51 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type Generator struct { 10 | g *group 11 | } 12 | 13 | // New will create a new generator which hold the group reference. 14 | func New() *Generator { 15 | return &Generator{g: NewGroup()} 16 | } 17 | 18 | func (g *Generator) NewGroup() (ng *group) { 19 | ng = NewGroup() 20 | g.g.append(ng) 21 | return ng 22 | } 23 | 24 | // Write will write the group into the given writer. 25 | func (g *Generator) Write(w io.Writer) { 26 | g.g.render(w) 27 | } 28 | 29 | // WriteFile will write the group into the given path. 30 | func (g *Generator) WriteFile(path string) error { 31 | file, err := os.Create(path) 32 | if err != nil { 33 | return fmt.Errorf("create file %s: %s", path, err) 34 | } 35 | g.g.render(file) 36 | return nil 37 | } 38 | 39 | // AppendFile will append the group after the give path. 40 | func (g *Generator) AppendFile(path string) error { 41 | file, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR, 0644) 42 | if err != nil { 43 | return fmt.Errorf("create file %s: %s", path, err) 44 | } 45 | g.g.render(file) 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /var_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVar(t *testing.T) { 8 | t.Run("single", func(t *testing.T) { 9 | buf := pool.Get() 10 | defer buf.Free() 11 | 12 | expected := "var Version=2" 13 | 14 | Var(). 15 | AddField("Version", Lit(2)). 16 | render(buf) 17 | 18 | compareAST(t, expected, buf.String()) 19 | }) 20 | 21 | t.Run("typed", func(t *testing.T) { 22 | buf := pool.Get() 23 | defer buf.Free() 24 | 25 | expected := "var Version int =2" 26 | 27 | Var(). 28 | AddTypedField("Version", "int", Lit(2)). 29 | render(buf) 30 | 31 | compareAST(t, expected, buf.String()) 32 | }) 33 | 34 | t.Run("multiple", func(t *testing.T) { 35 | buf := pool.Get() 36 | defer buf.Free() 37 | 38 | expected := `var ( 39 | Version=2 40 | Description="Hello, World!" 41 | ) 42 | ` 43 | 44 | Var(). 45 | AddField("Version", Lit(2)). 46 | AddField("Description", Lit("Hello, World!")). 47 | render(buf) 48 | 49 | compareAST(t, expected, buf.String()) 50 | }) 51 | 52 | t.Run("decl", func(t *testing.T) { 53 | buf := pool.Get() 54 | defer buf.Free() 55 | 56 | expected := `var _ io.Reader` 57 | 58 | Var(). 59 | AddDecl("_", "io.Reader"). 60 | render(buf) 61 | 62 | compareAST(t, expected, buf.String()) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /const_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConst(t *testing.T) { 8 | t.Run("single", func(t *testing.T) { 9 | buf := pool.Get() 10 | defer buf.Free() 11 | 12 | expected := "const Version=2" 13 | 14 | Const(). 15 | AddField("Version", Lit(2)). 16 | render(buf) 17 | 18 | compareAST(t, expected, buf.String()) 19 | }) 20 | 21 | t.Run("typed", func(t *testing.T) { 22 | buf := pool.Get() 23 | defer buf.Free() 24 | 25 | expected := "const Version int =2" 26 | 27 | Const(). 28 | AddTypedField("Version", "int", Lit(2)). 29 | render(buf) 30 | 31 | compareAST(t, expected, buf.String()) 32 | }) 33 | 34 | t.Run("multiple", func(t *testing.T) { 35 | buf := pool.Get() 36 | defer buf.Free() 37 | 38 | expected := `const ( 39 | Version=2 40 | Description="Hello, World!" 41 | ) 42 | ` 43 | 44 | Const(). 45 | AddField("Version", Lit(2)). 46 | AddField("Description", Lit("Hello, World!")). 47 | render(buf) 48 | 49 | compareAST(t, expected, buf.String()) 50 | }) 51 | 52 | t.Run("line comment", func(t *testing.T) { 53 | buf := pool.Get() 54 | defer buf.Free() 55 | 56 | expected := `const ( 57 | // Version is the version 58 | Version=2 59 | ) 60 | ` 61 | 62 | Const(). 63 | AddLineComment("Version is the version"). 64 | AddField("Version", Lit(2)). 65 | render(buf) 66 | 67 | compareAST(t, expected, buf.String()) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /import.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type iimport struct { 6 | items *group 7 | } 8 | 9 | // Import will start a new import group. 10 | func Import() *iimport { 11 | i := &iimport{ 12 | items: newGroup("(", ")", "\n"), 13 | } 14 | i.items.omitWrapIf = func() bool { 15 | return i.items.length() <= 1 16 | } 17 | return i 18 | } 19 | 20 | func (i *iimport) render(w io.Writer) { 21 | // Don't need to render anything if import is empty 22 | if i.items.length() == 0 { 23 | return 24 | } 25 | writeString(w, "import ") 26 | i.items.render(w) 27 | } 28 | 29 | // AddPath will import a new path, like `"context"` 30 | func (i *iimport) AddPath(name string) *iimport { 31 | i.items.append(Lit(name)) 32 | return i 33 | } 34 | 35 | // AddDot will import a new path with dot, like `. "context"` 36 | func (i *iimport) AddDot(name string) *iimport { 37 | i.items.append(String(`. "%s"`, name)) 38 | return i 39 | } 40 | 41 | // AddBlank will import a new path with black, like `_ "context"` 42 | func (i *iimport) AddBlank(name string) *iimport { 43 | i.items.append(String(`_ "%s"`, name)) 44 | return i 45 | } 46 | 47 | // AddAlias will import a new path with alias, like `ctx "context"` 48 | func (i *iimport) AddAlias(name, alias string) *iimport { 49 | i.items.append(String(`%s "%s"`, alias, name)) 50 | return i 51 | } 52 | 53 | // AddLine will insert a new line here. 54 | func (i *iimport) AddLine() *iimport { 55 | i.items.append(Line()) 56 | return i 57 | } 58 | 59 | // AddLineComment will insert a new line comment here. 60 | func (i *iimport) AddLineComment(content string, args ...interface{}) *iimport { 61 | i.items.append(LineComment(content, args...)) 62 | return i 63 | } 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/). 7 | 8 | ## [v0.3.0] - 2021-11-04 9 | 10 | ### Added 11 | 12 | - Increase test coverage 13 | - feat: Add Generator as exported type (#10) 14 | 15 | ## [v0.2.0] - 2021-09-15 16 | 17 | ### Added 18 | 19 | - feat: Add group.AddString support 20 | - feat: Implement type and type alias support 21 | 22 | ### Changed 23 | 24 | - function without `AddBody` will not generate empty body anymore. 25 | 26 | ## [v0.1.0] - 2021-09-08 27 | 28 | ### Added 29 | 30 | - feat: Add interface support (#4) 31 | - feat: Implement AppendFile 32 | - feat: Field accept both string and Node as input 33 | - feat: Add typed field support 34 | - feat: Export logic statement for group 35 | - feat: Add function call support 36 | - feat: Allow all body accept interface instead 37 | - feat: Add defer and function call support 38 | - Add var decl support 39 | - feat: Add test case for struct 40 | - API Redesign 41 | 42 | ### Fixed 43 | 44 | - fix: Struct should not have NamedLineComment 45 | - fix: struct Field should not return nil 46 | - fix: Insert a new line before closing struct 47 | - fix: Insert new line after switch case 48 | 49 | ### Refactor 50 | 51 | - Change return to accept input instead of function call 52 | - refactor: Make comment as float just like go ast (#6) 53 | 54 | ## [v0.0.2] - 2021-09-03 55 | 56 | ### Added 57 | 58 | - feat: Implement struct support 59 | - feat: Implement named line comment support 60 | 61 | ### Fixed 62 | 63 | - fix: Comment should be renamed to LineComment 64 | 65 | ## v0.0.1 - 2021-09-03 66 | 67 | ### Added 68 | 69 | - Implement basic functions 70 | - feat: Add omit wrap support 71 | 72 | [v0.3.0]: https://github.com/Xuanwo/gg/compare/v0.2.0...v0.3.0 73 | [v0.2.0]: https://github.com/Xuanwo/gg/compare/v0.1.0...v0.2.0 74 | [v0.1.0]: https://github.com/Xuanwo/gg/compare/v0.0.2...v0.1.0 75 | [v0.0.2]: https://github.com/Xuanwo/gg/compare/v0.0.1...v0.0.2 76 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type isignature struct { 6 | comments *group 7 | name string 8 | parameters *group 9 | results *group 10 | } 11 | 12 | func signature(name string) *isignature { 13 | i := &isignature{ 14 | name: name, 15 | comments: newGroup("", "", "\n"), 16 | parameters: newGroup("(", ")", ","), 17 | results: newGroup("(", ")", ","), 18 | } 19 | // We should omit the `()` if result is empty 20 | // Read about omit in NewFunction comments. 21 | i.results.omitWrapIf = func() bool { 22 | l := i.results.length() 23 | if l == 0 { 24 | // There is no result fields, we can omit `()` safely. 25 | return true 26 | } 27 | return false 28 | } 29 | return i 30 | } 31 | 32 | func (i *isignature) render(w io.Writer) { 33 | // Render function name 34 | writeString(w, i.name) 35 | 36 | // Render parameters 37 | i.parameters.render(w) 38 | // Render results 39 | i.results.render(w) 40 | } 41 | 42 | func (i *isignature) AddParameter(name, typ interface{}) *isignature { 43 | i.parameters.append(field(name, typ, " ")) 44 | return i 45 | } 46 | 47 | func (i *isignature) AddResult(name, typ interface{}) *isignature { 48 | i.results.append(field(name, typ, " ")) 49 | return i 50 | } 51 | 52 | type iinterface struct { 53 | name string 54 | items *group 55 | } 56 | 57 | func Interface(name string) *iinterface { 58 | return &iinterface{ 59 | name: name, 60 | items: newGroup("{\n", "}", "\n"), 61 | } 62 | } 63 | 64 | func (i *iinterface) render(w io.Writer) { 65 | writeStringF(w, "type %s interface", i.name) 66 | i.items.render(w) 67 | } 68 | 69 | func (i *iinterface) NewFunction(name string) *isignature { 70 | sig := signature(name) 71 | i.items.append(sig) 72 | return sig 73 | } 74 | 75 | // AddLineComment will insert a new line comment. 76 | func (i *iinterface) AddLineComment(content string, args ...interface{}) *iinterface { 77 | i.items.append(LineComment(content, args...)) 78 | return i 79 | } 80 | 81 | // AddLine will insert a new line. 82 | func (i *iinterface) AddLine() *iinterface { 83 | i.items.append(Line()) 84 | return i 85 | } 86 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestString(t *testing.T) { 8 | t.Run("simple string", func(t *testing.T) { 9 | buf := pool.Get() 10 | defer buf.Free() 11 | 12 | expected := "Hello, World!" 13 | 14 | String(expected).render(buf) 15 | 16 | compareAST(t, expected, buf.String()) 17 | }) 18 | t.Run("format string", func(t *testing.T) { 19 | buf := pool.Get() 20 | defer buf.Free() 21 | 22 | expected := "Hello, World!" 23 | 24 | String("Hello, %s!", "World").render(buf) 25 | 26 | compareAST(t, expected, buf.String()) 27 | }) 28 | } 29 | 30 | func TestLineComment(t *testing.T) { 31 | t.Run("simple comment", func(t *testing.T) { 32 | buf := pool.Get() 33 | defer buf.Free() 34 | 35 | expected := "// Hello, World!" 36 | 37 | LineComment("Hello, World!").render(buf) 38 | 39 | compareAST(t, expected, buf.String()) 40 | }) 41 | t.Run("format comment", func(t *testing.T) { 42 | buf := pool.Get() 43 | defer buf.Free() 44 | 45 | expected := "// Hello, World!" 46 | 47 | LineComment("Hello, %s!", "World").render(buf) 48 | 49 | compareAST(t, expected, buf.String()) 50 | }) 51 | } 52 | 53 | func TestLit(t *testing.T) { 54 | buf := pool.Get() 55 | defer buf.Free() 56 | 57 | expected := "true" 58 | 59 | Lit(true).render(buf) 60 | 61 | compareAST(t, expected, buf.String()) 62 | } 63 | 64 | func TestFormatComment(t *testing.T) { 65 | cases := []struct { 66 | name string 67 | input string 68 | expect string 69 | }{ 70 | { 71 | "short line", 72 | "Value comment", 73 | "// Value comment", 74 | }, 75 | { 76 | "long single line", 77 | "These is a long line that we need to do line break at 140. However, this long line is not long enough, so we still need to pollute a lot water in it. After all these jobs, we can test this long line.", 78 | `// These is a long line that we need to do line break at 140. However, this long line is not long enough, 79 | // so we still need to pollute a lot water in it. After all these jobs, we can test this long line.`, 80 | }, 81 | { 82 | "multi lines", 83 | `There is line which has 84 | its own line break.`, 85 | `// There is line which has 86 | // its own line break.`, 87 | }, 88 | } 89 | 90 | for _, v := range cases { 91 | t.Run(v.name, func(t *testing.T) { 92 | compareAST(t, v.expect, formatLineComment(v.input)) 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /logic.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type iif struct { 6 | judge Node 7 | body *group 8 | } 9 | 10 | func If(judge interface{}) *iif { 11 | return &iif{ 12 | judge: parseNode(judge), 13 | body: newGroup("{\n", "\n}", "\n"), 14 | } 15 | } 16 | func (i *iif) render(w io.Writer) { 17 | writeString(w, "if ") 18 | i.judge.render(w) 19 | i.body.render(w) 20 | } 21 | 22 | func (i *iif) AddBody(node ...interface{}) *iif { 23 | i.body.append(node...) 24 | return i 25 | } 26 | 27 | type ifor struct { 28 | judge Node 29 | body *group 30 | } 31 | 32 | func (i *ifor) render(w io.Writer) { 33 | writeString(w, "for ") 34 | i.judge.render(w) 35 | i.body.render(w) 36 | } 37 | 38 | func For(judge interface{}) *ifor { 39 | return &ifor{ 40 | judge: parseNode(judge), 41 | body: newGroup("{\n", "\n}", "\n"), 42 | } 43 | } 44 | 45 | func (i *ifor) AddBody(node ...interface{}) *ifor { 46 | i.body.append(node...) 47 | return i 48 | } 49 | 50 | type icase struct { 51 | judge Node // judge == nil means it's a default case. 52 | body *group 53 | } 54 | 55 | func (i *icase) render(w io.Writer) { 56 | if i.judge == nil { 57 | writeString(w, "default:") 58 | } else { 59 | writeString(w, "case ") 60 | i.judge.render(w) 61 | writeString(w, ":") 62 | } 63 | i.body.render(w) 64 | } 65 | 66 | func (i *icase) AddBody(node ...interface{}) *icase { 67 | i.body.append(node...) 68 | return i 69 | } 70 | 71 | type iswitch struct { 72 | judge Node 73 | cases []*icase 74 | defaultCase *icase 75 | } 76 | 77 | func (i *iswitch) render(w io.Writer) { 78 | writeString(w, "switch ") 79 | i.judge.render(w) 80 | writeString(w, "{\n") 81 | for _, c := range i.cases { 82 | c.render(w) 83 | writeString(w, "\n") 84 | } 85 | if i.defaultCase != nil { 86 | i.defaultCase.render(w) 87 | writeString(w, "\n") 88 | } 89 | writeString(w, "}") 90 | } 91 | 92 | func Switch(judge interface{}) *iswitch { 93 | return &iswitch{ 94 | judge: parseNode(judge), 95 | } 96 | } 97 | func (i *iswitch) NewCase(judge Node) *icase { 98 | ic := &icase{ 99 | judge: judge, 100 | body: newGroup("\n", "", "\n"), 101 | } 102 | i.cases = append(i.cases, ic) 103 | return ic 104 | } 105 | 106 | func (i *iswitch) NewDefault() *icase { 107 | ic := &icase{ 108 | body: newGroup("\n", "", "\n"), 109 | } 110 | i.cases = append(i.cases, ic) 111 | return ic 112 | } 113 | 114 | func Continue() Node { 115 | return String("continue") 116 | } 117 | -------------------------------------------------------------------------------- /function.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "io" 4 | 5 | type ifunction struct { 6 | name string 7 | receiver Node 8 | parameters *group 9 | results *group 10 | body *group 11 | call *icall 12 | } 13 | 14 | // Function represent both method and function in Go. 15 | // 16 | // NOTES 17 | // 18 | // If `WithReceiver`, we will generate a method: 19 | // func (t test) Test() 20 | // 21 | // If `WithCall`, we will generate a function call: 22 | // func Test(){}() 23 | // 24 | // If `AddBody`, we will generate like a function definition without body: 25 | // func Test() { 26 | // println("Hello, World!") 27 | // } 28 | func Function(name string) *ifunction { 29 | i := &ifunction{ 30 | name: name, 31 | parameters: newGroup("(", ")", ","), 32 | results: newGroup("(", ")", ","), 33 | body: newGroup("{\n", "}", "\n"), 34 | } 35 | // We should omit the `()` if result is empty 36 | i.results.omitWrapIf = func() bool { 37 | l := i.results.length() 38 | if l == 0 { 39 | // There is no result fields, we can omit `()` safely. 40 | return true 41 | } 42 | // NOTE: We also need to omit `()` while there is only one field, 43 | // and the field name is empty, like `test() (int64) => test() int64`. 44 | // But it's hard to implement in render side, so we let `go fmt` to do the job. 45 | return false 46 | } 47 | return i 48 | } 49 | 50 | func (i *ifunction) render(w io.Writer) { 51 | writeString(w, "func ") 52 | 53 | // Render receiver 54 | if i.receiver != nil { 55 | writeString(w, "(") 56 | i.receiver.render(w) 57 | writeString(w, ")") 58 | } 59 | 60 | // Render function name 61 | writeString(w, i.name) 62 | 63 | // Render parameters 64 | i.parameters.render(w) 65 | 66 | // Render results 67 | i.results.render(w) 68 | 69 | // Only render body while there is a body or a call. 70 | // 71 | // This will add extra burden for functions that have empty body. 72 | // But it's a rare case, and we can always add an empty line in body to workaround. 73 | if i.body.length() > 0 || i.call != nil { 74 | i.body.render(w) 75 | } 76 | 77 | // Only render function call while there is a call. 78 | if i.call != nil { 79 | i.call.render(w) 80 | } 81 | } 82 | 83 | func (i *ifunction) WithReceiver(name, typ interface{}) *ifunction { 84 | i.receiver = field(name, typ, " ") 85 | return i 86 | } 87 | 88 | func (i *ifunction) WithCall(params ...interface{}) *ifunction { 89 | i.call = Call("").AddParameter(params...) 90 | return i 91 | } 92 | 93 | func (i *ifunction) AddParameter(name, typ interface{}) *ifunction { 94 | i.parameters.append(field(name, typ, " ")) 95 | return i 96 | } 97 | 98 | func (i *ifunction) AddResult(name, typ interface{}) *ifunction { 99 | i.results.append(field(name, typ, " ")) 100 | return i 101 | } 102 | 103 | func (i *ifunction) AddBody(node ...interface{}) *ifunction { 104 | i.body.append(node...) 105 | return i 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/Xuanwo/gg/workflows/Unit%20Test/badge.svg?branch=master)](https://github.com/Xuanwo/gg/actions?query=workflow%3A%22Unit+Test%22) 2 | [![Go dev](https://pkg.go.dev/badge/github.com/Xuanwo/gg)](https://pkg.go.dev/github.com/Xuanwo/gg) 3 | [![License](https://img.shields.io/badge/license-apache%20v2-blue.svg)](https://github.com/Xuanwo/gg/blob/master/LICENSE) 4 | [![matrix](https://img.shields.io/matrix/xuanwo@gg:matrix.org.svg?logo=matrix)](https://matrix.to/#/#xuanwo@gg:matrix.org) 5 | 6 | # gg 7 | 8 | `gg` is a General Golang Code Generator: A Good Game to play with Golang. 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | 16 | . "github.com/Xuanwo/gg" 17 | ) 18 | 19 | func main() { 20 | f := NewGroup() 21 | f.AddPackage("main") 22 | f.NewImport(). 23 | AddPath("fmt") 24 | f.NewFunction("main").AddBody( 25 | String(`fmt.Println("%s")`, "Hello, World!"), 26 | ) 27 | fmt.Println(f.String()) 28 | } 29 | ``` 30 | 31 | Output (after `go fmt`) 32 | 33 | ```go 34 | package main 35 | 36 | import "fmt" 37 | 38 | func main() { 39 | fmt.Println("Hello, World!") 40 | } 41 | ``` 42 | 43 | ## Design 44 | 45 | `gg` is a general golang code generator that designed for resolving problems exists in the following tools: 46 | 47 | - [text/template](https://pkg.go.dev/text/template): Additional syntax, Steep learning curve, Complex logic is difficult to maintain 48 | - [dave/jennifer](https://github.com/dave/jennifer): Overly abstract APIs, user need to take care about `()`, `,` everywhere. 49 | - [kubernetes-sigs/kubebuilder](https://github.com/kubernetes-sigs/kubebuilder): Parse data from struct tags/comments, not a general code generator. 50 | 51 | In short, `gg` will provide near-native golang syntax and helpful API so play a good game with Golang. With `gg`, we can generate golang code more easily and understandable. 52 | 53 | ## Usage 54 | 55 | ### Package Name 56 | 57 | ```go 58 | f := Group() 59 | f.AddPackage("main") 60 | // package main 61 | ``` 62 | 63 | ### Imports 64 | 65 | ```go 66 | f := Group() 67 | f.NewImport(). 68 | AddPath("context"). 69 | AddDot("math"). 70 | AddBlank("time"). 71 | AddAlias("x", "testing") 72 | // import ( 73 | // "context" 74 | // . "math" 75 | // _ "time" 76 | // x "testing" 77 | // ) 78 | ``` 79 | 80 | ### Function 81 | 82 | ```go 83 | f := Group() 84 | f.NewFunction("hello"). 85 | WithReceiver("v", "*World"). 86 | AddParameter("content", "string"). 87 | AddParameter("times", "int"). 88 | AddResult("v", "string"). 89 | AddBody(gg.String(`return fmt.Sprintf("say %s in %d times", content, times)`)) 90 | // func (v *World) hello(content string, times int) (v string) { 91 | // return fmt.Sprintf("say %s in %d times", content, times) 92 | //} 93 | ``` 94 | 95 | ### Struct 96 | 97 | ```go 98 | f := Group() 99 | f.NewStruct("World"). 100 | AddField("x", "int64"). 101 | AddField("y", "string") 102 | // type World struct { 103 | // x int64 104 | // y string 105 | //} 106 | ``` 107 | 108 | ## Acknowledgement 109 | 110 | - `gg` is inspired by [dave/jennifer](https://github.com/dave/jennifer), I borrowed most ideas and some code from it. Nice work! 111 | -------------------------------------------------------------------------------- /function_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import "testing" 4 | 5 | func TestFunction(t *testing.T) { 6 | t.Run("no receiver", func(t *testing.T) { 7 | buf := pool.Get() 8 | defer buf.Free() 9 | 10 | expected := `func Test(a int, b string) (d uint)` 11 | 12 | Function("Test"). 13 | AddParameter("a", "int"). 14 | AddParameter("b", "string"). 15 | AddResult("d", "uint"). 16 | render(buf) 17 | 18 | compareAST(t, expected, buf.String()) 19 | }) 20 | t.Run("has receiver", func(t *testing.T) { 21 | buf := pool.Get() 22 | defer buf.Free() 23 | 24 | expected := `func (r *Q) Test() (a int, b int64, d string) { 25 | return "Hello, World!" 26 | }` 27 | Function("Test"). 28 | WithReceiver("r", "*Q"). 29 | AddResult("a", "int"). 30 | AddResult("b", "int64"). 31 | AddResult("d", "string"). 32 | AddBody( 33 | String(`return "Hello, World!"`), 34 | ). 35 | render(buf) 36 | 37 | compareAST(t, expected, buf.String()) 38 | }) 39 | 40 | t.Run("node input", func(t *testing.T) { 41 | buf := pool.Get() 42 | defer buf.Free() 43 | 44 | expected := `func (r *Q) Test() (a int, b int64, d string) { 45 | return "Hello, World!" 46 | }` 47 | Function("Test"). 48 | WithReceiver("r", "*Q"). 49 | AddResult("a", String("int")). 50 | AddResult("b", "int64"). 51 | AddResult("d", "string"). 52 | AddBody( 53 | String(`return "Hello, World!"`), 54 | ). 55 | render(buf) 56 | 57 | compareAST(t, expected, buf.String()) 58 | }) 59 | 60 | t.Run("call", func(t *testing.T) { 61 | buf := pool.Get() 62 | defer buf.Free() 63 | 64 | expected := `func(){}()` 65 | 66 | fn := Function("") 67 | fn.WithCall() 68 | fn.render(buf) 69 | 70 | compareAST(t, expected, buf.String()) 71 | }) 72 | 73 | t.Run("no name result - no receiver - single result", func(t *testing.T) { 74 | buf := pool.Get() 75 | defer buf.Free() 76 | 77 | expected := `func Test(a int) (int)` 78 | 79 | Function("Test"). 80 | AddParameter("a", "int"). 81 | AddResult("", "int"). 82 | render(buf) 83 | 84 | compareAST(t, expected, buf.String()) 85 | }) 86 | 87 | t.Run("no name result - no receiver - multi result", func(t *testing.T) { 88 | buf := pool.Get() 89 | defer buf.Free() 90 | 91 | expected := `func Test(a int) (int, string, error)` 92 | 93 | Function("Test"). 94 | AddParameter("a", "int"). 95 | AddResult("", "int"). 96 | AddResult("", "string"). 97 | AddResult("", "error"). 98 | render(buf) 99 | 100 | compareAST(t, expected, buf.String()) 101 | }) 102 | 103 | t.Run("no name result - has receiver - single result", func(t *testing.T) { 104 | buf := pool.Get() 105 | defer buf.Free() 106 | 107 | expected := `func (r *Q) Test(a int) (int)` 108 | 109 | Function("Test"). 110 | WithReceiver("r", "*Q"). 111 | AddParameter("a", "int"). 112 | AddResult("", "int"). 113 | render(buf) 114 | 115 | compareAST(t, expected, buf.String()) 116 | }) 117 | 118 | t.Run("no name result - has receiver - multi result", func(t *testing.T) { 119 | buf := pool.Get() 120 | defer buf.Free() 121 | 122 | expected := `func (r *Q) Test(a int) (int, string, error)` 123 | 124 | Function("Test"). 125 | WithReceiver("r", "*Q"). 126 | AddParameter("a", "int"). 127 | AddResult("", "int"). 128 | AddResult("", "string"). 129 | AddResult("", "error"). 130 | render(buf) 131 | 132 | compareAST(t, expected, buf.String()) 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Xuanwo/go-bufferpool" 6 | "io" 7 | "strings" 8 | "text/template" 9 | ) 10 | 11 | var pool = bufferpool.New(1024) 12 | 13 | // TODO: we will support use to config this logic. 14 | const lineLength = 80 15 | 16 | // All internal types are prefixed with `i` to avoid conflict with golang keywords. 17 | type istring string 18 | 19 | func (v *istring) render(w io.Writer) { 20 | writeString(w, string(*v)) 21 | } 22 | 23 | // S is an alias to String. 24 | // TODO: can we find a new name for this? 25 | var S = String 26 | 27 | // String will add a format string in NewGroup, just like fmt.Printf. 28 | func String(format string, args ...interface{}) *istring { 29 | if len(args) == 0 { 30 | x := istring(format) 31 | return &x 32 | } 33 | x := istring(fmt.Sprintf(format, args...)) 34 | return &x 35 | } 36 | 37 | func formatLineComment(comment string) string { 38 | buf := pool.Get() 39 | defer buf.Free() 40 | 41 | // Trim space before going further. 42 | comment = strings.TrimSpace(comment) 43 | 44 | // Split comment into lines (we will keep original line break.) 45 | lines := strings.Split(comment, "\n") 46 | 47 | for _, line := range lines { 48 | cur := 0 49 | 50 | // Start a comment line. 51 | buf.AppendString("//") 52 | 53 | // Split comment into words 54 | words := strings.Split(line, " ") 55 | 56 | for _, word := range words { 57 | // NewIf current line is long enough we need to break it. 58 | if cur >= lineLength { 59 | buf.AppendString("\n//") 60 | cur = 0 61 | } 62 | 63 | buf.AppendString(" ") 64 | buf.AppendString(word) 65 | cur += len(word) 66 | } 67 | buf.AppendString("\n") 68 | } 69 | 70 | return strings.TrimSuffix(buf.String(), "\n") 71 | } 72 | 73 | func LineComment(content string, args ...interface{}) *istring { 74 | if len(args) != 0 { 75 | content = fmt.Sprintf(content, args...) 76 | } 77 | content = formatLineComment(content) 78 | return String(content) 79 | } 80 | 81 | type lit struct { 82 | value interface{} 83 | } 84 | 85 | func Lit(value interface{}) *lit { 86 | return &lit{value: value} 87 | } 88 | 89 | func (v *lit) render(w io.Writer) { 90 | var out string 91 | 92 | // Code borrowed from github.com/dave/jennifer 93 | switch v.value.(type) { 94 | case bool, string, int, complex128: 95 | out = fmt.Sprintf("%#v", v.value) 96 | case float64: 97 | out = fmt.Sprintf("%#v", v.value) 98 | // NewIf the formatted ivalue is not in scientific notation, and does not have a dot, then 99 | // we add ".0". Otherwise it will be interpreted as an int. 100 | // See: 101 | // https://github.com/dave/jennifer/issues/39 102 | // https://github.com/golang/go/issues/26363 103 | if !strings.Contains(out, ".") && !strings.Contains(out, "e") { 104 | out += ".0" 105 | } 106 | case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: 107 | out = fmt.Sprintf("%T(%#v)", v.value, v.value) 108 | case complex64: 109 | out = fmt.Sprintf("%T%#v", v.value, v.value) 110 | default: 111 | panic(fmt.Sprintf("unsupported type for literal: %T", v.value)) 112 | } 113 | 114 | writeString(w, out) 115 | } 116 | 117 | func (v *lit) String() string { 118 | buf := pool.Get() 119 | defer buf.Free() 120 | 121 | v.render(buf) 122 | return buf.String() 123 | } 124 | 125 | func Line() Node { 126 | return String("\n") 127 | } 128 | 129 | func Template(data interface{}, tmpl string) Node { 130 | buf := pool.Get() 131 | defer buf.Free() 132 | 133 | t := template.Must(template.New("").Parse(tmpl)) 134 | err := t.Execute(buf, data) 135 | if err != nil { 136 | panic(fmt.Errorf("template execute: %v", err)) 137 | } 138 | return String(buf.String()) 139 | } 140 | -------------------------------------------------------------------------------- /group_test.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/format" 8 | "go/parser" 9 | "go/token" 10 | "log" 11 | "os" 12 | "testing" 13 | "text/template" 14 | ) 15 | 16 | func ExampleNewGroup() { 17 | f := NewGroup() 18 | f.AddPackage("main") 19 | f.NewImport().AddPath("fmt") 20 | f.NewFunction("main").AddBody( 21 | String(`fmt.Println("%s")`, "Hello, World!"), 22 | ) 23 | fmt.Println(f.String()) 24 | // Output: 25 | // package main 26 | // 27 | // import "fmt" 28 | // func main(){ 29 | // fmt.Println("Hello, World!")} 30 | } 31 | 32 | func TestParseAST(t *testing.T) { 33 | content := `package main 34 | 35 | import "fmt" 36 | 37 | func main() { 38 | fmt.Println("Hello, World!") 39 | } 40 | ` 41 | fset := token.NewFileSet() 42 | f, err := parser.ParseFile(fset, "", content, parser.AllErrors) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | ast.Print(fset, f) 47 | } 48 | 49 | func TestViaGolangAST(t *testing.T) { 50 | fset := token.NewFileSet() 51 | f := &ast.File{ 52 | Name: ast.NewIdent("main"), 53 | Scope: ast.NewScope(nil), 54 | } 55 | 56 | f.Decls = append(f.Decls, &ast.GenDecl{ 57 | Tok: token.IMPORT, 58 | Specs: []ast.Spec{ 59 | &ast.ImportSpec{ 60 | Path: &ast.BasicLit{ 61 | Kind: token.STRING, 62 | Value: `"fmt"`, 63 | }, 64 | }, 65 | }, 66 | }) 67 | 68 | f.Decls = append(f.Decls, &ast.FuncDecl{ 69 | Name: ast.NewIdent("main"), 70 | Type: &ast.FuncType{}, 71 | Body: &ast.BlockStmt{ 72 | List: []ast.Stmt{ 73 | &ast.ExprStmt{X: &ast.CallExpr{ 74 | Fun: &ast.SelectorExpr{ 75 | X: ast.NewIdent("fmt"), 76 | Sel: ast.NewIdent("Println"), 77 | }, 78 | Args: []ast.Expr{ 79 | &ast.BasicLit{ 80 | Kind: token.STRING, 81 | Value: `"Hello, World!"`, 82 | }, 83 | }, 84 | }}, 85 | }, 86 | }, 87 | }) 88 | 89 | err := format.Node(os.Stdout, fset, f) 90 | if err != nil { 91 | log.Fatalf("ast is incorrect") 92 | } 93 | } 94 | 95 | func TestViaString(t *testing.T) { 96 | b := &bytes.Buffer{} 97 | 98 | fmt.Fprintf(b, "package %s\n\n", "main") 99 | fmt.Fprintf(b, "import %s\n\n", `"fmt"`) 100 | fmt.Fprintf(b, "func main() {\n") 101 | fmt.Fprintf(b, "\tfmt.Println(%s)\n", `"Hello, World!"`) 102 | fmt.Fprint(b, "}\n") 103 | 104 | fmt.Println(b.String()) 105 | } 106 | 107 | func TestViaGolangTemplate(t *testing.T) { 108 | b := &bytes.Buffer{} 109 | 110 | data := struct { 111 | Package string 112 | Import []string 113 | Content string 114 | }{ 115 | Package: "main", 116 | Import: []string{"fmt"}, 117 | Content: "Hello, World!", 118 | } 119 | 120 | tmpl := `package {{ .Package }} 121 | 122 | import ( 123 | {{ range $_, $v := .Import -}} 124 | "{{ $v }}" 125 | {{ end -}} 126 | ) 127 | 128 | func main() { 129 | fmt.Println("{{ .Content }}") 130 | }` 131 | 132 | err := template.Must(template.New("test").Parse(tmpl)).Execute(b, data) 133 | if err != nil { 134 | t.Error(err) 135 | } 136 | 137 | fmt.Println(b.String()) 138 | } 139 | 140 | func TestGroup(t *testing.T) { 141 | buf := pool.Get() 142 | defer buf.Free() 143 | 144 | expected := `import "math" 145 | 146 | // test line comment 147 | // test add string 148 | 149 | type Test string 150 | type Alias = string 151 | 152 | if true {} 153 | for true {} 154 | switch true {} 155 | var name = 123 156 | const name = 123 157 | 158 | func test() { 159 | } 160 | 161 | type test struct {} 162 | type test interface {} 163 | ` 164 | 165 | g := NewGroup() 166 | g.NewImport().AddPath("math") 167 | 168 | g.AddLineComment("test line comment") 169 | g.AddLine() 170 | g.AddString("// test add string") 171 | g.AddType("Test", "string") 172 | g.AddTypeAlias("Alias", "string") 173 | g.NewIf("true") 174 | g.NewFor("true") 175 | g.NewSwitch("true") 176 | g.NewVar().AddField("name", "123") 177 | g.NewConst().AddField("name", "123") 178 | g.NewFunction("test").AddBody(Line()) 179 | g.NewStruct("test") 180 | g.NewInterface("test") 181 | 182 | g.render(buf) 183 | 184 | compareAST(t, expected, buf.String()) 185 | } 186 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | package gg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | func NewGroup() *group { 10 | return newGroup("", "", "\n") 11 | } 12 | 13 | func newGroup(open, close, sep string) *group { 14 | return &group{ 15 | open: open, 16 | close: close, 17 | separator: sep, 18 | } 19 | } 20 | 21 | type group struct { 22 | items []Node 23 | open string 24 | close string 25 | separator string 26 | 27 | // NewIf this result is true, we will omit the wrap like `()`, `{}`. 28 | omitWrapIf func() bool 29 | } 30 | 31 | func (g *group) length() int { 32 | return len(g.items) 33 | } 34 | 35 | func (g *group) shouldOmitWrap() bool { 36 | if g.omitWrapIf == nil { 37 | return false 38 | } 39 | return g.omitWrapIf() 40 | } 41 | 42 | func (g *group) append(node ...interface{}) *group { 43 | if len(node) == 0 { 44 | return g 45 | } 46 | g.items = append(g.items, parseNodes(node)...) 47 | return g 48 | } 49 | 50 | func (g *group) render(w io.Writer) { 51 | if g.open != "" && !g.shouldOmitWrap() { 52 | writeString(w, g.open) 53 | } 54 | 55 | isfirst := true 56 | for _, node := range g.items { 57 | if !isfirst { 58 | writeString(w, g.separator) 59 | } 60 | node.render(w) 61 | isfirst = false 62 | } 63 | 64 | if g.close != "" && !g.shouldOmitWrap() { 65 | writeString(w, g.close) 66 | } 67 | } 68 | 69 | // Deprecated: use `Generator.Write(w)` instead. 70 | func (g *group) Write(w io.Writer) { 71 | g.render(w) 72 | } 73 | 74 | // Deprecated: use `Generator.WriteFile(w)` instead. 75 | func (g *group) WriteFile(path string) error { 76 | file, err := os.Create(path) 77 | if err != nil { 78 | return fmt.Errorf("create file %s: %s", path, err) 79 | } 80 | g.render(file) 81 | return nil 82 | } 83 | 84 | // Deprecated: use `Generator.AppendFile(w)` instead. 85 | func (g *group) AppendFile(path string) error { 86 | file, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR, 0644) 87 | if err != nil { 88 | return fmt.Errorf("create file %s: %s", path, err) 89 | } 90 | g.render(file) 91 | return nil 92 | } 93 | 94 | func (g *group) String() string { 95 | buf := pool.Get() 96 | defer buf.Free() 97 | 98 | g.render(buf) 99 | return buf.String() 100 | } 101 | 102 | func (g *group) AddLineComment(content string, args ...interface{}) *group { 103 | g.append(LineComment(content, args...)) 104 | return g 105 | } 106 | 107 | func (g *group) AddPackage(name string) *group { 108 | g.append(Package(name)) 109 | return g 110 | } 111 | 112 | func (g *group) AddLine() *group { 113 | g.append(Line()) 114 | return g 115 | } 116 | 117 | func (g *group) AddString(content string, args ...interface{}) *group { 118 | g.append(S(content, args...)) 119 | return g 120 | } 121 | 122 | func (g *group) AddType(name string, typ interface{}) *group { 123 | g.append(Type(name, typ)) 124 | return g 125 | } 126 | 127 | func (g *group) AddTypeAlias(name string, typ interface{}) *group { 128 | g.append(TypeAlias(name, typ)) 129 | return g 130 | } 131 | 132 | func (g *group) NewImport() *iimport { 133 | i := Import() 134 | g.append(i) 135 | return i 136 | } 137 | 138 | func (g *group) NewIf(judge interface{}) *iif { 139 | i := If(judge) 140 | g.append(i) 141 | return i 142 | } 143 | 144 | func (g *group) NewFor(judge interface{}) *ifor { 145 | i := For(judge) 146 | g.append(i) 147 | return i 148 | } 149 | 150 | func (g *group) NewSwitch(judge interface{}) *iswitch { 151 | i := Switch(judge) 152 | g.append(i) 153 | return i 154 | } 155 | 156 | func (g *group) NewVar() *ivar { 157 | i := Var() 158 | g.append(i) 159 | return i 160 | } 161 | 162 | func (g *group) NewConst() *iconst { 163 | i := Const() 164 | g.append(i) 165 | return i 166 | } 167 | 168 | func (g *group) NewFunction(name string) *ifunction { 169 | f := Function(name) 170 | g.append(f) 171 | return f 172 | } 173 | 174 | func (g *group) NewStruct(name string) *istruct { 175 | i := Struct(name) 176 | g.append(i) 177 | return i 178 | } 179 | 180 | func (g *group) NewInterface(name string) *iinterface { 181 | i := Interface(name) 182 | g.append(i) 183 | return i 184 | } 185 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------