├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc.go ├── examples ├── davechaney │ ├── func_thing.go │ ├── gen.sh │ ├── generic_max.go │ ├── max_test.go │ └── numbers_func_thing.go ├── go-generate │ ├── gen-go-generate.go │ └── go-generate.go └── queue │ ├── build.sh │ ├── queue_generic.go │ └── queue_generic_test.go ├── generic ├── doc.go └── generic.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── out ├── lazy_file.go └── lazy_file_test.go └── parse ├── builtins.go ├── doc.go ├── errors.go ├── parse.go ├── parse_int_test.go ├── parse_test.go ├── test ├── bugreports │ ├── cell_int.go │ ├── cell_x.go │ ├── generic_digraph.go │ ├── generic_new_and_make_slice.go │ ├── int_digraph.go │ ├── int_new_and_make_slice.go │ ├── interface_generic_type.go │ ├── interface_uint8.go │ ├── negation_generic.go │ └── negation_string.go ├── buildtags │ ├── buildtags.go │ ├── buildtags_expected.go │ └── buildtags_expected_nostrip.go ├── multipletypes │ ├── custom_types.go │ ├── custom_types_simplemap.go │ ├── generic_simplemap.go │ ├── generic_simplemap_test.go │ ├── interface_int_simplemap.go │ └── string_int_simplemap.go ├── multipletypesets │ ├── generic_simplemap.go │ ├── generic_simplemap_test.go │ └── many_simplemaps.go ├── numbers │ ├── generic_number.go │ └── int_number.go ├── queue │ ├── changed │ │ └── int_queue.go │ ├── float32_queue.go │ ├── generic_queue.go │ ├── generic_queue_test.go │ └── int_queue.go ├── slice │ ├── ensure_slice.go │ └── ensure_slice_test.go └── unexported │ ├── custom_types.go │ ├── generic_internal.go │ └── mytype_internal.go ├── typesets.go └── typesets_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | genny 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11 5 | - 1.12 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 cheekybits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genny - Generics for Go 2 | 3 | [![Build Status](https://travis-ci.org/cheekybits/genny.svg?branch=master)](https://travis-ci.org/cheekybits/genny) [![GoDoc](https://godoc.org/github.com/cheekybits/genny/parse?status.png)](http://godoc.org/github.com/cheekybits/genny/parse) 4 | 5 | Install: 6 | 7 | ``` 8 | go get github.com/cheekybits/genny 9 | ``` 10 | 11 | ===== 12 | 13 | (pron. Jenny) by Mat Ryer ([@matryer](https://twitter.com/matryer)) and Tyler Bunnell ([@TylerJBunnell](https://twitter.com/TylerJBunnell)). 14 | 15 | Until the Go core team include support for [generics in Go](http://golang.org/doc/faq#generics), `genny` is a code-generation generics solution. It allows you write normal buildable and testable Go code which, when processed by the `genny gen` tool, will replace the generics with specific types. 16 | 17 | * Generic code is valid Go code 18 | * Generic code compiles and can be tested 19 | * Use `stdin` and `stdout` or specify in and out files 20 | * Supports Go 1.4's [go generate](http://tip.golang.org/doc/go1.4#gogenerate) 21 | * Multiple specific types will generate every permutation 22 | * Use `BUILTINS` and `NUMBERS` wildtype to generate specific code for all built-in (and number) Go types 23 | * Function names and comments also get updated 24 | 25 | ## Library 26 | 27 | We have started building a [library of common things](https://github.com/cheekybits/gennylib), and you can use `genny get` to generate the specific versions you need. 28 | 29 | For example: `genny get maps/concurrentmap.go "KeyType=BUILTINS ValueType=BUILTINS"` will print out generated code for all types for a concurrent map. Any file in the library may be generated locally in this way using all the same options given to `genny gen`. 30 | 31 | ## Usage 32 | 33 | ``` 34 | genny [{flags}] gen "{types}" 35 | 36 | gen - generates type specific code from generic code. 37 | get - fetch a generic template from the online library and gen it. 38 | 39 | {flags} - (optional) Command line flags (see below) 40 | {types} - (required) Specific types for each generic type in the source 41 | {types} format: {generic}={specific}[,another][ {generic2}={specific2}] 42 | 43 | Examples: 44 | Generic=Specific 45 | Generic1=Specific1 Generic2=Specific2 46 | Generic1=Specific1,Specific2 Generic2=Specific3,Specific4 47 | 48 | Flags: 49 | -in="": file to parse instead of stdin 50 | -out="": file to save output to instead of stdout 51 | -pkg="": package name for generated files 52 | -tag="": build tag that is stripped from output 53 | ``` 54 | 55 | * Comma separated type lists will generate code for each type 56 | 57 | ### Flags 58 | 59 | * `-in` - specify the input file (rather than using stdin) 60 | * `-out` - specify the output file (rather than using stdout) 61 | 62 | ### go generate 63 | 64 | To use Go 1.4's `go generate` capability, insert the following comment in your source code file: 65 | 66 | ``` 67 | //go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int" 68 | ``` 69 | 70 | * Start the line with `//go:generate ` 71 | * Use the `-in` and `-out` flags to specify the files to work on 72 | * Use the `genny` command as usual after the flags 73 | 74 | Now, running `go generate` (in a shell) for the package will cause the generic versions of the files to be generated. 75 | 76 | * The output file will be overwritten, so it's safe to call `go generate` many times 77 | * Use `$GOFILE` to refer to the current file 78 | * The `//go:generate` line will be removed from the output 79 | 80 | To see a real example of how to use `genny` with `go generate`, look in the [example/go-generate directory](https://github.com/cheekybits/genny/tree/master/examples/go-generate). 81 | 82 | ## How it works 83 | 84 | Define your generic types using the special `generic.Type` placeholder type: 85 | 86 | ```go 87 | type KeyType generic.Type 88 | type ValueType generic.Type 89 | ``` 90 | 91 | * You can use as many as you like 92 | * Give them meaningful names 93 | 94 | Then write the generic code referencing the types as your normally would: 95 | 96 | ```go 97 | func SetValueTypeForKeyType(key KeyType, value ValueType) { /* ... */ } 98 | ``` 99 | 100 | * Generic type names will also be replaced in comments and function names (see Real example below) 101 | 102 | Since `generic.Type` is a real Go type, your code will compile, and you can even write unit tests against your generic code. 103 | 104 | #### Generating specific versions 105 | 106 | Pass the file through the `genny gen` tool with the specific types as the argument: 107 | 108 | ``` 109 | cat generic.go | genny gen "KeyType=string ValueType=interface{}" 110 | ``` 111 | 112 | The output will be the complete Go source file with the generic types replaced with the types specified in the arguments. 113 | 114 | ## Real example 115 | 116 | Given [this generic Go code](https://github.com/cheekybits/genny/tree/master/examples/queue) which compiles and is tested: 117 | 118 | ```go 119 | package queue 120 | 121 | import "github.com/cheekybits/genny/generic" 122 | 123 | // NOTE: this is how easy it is to define a generic type 124 | type Something generic.Type 125 | 126 | // SomethingQueue is a queue of Somethings. 127 | type SomethingQueue struct { 128 | items []Something 129 | } 130 | 131 | func NewSomethingQueue() *SomethingQueue { 132 | return &SomethingQueue{items: make([]Something, 0)} 133 | } 134 | func (q *SomethingQueue) Push(item Something) { 135 | q.items = append(q.items, item) 136 | } 137 | func (q *SomethingQueue) Pop() Something { 138 | item := q.items[0] 139 | q.items = q.items[1:] 140 | return item 141 | } 142 | ``` 143 | 144 | When `genny gen` is invoked like this: 145 | 146 | ``` 147 | cat source.go | genny gen "Something=string" 148 | ``` 149 | 150 | It outputs: 151 | 152 | ```go 153 | // This file was automatically generated by genny. 154 | // Any changes will be lost if this file is regenerated. 155 | // see https://github.com/cheekybits/genny 156 | 157 | package queue 158 | 159 | // StringQueue is a queue of Strings. 160 | type StringQueue struct { 161 | items []string 162 | } 163 | 164 | func NewStringQueue() *StringQueue { 165 | return &StringQueue{items: make([]string, 0)} 166 | } 167 | func (q *StringQueue) Push(item string) { 168 | q.items = append(q.items, item) 169 | } 170 | func (q *StringQueue) Pop() string { 171 | item := q.items[0] 172 | q.items = q.items[1:] 173 | return item 174 | } 175 | ``` 176 | 177 | To get a _something_ for every built-in Go type plus one of your own types, you could run: 178 | 179 | ``` 180 | cat source.go | genny gen "Something=BUILTINS,*MyType" 181 | ``` 182 | 183 | #### More examples 184 | 185 | Check out the [test code files](https://github.com/cheekybits/genny/tree/master/parse/test) for more real examples. 186 | 187 | ## Writing test code 188 | 189 | Once you have defined a generic type with some code worth testing: 190 | 191 | ```go 192 | package slice 193 | 194 | import ( 195 | "log" 196 | "reflect" 197 | 198 | "github.com/stretchr/gogen/generic" 199 | ) 200 | 201 | type MyType generic.Type 202 | 203 | func EnsureMyTypeSlice(objectOrSlice interface{}) []MyType { 204 | log.Printf("%v", reflect.TypeOf(objectOrSlice)) 205 | switch obj := objectOrSlice.(type) { 206 | case []MyType: 207 | log.Println(" returning it untouched") 208 | return obj 209 | case MyType: 210 | log.Println(" wrapping in slice") 211 | return []MyType{obj} 212 | default: 213 | panic("ensure slice needs MyType or []MyType") 214 | } 215 | } 216 | ``` 217 | 218 | You can treat it like any normal Go type in your test code: 219 | 220 | ```go 221 | func TestEnsureMyTypeSlice(t *testing.T) { 222 | 223 | myType := new(MyType) 224 | slice := EnsureMyTypeSlice(myType) 225 | if assert.NotNil(t, slice) { 226 | assert.Equal(t, slice[0], myType) 227 | } 228 | 229 | slice = EnsureMyTypeSlice(slice) 230 | log.Printf("%#v", slice[0]) 231 | if assert.NotNil(t, slice) { 232 | assert.Equal(t, slice[0], myType) 233 | } 234 | 235 | } 236 | ``` 237 | 238 | ### Understanding what `generic.Type` is 239 | 240 | Because `generic.Type` is an empty interface type (literally `interface{}`) every other type will be considered to be a `generic.Type` if you are switching on the type of an object. Of course, once the specific versions are generated, this issue goes away but it's worth knowing when you are writing your tests against generic code. 241 | 242 | ### Contributions 243 | 244 | * See the [API documentation for the parse package](http://godoc.org/github.com/cheekybits/genny/parse) 245 | * Please do TDD 246 | * All input welcome 247 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package main is the command line tool for Genny. 2 | package main 3 | -------------------------------------------------------------------------------- /examples/davechaney/func_thing.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type ThisNumberType generic.Number 6 | 7 | func ThisNumberTypeMax(fn func(a, b ThisNumberType) bool, a, b ThisNumberType) ThisNumberType { 8 | if fn(a, b) { 9 | return a 10 | } 11 | return b 12 | } 13 | -------------------------------------------------------------------------------- /examples/davechaney/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat ./generic_max.go | ../../genny gen "NumberType=NUMBERS" > numbers_max_get.go 3 | cat ./func_thing.go | ../../genny gen "ThisNumberType=NUMBERS" > numbers_func_thing.go 4 | -------------------------------------------------------------------------------- /examples/davechaney/generic_max.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type NumberType generic.Number 6 | 7 | // NumberTypeMax gets the maximum number from the 8 | // two specified. 9 | func NumberTypeMax(a, b NumberType) NumberType { 10 | if a > b { 11 | return a 12 | } 13 | return b 14 | } 15 | -------------------------------------------------------------------------------- /examples/davechaney/max_test.go: -------------------------------------------------------------------------------- 1 | package math_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cheekybits/genny/examples/davechaney" 7 | ) 8 | 9 | func TestNumberTypeMax(t *testing.T) { 10 | 11 | var v math.NumberType 12 | v = math.NumberTypeMax(10, 20) 13 | if v != 20 { 14 | t.Errorf("Max of 10 and 20 is 20") 15 | } 16 | 17 | v = math.NumberTypeMax(20, 20) 18 | if v != 20 { 19 | t.Errorf("Max of 20 and 20 is 20") 20 | } 21 | 22 | v = math.NumberTypeMax(25, 20) 23 | if v != 25 { 24 | t.Errorf("Max of 25 and 20 is 25") 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/davechaney/numbers_func_thing.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package math 6 | 7 | func Float32Max(fn func(a, b float32) bool, a, b float32) float32 { 8 | if fn(a, b) { 9 | return a 10 | } 11 | return b 12 | } 13 | 14 | func Float64Max(fn func(a, b float64) bool, a, b float64) float64 { 15 | if fn(a, b) { 16 | return a 17 | } 18 | return b 19 | } 20 | 21 | func IntMax(fn func(a, b int) bool, a, b int) int { 22 | if fn(a, b) { 23 | return a 24 | } 25 | return b 26 | } 27 | 28 | func Int16Max(fn func(a, b int16) bool, a, b int16) int16 { 29 | if fn(a, b) { 30 | return a 31 | } 32 | return b 33 | } 34 | 35 | func Int32Max(fn func(a, b int32) bool, a, b int32) int32 { 36 | if fn(a, b) { 37 | return a 38 | } 39 | return b 40 | } 41 | 42 | func Int64Max(fn func(a, b int64) bool, a, b int64) int64 { 43 | if fn(a, b) { 44 | return a 45 | } 46 | return b 47 | } 48 | 49 | func Int8Max(fn func(a, b int8) bool, a, b int8) int8 { 50 | if fn(a, b) { 51 | return a 52 | } 53 | return b 54 | } 55 | 56 | func UintMax(fn func(a, b uint) bool, a, b uint) uint { 57 | if fn(a, b) { 58 | return a 59 | } 60 | return b 61 | } 62 | 63 | func Uint16Max(fn func(a, b uint16) bool, a, b uint16) uint16 { 64 | if fn(a, b) { 65 | return a 66 | } 67 | return b 68 | } 69 | 70 | func Uint32Max(fn func(a, b uint32) bool, a, b uint32) uint32 { 71 | if fn(a, b) { 72 | return a 73 | } 74 | return b 75 | } 76 | 77 | func Uint64Max(fn func(a, b uint64) bool, a, b uint64) uint64 { 78 | if fn(a, b) { 79 | return a 80 | } 81 | return b 82 | } 83 | 84 | func Uint8Max(fn func(a, b uint8) bool, a, b uint8) uint8 { 85 | if fn(a, b) { 86 | return a 87 | } 88 | return b 89 | } 90 | -------------------------------------------------------------------------------- /examples/go-generate/gen-go-generate.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package gogenerate 6 | 7 | type StringStringMap map[string]string 8 | 9 | func NewStringStringMap() map[string]string { 10 | return make(map[string]string) 11 | } 12 | 13 | type StringIntMap map[string]int 14 | 15 | func NewStringIntMap() map[string]int { 16 | return make(map[string]int) 17 | } 18 | 19 | type IntStringMap map[int]string 20 | 21 | func NewIntStringMap() map[int]string { 22 | return make(map[int]string) 23 | } 24 | 25 | type IntIntMap map[int]int 26 | 27 | func NewIntIntMap() map[int]int { 28 | return make(map[int]int) 29 | } 30 | -------------------------------------------------------------------------------- /examples/go-generate/go-generate.go: -------------------------------------------------------------------------------- 1 | package gogenerate 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | //go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int" 6 | 7 | type KeyType generic.Type 8 | type ValueType generic.Type 9 | 10 | type KeyTypeValueTypeMap map[KeyType]ValueType 11 | 12 | func NewKeyTypeValueTypeMap() map[KeyType]ValueType { 13 | return make(map[KeyType]ValueType) 14 | } 15 | -------------------------------------------------------------------------------- /examples/queue/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat ./queue_generic.go | ../../genny gen "Generic=string,int" > queue_generic_gen.go 3 | -------------------------------------------------------------------------------- /examples/queue/queue_generic.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type Generic generic.Type 6 | 7 | // GenericQueue represents a queue of Generic types. 8 | type GenericQueue struct { 9 | items []Generic 10 | } 11 | 12 | // NewGenericQueue makes a new empty Generic queue. 13 | func NewGenericQueue() *GenericQueue { 14 | return &GenericQueue{items: make([]Generic, 0)} 15 | } 16 | 17 | // Enq adds an item to the queue. 18 | func (q *GenericQueue) Enq(obj Generic) *GenericQueue { 19 | q.items = append(q.items, obj) 20 | return q 21 | } 22 | 23 | // Deq removes and returns the next item in the queue. 24 | func (q *GenericQueue) Deq() Generic { 25 | obj := q.items[0] 26 | q.items = q.items[1:] 27 | return obj 28 | } 29 | 30 | // Len gets the current number of Generic items in the queue. 31 | func (q *GenericQueue) Len() int { 32 | return len(q.items) 33 | } 34 | -------------------------------------------------------------------------------- /examples/queue/queue_generic_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | 11 | q := NewGenericQueue() 12 | assert.NotNil(t, q) 13 | 14 | } 15 | 16 | func TestEnqueueAndDequeue(t *testing.T) { 17 | 18 | item1 := new(Generic) 19 | item2 := new(Generic) 20 | q := NewGenericQueue() 21 | 22 | assert.Equal(t, q, q.Enq(item1), "Enq should return the queue") 23 | assert.Equal(t, 1, q.Len()) 24 | assert.Equal(t, q, q.Enq(item2), "Enq should return the queue") 25 | assert.Equal(t, 2, q.Len()) 26 | 27 | assert.Equal(t, item1, q.Deq()) 28 | assert.Equal(t, 1, q.Len()) 29 | assert.Equal(t, item2, q.Deq()) 30 | assert.Equal(t, 0, q.Len()) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /generic/doc.go: -------------------------------------------------------------------------------- 1 | // Package generic contains the generic marker types. 2 | package generic 3 | -------------------------------------------------------------------------------- /generic/generic.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | // Type is the placeholder type that indicates a generic value. 4 | // When genny is executed, variables of this type will be replaced with 5 | // references to the specific types. 6 | // var GenericType generic.Type 7 | type Type interface{} 8 | 9 | // Number is the placehoder type that indiccates a generic numerical value. 10 | // When genny is executed, variables of this type will be replaced with 11 | // references to the specific types. 12 | // var GenericType generic.Number 13 | type Number float64 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cheekybits/genny 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/stretchr/testify v1.3.0 7 | golang.org/x/tools v0.0.0-20190328030505-8f05a32dce9f 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 7 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 10 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 11 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 12 | golang.org/x/tools v0.0.0-20190328030505-8f05a32dce9f h1:MC9ePo9/ITLPbt6QsoTD78Pg07by0QbfGLUQpkJ01QM= 13 | golang.org/x/tools v0.0.0-20190328030505-8f05a32dce9f/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "os" 11 | "strings" 12 | 13 | "github.com/cheekybits/genny/out" 14 | "github.com/cheekybits/genny/parse" 15 | ) 16 | 17 | /* 18 | 19 | source | genny gen [-in=""] [-out=""] [-pkg=""] "KeyType=string,int ValueType=string,int" 20 | 21 | */ 22 | 23 | const ( 24 | _ = iota 25 | exitcodeInvalidArgs 26 | exitcodeInvalidTypeSet 27 | exitcodeStdinFailed 28 | exitcodeGenFailed 29 | exitcodeGetFailed 30 | exitcodeSourceFileInvalid 31 | exitcodeDestFileFailed 32 | ) 33 | 34 | func main() { 35 | var ( 36 | in = flag.String("in", "", "file to parse instead of stdin") 37 | out = flag.String("out", "", "file to save output to instead of stdout") 38 | pkgName = flag.String("pkg", "", "package name for generated files") 39 | genTag = flag.String("tag", "", "build tag that is stripped from output") 40 | prefix = "https://github.com/metabition/gennylib/raw/master/" 41 | ) 42 | flag.Parse() 43 | args := flag.Args() 44 | 45 | if len(args) < 2 { 46 | usage() 47 | os.Exit(exitcodeInvalidArgs) 48 | } 49 | 50 | if strings.ToLower(args[0]) != "gen" && strings.ToLower(args[0]) != "get" { 51 | usage() 52 | os.Exit(exitcodeInvalidArgs) 53 | } 54 | 55 | // parse the typesets 56 | var setsArg = args[1] 57 | if strings.ToLower(args[0]) == "get" { 58 | setsArg = args[2] 59 | } 60 | typeSets, err := parse.TypeSet(setsArg) 61 | if err != nil { 62 | fatal(exitcodeInvalidTypeSet, err) 63 | } 64 | 65 | outWriter := newWriter(*out) 66 | outputFilename := *out 67 | if outputFilename == "" { 68 | outputFilename = "stdout" 69 | } 70 | 71 | if strings.ToLower(args[0]) == "get" { 72 | if len(args) != 3 { 73 | fmt.Println("not enough arguments to get") 74 | usage() 75 | os.Exit(exitcodeInvalidArgs) 76 | } 77 | r, err := http.Get(prefix + args[1]) 78 | if err != nil { 79 | fatal(exitcodeGetFailed, err) 80 | } 81 | b, err := ioutil.ReadAll(r.Body) 82 | if err != nil { 83 | fatal(exitcodeGetFailed, err) 84 | } 85 | r.Body.Close() 86 | br := bytes.NewReader(b) 87 | err = gen(*in, outputFilename, *pkgName, *genTag, br, typeSets, outWriter) 88 | } else if len(*in) > 0 { 89 | var file *os.File 90 | file, err = os.Open(*in) 91 | if err != nil { 92 | fatal(exitcodeSourceFileInvalid, err) 93 | } 94 | defer file.Close() 95 | err = gen(*in, outputFilename, *pkgName, *genTag, file, typeSets, outWriter) 96 | } else { 97 | var source []byte 98 | source, err = ioutil.ReadAll(os.Stdin) 99 | if err != nil { 100 | fatal(exitcodeStdinFailed, err) 101 | } 102 | reader := bytes.NewReader(source) 103 | err = gen("stdin", outputFilename, *pkgName, *genTag, reader, typeSets, outWriter) 104 | } 105 | 106 | // do the work 107 | if err != nil { 108 | fatal(exitcodeGenFailed, err) 109 | } 110 | 111 | } 112 | 113 | func usage() { 114 | fmt.Fprintln(os.Stderr, `usage: genny [{flags}] gen "{types}" 115 | 116 | gen - generates type specific code from generic code. 117 | get - fetch a generic template from the online library and gen it. 118 | 119 | {flags} - (optional) Command line flags (see below) 120 | {types} - (required) Specific types for each generic type in the source 121 | {types} format: {generic}={specific}[,another][ {generic2}={specific2}] 122 | 123 | Examples: 124 | Generic=Specific 125 | Generic1=Specific1 Generic2=Specific2 126 | Generic1=Specific1,Specific2 Generic2=Specific3,Specific4 127 | 128 | Flags:`) 129 | flag.PrintDefaults() 130 | } 131 | 132 | func newWriter(fileName string) io.Writer { 133 | if fileName == "" { 134 | return os.Stdout 135 | } 136 | lf := &out.LazyFile{FileName: fileName} 137 | defer lf.Close() 138 | return lf 139 | } 140 | 141 | func fatal(code int, a ...interface{}) { 142 | fmt.Println(a...) 143 | os.Exit(code) 144 | } 145 | 146 | // gen performs the generic generation. 147 | func gen(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typesets []map[string]string, out io.Writer) error { 148 | 149 | var output []byte 150 | var err error 151 | 152 | output, err = parse.Generics(filename, outputFilename, pkgName, tag, in, typesets) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | out.Write(output) 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | -------------------------------------------------------------------------------- /out/lazy_file.go: -------------------------------------------------------------------------------- 1 | package out 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | // LazyFile is an io.WriteCloser which defers creation of the file it is supposed to write in 9 | // till the first call to its write function in order to prevent creation of file, if no write 10 | // is supposed to happen. 11 | type LazyFile struct { 12 | // FileName is path to the file to which genny will write. 13 | FileName string 14 | file *os.File 15 | } 16 | 17 | // Close closes the file if it is created. Returns nil if no file is created. 18 | func (lw *LazyFile) Close() error { 19 | if lw.file != nil { 20 | return lw.file.Close() 21 | } 22 | return nil 23 | } 24 | 25 | // Write writes to the specified file and creates the file first time it is called. 26 | func (lw *LazyFile) Write(p []byte) (int, error) { 27 | if lw.file == nil { 28 | err := os.MkdirAll(path.Dir(lw.FileName), 0755) 29 | if err != nil { 30 | return 0, err 31 | } 32 | lw.file, err = os.Create(lw.FileName) 33 | if err != nil { 34 | return 0, err 35 | } 36 | } 37 | return lw.file.Write(p) 38 | } 39 | -------------------------------------------------------------------------------- /out/lazy_file_test.go: -------------------------------------------------------------------------------- 1 | package out_test 2 | 3 | import ( 4 | "github.com/cheekybits/genny/out" 5 | "github.com/stretchr/testify/assert" 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | const testFileName = "test-file.go" 12 | 13 | func tearDown() { 14 | var err = os.Remove(testFileName) 15 | if err != nil && !os.IsNotExist(err) { 16 | panic("Could not delete test file") 17 | } 18 | } 19 | 20 | func assertFileContains(t *testing.T, expected string) { 21 | file, err := os.Open(testFileName) 22 | if err != nil { 23 | panic(err) 24 | } 25 | fileBytes, err := ioutil.ReadAll(file) 26 | if err != nil { 27 | panic(err) 28 | } 29 | assert.Equal(t, expected, string(fileBytes), "File contents not written properly") 30 | } 31 | 32 | func TestMultipleWrites(t *testing.T) { 33 | defer tearDown() 34 | lf := out.LazyFile{FileName: testFileName} 35 | defer lf.Close() 36 | lf.Write([]byte("Word1")) 37 | lf.Write([]byte("Word2")) 38 | assertFileContains(t, "Word1Word2") 39 | } 40 | 41 | func TestNoWrite(t *testing.T) { 42 | defer tearDown() 43 | lf := out.LazyFile{FileName: testFileName} 44 | defer lf.Close() 45 | _, err := os.Stat(testFileName) 46 | assert.True(t, os.IsNotExist(err), "Expected file not to be created") 47 | } 48 | -------------------------------------------------------------------------------- /parse/builtins.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | // Builtins contains a slice of all built-in Go types. 4 | var Builtins = []string{ 5 | "bool", 6 | "byte", 7 | "complex128", 8 | "complex64", 9 | "error", 10 | "float32", 11 | "float64", 12 | "int", 13 | "int16", 14 | "int32", 15 | "int64", 16 | "int8", 17 | "rune", 18 | "string", 19 | "uint", 20 | "uint16", 21 | "uint32", 22 | "uint64", 23 | "uint8", 24 | "uintptr", 25 | } 26 | 27 | // Numbers contains a slice of all built-in number types. 28 | var Numbers = []string{ 29 | "float32", 30 | "float64", 31 | "int", 32 | "int16", 33 | "int32", 34 | "int64", 35 | "int8", 36 | "uint", 37 | "uint16", 38 | "uint32", 39 | "uint64", 40 | "uint8", 41 | } 42 | -------------------------------------------------------------------------------- /parse/doc.go: -------------------------------------------------------------------------------- 1 | // Package parse contains the generic code generation capabilities 2 | // that power genny. 3 | // 4 | // genny gen "{types}" 5 | // 6 | // gen - generates type specific code (to stdout) from generic code (via stdin) 7 | // 8 | // {types} - (required) Specific types for each generic type in the source 9 | // {types} format: {generic}={specific}[,another][ {generic2}={specific2}] 10 | // Examples: 11 | // Generic=Specific 12 | // Generic1=Specific1 Generic2=Specific2 13 | // Generic1=Specific1,Specific2 Generic2=Specific3,Specific4 14 | package parse 15 | -------------------------------------------------------------------------------- /parse/errors.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // errMissingSpecificType represents an error when a generic type is not 8 | // satisfied by a specific type. 9 | type errMissingSpecificType struct { 10 | GenericType string 11 | } 12 | 13 | // Error gets a human readable string describing this error. 14 | func (e errMissingSpecificType) Error() string { 15 | return "Missing specific type for '" + e.GenericType + "' generic type" 16 | } 17 | 18 | // errImports represents an error from goimports. 19 | type errImports struct { 20 | Err error 21 | } 22 | 23 | // Error gets a human readable string describing this error. 24 | func (e errImports) Error() string { 25 | return "Failed to goimports the generated code: " + e.Err.Error() 26 | } 27 | 28 | // errSource represents an error with the source file. 29 | type errSource struct { 30 | Err error 31 | } 32 | 33 | // Error gets a human readable string describing this error. 34 | func (e errSource) Error() string { 35 | return "Failed to parse source file: " + e.Err.Error() 36 | } 37 | 38 | type errBadTypeArgs struct { 39 | Message string 40 | Arg string 41 | } 42 | 43 | func (e errBadTypeArgs) Error() string { 44 | return "\"" + e.Arg + "\" is bad: " + e.Message 45 | } 46 | 47 | var errMissingTypeInformation = errors.New("No type arguments were specified and no \"// +gogen\" tag was found in the source.") 48 | -------------------------------------------------------------------------------- /parse/parse.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "go/ast" 8 | "go/parser" 9 | "go/scanner" 10 | "go/token" 11 | "io" 12 | "os" 13 | "strings" 14 | "unicode" 15 | 16 | "golang.org/x/tools/imports" 17 | ) 18 | 19 | var header = []byte(` 20 | 21 | // This file was automatically generated by genny. 22 | // Any changes will be lost if this file is regenerated. 23 | // see https://github.com/cheekybits/genny 24 | 25 | `) 26 | 27 | var ( 28 | packageKeyword = []byte("package") 29 | importKeyword = []byte("import") 30 | openBrace = []byte("(") 31 | closeBrace = []byte(")") 32 | genericPackage = "generic" 33 | genericType = "generic.Type" 34 | genericNumber = "generic.Number" 35 | linefeed = "\r\n" 36 | ) 37 | var unwantedLinePrefixes = [][]byte{ 38 | []byte("//go:generate genny "), 39 | } 40 | 41 | func subIntoLiteral(lit, typeTemplate, specificType string) string { 42 | if lit == typeTemplate { 43 | return specificType 44 | } 45 | if !strings.Contains(lit, typeTemplate) { 46 | return lit 47 | } 48 | specificLg := wordify(specificType, true) 49 | specificSm := wordify(specificType, false) 50 | result := strings.Replace(lit, typeTemplate, specificLg, -1) 51 | if strings.HasPrefix(result, specificLg) && !isExported(lit) { 52 | return strings.Replace(result, specificLg, specificSm, 1) 53 | } 54 | return result 55 | } 56 | 57 | func subTypeIntoComment(line, typeTemplate, specificType string) string { 58 | var subbed string 59 | for _, w := range strings.Fields(line) { 60 | subbed = subbed + subIntoLiteral(w, typeTemplate, specificType) + " " 61 | } 62 | return subbed 63 | } 64 | 65 | // Does the heavy lifting of taking a line of our code and 66 | // sbustituting a type into there for our generic type 67 | func subTypeIntoLine(line, typeTemplate, specificType string) string { 68 | src := []byte(line) 69 | var s scanner.Scanner 70 | fset := token.NewFileSet() 71 | file := fset.AddFile("", fset.Base(), len(src)) 72 | s.Init(file, src, nil, scanner.ScanComments) 73 | output := "" 74 | for { 75 | _, tok, lit := s.Scan() 76 | if tok == token.EOF { 77 | break 78 | } else if tok == token.COMMENT { 79 | subbed := subTypeIntoComment(lit, typeTemplate, specificType) 80 | output = output + subbed + " " 81 | } else if tok.IsLiteral() { 82 | subbed := subIntoLiteral(lit, typeTemplate, specificType) 83 | output = output + subbed + " " 84 | } else { 85 | output = output + tok.String() + " " 86 | } 87 | } 88 | return output 89 | } 90 | 91 | // typeSet looks like "KeyType: int, ValueType: string" 92 | func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]string) ([]byte, error) { 93 | 94 | // ensure we are at the beginning of the file 95 | in.Seek(0, os.SEEK_SET) 96 | 97 | // parse the source file 98 | fs := token.NewFileSet() 99 | file, err := parser.ParseFile(fs, filename, in, 0) 100 | if err != nil { 101 | return nil, &errSource{Err: err} 102 | } 103 | 104 | // make sure every generic.Type is represented in the types 105 | // argument. 106 | for _, decl := range file.Decls { 107 | switch it := decl.(type) { 108 | case *ast.GenDecl: 109 | for _, spec := range it.Specs { 110 | ts, ok := spec.(*ast.TypeSpec) 111 | if !ok { 112 | continue 113 | } 114 | switch tt := ts.Type.(type) { 115 | case *ast.SelectorExpr: 116 | if name, ok := tt.X.(*ast.Ident); ok { 117 | if name.Name == genericPackage { 118 | if _, ok := typeSet[ts.Name.Name]; !ok { 119 | return nil, &errMissingSpecificType{GenericType: ts.Name.Name} 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | in.Seek(0, os.SEEK_SET) 129 | 130 | var buf bytes.Buffer 131 | 132 | comment := "" 133 | scanner := bufio.NewScanner(in) 134 | for scanner.Scan() { 135 | 136 | line := scanner.Text() 137 | 138 | // does this line contain generic.Type? 139 | if strings.Contains(line, genericType) || strings.Contains(line, genericNumber) { 140 | comment = "" 141 | continue 142 | } 143 | 144 | for t, specificType := range typeSet { 145 | if strings.Contains(line, t) { 146 | newLine := subTypeIntoLine(line, t, specificType) 147 | line = newLine 148 | } 149 | } 150 | 151 | if comment != "" { 152 | buf.WriteString(makeLine(comment)) 153 | comment = "" 154 | } 155 | 156 | // is this line a comment? 157 | // TODO: should we handle /* */ comments? 158 | if strings.HasPrefix(line, "//") { 159 | // record this line to print later 160 | comment = line 161 | continue 162 | } 163 | 164 | // write the line 165 | buf.WriteString(makeLine(line)) 166 | } 167 | 168 | // write it out 169 | return buf.Bytes(), nil 170 | } 171 | 172 | // Generics parses the source file and generates the bytes replacing the 173 | // generic types for the keys map with the specific types (its value). 174 | func Generics(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typeSets []map[string]string) ([]byte, error) { 175 | var localUnwantedLinePrefixes [][]byte 176 | localUnwantedLinePrefixes = append(localUnwantedLinePrefixes, unwantedLinePrefixes...) 177 | 178 | if tag != "" { 179 | localUnwantedLinePrefixes = append(localUnwantedLinePrefixes, []byte(fmt.Sprintf("// +build %s", tag))) 180 | } 181 | 182 | totalOutput := header 183 | 184 | for _, typeSet := range typeSets { 185 | 186 | // generate the specifics 187 | parsed, err := generateSpecific(filename, in, typeSet) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | totalOutput = append(totalOutput, parsed...) 193 | 194 | } 195 | 196 | // clean up the code line by line 197 | packageFound := false 198 | insideImportBlock := false 199 | var cleanOutputLines []string 200 | scanner := bufio.NewScanner(bytes.NewReader(totalOutput)) 201 | for scanner.Scan() { 202 | 203 | // end of imports block? 204 | if insideImportBlock { 205 | if bytes.HasSuffix(scanner.Bytes(), closeBrace) { 206 | insideImportBlock = false 207 | } 208 | continue 209 | } 210 | 211 | if bytes.HasPrefix(scanner.Bytes(), packageKeyword) { 212 | if packageFound { 213 | continue 214 | } else { 215 | packageFound = true 216 | } 217 | } else if bytes.HasPrefix(scanner.Bytes(), importKeyword) { 218 | if bytes.HasSuffix(scanner.Bytes(), openBrace) { 219 | insideImportBlock = true 220 | } 221 | continue 222 | } 223 | 224 | // check all localUnwantedLinePrefixes - and skip them 225 | skipline := false 226 | for _, prefix := range localUnwantedLinePrefixes { 227 | if bytes.HasPrefix(scanner.Bytes(), prefix) { 228 | skipline = true 229 | continue 230 | } 231 | } 232 | 233 | if skipline { 234 | continue 235 | } 236 | 237 | cleanOutputLines = append(cleanOutputLines, makeLine(scanner.Text())) 238 | } 239 | 240 | cleanOutput := strings.Join(cleanOutputLines, "") 241 | 242 | output := []byte(cleanOutput) 243 | var err error 244 | 245 | // change package name 246 | if pkgName != "" { 247 | output = changePackage(bytes.NewReader([]byte(output)), pkgName) 248 | } 249 | // fix the imports 250 | output, err = imports.Process(outputFilename, output, nil) 251 | if err != nil { 252 | return nil, &errImports{Err: err} 253 | } 254 | 255 | return output, nil 256 | } 257 | 258 | func makeLine(s string) string { 259 | return fmt.Sprintln(strings.TrimRight(s, linefeed)) 260 | } 261 | 262 | // isAlphaNumeric gets whether the rune is alphanumeric or _. 263 | func isAlphaNumeric(r rune) bool { 264 | return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) 265 | } 266 | 267 | // wordify turns a type into a nice word for function and type 268 | // names etc. 269 | func wordify(s string, exported bool) string { 270 | s = strings.TrimRight(s, "{}") 271 | s = strings.TrimLeft(s, "*&") 272 | s = strings.Replace(s, ".", "", -1) 273 | if !exported { 274 | return s 275 | } 276 | return strings.ToUpper(string(s[0])) + s[1:] 277 | } 278 | 279 | func changePackage(r io.Reader, pkgName string) []byte { 280 | var out bytes.Buffer 281 | sc := bufio.NewScanner(r) 282 | done := false 283 | 284 | for sc.Scan() { 285 | s := sc.Text() 286 | 287 | if !done && strings.HasPrefix(s, "package") { 288 | parts := strings.Split(s, " ") 289 | parts[1] = pkgName 290 | s = strings.Join(parts, " ") 291 | done = true 292 | } 293 | 294 | fmt.Fprintln(&out, s) 295 | } 296 | return out.Bytes() 297 | } 298 | 299 | func isExported(lit string) bool { 300 | if len(lit) == 0 { 301 | return false 302 | } 303 | return unicode.IsUpper(rune(lit[0])) 304 | } 305 | -------------------------------------------------------------------------------- /parse/parse_int_test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsAlphaNumeric(t *testing.T) { 10 | 11 | for _, r := range []rune{'a', '1', '_', 'A', 'Z'} { 12 | assert.True(t, isAlphaNumeric(r)) 13 | } 14 | for _, r := range []rune{' ', '[', ']', '!', '"'} { 15 | assert.False(t, isAlphaNumeric(r)) 16 | } 17 | 18 | } 19 | 20 | func TestWordify(t *testing.T) { 21 | 22 | for word, wordified := range map[string]string{ 23 | "int": "Int", 24 | "*int": "Int", 25 | "string": "String", 26 | "*MyType": "MyType", 27 | "*myType": "MyType", 28 | "interface{}": "Interface", 29 | "pack.type": "Packtype", 30 | "*pack.type": "Packtype", 31 | } { 32 | assert.Equal(t, wordified, wordify(word, true)) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /parse/parse_test.go: -------------------------------------------------------------------------------- 1 | package parse_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/cheekybits/genny/parse" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var tests = []struct { 14 | // input 15 | filename string 16 | outputFilename string 17 | pkgName string 18 | in string 19 | tag string 20 | types []map[string]string 21 | 22 | // expectations 23 | expectedOut string 24 | expectedErr error 25 | }{ 26 | { 27 | filename: "generic_queue.go", 28 | in: `test/queue/generic_queue.go`, 29 | types: []map[string]string{{"Something": "int"}}, 30 | expectedOut: `test/queue/int_queue.go`, 31 | }, 32 | { 33 | filename: "generic_queue.go", 34 | pkgName: "changed", 35 | in: `test/queue/generic_queue.go`, 36 | types: []map[string]string{{"Something": "int"}}, 37 | expectedOut: `test/queue/changed/int_queue.go`, 38 | }, 39 | { 40 | filename: "generic_queue.go", 41 | in: `test/queue/generic_queue.go`, 42 | types: []map[string]string{{"Something": "float32"}}, 43 | expectedOut: `test/queue/float32_queue.go`, 44 | }, 45 | { 46 | filename: "generic_simplemap.go", 47 | in: `test/multipletypes/generic_simplemap.go`, 48 | types: []map[string]string{{"KeyType": "string", "ValueType": "int"}}, 49 | expectedOut: `test/multipletypes/string_int_simplemap.go`, 50 | }, 51 | { 52 | filename: "generic_simplemap.go", 53 | in: `test/multipletypes/generic_simplemap.go`, 54 | types: []map[string]string{{"KeyType": "interface{}", "ValueType": "int"}}, 55 | expectedOut: `test/multipletypes/interface_int_simplemap.go`, 56 | }, 57 | { 58 | filename: "generic_simplemap.go", 59 | in: `test/multipletypes/generic_simplemap.go`, 60 | types: []map[string]string{{"KeyType": "*MyType1", "ValueType": "*MyOtherType"}}, 61 | expectedOut: `test/multipletypes/custom_types_simplemap.go`, 62 | }, 63 | { 64 | filename: "generic_internal.go", 65 | in: `test/unexported/generic_internal.go`, 66 | types: []map[string]string{{"secret": "*myType"}}, 67 | expectedOut: `test/unexported/mytype_internal.go`, 68 | }, 69 | { 70 | filename: "generic_simplemap.go", 71 | in: `test/multipletypesets/generic_simplemap.go`, 72 | types: []map[string]string{ 73 | {"KeyType": "int", "ValueType": "string"}, 74 | {"KeyType": "float64", "ValueType": "bool"}, 75 | }, 76 | expectedOut: `test/multipletypesets/many_simplemaps.go`, 77 | }, 78 | { 79 | filename: "generic_number.go", 80 | in: `test/numbers/generic_number.go`, 81 | types: []map[string]string{{"NumberType": "int"}}, 82 | expectedOut: `test/numbers/int_number.go`, 83 | }, 84 | { 85 | filename: "generic_digraph.go", 86 | in: `test/bugreports/generic_digraph.go`, 87 | types: []map[string]string{{"Node": "int"}}, 88 | expectedOut: `test/bugreports/int_digraph.go`, 89 | }, 90 | { 91 | filename: "generic_new_and_make_slice.go", 92 | in: `test/bugreports/generic_new_and_make_slice.go`, 93 | types: []map[string]string{{"NumberType": "int"}}, 94 | expectedOut: `test/bugreports/int_new_and_make_slice.go`, 95 | }, 96 | { 97 | filename: "cell_x.go", 98 | in: `test/bugreports/cell_x.go`, 99 | types: []map[string]string{{"X": "int"}}, 100 | expectedOut: `test/bugreports/cell_int.go`, 101 | }, 102 | { 103 | filename: "interface_generic_type.go", 104 | in: `test/bugreports/interface_generic_type.go`, 105 | types: []map[string]string{{"GenericType": "uint8"}}, 106 | expectedOut: `test/bugreports/interface_uint8.go`, 107 | }, 108 | { 109 | filename: "negation_generic.go", 110 | in: `test/bugreports/negation_generic.go`, 111 | types: []map[string]string{{"SomeThing": "string"}}, 112 | expectedOut: `test/bugreports/negation_string.go`, 113 | }, 114 | { 115 | filename: "buildtags.go", 116 | in: `test/buildtags/buildtags.go`, 117 | types: []map[string]string{{"_t_": "int"}}, 118 | expectedOut: `test/buildtags/buildtags_expected.go`, 119 | tag: "genny", 120 | }, 121 | { 122 | filename: "buildtags.go", 123 | in: `test/buildtags/buildtags.go`, 124 | types: []map[string]string{{"_t_": "string"}}, 125 | expectedOut: `test/buildtags/buildtags_expected_nostrip.go`, 126 | tag: "", 127 | }, 128 | } 129 | 130 | func TestParse(t *testing.T) { 131 | 132 | for _, test := range tests { 133 | 134 | test.in = contents(test.in) 135 | test.expectedOut = contents(test.expectedOut) 136 | 137 | bytes, err := parse.Generics(test.filename, test.outputFilename, test.pkgName, test.tag, strings.NewReader(test.in), test.types) 138 | 139 | // check the error 140 | if test.expectedErr == nil { 141 | assert.NoError(t, err, "(%s) No error was expected but got: %s", test.filename, err) 142 | } else { 143 | assert.NotNil(t, err, "(%s) No error was returned by one was expected: %s", test.filename, test.expectedErr) 144 | assert.IsType(t, test.expectedErr, err, "(%s) Generate should return object of type %v", test.filename, test.expectedErr) 145 | } 146 | 147 | // assert the response 148 | if !assert.Equal(t, string(bytes), test.expectedOut, "Parse didn't generate the expected output.") { 149 | log.Println("EXPECTED: " + test.expectedOut) 150 | log.Println("ACTUAL: " + string(bytes)) 151 | } 152 | 153 | } 154 | 155 | } 156 | 157 | func contents(s string) string { 158 | if strings.HasSuffix(s, "go") { 159 | file, err := ioutil.ReadFile(s) 160 | if err != nil { 161 | panic(err) 162 | } 163 | return string(file) 164 | } 165 | return s 166 | } 167 | -------------------------------------------------------------------------------- /parse/test/bugreports/cell_int.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package bugreports 6 | 7 | // CellInt is result of generating code via genny for type int 8 | type CellInt struct { 9 | Value int 10 | } 11 | 12 | const constantInt = 1 13 | 14 | func funcInt(p CellInt) {} 15 | 16 | // exampleInt does some instantation and function calls for types inclueded in this file. 17 | // Targets github issue 15 18 | func exampleInt() { 19 | aCellInt := CellInt{} 20 | anotherCellInt := CellInt{} 21 | if aCellInt != anotherCellInt { 22 | println(constantInt) 23 | panic(constantInt) 24 | } 25 | funcInt(CellInt{}) 26 | } 27 | -------------------------------------------------------------------------------- /parse/test/bugreports/cell_x.go: -------------------------------------------------------------------------------- 1 | package bugreports 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | // X is the type generic type used in tests 6 | type X generic.Type 7 | 8 | // CellX is result of generating code via genny for type X 9 | type CellX struct { 10 | Value X 11 | } 12 | 13 | const constantX = 1 14 | 15 | func funcX(p CellX) {} 16 | 17 | // exampleX does some instantation and function calls for types inclueded in this file. 18 | // Targets github issue 15 19 | func exampleX() { 20 | aCellX := CellX{} 21 | anotherCellX := CellX{} 22 | if aCellX != anotherCellX { 23 | println(constantX) 24 | panic(constantX) 25 | } 26 | funcX(CellX{}) 27 | } 28 | -------------------------------------------------------------------------------- /parse/test/bugreports/generic_digraph.go: -------------------------------------------------------------------------------- 1 | package bugreports 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type Node generic.Type 6 | 7 | type DigraphNode struct { 8 | nodes map[Node][]Node 9 | } 10 | 11 | func NewDigraphNode() *DigraphNode { 12 | return &DigraphNode{ 13 | nodes: make(map[Node][]Node), 14 | } 15 | } 16 | 17 | func (dig *DigraphNode) Add(n Node) { 18 | if _, exists := dig.nodes[n]; exists { 19 | return 20 | } 21 | 22 | dig.nodes[n] = nil 23 | } 24 | 25 | func (dig *DigraphNode) Connect(a, b Node) { 26 | dig.Add(a) 27 | dig.Add(b) 28 | 29 | dig.nodes[a] = append(dig.nodes[a], b) 30 | } 31 | -------------------------------------------------------------------------------- /parse/test/bugreports/generic_new_and_make_slice.go: -------------------------------------------------------------------------------- 1 | package bugreports 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | // NumberType will be replaced in tests 6 | type NumberType generic.Number 7 | 8 | // ObjNumberType is the struct used for tests. 9 | type ObjNumberType struct { 10 | v NumberType 11 | } 12 | 13 | // NewNumberTypes calls new on ObjNumberType and instantiates slice. 14 | // Targets github issues #36 and #49 15 | func NewNumberTypes() (*ObjNumberType, []ObjNumberType) { 16 | n := new(ObjNumberType) 17 | m := make([]ObjNumberType, 0) 18 | return n, m 19 | } 20 | -------------------------------------------------------------------------------- /parse/test/bugreports/int_digraph.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package bugreports 6 | 7 | type DigraphInt struct { 8 | nodes map[int][]int 9 | } 10 | 11 | func NewDigraphInt() *DigraphInt { 12 | return &DigraphInt{ 13 | nodes: make(map[int][]int), 14 | } 15 | } 16 | 17 | func (dig *DigraphInt) Add(n int) { 18 | if _, exists := dig.nodes[n]; exists { 19 | return 20 | } 21 | 22 | dig.nodes[n] = nil 23 | } 24 | 25 | func (dig *DigraphInt) Connect(a, b int) { 26 | dig.Add(a) 27 | dig.Add(b) 28 | 29 | dig.nodes[a] = append(dig.nodes[a], b) 30 | } 31 | -------------------------------------------------------------------------------- /parse/test/bugreports/int_new_and_make_slice.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package bugreports 6 | 7 | // ObjInt is the struct used for tests. 8 | type ObjInt struct { 9 | v int 10 | } 11 | 12 | // NewInts calls new on ObjInt and instantiates slice. 13 | // Targets github issues #36 and #49 14 | func NewInts() (*ObjInt, []ObjInt) { 15 | n := new(ObjInt) 16 | m := make([]ObjInt, 0) 17 | return n, m 18 | } 19 | -------------------------------------------------------------------------------- /parse/test/bugreports/interface_generic_type.go: -------------------------------------------------------------------------------- 1 | package bugreports 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type GenericType generic.Type 6 | 7 | type InterfaceGenericType interface { 8 | DoSomthingGenericType() 9 | } 10 | 11 | // Call calls a method on an instance of generic interface. 12 | // Targets github issue 49 13 | func CallWithGenericType(i InterfaceGenericType) { 14 | i.DoSomthingGenericType() 15 | } 16 | -------------------------------------------------------------------------------- /parse/test/bugreports/interface_uint8.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package bugreports 6 | 7 | type InterfaceUint8 interface { 8 | DoSomthingUint8() 9 | } 10 | 11 | // Call calls a method on an instance of generic interface. 12 | // Targets github issue 49 13 | func CallWithUint8(i InterfaceUint8) { 14 | i.DoSomthingUint8() 15 | } 16 | -------------------------------------------------------------------------------- /parse/test/bugreports/negation_generic.go: -------------------------------------------------------------------------------- 1 | package bugreports 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type SomeThing generic.Type 6 | 7 | func ContainsSomeThing(slice []SomeThing, element SomeThing) bool { 8 | return false 9 | } 10 | 11 | // ContainsAllSomeThing targets github issue 36 12 | func ContainsAllSomeThing(slice []SomeThing, other []SomeThing) bool { 13 | for _, e := range other { 14 | if !ContainsSomeThing(slice, e) { 15 | return false 16 | } 17 | } 18 | return true 19 | } 20 | -------------------------------------------------------------------------------- /parse/test/bugreports/negation_string.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package bugreports 6 | 7 | func ContainsString(slice []string, element string) bool { 8 | return false 9 | } 10 | 11 | // ContainsAllString targets github issue 36 12 | func ContainsAllString(slice []string, other []string) bool { 13 | for _, e := range other { 14 | if !ContainsString(slice, e) { 15 | return false 16 | } 17 | } 18 | return true 19 | } 20 | -------------------------------------------------------------------------------- /parse/test/buildtags/buildtags.go: -------------------------------------------------------------------------------- 1 | // +build x,y z 2 | // +build genny 3 | 4 | package buildtags 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/cheekybits/genny/generic" 10 | ) 11 | 12 | type _t_ generic.Type 13 | 14 | func _t_Print(t _t_) { 15 | fmt.Println(t) 16 | } 17 | -------------------------------------------------------------------------------- /parse/test/buildtags/buildtags_expected.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | // +build x,y z 6 | 7 | package buildtags 8 | 9 | import "fmt" 10 | 11 | func intPrint(t int) { 12 | fmt.Println(t) 13 | } 14 | -------------------------------------------------------------------------------- /parse/test/buildtags/buildtags_expected_nostrip.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | // +build x,y z 6 | // +build genny 7 | 8 | package buildtags 9 | 10 | import "fmt" 11 | 12 | func stringPrint(t string) { 13 | fmt.Println(t) 14 | } 15 | -------------------------------------------------------------------------------- /parse/test/multipletypes/custom_types.go: -------------------------------------------------------------------------------- 1 | package multipletypes 2 | 3 | type MyType1 struct{} 4 | 5 | type MyOtherType struct{} 6 | -------------------------------------------------------------------------------- /parse/test/multipletypes/custom_types_simplemap.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package multipletypes 6 | 7 | type MyType1MyOtherTypeMap map[*MyType1]*MyOtherType 8 | 9 | func (m MyType1MyOtherTypeMap) Has(key *MyType1) bool { 10 | _, ok := m[key] 11 | return ok 12 | } 13 | 14 | func (m MyType1MyOtherTypeMap) Get(key *MyType1) *MyOtherType { 15 | return m[key] 16 | } 17 | 18 | func (m MyType1MyOtherTypeMap) Set(key *MyType1, value *MyOtherType) MyType1MyOtherTypeMap { 19 | m[key] = value 20 | return m 21 | } 22 | -------------------------------------------------------------------------------- /parse/test/multipletypes/generic_simplemap.go: -------------------------------------------------------------------------------- 1 | package multipletypes 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type KeyType generic.Type 6 | type ValueType generic.Type 7 | 8 | type KeyTypeValueTypeMap map[KeyType]ValueType 9 | 10 | func (m KeyTypeValueTypeMap) Has(key KeyType) bool { 11 | _, ok := m[key] 12 | return ok 13 | } 14 | 15 | func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType { 16 | return m[key] 17 | } 18 | 19 | func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap { 20 | m[key] = value 21 | return m 22 | } 23 | -------------------------------------------------------------------------------- /parse/test/multipletypes/generic_simplemap_test.go: -------------------------------------------------------------------------------- 1 | package multipletypes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSimpleMap(t *testing.T) { 10 | 11 | key1 := new(KeyType) 12 | key2 := new(KeyType) 13 | value1 := new(ValueType) 14 | m := make(KeyTypeValueTypeMap) 15 | 16 | assert.Equal(t, m, m.Set(key1, value1)) 17 | assert.True(t, m.Has(key1)) 18 | assert.False(t, m.Has(key2)) 19 | assert.Equal(t, value1, m.Get(key1)) 20 | 21 | } 22 | 23 | func TestCustomTypesMap(t *testing.T) { 24 | 25 | key1 := new(MyType1) 26 | key2 := new(MyType1) 27 | value1 := new(MyOtherType) 28 | m := make(MyType1MyOtherTypeMap) 29 | 30 | assert.Equal(t, m, m.Set(key1, value1)) 31 | assert.True(t, m.Has(key1)) 32 | assert.False(t, m.Has(key2)) 33 | assert.Equal(t, value1, m.Get(key1)) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /parse/test/multipletypes/interface_int_simplemap.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package multipletypes 6 | 7 | type InterfaceIntMap map[interface{}]int 8 | 9 | func (m InterfaceIntMap) Has(key interface{}) bool { 10 | _, ok := m[key] 11 | return ok 12 | } 13 | 14 | func (m InterfaceIntMap) Get(key interface{}) int { 15 | return m[key] 16 | } 17 | 18 | func (m InterfaceIntMap) Set(key interface{}, value int) InterfaceIntMap { 19 | m[key] = value 20 | return m 21 | } 22 | -------------------------------------------------------------------------------- /parse/test/multipletypes/string_int_simplemap.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package multipletypes 6 | 7 | type StringIntMap map[string]int 8 | 9 | func (m StringIntMap) Has(key string) bool { 10 | _, ok := m[key] 11 | return ok 12 | } 13 | 14 | func (m StringIntMap) Get(key string) int { 15 | return m[key] 16 | } 17 | 18 | func (m StringIntMap) Set(key string, value int) StringIntMap { 19 | m[key] = value 20 | return m 21 | } 22 | -------------------------------------------------------------------------------- /parse/test/multipletypesets/generic_simplemap.go: -------------------------------------------------------------------------------- 1 | package multipletypesets 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/cheekybits/genny/generic" 7 | ) 8 | 9 | type KeyType generic.Type 10 | type ValueType generic.Type 11 | 12 | type KeyTypeValueTypeMap map[KeyType]ValueType 13 | 14 | func (m KeyTypeValueTypeMap) Has(key KeyType) bool { 15 | _, ok := m[key] 16 | return ok 17 | } 18 | 19 | func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType { 20 | return m[key] 21 | } 22 | 23 | func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap { 24 | log.Println(value) 25 | m[key] = value 26 | return m 27 | } 28 | -------------------------------------------------------------------------------- /parse/test/multipletypesets/generic_simplemap_test.go: -------------------------------------------------------------------------------- 1 | package multipletypesets 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSimpleMap(t *testing.T) { 10 | 11 | key1 := new(KeyType) 12 | key2 := new(KeyType) 13 | value1 := new(ValueType) 14 | m := make(KeyTypeValueTypeMap) 15 | 16 | assert.Equal(t, m, m.Set(key1, value1)) 17 | assert.True(t, m.Has(key1)) 18 | assert.False(t, m.Has(key2)) 19 | assert.Equal(t, value1, m.Get(key1)) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /parse/test/multipletypesets/many_simplemaps.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package multipletypesets 6 | 7 | import "log" 8 | 9 | type IntStringMap map[int]string 10 | 11 | func (m IntStringMap) Has(key int) bool { 12 | _, ok := m[key] 13 | return ok 14 | } 15 | 16 | func (m IntStringMap) Get(key int) string { 17 | return m[key] 18 | } 19 | 20 | func (m IntStringMap) Set(key int, value string) IntStringMap { 21 | log.Println(value) 22 | m[key] = value 23 | return m 24 | } 25 | 26 | type Float64BoolMap map[float64]bool 27 | 28 | func (m Float64BoolMap) Has(key float64) bool { 29 | _, ok := m[key] 30 | return ok 31 | } 32 | 33 | func (m Float64BoolMap) Get(key float64) bool { 34 | return m[key] 35 | } 36 | 37 | func (m Float64BoolMap) Set(key float64, value bool) Float64BoolMap { 38 | log.Println(value) 39 | m[key] = value 40 | return m 41 | } 42 | -------------------------------------------------------------------------------- /parse/test/numbers/generic_number.go: -------------------------------------------------------------------------------- 1 | package numbers 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | type NumberType generic.Number 6 | 7 | func NumberTypeMax(a, b NumberType) NumberType { 8 | if a > b { 9 | return a 10 | } 11 | return b 12 | } 13 | -------------------------------------------------------------------------------- /parse/test/numbers/int_number.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package numbers 6 | 7 | func IntMax(a, b int) int { 8 | if a > b { 9 | return a 10 | } 11 | return b 12 | } 13 | -------------------------------------------------------------------------------- /parse/test/queue/changed/int_queue.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package changed 6 | 7 | // IntQueue is a queue of Ints. 8 | type IntQueue struct { 9 | items []int 10 | } 11 | 12 | func NewIntQueue() *IntQueue { 13 | return &IntQueue{items: make([]int, 0)} 14 | } 15 | func (q *IntQueue) Push(item int) { 16 | q.items = append(q.items, item) 17 | } 18 | func (q *IntQueue) Pop() int { 19 | item := q.items[0] 20 | q.items = q.items[1:] 21 | return item 22 | } 23 | -------------------------------------------------------------------------------- /parse/test/queue/float32_queue.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package queue 6 | 7 | // Float32Queue is a queue of Float32s. 8 | type Float32Queue struct { 9 | items []float32 10 | } 11 | 12 | func NewFloat32Queue() *Float32Queue { 13 | return &Float32Queue{items: make([]float32, 0)} 14 | } 15 | func (q *Float32Queue) Push(item float32) { 16 | q.items = append(q.items, item) 17 | } 18 | func (q *Float32Queue) Pop() float32 { 19 | item := q.items[0] 20 | q.items = q.items[1:] 21 | return item 22 | } 23 | -------------------------------------------------------------------------------- /parse/test/queue/generic_queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | // Something is a type that does something 6 | type Something generic.Type 7 | 8 | // SomethingQueue is a queue of Somethings. 9 | type SomethingQueue struct { 10 | items []Something 11 | } 12 | 13 | func NewSomethingQueue() *SomethingQueue { 14 | return &SomethingQueue{items: make([]Something, 0)} 15 | } 16 | func (q *SomethingQueue) Push(item Something) { 17 | q.items = append(q.items, item) 18 | } 19 | func (q *SomethingQueue) Pop() Something { 20 | item := q.items[0] 21 | q.items = q.items[1:] 22 | return item 23 | } 24 | -------------------------------------------------------------------------------- /parse/test/queue/generic_queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "testing" 4 | 5 | func TestSomethingQueue(t *testing.T) { 6 | 7 | var item1 *Something = new(Something) 8 | var item2 *Something = new(Something) 9 | var item3 *Something = new(Something) 10 | 11 | q := NewSomethingQueue() 12 | 13 | q.Push(item1) 14 | if len(q.items) != 1 { 15 | t.Error("Push should add the item") 16 | } 17 | q.Push(item2) 18 | if len(q.items) != 2 { 19 | t.Error("Push should add the item") 20 | } 21 | q.Push(item3) 22 | if len(q.items) != 3 { 23 | t.Error("Push should add the item") 24 | } 25 | 26 | if q.Pop() != item1 { 27 | t.Error("Pop should return items in the order in which they were added") 28 | } 29 | if len(q.items) != 2 { 30 | t.Error("Pop should remove the item") 31 | } 32 | if q.Pop() != item2 { 33 | t.Error("Pop should return items in the order in which they were added") 34 | } 35 | if len(q.items) != 1 { 36 | t.Error("Pop should remove the item") 37 | } 38 | if q.Pop() != item3 { 39 | t.Error("Pop should return items in the order in which they were added") 40 | } 41 | if len(q.items) != 0 { 42 | t.Error("Pop should remove the item") 43 | } 44 | 45 | } 46 | 47 | func TestIntQueue(t *testing.T) { 48 | 49 | var item1 int = 1 50 | var item2 int = 2 51 | var item3 int = 3 52 | 53 | q := NewIntQueue() 54 | 55 | q.Push(item1) 56 | if len(q.items) != 1 { 57 | t.Error("Push should add the item") 58 | } 59 | q.Push(item2) 60 | if len(q.items) != 2 { 61 | t.Error("Push should add the item") 62 | } 63 | q.Push(item3) 64 | if len(q.items) != 3 { 65 | t.Error("Push should add the item") 66 | } 67 | 68 | if q.Pop() != item1 { 69 | t.Error("Pop should return items in the order in which they were added") 70 | } 71 | if len(q.items) != 2 { 72 | t.Error("Pop should remove the item") 73 | } 74 | if q.Pop() != item2 { 75 | t.Error("Pop should return items in the order in which they were added") 76 | } 77 | if len(q.items) != 1 { 78 | t.Error("Pop should remove the item") 79 | } 80 | if q.Pop() != item3 { 81 | t.Error("Pop should return items in the order in which they were added") 82 | } 83 | if len(q.items) != 0 { 84 | t.Error("Pop should remove the item") 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /parse/test/queue/int_queue.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package queue 6 | 7 | // IntQueue is a queue of Ints. 8 | type IntQueue struct { 9 | items []int 10 | } 11 | 12 | func NewIntQueue() *IntQueue { 13 | return &IntQueue{items: make([]int, 0)} 14 | } 15 | func (q *IntQueue) Push(item int) { 16 | q.items = append(q.items, item) 17 | } 18 | func (q *IntQueue) Pop() int { 19 | item := q.items[0] 20 | q.items = q.items[1:] 21 | return item 22 | } 23 | -------------------------------------------------------------------------------- /parse/test/slice/ensure_slice.go: -------------------------------------------------------------------------------- 1 | package slice 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | 7 | "github.com/cheekybits/genny/generic" 8 | ) 9 | 10 | type MyType generic.Type 11 | 12 | func EnsureSlice(objectOrSlice interface{}) []MyType { 13 | log.Printf("%v", reflect.TypeOf(objectOrSlice)) 14 | switch obj := objectOrSlice.(type) { 15 | case []MyType: 16 | log.Println(" returning it untouched") 17 | return obj 18 | case MyType, *MyType: 19 | log.Println(" wrapping in slice") 20 | return []MyType{obj} 21 | default: 22 | panic("ensure slice needs MyType or []MyType") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /parse/test/slice/ensure_slice_test.go: -------------------------------------------------------------------------------- 1 | package slice 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEnsureSlice(t *testing.T) { 11 | 12 | myType := new(MyType) 13 | slice := EnsureSlice(myType) 14 | if assert.NotNil(t, slice) { 15 | assert.Equal(t, slice[0], myType) 16 | } 17 | 18 | slice = EnsureSlice(slice) 19 | log.Printf("%#v", slice[0]) 20 | if assert.NotNil(t, slice) { 21 | assert.Equal(t, slice[0], myType) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /parse/test/unexported/custom_types.go: -------------------------------------------------------------------------------- 1 | package unexported 2 | 3 | type myType struct{} 4 | -------------------------------------------------------------------------------- /parse/test/unexported/generic_internal.go: -------------------------------------------------------------------------------- 1 | package unexported 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cheekybits/genny/generic" 7 | ) 8 | 9 | type secret generic.Type 10 | 11 | func secretInspect(s secret) string { 12 | return fmt.Sprintf("%#v", s) 13 | } 14 | -------------------------------------------------------------------------------- /parse/test/unexported/mytype_internal.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package unexported 6 | 7 | import "fmt" 8 | 9 | func myTypeInspect(s *myType) string { 10 | return fmt.Sprintf("%#v", s) 11 | } 12 | -------------------------------------------------------------------------------- /parse/typesets.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import "strings" 4 | 5 | const ( 6 | typeSep = " " 7 | keyValueSep = "=" 8 | valuesSep = "," 9 | builtins = "BUILTINS" 10 | numbers = "NUMBERS" 11 | ) 12 | 13 | // TypeSet turns a type string into a []map[string]string 14 | // that can be given to parse.Generics for it to do its magic. 15 | // 16 | // Acceptable args are: 17 | // 18 | // Person=man 19 | // Person=man Animal=dog 20 | // Person=man Animal=dog Animal2=cat 21 | // Person=man,woman Animal=dog,cat 22 | // Person=man,woman,child Animal=dog,cat Place=london,paris 23 | func TypeSet(arg string) ([]map[string]string, error) { 24 | 25 | types := make(map[string][]string) 26 | var keys []string 27 | for _, pair := range strings.Split(arg, typeSep) { 28 | segs := strings.Split(pair, keyValueSep) 29 | if len(segs) != 2 { 30 | return nil, &errBadTypeArgs{Arg: arg, Message: "Generic=Specific expected"} 31 | } 32 | key := segs[0] 33 | keys = append(keys, key) 34 | types[key] = make([]string, 0) 35 | for _, t := range strings.Split(segs[1], valuesSep) { 36 | if t == builtins { 37 | types[key] = append(types[key], Builtins...) 38 | } else if t == numbers { 39 | types[key] = append(types[key], Numbers...) 40 | } else { 41 | types[key] = append(types[key], t) 42 | } 43 | } 44 | } 45 | 46 | cursors := make(map[string]int) 47 | for _, key := range keys { 48 | cursors[key] = 0 49 | } 50 | 51 | outChan := make(chan map[string]string) 52 | go func() { 53 | buildTypeSet(keys, 0, cursors, types, outChan) 54 | close(outChan) 55 | }() 56 | 57 | var typeSets []map[string]string 58 | for typeSet := range outChan { 59 | typeSets = append(typeSets, typeSet) 60 | } 61 | 62 | return typeSets, nil 63 | 64 | } 65 | 66 | func buildTypeSet(keys []string, keyI int, cursors map[string]int, types map[string][]string, out chan<- map[string]string) { 67 | key := keys[keyI] 68 | for cursors[key] < len(types[key]) { 69 | if keyI < len(keys)-1 { 70 | buildTypeSet(keys, keyI+1, copycursors(cursors), types, out) 71 | } else { 72 | // build the typeset for this combination 73 | ts := make(map[string]string) 74 | for k, vals := range types { 75 | ts[k] = vals[cursors[k]] 76 | } 77 | out <- ts 78 | } 79 | cursors[key]++ 80 | } 81 | } 82 | 83 | func copycursors(source map[string]int) map[string]int { 84 | copy := make(map[string]int) 85 | for k, v := range source { 86 | copy[k] = v 87 | } 88 | return copy 89 | } 90 | -------------------------------------------------------------------------------- /parse/typesets_test.go: -------------------------------------------------------------------------------- 1 | package parse_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cheekybits/genny/parse" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestArgsToTypeset(t *testing.T) { 11 | 12 | args := "Person=man,woman Animal=dog,cat Place=london,paris" 13 | ts, err := parse.TypeSet(args) 14 | 15 | if assert.NoError(t, err) { 16 | if assert.Equal(t, 8, len(ts)) { 17 | 18 | assert.Equal(t, ts[0]["Person"], "man") 19 | assert.Equal(t, ts[0]["Animal"], "dog") 20 | assert.Equal(t, ts[0]["Place"], "london") 21 | 22 | assert.Equal(t, ts[1]["Person"], "man") 23 | assert.Equal(t, ts[1]["Animal"], "dog") 24 | assert.Equal(t, ts[1]["Place"], "paris") 25 | 26 | assert.Equal(t, ts[2]["Person"], "man") 27 | assert.Equal(t, ts[2]["Animal"], "cat") 28 | assert.Equal(t, ts[2]["Place"], "london") 29 | 30 | assert.Equal(t, ts[3]["Person"], "man") 31 | assert.Equal(t, ts[3]["Animal"], "cat") 32 | assert.Equal(t, ts[3]["Place"], "paris") 33 | 34 | assert.Equal(t, ts[4]["Person"], "woman") 35 | assert.Equal(t, ts[4]["Animal"], "dog") 36 | assert.Equal(t, ts[4]["Place"], "london") 37 | 38 | assert.Equal(t, ts[5]["Person"], "woman") 39 | assert.Equal(t, ts[5]["Animal"], "dog") 40 | assert.Equal(t, ts[5]["Place"], "paris") 41 | 42 | assert.Equal(t, ts[6]["Person"], "woman") 43 | assert.Equal(t, ts[6]["Animal"], "cat") 44 | assert.Equal(t, ts[6]["Place"], "london") 45 | 46 | assert.Equal(t, ts[7]["Person"], "woman") 47 | assert.Equal(t, ts[7]["Animal"], "cat") 48 | assert.Equal(t, ts[7]["Place"], "paris") 49 | 50 | } 51 | } 52 | 53 | ts, err = parse.TypeSet("Person=man Animal=dog Place=london") 54 | if assert.NoError(t, err) { 55 | assert.Equal(t, 1, len(ts)) 56 | } 57 | ts, err = parse.TypeSet("Person=1,2,3,4,5 Animal=1,2,3,4,5 Place=1,2,3,4,5") 58 | if assert.NoError(t, err) { 59 | assert.Equal(t, 125, len(ts)) 60 | } 61 | ts, err = parse.TypeSet("Person=1 Animal=1,2,3,4,5 Place=1,2") 62 | if assert.NoError(t, err) { 63 | assert.Equal(t, 10, len(ts)) 64 | } 65 | 66 | ts, err = parse.TypeSet("Person=interface{} Animal=interface{} Place=interface{}") 67 | if assert.NoError(t, err) { 68 | assert.Equal(t, 1, len(ts)) 69 | assert.Equal(t, ts[0]["Animal"], "interface{}") 70 | assert.Equal(t, ts[0]["Person"], "interface{}") 71 | assert.Equal(t, ts[0]["Place"], "interface{}") 72 | } 73 | 74 | } 75 | --------------------------------------------------------------------------------