├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── root.go ├── gen ├── expr.go ├── file_test.go ├── formatting.go ├── main.go ├── stmt.go └── type.go ├── main.go └── run ├── run.go └── run_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrew Loder 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tojen 2 | ====== 3 | 4 | tojen is a code generator that generates 5 | [jennifer](http://www.github.com/dave/jennifer) code from a existing file. 6 | 7 | ## Why? 8 | 9 | Well writing code that generates code is tedious. This tool removes some of the 10 | tedium by setting up a base that can be changed and extended to suit your needs 11 | for code generation. 12 | 13 | This was mostly inspired by the functionality of the go [text/template](https://golang.org/pkg/text/template/) system. The advantage is that static code was easy to write, but dynamic typesafe code was a big challenge. Also as the project grew the templates would get harder and harder to read. 14 | 15 | I created this project to further bridge the gap between the advantages of the text/template and keeping all the generation in the go language with jennifer. 16 | 17 | ## How? 18 | 19 | The command line is all you need. 20 | 21 | ``` 22 | go install github.com/aloder/tojen 23 | ``` 24 | In your terminal 25 | ``` 26 | tojen gen [source file] 27 | ``` 28 | This just takes the sourcefile and outputs the code in the terminal. 29 | 30 | ``` 31 | tojen gen [source file] [output file] 32 | ``` 33 | This takes the source file and outputs the code in the specified file 34 | 35 | ## Examples 36 | 37 | ### Hello World 38 | 39 | File main.go is defined as: 40 | 41 | ```go 42 | package main 43 | 44 | import "fmt" 45 | 46 | func main() { 47 | fmt.Println("Hello World!") 48 | } 49 | ``` 50 | 51 | Running command: 52 | ``` 53 | tojen gen main.go 54 | ``` 55 | 56 | Will print out: 57 | 58 | ```go 59 | package main 60 | 61 | import jen "github.com/dave/jennifer/jen" 62 | 63 | func genDeclAt15() jen.Code { 64 | return jen.Null() 65 | } 66 | func genFuncmain() jen.Code { 67 | return jen.Func().Id("main").Params().Block(jen.Qual("fmt", "Println").Call(jen.Lit("Hello World!"))) 68 | } 69 | func genFile() *jen.File { 70 | ret := jen.NewFile("main") 71 | ret.Add(genDeclAt15()) 72 | ret.Add(genFuncmain()) 73 | return ret 74 | } 75 | ``` 76 | 77 | It print out to the console because there was no secondary argument. This is useful for writing out simple end results that you would like jennifer to write then copying them into your code. If you would like to save the file, set the second argument. 78 | 79 | ### Static Struct 80 | 81 | Say you want to generate a static struct. 82 | 83 | ```go 84 | package model 85 | 86 | type User struct { 87 | Name string 88 | Email string 89 | Password string 90 | } 91 | ``` 92 | 93 | Running the command 94 | 95 | ``` 96 | tojen gen [path to user file] [output file] 97 | ``` 98 | 99 | Generates this 100 | ```go 101 | package main 102 | 103 | import jen "github.com/dave/jennifer/jen" 104 | 105 | func genDeclAt16() jen.Code { 106 | return jen.Null().Type().Id("User").Struct( 107 | jen.Id("Name").Id("string"), 108 | jen.Id("Email").Id("string"), 109 | jen.Id("Password").Id("string")) 110 | } 111 | func genFile() *jen.File { 112 | ret := jen.NewFile("model") 113 | ret.Add(genDeclAt16()) 114 | return ret 115 | } 116 | ``` 117 | 118 | The Idea of this package is not to generate and forget but rather to establish a 119 | boilerplate that allows you to extend and modify. 120 | 121 | If I only wanted the user struct code I would modify it to this: 122 | 123 | ```go 124 | func genUserStruct() jen.Code { 125 | return jen.Type().Id("User").Struct( 126 | jen.Id("Name").Id("string"), 127 | jen.Id("Email").Id("string"), 128 | jen.Id("Password").Id("string")) 129 | } 130 | ``` 131 | Now we have usable generation of static code that can be used in a project using jennifer. 132 | 133 | ## Notes 134 | 135 | Feel free to create an issue if you are having a problem or have a feature request. Pull requests are welcome as well. 136 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/aloder/tojen/gen" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func Execute() { 13 | var packageName string 14 | var genMain bool 15 | var formating bool 16 | 17 | var cmdGen = &cobra.Command{ 18 | Use: "gen [path to file] [output path]", 19 | Short: "Generate code from file", 20 | Long: `Generate code from a .go file. If output path is set then it will write the generated code to the output path, otherwise it will print it out to the console.`, 21 | Args: cobra.MinimumNArgs(1), 22 | Run: func(cmd *cobra.Command, args []string) { 23 | b, err := ioutil.ReadFile(args[0]) 24 | if err != nil { 25 | fmt.Println(err) 26 | os.Exit(1) 27 | } 28 | if packageName == "" { 29 | packageName = "main" 30 | } 31 | retBytes, err := gen.GenerateFileBytes(b, packageName, genMain, formating) 32 | if err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | if len(args) == 2 { 37 | osFile, err := os.Create(args[1]) 38 | if err != nil { 39 | fmt.Println(err) 40 | os.Exit(1) 41 | } 42 | _, err = osFile.Write(retBytes) 43 | if err != nil { 44 | fmt.Println(err) 45 | os.Exit(1) 46 | } 47 | fmt.Println("Successfuly wrote file to " + args[1]) 48 | err = osFile.Close() 49 | if err != nil { 50 | fmt.Println(err) 51 | os.Exit(1) 52 | } 53 | os.Exit(0) 54 | } 55 | fmt.Println(string(retBytes)) 56 | os.Exit(0) 57 | }, 58 | } 59 | 60 | var rootCmd = &cobra.Command{ 61 | Use: "tojen", 62 | Short: "Generate jennifer code from file", 63 | Long: `Generate jennifer code from a file with the command gen`, 64 | } 65 | cmdGen.Flags().StringVarP(&packageName, "package", "p", "", "Name of package") 66 | cmdGen.Flags().BoolVarP(&genMain, "main", "m", false, "Generate main function that prints out the generated code when called -- used for testing.") 67 | 68 | cmdGen.Flags().BoolVarP(&formating, "formatted", "f", false, "Format the generated code EXPERIMENTAL") 69 | 70 | rootCmd.AddCommand(cmdGen) 71 | rootCmd.Execute() 72 | 73 | } 74 | -------------------------------------------------------------------------------- /gen/expr.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "go/ast" 5 | "reflect" 6 | 7 | "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | func genExprs(s []ast.Expr) jen.Code { 11 | if len(s) == 0 { 12 | return jen.Null() 13 | } 14 | if len(s) == 1 { 15 | return genExpr(s[0]) 16 | } 17 | code := genExprsCode(s) 18 | return jen.Dot("List").Call(code...) 19 | } 20 | 21 | func genExprsCode(s []ast.Expr) []jen.Code { 22 | var code []jen.Code 23 | for _, expr := range s { 24 | code = append(code, jen.Id("jen").Add(genExpr(expr))) 25 | } 26 | return code 27 | } 28 | 29 | func genExpr(s ast.Expr) jen.Code { 30 | if s == nil { 31 | return jen.Null() 32 | } 33 | switch t := s.(type) { 34 | case *ast.Ident: 35 | return ident(t) 36 | case *ast.Ellipsis: 37 | return ellipsis(t) 38 | case *ast.BasicLit: 39 | return basicLit(t) 40 | case *ast.FuncLit: 41 | return funcLit(t) 42 | case *ast.CompositeLit: 43 | return compositeLit(t) 44 | case *ast.ParenExpr: 45 | return parenExpr(t) 46 | case *ast.SelectorExpr: 47 | return selectorExpr(t) 48 | case *ast.IndexExpr: 49 | return indexExpr(t) 50 | case *ast.SliceExpr: 51 | return sliceExpr(t) 52 | case *ast.TypeAssertExpr: 53 | return typeAssertExpr(t) 54 | case *ast.CallExpr: 55 | return callExpr(t) 56 | case *ast.StarExpr: 57 | return starExpr(t) 58 | case *ast.UnaryExpr: 59 | return unaryExpr(t) 60 | case *ast.BinaryExpr: 61 | return binaryExpr(t) 62 | case *ast.KeyValueExpr: 63 | return keyValueExpr(t) 64 | case *ast.ArrayType: 65 | return arrayType(t) 66 | case *ast.StructType: 67 | return structType(t) 68 | case *ast.FuncType: 69 | return funcType(t) 70 | case *ast.InterfaceType: 71 | return interfaceType(t) 72 | case *ast.MapType: 73 | return mapType(t) 74 | case *ast.ChanType: 75 | return chanType(t) 76 | } 77 | panic("Not Handled gen expr: " + reflect.TypeOf(s).String() + " at " + string(s.Pos())) 78 | } 79 | func ellipsis(t *ast.Ellipsis) jen.Code { 80 | return jen.Dot("Op").Call(jen.Lit("...")).Add(genExpr(t.Elt)) 81 | } 82 | 83 | func funcLit(t *ast.FuncLit) jen.Code { 84 | return jen.Dot("Func").Call().Add(funcType(t.Type)).Add(blockStmt(t.Body)) 85 | } 86 | 87 | func compositeLit(t *ast.CompositeLit) jen.Code { 88 | return jen.Add(genExpr(t.Type)).Dot("Values").Call(genExprsCode(t.Elts)...) 89 | } 90 | 91 | func parenExpr(t *ast.ParenExpr) jen.Code { 92 | return jen.Dot("Parens").Call(jen.Id("jen").Add(genExpr(t.X))) 93 | } 94 | 95 | func indexExpr(t *ast.IndexExpr) jen.Code { 96 | return jen.Add(genExpr(t.X)).Dot("Index").Call(jen.Id("jen").Add(genExpr(t.Index))) 97 | } 98 | func starExpr(t *ast.StarExpr) jen.Code { 99 | return jen.Dot("Op").Call(jen.Lit("*")).Add(genExpr(t.X)) 100 | } 101 | func unaryExpr(t *ast.UnaryExpr) jen.Code { 102 | return jen.Dot("Op").Call(jen.Lit(t.Op.String())).Add(genExpr(t.X)) 103 | } 104 | func binaryExpr(t *ast.BinaryExpr) jen.Code { 105 | return jen.Add(genExpr(t.X)).Dot("Op").Call(jen.Lit(t.Op.String())).Add(genExpr(t.Y)) 106 | } 107 | 108 | func keyValueExpr(t *ast.KeyValueExpr) jen.Code { 109 | ret := jen.Add(genExpr(t.Key)).Dot("Op").Call(jen.Lit(":")).Add(genExpr(t.Value)) 110 | return ret 111 | } 112 | 113 | func mapType(t *ast.MapType) jen.Code { 114 | ret := jen.Dot("Map").Call( 115 | jen.Id("jen").Add(genExpr(t.Key)), 116 | ).Add(genExpr(t.Value)) 117 | return ret 118 | } 119 | 120 | func selectorExpr(t *ast.SelectorExpr) jen.Code { 121 | dent, ok := t.X.(*ast.Ident) 122 | if ok { 123 | path, ok := paths[dent.String()] 124 | if ok { 125 | return jen.Dot("Qual").Call(jen.Lit(path), jen.Lit(t.Sel.String())) 126 | } 127 | } 128 | return jen.Add(genExpr(t.X)).Dot("Dot").Call(jen.Lit(t.Sel.String())) 129 | } 130 | 131 | func identsList(s []*ast.Ident) jen.Code { 132 | if len(s) == 0 { 133 | return jen.Null() 134 | } 135 | if len(s) == 1 { 136 | return ident(s[0]) 137 | } 138 | var n []jen.Code 139 | for _, name := range s { 140 | n = append(n, jen.Id("jen").Add(ident(name))) 141 | } 142 | return jen.Dot("List").Call(jen.List(n...)) 143 | } 144 | 145 | func ident(s *ast.Ident) jen.Code { 146 | return jen.Dot("Id").Call(jen.Lit(s.String())) 147 | } 148 | 149 | func typeAssertExpr(t *ast.TypeAssertExpr) jen.Code { 150 | ret2 := jen.Add(genExpr(t.X)).Dot("Assert") 151 | if t.Type == nil { 152 | return ret2.Call(jen.Id("jen").Dot("Type").Call()) 153 | } 154 | return ret2.Call(jen.Id("jen").Add(genExpr(t.Type))) 155 | } 156 | 157 | func callExpr(t *ast.CallExpr) jen.Code { 158 | args := genExprsCode(t.Args) 159 | if t.Ellipsis.IsValid() { 160 | args[len(args)-1] = jen.Add(args[len(args)-1]).Dot("Op").Call(jen.Lit("...")) 161 | } 162 | return jen.Add(genExpr(t.Fun)).Dot("Call").Call(args...) 163 | } 164 | 165 | func sliceExpr(t *ast.SliceExpr) jen.Code { 166 | code := []jen.Code{ 167 | jen.Id("jen").Dot("Empty").Call(), 168 | jen.Id("jen").Dot("Empty").Call(), 169 | } 170 | if t.Low != nil { 171 | code[0] = jen.Id("jen").Add(genExpr(t.Low)) 172 | } 173 | if t.High != nil { 174 | code[1] = jen.Id("jen").Add(genExpr(t.High)) 175 | } 176 | if t.Slice3 { 177 | code = append(code, jen.Id("jen").Dot("Empty").Call()) 178 | if t.Max != nil { 179 | code[2] = jen.Id("jen").Add(genExpr(t.Max)) 180 | } 181 | } 182 | return jen.Add(genExpr(t.X)).Dot("Index").Call(code...) 183 | } 184 | 185 | func chanType(t *ast.ChanType) jen.Code { 186 | ret2 := jen.Null() 187 | if t.Arrow.IsValid() { 188 | ret2.Dot("Op") 189 | switch t.Dir { 190 | case ast.SEND: 191 | ret2.Call(jen.Lit("->")) 192 | case ast.RECV: 193 | ret2.Call(jen.Lit("<-")) 194 | } 195 | } else { 196 | ret2.Dot("Chan").Call() 197 | } 198 | return ret2.Add(genExpr(t.Value)) 199 | } 200 | -------------------------------------------------------------------------------- /gen/file_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "testing" 7 | 8 | "github.com/aloder/tojen/run" 9 | "github.com/pkg/errors" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | type tcg struct { 14 | Name string 15 | Code string 16 | } 17 | 18 | var tests = []tcg{ 19 | tcg{ 20 | "first", 21 | `package main 22 | 23 | func main() {} 24 | `, 25 | }, 26 | tcg{ 27 | "empty statement", 28 | `package main 29 | 30 | func main() { 31 | ; 32 | } 33 | `, 34 | }, 35 | tcg{ 36 | "defer statement", 37 | `package main 38 | func main() { 39 | defer func() {}() 40 | } 41 | `, 42 | }, 43 | tcg{ 44 | "var declaration", 45 | `package main 46 | func main() { 47 | var no = false 48 | } 49 | `, 50 | }, 51 | tcg{ 52 | "multi var declaration", 53 | `package main 54 | func main() { 55 | var no, yes = false, true 56 | } 57 | `, 58 | }, 59 | tcg{ 60 | "Multiple VarDecl", 61 | `package main 62 | 63 | func main() { 64 | i, x := 1, 2 65 | } 66 | `, 67 | }, 68 | tcg{ 69 | "struct", 70 | `package main 71 | type A struct { 72 | Name string 73 | } 74 | func main() { 75 | v := A{"new"} 76 | println(v.Name) 77 | } 78 | `, 79 | }, 80 | tcg{ 81 | "interface", 82 | `package main 83 | 84 | type I interface { 85 | Name() String 86 | } 87 | type A struct { 88 | name string 89 | } 90 | func (a *A) Name()string{ 91 | return a.name 92 | } 93 | func try(i I)string{ 94 | return i.Name() 95 | } 96 | func main() { 97 | v := A{"New"} 98 | try(v) 99 | } 100 | `, 101 | }, 102 | tcg{ 103 | "Map", 104 | `package main 105 | 106 | func main() { 107 | m := map[string]int{"one": 1} 108 | println(m["one"]) 109 | } 110 | `, 111 | }, 112 | tcg{ 113 | "Type Assert", 114 | `package main 115 | 116 | func main() { 117 | var i interface{} = "hello" 118 | s := i.(string) 119 | println(s) 120 | } 121 | `, 122 | }, 123 | tcg{ 124 | "If statement", 125 | `package main 126 | func f() int { 127 | return 1 128 | } 129 | func main() { 130 | i := 1 131 | if i < 10 { 132 | println(i) 133 | } 134 | } 135 | func ifElse() { 136 | if i < 20 { 137 | println(i) 138 | } else { 139 | println(20) 140 | } 141 | } 142 | func ifInit() { 143 | if x := f(); x <10{ 144 | println(x) 145 | } 146 | } 147 | `, 148 | }, 149 | tcg{ 150 | "switch statement Init", 151 | `package main 152 | 153 | func main() { 154 | switch x := "hello"; x { 155 | case "hi": 156 | println("hi") 157 | case "hello": 158 | println("hello") 159 | } 160 | }`, 161 | }, 162 | tcg{ 163 | "switch statement Type", 164 | `package main 165 | 166 | func do(i interface{}) { 167 | switch v := i.(type) { 168 | case int: 169 | println(v*2) 170 | case string: 171 | println(len(v)) 172 | default: 173 | println("Dont know type") 174 | } 175 | } 176 | func main() { 177 | do(21) 178 | do("hello") 179 | do(true) 180 | } 181 | `, 182 | }, 183 | tcg{ 184 | "switch statement Type With Init and fallthrough", 185 | `package main 186 | 187 | func do(i interface{}) { 188 | switch x:=i; v := x.(type) { 189 | case int: 190 | println(v*2) 191 | case string: 192 | println(len(v)) 193 | fallthrough 194 | case bool: 195 | println(v) 196 | default: 197 | println("Dont know type") 198 | } 199 | } 200 | func main() { 201 | do(21) 202 | do("hello") 203 | do(true) 204 | } 205 | `, 206 | }, 207 | tcg{ 208 | "For Branch Statments", 209 | `package main 210 | 211 | func do(i interface{}) { 212 | for x := 0; x < 20; x++ { 213 | if x ==10 { 214 | break 215 | } 216 | if x == 1 { 217 | continue 218 | } 219 | } 220 | } 221 | func main() { 222 | fmt.Println(1) 223 | goto End 224 | fmt.Println(2) 225 | End: 226 | fmt.Println(3) 227 | } 228 | `, 229 | }, 230 | tcg{ 231 | "Channel Tour Go Example", 232 | `package main 233 | 234 | import "fmt" 235 | 236 | func sum(s []int, c chan int) { 237 | sum := 0 238 | for _, v := range s { 239 | sum += v 240 | } 241 | c <- sum 242 | } 243 | func main() { 244 | s := []int{7, 2, 8, -9, 4, 0} 245 | c := make(chan int) 246 | go sum(s[:len(s)/2], c) 247 | go sum(s[len(s)/2:], c) 248 | x, y := <-c, <-c 249 | fmt.Println(x, y, x+y) 250 | } 251 | `, 252 | }, 253 | tcg{ 254 | "Select", 255 | `package main 256 | 257 | func main() { 258 | var c, c1, c2, c3, c4 chan int 259 | var i1, i2 int 260 | select { 261 | case i1 = <- c1: 262 | print("received", i1, " from c1") 263 | print("more ") 264 | case c2 <- i2: 265 | print("again") 266 | case i3, ok := (<-c3): 267 | default: 268 | print("No communication") 269 | } 270 | } 271 | `, 272 | }, 273 | tcg{ 274 | "imports + annon", 275 | `package main 276 | import ( 277 | "fmt" 278 | "io/ioutil" 279 | _ "lib/math" 280 | ) 281 | 282 | func main() { 283 | fmt.Println("Hello World!") 284 | ioutil.TempDir("go", "fs") 285 | } 286 | `, 287 | }, 288 | tcg{ 289 | "function literal", 290 | `package main 291 | import ( 292 | "fmt" 293 | "sort" 294 | ) 295 | 296 | func main() { 297 | people := []string{"Alice", "Bob", "Dave"} 298 | sort.Slice(people, func(i, j int) bool { 299 | return len(people[i]) < len(people[j]) 300 | }) 301 | fmt.Println(people) 302 | } 303 | `, 304 | }, 305 | tcg{ 306 | "Escaped Characters", 307 | `package main 308 | 309 | func main() { 310 | print("\n") 311 | }`, 312 | }, 313 | tcg{ 314 | "slices of slices", 315 | `package main 316 | 317 | import ( 318 | "fmt" 319 | "strings" 320 | ) 321 | 322 | func main() { 323 | board := [][]string{[]string{"_", "_", "_"},[]string{"_", "_", "_"},[]string{"_", "_", "_"}} 324 | board[0][0] = "X" 325 | board[2][2] = "O" 326 | board[1][2] = "X" 327 | board[1][0] = "O" 328 | board[0][2] = "X" 329 | for i := 0; i < len(board); i++ { 330 | fmt.Printf("%s\n", strings.Join(board[i], " ")) 331 | } 332 | }`, 333 | }, 334 | tcg{ 335 | "Ellipsis", 336 | `package main 337 | 338 | import "fmt" 339 | 340 | func Sum(nums ...int) int { 341 | res := 0 342 | for _, n := range nums { 343 | res += n 344 | } 345 | return res 346 | } 347 | func main() { 348 | s := []int{1, 2, 3} 349 | fmt.Println(Sum(s...)) 350 | }`, 351 | }, 352 | tcg{ 353 | "Triple Slice", 354 | `package main 355 | 356 | func main() { 357 | source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"} 358 | takeOneCapOne := source[2:3:3] 359 | println(takeOneCapOne) 360 | }`, 361 | }, 362 | tcg{ 363 | "Fibonacci channels", 364 | `package main 365 | 366 | import ( 367 | "fmt" 368 | "time" 369 | ) 370 | 371 | func main() { 372 | fibonacci := func() chan uint64 { 373 | c := make(chan uint64) 374 | go func() { 375 | var x, y uint64 = 0, 1 376 | for ; y < (1 << 63); c <- y { 377 | x, y = y, x+y 378 | } 379 | close(c) 380 | }() 381 | return c 382 | } 383 | c := fibonacci() 384 | for x, ok := <-c; ok; x, ok = <-c { 385 | time.Sleep(time.Second) 386 | fmt.Println(x) 387 | } 388 | }`, 389 | }, 390 | tcg{ 391 | "Literals", 392 | `package main 393 | 394 | func main() { 395 | flo := 1.2 396 | flo2 := -1.2 397 | b := true 398 | x := false 399 | i := 1 400 | str := "hello World" 401 | ch := 'a' 402 | }`, 403 | }, 404 | } 405 | 406 | func TestFile(t *testing.T) { 407 | for i, tc := range tests { 408 | test := tc 409 | t.Run(tc.Name, func(t *testing.T) { 410 | fmtBytes, err := format.Source([]byte(test.Code)) 411 | if err != nil { 412 | assert.Nil(t, errors.Wrap(err, "Formating error on number: "+string(i)+" name: "+test.Name)) 413 | return 414 | } 415 | goFormatTest := string(fmtBytes) 416 | file := GenerateFile([]byte(test.Code), "main", true) 417 | resultB := &bytes.Buffer{} 418 | err = file.Render(resultB) 419 | if err != nil { 420 | assert.Nil(t, err, "Could not render test file: \n"+goFormatTest) 421 | return 422 | } 423 | ret, err := run.Exec(resultB.String()) 424 | if err != nil { 425 | assert.Nil(t, err, "Could not execute rendered test file: \n"+resultB.String()) 426 | return 427 | } 428 | fmtBytes, err = format.Source([]byte(*ret)) 429 | if err != nil { 430 | assert.Nil(t, err, "Could not format file: \n"+*ret+"\n\n"+resultB.String()) 431 | return 432 | } 433 | assert.Equal(t, goFormatTest, string(fmtBytes), "Gen Code: \n"+resultB.String()) 434 | }) 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /gen/formatting.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "go/format" 5 | "regexp" 6 | ) 7 | 8 | // EXPERIMENTAL 9 | 10 | func formatNulls(file []byte) []byte { 11 | reg := regexp.MustCompile("(jen[.]Null[(][)][.])") 12 | return reg.ReplaceAll(file, []byte("jen.")) 13 | } 14 | func formatStructs(file []byte) []byte { 15 | reg2 := regexp.MustCompile(`Struct([(]).+[)]`) 16 | ret := reg2.ReplaceAllFunc(file, func(b []byte) []byte { 17 | println(string("find")) 18 | reg := regexp.MustCompile(`(Struct[(])`) 19 | b2 := reg.ReplaceAll(b, []byte("$0\n")) 20 | reg2 := regexp.MustCompile(`([)])([)])$`) 21 | b3 := reg2.ReplaceAll(b2, []byte("$1,\n$2")) 22 | reg3 := regexp.MustCompile(",") 23 | commas := reg3.ReplaceAll(b3, []byte("$0\n")) 24 | return commas 25 | }) 26 | 27 | return ret 28 | } 29 | func formatBlock(file []byte) []byte { 30 | reg2 := regexp.MustCompile(`Block([(]).+[)]`) 31 | ret := reg2.ReplaceAllFunc(file, func(b []byte) []byte { 32 | println(string("find")) 33 | reg := regexp.MustCompile(`(Block[(])`) 34 | b2 := reg.ReplaceAll(b, []byte("$0\n")) 35 | reg2 := regexp.MustCompile(`([)])([)])$`) 36 | b3 := reg2.ReplaceAll(b2, []byte("$1,\n$2")) 37 | reg3 := regexp.MustCompile(",") 38 | commas := reg3.ReplaceAll(b3, []byte("$0\n")) 39 | return commas 40 | }) 41 | 42 | return ret 43 | } 44 | func formatParams(file []byte) []byte { 45 | reg2 := regexp.MustCompile(`(:?Params)[(].+"[)]{2}.`) 46 | ret := reg2.ReplaceAllFunc(file, func(b []byte) []byte { 47 | println(string("find params")) 48 | println(string(b)) 49 | println(string("End params")) 50 | reg := regexp.MustCompile(`(Params[(])`) 51 | b2 := reg.ReplaceAll(b, []byte("$0\n")) 52 | reg2 := regexp.MustCompile(`([)])([)])$`) 53 | b3 := reg2.ReplaceAll(b2, []byte("$1,\n$2")) 54 | reg3 := regexp.MustCompile(",") 55 | commas := reg3.ReplaceAll(b3, []byte("$0\n")) 56 | return commas 57 | }) 58 | 59 | return ret 60 | } 61 | func goFormat(file []byte) ([]byte, error) { 62 | fmtBytes, err := format.Source([]byte(file)) 63 | if err != nil { 64 | return file, err 65 | } 66 | return fmtBytes, nil 67 | } 68 | -------------------------------------------------------------------------------- /gen/main.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/dave/jennifer/jen" 12 | ) 13 | 14 | var jenImp = "github.com/dave/jennifer/jen" 15 | 16 | func funcDecl(s *ast.FuncDecl) jen.Code { 17 | ret := jen.Qual("github.com/dave/jennifer/jen", "Func").Call() 18 | if s.Recv != nil { 19 | ret.Dot("Params").Call(fieldList(s.Recv)...) 20 | } 21 | ret.Add(ident(s.Name)) 22 | ret.Add(funcType(s.Type)) 23 | ret.Add(blockStmt(s.Body)) 24 | return ret 25 | } 26 | 27 | var paths = map[string]string{} 28 | var formating = false 29 | 30 | // GenerateFileBytes takes an array of bytes and transforms it into jennifer 31 | // code 32 | func GenerateFileBytes(s []byte, packName string, main bool, formating bool) ([]byte, error) { 33 | file := GenerateFile(s, packName, main) 34 | b := &bytes.Buffer{} 35 | err := file.Render(b) 36 | if err != nil { 37 | return s, err 38 | } 39 | ret := b.Bytes() 40 | if formating { 41 | ret = formatNulls(ret) 42 | ret = formatStructs(ret) 43 | ret = formatBlock(ret) 44 | ret = formatParams(ret) 45 | ret, err = goFormat(ret) 46 | if err != nil { 47 | return ret, err 48 | } 49 | } 50 | return ret, nil 51 | } 52 | 53 | func imports(imports []*ast.ImportSpec) (map[string]string, []jen.Code) { 54 | p := make(map[string]string) 55 | anonImports := []jen.Code{} 56 | for _, i := range imports { 57 | pathVal := i.Path.Value[1 : len(i.Path.Value)-1] 58 | name := pathVal 59 | 60 | if i.Name == nil { 61 | idx := strings.Index(pathVal, "/") 62 | if idx != -1 { 63 | name = pathVal[idx+1:] 64 | } 65 | } else { 66 | name = i.Name.String() 67 | if name == "." { 68 | panic(". imports not supported") 69 | } 70 | if name == "_" { 71 | anonImports = append(anonImports, jen.Lit(pathVal)) 72 | continue 73 | } 74 | } 75 | p[name] = pathVal 76 | } 77 | return p, anonImports 78 | } 79 | 80 | // GenerateFile Generates a jennifer file given a series of bytes a package name 81 | // and if you want a main function or not 82 | func GenerateFile(s []byte, packName string, main bool) *jen.File { 83 | file := jen.NewFile(packName) 84 | astFile := parseFile(s) 85 | var anonImports []jen.Code 86 | // paths is a global variable to map the exported object to the import 87 | paths, anonImports = imports(astFile.Imports) 88 | 89 | // generate the generative code based on the file 90 | decls := []string{} 91 | for _, decl := range astFile.Decls { 92 | code, name := makeJenCode(decl) 93 | file.Add(code) 94 | decls = append(decls, name) 95 | } 96 | 97 | // generate the function that pieces togeather all the code 98 | var codes []jen.Code 99 | codes = append(codes, genNewJenFile(astFile.Name.String())) 100 | // add anon imports i.e. _ for side effects 101 | if len(anonImports) > 0 { 102 | codes = append(codes, jen.Id("ret").Dot("Anon").Call(anonImports...)) 103 | } 104 | // add the generated functions to the created jen file 105 | for _, name := range decls { 106 | codes = append(codes, jen.Id("ret").Dot("Add").Call(jen.Id(name).Call())) 107 | } 108 | // return the created jen file 109 | codes = append(codes, jen.Return().Id("ret")) 110 | // add the patch function to the output file 111 | file.Add( 112 | jen.Func().Id("genFile").Params().Op("*").Qual(jenImp, "File").Block(codes...), 113 | ) 114 | // if main then generate a main function that prints out the output of the 115 | // patch function 116 | if main { 117 | file.Add(genMainFunc()) 118 | } 119 | return file 120 | } 121 | 122 | func genNewJenFile(name string) jen.Code { 123 | return jen.Id("ret").Op(":=").Qual(jenImp, "NewFile").Call(jen.Lit(name)) 124 | } 125 | 126 | func genMainFunc() jen.Code { 127 | return jen.Func().Id("main").Params().Block( 128 | jen.Id("ret").Op(":=").Id("genFile").Call(), 129 | jen.Qual("fmt", "Printf").Call( 130 | jen.Lit("%#v"), 131 | jen.Id("ret"), 132 | ), 133 | ) 134 | } 135 | 136 | func makeJenCode(s ast.Decl) (jen.Code, string) { 137 | inner := jen.Null() 138 | name := "" 139 | switch t := s.(type) { 140 | case *ast.GenDecl: 141 | name = "genDeclAt" + strconv.Itoa(int(t.TokPos)) 142 | inner.Add(genDecl(t)) 143 | case *ast.FuncDecl: 144 | name = "genFunc" + t.Name.String() 145 | inner.Add(funcDecl(t)) 146 | } 147 | return makeJenFileFunc(name, inner), name 148 | } 149 | func makeJenFileFunc(name string, block jen.Code) jen.Code { 150 | return jen.Func().Id(name).Params().Qual(jenImp, "Code").Block( 151 | jen.Return().Add(block), 152 | ) 153 | } 154 | 155 | func parseFile(code []byte) *ast.File { 156 | fset := token.NewFileSet() 157 | f, err := parser.ParseFile(fset, "", code, parser.ParseComments) 158 | if err != nil { 159 | panic(err) 160 | } 161 | return f 162 | } 163 | 164 | func genDecl(g *ast.GenDecl) jen.Code { 165 | ret := jen.Qual(jenImp, "Null").Call() 166 | for _, spec := range g.Specs { 167 | switch s := spec.(type) { 168 | case *ast.ValueSpec: 169 | ret.Add(valueSpec(s)) 170 | case *ast.TypeSpec: 171 | ret.Add(typeSpec(s)) 172 | } 173 | } 174 | return ret 175 | } 176 | 177 | func typeSpec(s *ast.TypeSpec) jen.Code { 178 | return jen.Dot("Type").Call().Add(ident(s.Name)).Add(genExpr(s.Type)) 179 | } 180 | 181 | func valueSpec(s *ast.ValueSpec) jen.Code { 182 | ret := jen.Dot("Var").Call() 183 | ret.Add(identsList(s.Names)) 184 | ret.Add(genExpr(s.Type)) 185 | if len(s.Values) > 0 { 186 | ret.Dot("Op").Call(jen.Lit("=")) 187 | ret.Add(genExprs(s.Values)) 188 | } 189 | return ret 190 | } 191 | 192 | func basicLit(b *ast.BasicLit) jen.Code { 193 | switch b.Kind { 194 | case token.INT: 195 | i, err := strconv.ParseInt(b.Value, 10, 32) 196 | if err != nil { 197 | return nil 198 | } 199 | return jen.Dot("Lit").Call(jen.Lit(int(i))) 200 | case token.FLOAT: 201 | return jen.Dot("Lit").Call(jen.Id(b.Value)) 202 | case token.IMAG: 203 | panic("Cannot parse Imaginary Numbers") 204 | case token.CHAR: 205 | return jen.Dot("Id").Call(jen.Id("\"" + b.Value + "\"")) 206 | case token.STRING: 207 | return jen.Dot("Lit").Call(jen.Id(b.Value)) 208 | } 209 | return nil 210 | } 211 | -------------------------------------------------------------------------------- /gen/stmt.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | "reflect" 7 | 8 | "github.com/dave/jennifer/jen" 9 | ) 10 | 11 | func stmt(s ast.Stmt) jen.Code { 12 | switch t := s.(type) { 13 | case *ast.BadStmt: 14 | case *ast.DeclStmt: 15 | return declStmt(t) 16 | case *ast.GoStmt: 17 | return goStmt(t) 18 | case *ast.DeferStmt: 19 | return deferStmt(t) 20 | case *ast.EmptyStmt: 21 | return emptyStmt(t) 22 | case *ast.LabeledStmt: 23 | return labeledStmt(t) 24 | case *ast.ExprStmt: 25 | return exprStmt(t) 26 | case *ast.SendStmt: 27 | return sendStmt(t) 28 | case *ast.IncDecStmt: 29 | return incDecStmt(t) 30 | case *ast.AssignStmt: 31 | return assignStmt(t) 32 | case *ast.ReturnStmt: 33 | return returnStmt(t) 34 | case *ast.BranchStmt: 35 | return branchStmt(t) 36 | case *ast.BlockStmt: 37 | return blockStmt(t) 38 | case *ast.IfStmt: 39 | return ifStmt(t) 40 | case *ast.CaseClause: 41 | return caseClause(t) 42 | case *ast.SwitchStmt: 43 | return switchStmt(t) 44 | case *ast.TypeSwitchStmt: 45 | return typeSwitchStmt(t) 46 | case *ast.CommClause: 47 | return commClause(t) 48 | case *ast.SelectStmt: 49 | return selectStmt(t) 50 | case *ast.ForStmt: 51 | return forStmt(t) 52 | case *ast.RangeStmt: 53 | return rangeStmt(t) 54 | } 55 | panic("Not Handled: " + reflect.TypeOf(s).String() + " at " + string(s.Pos())) 56 | } 57 | 58 | func declStmt(t *ast.DeclStmt) jen.Code { 59 | return genDecl(t.Decl.(*ast.GenDecl)) 60 | } 61 | 62 | func emptyStmt(t *ast.EmptyStmt) jen.Code { 63 | return jen.Id("jen").Dot("Empty").Call() 64 | } 65 | 66 | func exprStmt(t *ast.ExprStmt) jen.Code { 67 | return jen.Id("jen").Add(genExpr(t.X)) 68 | } 69 | 70 | func goStmt(t *ast.GoStmt) jen.Code { 71 | ret := jen.Id("jen") 72 | return ret.Dot("Go").Call().Add(genExpr(t.Call)) 73 | } 74 | 75 | func deferStmt(t *ast.DeferStmt) jen.Code { 76 | ret := jen.Id("jen") 77 | return ret.Dot("Defer").Call().Add(genExpr(t.Call)) 78 | } 79 | 80 | func labeledStmt(t *ast.LabeledStmt) jen.Code { 81 | ret := jen.Id("jen") 82 | return ret.Add(ident(t.Label)).Dot("Op").Call(jen.Lit(":")).Dot("Line").Call().Dot("Add").Call(stmt(t.Stmt)) 83 | } 84 | 85 | func sendStmt(t *ast.SendStmt) jen.Code { 86 | ret := jen.Id("jen") 87 | return ret.Add(genExpr(t.Chan)).Dot("Op").Call(jen.Lit("<-")).Add(genExpr(t.Value)) 88 | } 89 | 90 | func incDecStmt(t *ast.IncDecStmt) jen.Code { 91 | ret := jen.Id("jen") 92 | return ret.Add(genExpr(t.X)).Dot("Op").Call(jen.Lit(t.Tok.String())) 93 | } 94 | 95 | func assignStmt(t *ast.AssignStmt) jen.Code { 96 | ret := jen.Id("jen") 97 | return ret.Add(genExprs(t.Lhs)).Dot("Op").Call(jen.Lit(t.Tok.String())).Add(genExprs(t.Rhs)) 98 | } 99 | 100 | func returnStmt(t *ast.ReturnStmt) jen.Code { 101 | ret := jen.Id("jen") 102 | return ret.Dot("Return").Call().Add(genExprs(t.Results)) 103 | } 104 | 105 | func caseClause(t *ast.CaseClause) jen.Code { 106 | ret := jen.Id("jen") 107 | if t.List == nil { 108 | return ret.Dot("Default").Call().Dot("Block").Call(stmts(t.Body)...) 109 | } 110 | return ret.Dot("Case").Call(genExprsCode(t.List)...).Dot("Block").Call(stmts(t.Body)...) 111 | } 112 | 113 | func typeSwitchStmt(t *ast.TypeSwitchStmt) jen.Code { 114 | ret := jen.Id("jen") 115 | var cond []jen.Code 116 | if t.Init != nil { 117 | cond = append(cond, stmt(t.Init)) 118 | } 119 | if t.Assign != nil { 120 | cond = append(cond, stmt(t.Assign)) 121 | } 122 | return ret.Dot("Switch").Call(cond...).Add(blockStmt(t.Body)) 123 | } 124 | 125 | func commClause(t *ast.CommClause) jen.Code { 126 | ret := jen.Id("jen") 127 | if t.Comm == nil { 128 | return ret.Dot("Default").Call().Dot("Block").Call(stmts(t.Body)...) 129 | } 130 | return ret.Dot("Case").Call(stmt(t.Comm)).Dot("Block").Call(stmts(t.Body)...) 131 | } 132 | 133 | func selectStmt(t *ast.SelectStmt) jen.Code { 134 | ret := jen.Id("jen") 135 | return ret.Dot("Select").Call().Add(blockStmt(t.Body)) 136 | } 137 | 138 | func branchStmt(t *ast.BranchStmt) jen.Code { 139 | ret := jen.Id("jen") 140 | switch t.Tok { 141 | case token.BREAK: 142 | return ret.Dot("Break").Call() 143 | case token.CONTINUE: 144 | return ret.Dot("Continue").Call() 145 | case token.GOTO: 146 | return ret.Dot("Goto").Call().Add(ident(t.Label)) 147 | case token.FALLTHROUGH: 148 | return ret.Dot("Fallthrough").Call() 149 | } 150 | return nil 151 | } 152 | 153 | func ifStmt(t *ast.IfStmt) jen.Code { 154 | var cond []jen.Code 155 | if t.Init != nil { 156 | cond = append(cond, stmt(t.Init)) 157 | } 158 | if t.Cond != nil { 159 | cond = append(cond, jen.Id("jen").Add(genExpr(t.Cond))) 160 | } 161 | ret := jen.Id("jen").Dot("If").Call( 162 | cond..., 163 | ).Add(blockStmt(t.Body)) 164 | if t.Else != nil { 165 | ret.Dot("Else").Call().Add(stmt(t.Else)) 166 | } 167 | return ret 168 | } 169 | 170 | func switchStmt(t *ast.SwitchStmt) jen.Code { 171 | var cond []jen.Code 172 | if t.Init != nil { 173 | cond = append(cond, stmt(t.Init)) 174 | } 175 | if t.Tag != nil { 176 | cond = append(cond, jen.Id("jen").Add(genExpr(t.Tag))) 177 | } 178 | return jen.Id("jen").Dot("Switch").Call(cond...).Add(blockStmt(t.Body)) 179 | } 180 | 181 | func forStmt(t *ast.ForStmt) jen.Code { 182 | ret := jen.Id("jen") 183 | var code []jen.Code 184 | if t.Init != nil { 185 | code = append(code, stmt(t.Init)) 186 | } 187 | if t.Init == nil && t.Cond != nil && t.Post != nil { 188 | code = append(code, jen.Id("jen").Dot("Empty").Call()) 189 | } 190 | if t.Cond != nil { 191 | code = append(code, jen.Id("jen").Add(genExpr(t.Cond))) 192 | } 193 | if t.Post != nil { 194 | code = append(code, stmt(t.Post)) 195 | } 196 | return ret.Dot("For").Call( 197 | code..., 198 | ).Add(blockStmt(t.Body)) 199 | } 200 | 201 | func rangeStmt(t *ast.RangeStmt) jen.Code { 202 | return jen.Id("jen").Dot("For").Call( 203 | jen.Id("jen").Add( 204 | jen.Dot("List").Call(genExprsCode([]ast.Expr{t.Key, t.Value})...), 205 | ).Dot("Op").Call( 206 | jen.Lit(t.Tok.String()), 207 | ).Dot("Range").Call().Add(genExpr(t.X)), 208 | ).Add(blockStmt(t.Body)) 209 | } 210 | 211 | func blockStmt(s *ast.BlockStmt) jen.Code { 212 | ret := stmts(s.List) 213 | return jen.Dot("Block").Call(ret...) 214 | } 215 | 216 | func stmts(s []ast.Stmt) []jen.Code { 217 | var ret []jen.Code 218 | for _, st := range s { 219 | ret = append(ret, stmt(st)) 220 | } 221 | return ret 222 | } 223 | 224 | func fieldList(fl *ast.FieldList) []jen.Code { 225 | var paramsCode []jen.Code 226 | if fl == nil { 227 | return paramsCode 228 | } 229 | for _, p := range fl.List { 230 | code := jen.Id("jen") 231 | code.Add(identsList(p.Names)) 232 | code.Add(genExpr(p.Type)) 233 | paramsCode = append(paramsCode, code) 234 | } 235 | return paramsCode 236 | } 237 | -------------------------------------------------------------------------------- /gen/type.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "go/ast" 5 | 6 | "github.com/dave/jennifer/jen" 7 | ) 8 | 9 | func funcType(s *ast.FuncType) jen.Code { 10 | var ret jen.Statement 11 | params := fieldList(s.Params) 12 | ret.Dot("Params").Call(params...) 13 | results := fieldList(s.Results) 14 | if len(results) > 0 { 15 | ret.Dot("Params").Call(results...) 16 | } 17 | return &ret 18 | } 19 | func arrayType(s *ast.ArrayType) jen.Code { 20 | return jen.Dot("Index").Call().Add(genExpr(s.Elt)) 21 | } 22 | func structType(s *ast.StructType) jen.Code { 23 | return jen.Dot("Struct").Call(fieldList(s.Fields)...) 24 | } 25 | 26 | func interfaceType(s *ast.InterfaceType) jen.Code { 27 | return jen.Dot("Interface").Call(fieldList(s.Methods)...) 28 | } 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/aloder/tojen/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /run/run.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | ) 9 | 10 | // Exec executes a golang string 11 | func Exec(code string) (*string, error) { 12 | dir, err := ioutil.TempDir("", "goexec") 13 | if err != nil { 14 | return nil, err 15 | } 16 | path := filepath.Join(dir, "main.go") 17 | file, err := os.Create(path) 18 | if err != nil { 19 | return nil, err 20 | } 21 | _, err = file.WriteString(code) 22 | if err != nil { 23 | return nil, err 24 | } 25 | err = file.Close() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | cmd := exec.Command("go", "run", path) 31 | bout, err := cmd.CombinedOutput() 32 | if err != nil { 33 | return nil, err 34 | } 35 | str := string(bout) 36 | return &str, nil 37 | } 38 | -------------------------------------------------------------------------------- /run/run_test.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRunExec(t *testing.T) { 10 | one := ` 11 | package main 12 | 13 | func main() { 14 | println("Hello World!") 15 | } 16 | ` 17 | out, err := Exec(one) 18 | assert.Nil(t, err) 19 | assert.Equal(t, "Hello World!\n", *out) 20 | } 21 | --------------------------------------------------------------------------------