mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-29 17:28:29 +02:00
rewrite checkpoint
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
genny
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
# - 1.0
|
||||
# - 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 cheekybits
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
# genny - Generics for Go
|
||||
|
||||
[](https://travis-ci.org/cheekybits/genny) [](http://godoc.org/github.com/cheekybits/genny/parse)
|
||||
|
||||
Install:
|
||||
|
||||
```
|
||||
go get github.com/cheekybits/genny
|
||||
```
|
||||
|
||||
=====
|
||||
|
||||
(pron. Jenny) by Mat Ryer ([@matryer](https://twitter.com/matryer)) and Tyler Bunnell ([@TylerJBunnell](https://twitter.com/TylerJBunnell)).
|
||||
|
||||
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.
|
||||
|
||||
* Generic code is valid Go code
|
||||
* Generic code compiles and can be tested
|
||||
* Use `stdin` and `stdout` or specify in and out files
|
||||
* Supports Go 1.4's [go generate](http://tip.golang.org/doc/go1.4#gogenerate)
|
||||
* Multiple specific types will generate every permutation
|
||||
* Use `BUILTINS` and `NUMBERS` wildtype to generate specific code for all built-in (and number) Go types
|
||||
* Function names and comments also get updated
|
||||
|
||||
## Library
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
genny [{flags}] gen "{types}"
|
||||
|
||||
gen - generates type specific code from generic code.
|
||||
get <package/file> - fetch a generic template from the online library and gen it.
|
||||
|
||||
{types} - (optional) Command line flags (see below)
|
||||
{types} - (required) Specific types for each generic type in the source
|
||||
{types} format: {generic}={specific}[,another][ {generic2}={specific2}]
|
||||
|
||||
Examples:
|
||||
Generic=Specific
|
||||
Generic1=Specific1 Generic2=Specific2
|
||||
Generic1=Specific1,Specific2 Generic2=Specific3,Specific4
|
||||
|
||||
Flags:
|
||||
-in="": file to parse instead of stdin
|
||||
-out="": file to save output to instead of stdout
|
||||
-pkg="": package name for generated files
|
||||
```
|
||||
|
||||
* Comma separated type lists will generate code for each type
|
||||
|
||||
### Flags
|
||||
|
||||
* `-in` - specify the input file (rather than using stdin)
|
||||
* `-out` - specify the output file (rather than using stdout)
|
||||
|
||||
### go generate
|
||||
|
||||
To use Go 1.4's `go generate` capability, insert the following comment in your source code file:
|
||||
|
||||
```
|
||||
//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int"
|
||||
```
|
||||
|
||||
* Start the line with `//go:generate `
|
||||
* Use the `-in` and `-out` flags to specify the files to work on
|
||||
* Use the `genny` command as usual after the flags
|
||||
|
||||
Now, running `go generate` (in a shell) for the package will cause the generic versions of the files to be generated.
|
||||
|
||||
* The output file will be overwritten, so it's safe to call `go generate` many times
|
||||
* Use `$GOFILE` to refer to the current file
|
||||
* The `//go:generate` line will be removed from the output
|
||||
|
||||
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).
|
||||
|
||||
## How it works
|
||||
|
||||
Define your generic types using the special `generic.Type` placeholder type:
|
||||
|
||||
```go
|
||||
type KeyType generic.Type
|
||||
type ValueType generic.Type
|
||||
```
|
||||
|
||||
* You can use as many as you like
|
||||
* Give them meaningful names
|
||||
|
||||
Then write the generic code referencing the types as your normally would:
|
||||
|
||||
```go
|
||||
func SetValueTypeForKeyType(key KeyType, value ValueType) { /* ... */ }
|
||||
```
|
||||
|
||||
* Generic type names will also be replaced in comments and function names (see Real example below)
|
||||
|
||||
Since `generic.Type` is a real Go type, your code will compile, and you can even write unit tests against your generic code.
|
||||
|
||||
#### Generating specific versions
|
||||
|
||||
Pass the file through the `genny gen` tool with the specific types as the argument:
|
||||
|
||||
```
|
||||
cat generic.go | genny gen "KeyType=string ValueType=interface{}"
|
||||
```
|
||||
|
||||
The output will be the complete Go source file with the generic types replaced with the types specified in the arguments.
|
||||
|
||||
## Real example
|
||||
|
||||
Given [this generic Go code](https://github.com/cheekybits/genny/tree/master/examples/queue) which compiles and is tested:
|
||||
|
||||
```go
|
||||
package queue
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
// NOTE: this is how easy it is to define a generic type
|
||||
type Something generic.Type
|
||||
|
||||
// SomethingQueue is a queue of Somethings.
|
||||
type SomethingQueue struct {
|
||||
items []Something
|
||||
}
|
||||
|
||||
func NewSomethingQueue() *SomethingQueue {
|
||||
return &SomethingQueue{items: make([]Something, 0)}
|
||||
}
|
||||
func (q *SomethingQueue) Push(item Something) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *SomethingQueue) Pop() Something {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
```
|
||||
|
||||
When `genny gen` is invoked like this:
|
||||
|
||||
```
|
||||
cat source.go | genny gen "Something=string"
|
||||
```
|
||||
|
||||
It outputs:
|
||||
|
||||
```go
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package queue
|
||||
|
||||
// StringQueue is a queue of Strings.
|
||||
type StringQueue struct {
|
||||
items []string
|
||||
}
|
||||
|
||||
func NewStringQueue() *StringQueue {
|
||||
return &StringQueue{items: make([]string, 0)}
|
||||
}
|
||||
func (q *StringQueue) Push(item string) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *StringQueue) Pop() string {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
```
|
||||
|
||||
To get a _something_ for every built-in Go type plus one of your own types, you could run:
|
||||
|
||||
```
|
||||
cat source.go | genny gen "Something=BUILTINS,*MyType"
|
||||
```
|
||||
|
||||
#### More examples
|
||||
|
||||
Check out the [test code files](https://github.com/cheekybits/genny/tree/master/parse/test) for more real examples.
|
||||
|
||||
## Writing test code
|
||||
|
||||
Once you have defined a generic type with some code worth testing:
|
||||
|
||||
```go
|
||||
package slice
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
|
||||
"github.com/stretchr/gogen/generic"
|
||||
)
|
||||
|
||||
type MyType generic.Type
|
||||
|
||||
func EnsureMyTypeSlice(objectOrSlice interface{}) []MyType {
|
||||
log.Printf("%v", reflect.TypeOf(objectOrSlice))
|
||||
switch obj := objectOrSlice.(type) {
|
||||
case []MyType:
|
||||
log.Println(" returning it untouched")
|
||||
return obj
|
||||
case MyType:
|
||||
log.Println(" wrapping in slice")
|
||||
return []MyType{obj}
|
||||
default:
|
||||
panic("ensure slice needs MyType or []MyType")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can treat it like any normal Go type in your test code:
|
||||
|
||||
```go
|
||||
func TestEnsureMyTypeSlice(t *testing.T) {
|
||||
|
||||
myType := new(MyType)
|
||||
slice := EnsureMyTypeSlice(myType)
|
||||
if assert.NotNil(t, slice) {
|
||||
assert.Equal(t, slice[0], myType)
|
||||
}
|
||||
|
||||
slice = EnsureMyTypeSlice(slice)
|
||||
log.Printf("%#v", slice[0])
|
||||
if assert.NotNil(t, slice) {
|
||||
assert.Equal(t, slice[0], myType)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Understanding what `generic.Type` is
|
||||
|
||||
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.
|
||||
|
||||
### Contributions
|
||||
|
||||
* See the [API documentation for the parse package](http://godoc.org/github.com/cheekybits/genny/parse)
|
||||
* Please do TDD
|
||||
* All input welcome
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package main is the command line tool for Genny.
|
||||
package main
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package math
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type ThisNumberType generic.Number
|
||||
|
||||
func ThisNumberTypeMax(fn func(a, b ThisNumberType) bool, a, b ThisNumberType) ThisNumberType {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cat ./generic_max.go | ../../genny gen "NumberType=NUMBERS" > numbers_max_get.go
|
||||
cat ./func_thing.go | ../../genny gen "ThisNumberType=NUMBERS" > numbers_func_thing.go
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package math
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type NumberType generic.Number
|
||||
|
||||
// NumberTypeMax gets the maximum number from the
|
||||
// two specified.
|
||||
func NumberTypeMax(a, b NumberType) NumberType {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package math_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cheekybits/genny/examples/davechaney"
|
||||
)
|
||||
|
||||
func TestNumberTypeMax(t *testing.T) {
|
||||
|
||||
var v math.NumberType
|
||||
v = math.NumberTypeMax(10, 20)
|
||||
if v != 20 {
|
||||
t.Errorf("Max of 10 and 20 is 20")
|
||||
}
|
||||
|
||||
v = math.NumberTypeMax(20, 20)
|
||||
if v != 20 {
|
||||
t.Errorf("Max of 20 and 20 is 20")
|
||||
}
|
||||
|
||||
v = math.NumberTypeMax(25, 20)
|
||||
if v != 25 {
|
||||
t.Errorf("Max of 25 and 20 is 25")
|
||||
}
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package math
|
||||
|
||||
func Float32Max(fn func(a, b float32) bool, a, b float32) float32 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Float64Max(fn func(a, b float64) bool, a, b float64) float64 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func IntMax(fn func(a, b int) bool, a, b int) int {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Int16Max(fn func(a, b int16) bool, a, b int16) int16 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Int32Max(fn func(a, b int32) bool, a, b int32) int32 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Int64Max(fn func(a, b int64) bool, a, b int64) int64 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Int8Max(fn func(a, b int8) bool, a, b int8) int8 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func UintMax(fn func(a, b uint) bool, a, b uint) uint {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Uint16Max(fn func(a, b uint16) bool, a, b uint16) uint16 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Uint32Max(fn func(a, b uint32) bool, a, b uint32) uint32 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Uint64Max(fn func(a, b uint64) bool, a, b uint64) uint64 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Uint8Max(fn func(a, b uint8) bool, a, b uint8) uint8 {
|
||||
if fn(a, b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package gogenerate
|
||||
|
||||
type StringStringMap map[string]string
|
||||
|
||||
func NewStringStringMap() map[string]string {
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
type StringIntMap map[string]int
|
||||
|
||||
func NewStringIntMap() map[string]int {
|
||||
return make(map[string]int)
|
||||
}
|
||||
|
||||
type IntStringMap map[int]string
|
||||
|
||||
func NewIntStringMap() map[int]string {
|
||||
return make(map[int]string)
|
||||
}
|
||||
|
||||
type IntIntMap map[int]int
|
||||
|
||||
func NewIntIntMap() map[int]int {
|
||||
return make(map[int]int)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package gogenerate
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int"
|
||||
|
||||
type KeyType generic.Type
|
||||
type ValueType generic.Type
|
||||
|
||||
type KeyTypeValueTypeMap map[KeyType]ValueType
|
||||
|
||||
func NewKeyTypeValueTypeMap() map[KeyType]ValueType {
|
||||
return make(map[KeyType]ValueType)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
cat ./queue_generic.go | ../../genny gen "Generic=string,int" > queue_generic_gen.go
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package example
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type Generic generic.Type
|
||||
|
||||
// GenericQueue represents a queue of Generic types.
|
||||
type GenericQueue struct {
|
||||
items []Generic
|
||||
}
|
||||
|
||||
// NewGenericQueue makes a new empty Generic queue.
|
||||
func NewGenericQueue() *GenericQueue {
|
||||
return &GenericQueue{items: make([]Generic, 0)}
|
||||
}
|
||||
|
||||
// Enq adds an item to the queue.
|
||||
func (q *GenericQueue) Enq(obj Generic) *GenericQueue {
|
||||
q.items = append(q.items, obj)
|
||||
return q
|
||||
}
|
||||
|
||||
// Deq removes and returns the next item in the queue.
|
||||
func (q *GenericQueue) Deq() Generic {
|
||||
obj := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return obj
|
||||
}
|
||||
|
||||
// Len gets the current number of Generic items in the queue.
|
||||
func (q *GenericQueue) Len() int {
|
||||
return len(q.items)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package example
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
|
||||
q := NewGenericQueue()
|
||||
assert.NotNil(t, q)
|
||||
|
||||
}
|
||||
|
||||
func TestEnqueueAndDequeue(t *testing.T) {
|
||||
|
||||
item1 := new(Generic)
|
||||
item2 := new(Generic)
|
||||
q := NewGenericQueue()
|
||||
|
||||
assert.Equal(t, q, q.Enq(item1), "Enq should return the queue")
|
||||
assert.Equal(t, 1, q.Len())
|
||||
assert.Equal(t, q, q.Enq(item2), "Enq should return the queue")
|
||||
assert.Equal(t, 2, q.Len())
|
||||
|
||||
assert.Equal(t, item1, q.Deq())
|
||||
assert.Equal(t, 1, q.Len())
|
||||
assert.Equal(t, item2, q.Deq())
|
||||
assert.Equal(t, 0, q.Len())
|
||||
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package generic contains the generic marker types.
|
||||
package generic
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package generic
|
||||
|
||||
// Type is the placeholder type that indicates a generic value.
|
||||
// When genny is executed, variables of this type will be replaced with
|
||||
// references to the specific types.
|
||||
// var GenericType generic.Type
|
||||
type Type interface{}
|
||||
|
||||
// Number is the placehoder type that indiccates a generic numerical value.
|
||||
// When genny is executed, variables of this type will be replaced with
|
||||
// references to the specific types.
|
||||
// var GenericType generic.Number
|
||||
type Number float64
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/cheekybits/genny/parse"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
source | genny gen [-in=""] [-out=""] [-pkg=""] "KeyType=string,int ValueType=string,int"
|
||||
|
||||
*/
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
exitcodeInvalidArgs
|
||||
exitcodeInvalidTypeSet
|
||||
exitcodeStdinFailed
|
||||
exitcodeGenFailed
|
||||
exitcodeGetFailed
|
||||
exitcodeSourceFileInvalid
|
||||
exitcodeDestFileFailed
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
in = flag.String("in", "", "file to parse instead of stdin")
|
||||
out = flag.String("out", "", "file to save output to instead of stdout")
|
||||
pkgName = flag.String("pkg", "", "package name for generated files")
|
||||
prefix = "https://github.com/metabition/gennylib/raw/master/"
|
||||
)
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) < 2 {
|
||||
usage()
|
||||
os.Exit(exitcodeInvalidArgs)
|
||||
}
|
||||
|
||||
if strings.ToLower(args[0]) != "gen" && strings.ToLower(args[0]) != "get" {
|
||||
usage()
|
||||
os.Exit(exitcodeInvalidArgs)
|
||||
}
|
||||
|
||||
// parse the typesets
|
||||
var setsArg = args[1]
|
||||
if strings.ToLower(args[0]) == "get" {
|
||||
setsArg = args[2]
|
||||
}
|
||||
typeSets, err := parse.TypeSet(setsArg)
|
||||
if err != nil {
|
||||
fatal(exitcodeInvalidTypeSet, err)
|
||||
}
|
||||
|
||||
var outWriter io.Writer
|
||||
if len(*out) > 0 {
|
||||
err := os.MkdirAll(path.Dir(*out), 0755)
|
||||
if err != nil {
|
||||
fatal(exitcodeDestFileFailed, err)
|
||||
}
|
||||
|
||||
outFile, err := os.Create(*out)
|
||||
if err != nil {
|
||||
fatal(exitcodeDestFileFailed, err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
outWriter = outFile
|
||||
} else {
|
||||
outWriter = os.Stdout
|
||||
}
|
||||
|
||||
if strings.ToLower(args[0]) == "get" {
|
||||
if len(args) != 3 {
|
||||
fmt.Println("not enough arguments to get")
|
||||
usage()
|
||||
os.Exit(exitcodeInvalidArgs)
|
||||
}
|
||||
r, err := http.Get(prefix + args[1])
|
||||
if err != nil {
|
||||
fatal(exitcodeGetFailed, err)
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
fatal(exitcodeGetFailed, err)
|
||||
}
|
||||
r.Body.Close()
|
||||
br := bytes.NewReader(b)
|
||||
err = gen(*in, *pkgName, br, typeSets, outWriter)
|
||||
} else if len(*in) > 0 {
|
||||
var file *os.File
|
||||
file, err = os.Open(*in)
|
||||
if err != nil {
|
||||
fatal(exitcodeSourceFileInvalid, err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = gen(*in, *pkgName, file, typeSets, outWriter)
|
||||
} else {
|
||||
var source []byte
|
||||
source, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fatal(exitcodeStdinFailed, err)
|
||||
}
|
||||
reader := bytes.NewReader(source)
|
||||
err = gen("stdin", *pkgName, reader, typeSets, outWriter)
|
||||
}
|
||||
|
||||
// do the work
|
||||
if err != nil {
|
||||
fatal(exitcodeGenFailed, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, `usage: genny [{flags}] gen "{types}"
|
||||
|
||||
gen - generates type specific code from generic code.
|
||||
get <package/file> - fetch a generic template from the online library and gen it.
|
||||
|
||||
{flags} - (optional) Command line flags (see below)
|
||||
{types} - (required) Specific types for each generic type in the source
|
||||
{types} format: {generic}={specific}[,another][ {generic2}={specific2}]
|
||||
|
||||
Examples:
|
||||
Generic=Specific
|
||||
Generic1=Specific1 Generic2=Specific2
|
||||
Generic1=Specific1,Specific2 Generic2=Specific3,Specific4
|
||||
|
||||
Flags:`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func fatal(code int, a ...interface{}) {
|
||||
fmt.Println(a...)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// gen performs the generic generation.
|
||||
func gen(filename, pkgName string, in io.ReadSeeker, typesets []map[string]string, out io.Writer) error {
|
||||
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
output, err = parse.Generics(filename, pkgName, in, typesets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Write(output)
|
||||
return nil
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
package main_test
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package parse
|
||||
|
||||
// Builtins contains a slice of all built-in Go types.
|
||||
var Builtins = []string{
|
||||
"bool",
|
||||
"byte",
|
||||
"complex128",
|
||||
"complex64",
|
||||
"error",
|
||||
"float32",
|
||||
"float64",
|
||||
"int",
|
||||
"int16",
|
||||
"int32",
|
||||
"int64",
|
||||
"int8",
|
||||
"rune",
|
||||
"string",
|
||||
"uint",
|
||||
"uint16",
|
||||
"uint32",
|
||||
"uint64",
|
||||
"uint8",
|
||||
"uintptr",
|
||||
}
|
||||
|
||||
// Numbers contains a slice of all built-in number types.
|
||||
var Numbers = []string{
|
||||
"float32",
|
||||
"float64",
|
||||
"int",
|
||||
"int16",
|
||||
"int32",
|
||||
"int64",
|
||||
"int8",
|
||||
"uint",
|
||||
"uint16",
|
||||
"uint32",
|
||||
"uint64",
|
||||
"uint8",
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Package parse contains the generic code generation capabilities
|
||||
// that power genny.
|
||||
//
|
||||
// genny gen "{types}"
|
||||
//
|
||||
// gen - generates type specific code (to stdout) from generic code (via stdin)
|
||||
//
|
||||
// {types} - (required) Specific types for each generic type in the source
|
||||
// {types} format: {generic}={specific}[,another][ {generic2}={specific2}]
|
||||
// Examples:
|
||||
// Generic=Specific
|
||||
// Generic1=Specific1 Generic2=Specific2
|
||||
// Generic1=Specific1,Specific2 Generic2=Specific3,Specific4
|
||||
package parse
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// errMissingSpecificType represents an error when a generic type is not
|
||||
// satisfied by a specific type.
|
||||
type errMissingSpecificType struct {
|
||||
GenericType string
|
||||
}
|
||||
|
||||
// Error gets a human readable string describing this error.
|
||||
func (e errMissingSpecificType) Error() string {
|
||||
return "Missing specific type for '" + e.GenericType + "' generic type"
|
||||
}
|
||||
|
||||
// errImports represents an error from goimports.
|
||||
type errImports struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error gets a human readable string describing this error.
|
||||
func (e errImports) Error() string {
|
||||
return "Failed to goimports the generated code: " + e.Err.Error()
|
||||
}
|
||||
|
||||
// errSource represents an error with the source file.
|
||||
type errSource struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error gets a human readable string describing this error.
|
||||
func (e errSource) Error() string {
|
||||
return "Failed to parse source file: " + e.Err.Error()
|
||||
}
|
||||
|
||||
type errBadTypeArgs struct {
|
||||
Message string
|
||||
Arg string
|
||||
}
|
||||
|
||||
func (e errBadTypeArgs) Error() string {
|
||||
return "\"" + e.Arg + "\" is bad: " + e.Message
|
||||
}
|
||||
|
||||
var errMissingTypeInformation = errors.New("No type arguments were specified and no \"// +gogen\" tag was found in the source.")
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
type isExported bool
|
||||
|
||||
var header = []byte(`
|
||||
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
`)
|
||||
|
||||
var (
|
||||
packageKeyword = []byte("package")
|
||||
importKeyword = []byte("import")
|
||||
openBrace = []byte("(")
|
||||
closeBrace = []byte(")")
|
||||
space = " "
|
||||
genericPackage = "generic"
|
||||
genericType = "generic.Type"
|
||||
genericNumber = "generic.Number"
|
||||
linefeed = "\r\n"
|
||||
)
|
||||
var unwantedLinePrefixes = [][]byte{
|
||||
[]byte("//go:generate genny "),
|
||||
}
|
||||
|
||||
func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]string) ([]byte, error) {
|
||||
|
||||
// ensure we are at the beginning of the file
|
||||
in.Seek(0, os.SEEK_SET)
|
||||
|
||||
// parse the source file
|
||||
fs := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fs, filename, in, 0)
|
||||
if err != nil {
|
||||
return nil, &errSource{Err: err}
|
||||
}
|
||||
|
||||
// make sure every generic.Type is represented in the types
|
||||
// argument.
|
||||
for _, decl := range file.Decls {
|
||||
switch it := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
for _, spec := range it.Specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch tt := ts.Type.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if name, ok := tt.X.(*ast.Ident); ok {
|
||||
if name.Name == genericPackage {
|
||||
if _, ok := typeSet[ts.Name.Name]; !ok {
|
||||
return nil, &errMissingSpecificType{GenericType: ts.Name.Name}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go back to the start of the file
|
||||
in.Seek(0, os.SEEK_SET)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
comment := ""
|
||||
scanner := bufio.NewScanner(in)
|
||||
for scanner.Scan() {
|
||||
|
||||
l := scanner.Text()
|
||||
|
||||
// does this line contain generic.Type?
|
||||
if strings.Contains(l, genericType) || strings.Contains(l, genericNumber) {
|
||||
comment = ""
|
||||
continue
|
||||
}
|
||||
|
||||
for t, specificType := range typeSet {
|
||||
|
||||
// does the line contain our type
|
||||
if strings.Contains(l, t) {
|
||||
|
||||
var newLine string
|
||||
// check each word
|
||||
for _, word := range strings.Fields(l) {
|
||||
|
||||
i := 0
|
||||
for {
|
||||
i = strings.Index(word[i:], t) // find out where
|
||||
|
||||
if i > -1 {
|
||||
|
||||
// if this isn't an exact match
|
||||
if i > 0 && isAlphaNumeric(rune(word[i-1])) || i < len(word)-len(t) && isAlphaNumeric(rune(word[i+len(t)])) {
|
||||
// replace the word with a capitolized version
|
||||
word = strings.Replace(word, t, wordify(specificType, unicode.IsUpper(rune(strings.TrimLeft(word, "*&")[0]))), 1)
|
||||
} else {
|
||||
// replace the word as is
|
||||
word = strings.Replace(word, t, specificType, 1)
|
||||
}
|
||||
|
||||
} else {
|
||||
newLine = newLine + word + space
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
l = newLine
|
||||
}
|
||||
}
|
||||
|
||||
if comment != "" {
|
||||
buf.WriteString(line(comment))
|
||||
comment = ""
|
||||
}
|
||||
|
||||
// is this line a comment?
|
||||
// TODO: should we handle /* */ comments?
|
||||
if strings.HasPrefix(l, "//") {
|
||||
// record this line to print later
|
||||
comment = l
|
||||
continue
|
||||
}
|
||||
|
||||
// write the line
|
||||
buf.WriteString(line(l))
|
||||
}
|
||||
|
||||
// write it out
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Generics parses the source file and generates the bytes replacing the
|
||||
// generic types for the keys map with the specific types (its value).
|
||||
func Generics(filename, pkgName string, in io.ReadSeeker, typeSets []map[string]string) ([]byte, error) {
|
||||
|
||||
totalOutput := header
|
||||
|
||||
for _, typeSet := range typeSets {
|
||||
|
||||
// generate the specifics
|
||||
parsed, err := generateSpecific(filename, in, typeSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalOutput = append(totalOutput, parsed...)
|
||||
|
||||
}
|
||||
|
||||
// clean up the code line by line
|
||||
packageFound := false
|
||||
insideImportBlock := false
|
||||
var cleanOutputLines []string
|
||||
scanner := bufio.NewScanner(bytes.NewReader(totalOutput))
|
||||
for scanner.Scan() {
|
||||
|
||||
// end of imports block?
|
||||
if insideImportBlock {
|
||||
if bytes.HasSuffix(scanner.Bytes(), closeBrace) {
|
||||
insideImportBlock = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(scanner.Bytes(), packageKeyword) {
|
||||
if packageFound {
|
||||
continue
|
||||
} else {
|
||||
packageFound = true
|
||||
}
|
||||
} else if bytes.HasPrefix(scanner.Bytes(), importKeyword) {
|
||||
if bytes.HasSuffix(scanner.Bytes(), openBrace) {
|
||||
insideImportBlock = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// check all unwantedLinePrefixes - and skip them
|
||||
skipline := false
|
||||
for _, prefix := range unwantedLinePrefixes {
|
||||
if bytes.HasPrefix(scanner.Bytes(), prefix) {
|
||||
skipline = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if skipline {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanOutputLines = append(cleanOutputLines, line(scanner.Text()))
|
||||
}
|
||||
|
||||
cleanOutput := strings.Join(cleanOutputLines, "")
|
||||
|
||||
output := []byte(cleanOutput)
|
||||
var err error
|
||||
|
||||
// change package name
|
||||
if pkgName != "" {
|
||||
output = changePackage(bytes.NewReader([]byte(output)), pkgName)
|
||||
}
|
||||
// fix the imports
|
||||
output, err = imports.Process(filename, output, nil)
|
||||
if err != nil {
|
||||
return nil, &errImports{Err: err}
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func line(s string) string {
|
||||
return fmt.Sprintln(strings.TrimRight(s, linefeed))
|
||||
}
|
||||
|
||||
// isAlphaNumeric gets whether the rune is alphanumeric or _.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
|
||||
// wordify turns a type into a nice word for function and type
|
||||
// names etc.
|
||||
func wordify(s string, exported bool) string {
|
||||
s = strings.TrimRight(s, "{}")
|
||||
s = strings.TrimLeft(s, "*&")
|
||||
s = strings.Replace(s, ".", "", -1)
|
||||
if !exported {
|
||||
return s
|
||||
}
|
||||
return strings.ToUpper(string(s[0])) + s[1:]
|
||||
}
|
||||
|
||||
func changePackage(r io.Reader, pkgName string) []byte {
|
||||
var out bytes.Buffer
|
||||
sc := bufio.NewScanner(r)
|
||||
done := false
|
||||
|
||||
for sc.Scan() {
|
||||
s := sc.Text()
|
||||
|
||||
if !done && strings.HasPrefix(s, "package") {
|
||||
parts := strings.Split(s, " ")
|
||||
parts[1] = pkgName
|
||||
s = strings.Join(parts, " ")
|
||||
done = true
|
||||
}
|
||||
|
||||
fmt.Fprintln(&out, s)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAlphaNumeric(t *testing.T) {
|
||||
|
||||
for _, r := range []rune{'a', '1', '_', 'A', 'Z'} {
|
||||
assert.True(t, isAlphaNumeric(r))
|
||||
}
|
||||
for _, r := range []rune{' ', '[', ']', '!', '"'} {
|
||||
assert.False(t, isAlphaNumeric(r))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWordify(t *testing.T) {
|
||||
|
||||
for word, wordified := range map[string]string{
|
||||
"int": "Int",
|
||||
"*int": "Int",
|
||||
"string": "String",
|
||||
"*MyType": "MyType",
|
||||
"*myType": "MyType",
|
||||
"interface{}": "Interface",
|
||||
"pack.type": "Packtype",
|
||||
"*pack.type": "Packtype",
|
||||
} {
|
||||
assert.Equal(t, wordified, wordify(word, true))
|
||||
}
|
||||
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package parse_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cheekybits/genny/parse"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
// input
|
||||
filename string
|
||||
pkgName string
|
||||
in string
|
||||
types []map[string]string
|
||||
|
||||
// expectations
|
||||
expectedOut string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
filename: "generic_queue.go",
|
||||
in: `test/queue/generic_queue.go`,
|
||||
types: []map[string]string{{"Something": "int"}},
|
||||
expectedOut: `test/queue/int_queue.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_queue.go",
|
||||
pkgName: "changed",
|
||||
in: `test/queue/generic_queue.go`,
|
||||
types: []map[string]string{{"Something": "int"}},
|
||||
expectedOut: `test/queue/changed/int_queue.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_queue.go",
|
||||
in: `test/queue/generic_queue.go`,
|
||||
types: []map[string]string{{"Something": "float32"}},
|
||||
expectedOut: `test/queue/float32_queue.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_simplemap.go",
|
||||
in: `test/multipletypes/generic_simplemap.go`,
|
||||
types: []map[string]string{{"KeyType": "string", "ValueType": "int"}},
|
||||
expectedOut: `test/multipletypes/string_int_simplemap.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_simplemap.go",
|
||||
in: `test/multipletypes/generic_simplemap.go`,
|
||||
types: []map[string]string{{"KeyType": "interface{}", "ValueType": "int"}},
|
||||
expectedOut: `test/multipletypes/interface_int_simplemap.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_simplemap.go",
|
||||
in: `test/multipletypes/generic_simplemap.go`,
|
||||
types: []map[string]string{{"KeyType": "*MyType1", "ValueType": "*MyOtherType"}},
|
||||
expectedOut: `test/multipletypes/custom_types_simplemap.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_internal.go",
|
||||
in: `test/unexported/generic_internal.go`,
|
||||
types: []map[string]string{{"secret": "*myType"}},
|
||||
expectedOut: `test/unexported/mytype_internal.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_simplemap.go",
|
||||
in: `test/multipletypesets/generic_simplemap.go`,
|
||||
types: []map[string]string{
|
||||
{"KeyType": "int", "ValueType": "string"},
|
||||
{"KeyType": "float64", "ValueType": "bool"},
|
||||
},
|
||||
expectedOut: `test/multipletypesets/many_simplemaps.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_number.go",
|
||||
in: `test/numbers/generic_number.go`,
|
||||
types: []map[string]string{{"NumberType": "int"}},
|
||||
expectedOut: `test/numbers/int_number.go`,
|
||||
},
|
||||
{
|
||||
filename: "generic_digraph.go",
|
||||
in: `test/bugreports/generic_digraph.go`,
|
||||
types: []map[string]string{{"Node": "int"}},
|
||||
expectedOut: `test/bugreports/int_digraph.go`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
test.in = contents(test.in)
|
||||
test.expectedOut = contents(test.expectedOut)
|
||||
|
||||
bytes, err := parse.Generics(test.filename, test.pkgName, strings.NewReader(test.in), test.types)
|
||||
|
||||
// check the error
|
||||
if test.expectedErr == nil {
|
||||
assert.NoError(t, err, "(%s) No error was expected but got: %s", test.filename, err)
|
||||
} else {
|
||||
assert.NotNil(t, err, "(%s) No error was returned by one was expected: %s", test.filename, test.expectedErr)
|
||||
assert.IsType(t, test.expectedErr, err, "(%s) Generate should return object of type %v", test.filename, test.expectedErr)
|
||||
}
|
||||
|
||||
// assert the response
|
||||
if !assert.Equal(t, string(bytes), test.expectedOut, "Parse didn't generate the expected output.") {
|
||||
log.Println("EXPECTED: " + test.expectedOut)
|
||||
log.Println("ACTUAL: " + string(bytes))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func contents(s string) string {
|
||||
if strings.HasSuffix(s, "go") {
|
||||
file, err := ioutil.ReadFile(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(file)
|
||||
}
|
||||
return s
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package bugreports
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type Node generic.Type
|
||||
|
||||
type DigraphNode struct {
|
||||
nodes map[Node][]Node
|
||||
}
|
||||
|
||||
func NewDigraphNode() *DigraphNode {
|
||||
return &DigraphNode{
|
||||
nodes: make(map[Node][]Node),
|
||||
}
|
||||
}
|
||||
|
||||
func (dig *DigraphNode) Add(n Node) {
|
||||
if _, exists := dig.nodes[n]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
dig.nodes[n] = nil
|
||||
}
|
||||
|
||||
func (dig *DigraphNode) Connect(a, b Node) {
|
||||
dig.Add(a)
|
||||
dig.Add(b)
|
||||
|
||||
dig.nodes[a] = append(dig.nodes[a], b)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package bugreports
|
||||
|
||||
type DigraphInt struct {
|
||||
nodes map[int][]int
|
||||
}
|
||||
|
||||
func NewDigraphInt() *DigraphInt {
|
||||
return &DigraphInt{
|
||||
nodes: make(map[int][]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (dig *DigraphInt) Add(n int) {
|
||||
if _, exists := dig.nodes[n]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
dig.nodes[n] = nil
|
||||
}
|
||||
|
||||
func (dig *DigraphInt) Connect(a, b int) {
|
||||
dig.Add(a)
|
||||
dig.Add(b)
|
||||
|
||||
dig.nodes[a] = append(dig.nodes[a], b)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package multipletypes
|
||||
|
||||
type MyType1 struct{}
|
||||
|
||||
type MyOtherType struct{}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package multipletypes
|
||||
|
||||
type MyType1MyOtherTypeMap map[*MyType1]*MyOtherType
|
||||
|
||||
func (m MyType1MyOtherTypeMap) Has(key *MyType1) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m MyType1MyOtherTypeMap) Get(key *MyType1) *MyOtherType {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m MyType1MyOtherTypeMap) Set(key *MyType1, value *MyOtherType) MyType1MyOtherTypeMap {
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package multipletypes
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type KeyType generic.Type
|
||||
type ValueType generic.Type
|
||||
|
||||
type KeyTypeValueTypeMap map[KeyType]ValueType
|
||||
|
||||
func (m KeyTypeValueTypeMap) Has(key KeyType) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap {
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package multipletypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSimpleMap(t *testing.T) {
|
||||
|
||||
key1 := new(KeyType)
|
||||
key2 := new(KeyType)
|
||||
value1 := new(ValueType)
|
||||
m := make(KeyTypeValueTypeMap)
|
||||
|
||||
assert.Equal(t, m, m.Set(key1, value1))
|
||||
assert.True(t, m.Has(key1))
|
||||
assert.False(t, m.Has(key2))
|
||||
assert.Equal(t, value1, m.Get(key1))
|
||||
|
||||
}
|
||||
|
||||
func TestCustomTypesMap(t *testing.T) {
|
||||
|
||||
key1 := new(MyType1)
|
||||
key2 := new(MyType1)
|
||||
value1 := new(MyOtherType)
|
||||
m := make(MyType1MyOtherTypeMap)
|
||||
|
||||
assert.Equal(t, m, m.Set(key1, value1))
|
||||
assert.True(t, m.Has(key1))
|
||||
assert.False(t, m.Has(key2))
|
||||
assert.Equal(t, value1, m.Get(key1))
|
||||
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package multipletypes
|
||||
|
||||
type InterfaceIntMap map[interface{}]int
|
||||
|
||||
func (m InterfaceIntMap) Has(key interface{}) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m InterfaceIntMap) Get(key interface{}) int {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m InterfaceIntMap) Set(key interface{}, value int) InterfaceIntMap {
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package multipletypes
|
||||
|
||||
type StringIntMap map[string]int
|
||||
|
||||
func (m StringIntMap) Has(key string) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m StringIntMap) Get(key string) int {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m StringIntMap) Set(key string, value int) StringIntMap {
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
package multipletypesets
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/cheekybits/genny/generic"
|
||||
)
|
||||
|
||||
type KeyType generic.Type
|
||||
type ValueType generic.Type
|
||||
|
||||
type KeyTypeValueTypeMap map[KeyType]ValueType
|
||||
|
||||
func (m KeyTypeValueTypeMap) Has(key KeyType) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap {
|
||||
log.Println(value)
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
package multipletypesets
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSimpleMap(t *testing.T) {
|
||||
|
||||
key1 := new(KeyType)
|
||||
key2 := new(KeyType)
|
||||
value1 := new(ValueType)
|
||||
m := make(KeyTypeValueTypeMap)
|
||||
|
||||
assert.Equal(t, m, m.Set(key1, value1))
|
||||
assert.True(t, m.Has(key1))
|
||||
assert.False(t, m.Has(key2))
|
||||
assert.Equal(t, value1, m.Get(key1))
|
||||
|
||||
}
|
||||
Generated
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package multipletypesets
|
||||
|
||||
import "log"
|
||||
|
||||
type IntStringMap map[int]string
|
||||
|
||||
func (m IntStringMap) Has(key int) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m IntStringMap) Get(key int) string {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m IntStringMap) Set(key int, value string) IntStringMap {
|
||||
log.Println(value)
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
|
||||
type Float64BoolMap map[float64]bool
|
||||
|
||||
func (m Float64BoolMap) Has(key float64) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m Float64BoolMap) Get(key float64) bool {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
func (m Float64BoolMap) Set(key float64, value bool) Float64BoolMap {
|
||||
log.Println(value)
|
||||
m[key] = value
|
||||
return m
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package numbers
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
type NumberType generic.Number
|
||||
|
||||
func NumberTypeMax(a, b NumberType) NumberType {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package numbers
|
||||
|
||||
func IntMax(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package changed
|
||||
|
||||
// IntQueue is a queue of Ints.
|
||||
type IntQueue struct {
|
||||
items []int
|
||||
}
|
||||
|
||||
func NewIntQueue() *IntQueue {
|
||||
return &IntQueue{items: make([]int, 0)}
|
||||
}
|
||||
func (q *IntQueue) Push(item int) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *IntQueue) Pop() int {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package queue
|
||||
|
||||
// Float32Queue is a queue of Float32s.
|
||||
type Float32Queue struct {
|
||||
items []float32
|
||||
}
|
||||
|
||||
func NewFloat32Queue() *Float32Queue {
|
||||
return &Float32Queue{items: make([]float32, 0)}
|
||||
}
|
||||
func (q *Float32Queue) Push(item float32) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *Float32Queue) Pop() float32 {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package queue
|
||||
|
||||
import "github.com/cheekybits/genny/generic"
|
||||
|
||||
// Something is a type that does something
|
||||
type Something generic.Type
|
||||
|
||||
// SomethingQueue is a queue of Somethings.
|
||||
type SomethingQueue struct {
|
||||
items []Something
|
||||
}
|
||||
|
||||
func NewSomethingQueue() *SomethingQueue {
|
||||
return &SomethingQueue{items: make([]Something, 0)}
|
||||
}
|
||||
func (q *SomethingQueue) Push(item Something) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *SomethingQueue) Pop() Something {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package queue
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSomethingQueue(t *testing.T) {
|
||||
|
||||
var item1 *Something = new(Something)
|
||||
var item2 *Something = new(Something)
|
||||
var item3 *Something = new(Something)
|
||||
|
||||
q := NewSomethingQueue()
|
||||
|
||||
q.Push(item1)
|
||||
if len(q.items) != 1 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
q.Push(item2)
|
||||
if len(q.items) != 2 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
q.Push(item3)
|
||||
if len(q.items) != 3 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
|
||||
if q.Pop() != item1 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 2 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
if q.Pop() != item2 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 1 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
if q.Pop() != item3 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 0 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIntQueue(t *testing.T) {
|
||||
|
||||
var item1 int = 1
|
||||
var item2 int = 2
|
||||
var item3 int = 3
|
||||
|
||||
q := NewIntQueue()
|
||||
|
||||
q.Push(item1)
|
||||
if len(q.items) != 1 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
q.Push(item2)
|
||||
if len(q.items) != 2 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
q.Push(item3)
|
||||
if len(q.items) != 3 {
|
||||
t.Error("Push should add the item")
|
||||
}
|
||||
|
||||
if q.Pop() != item1 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 2 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
if q.Pop() != item2 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 1 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
if q.Pop() != item3 {
|
||||
t.Error("Pop should return items in the order in which they were added")
|
||||
}
|
||||
if len(q.items) != 0 {
|
||||
t.Error("Pop should remove the item")
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package queue
|
||||
|
||||
// IntQueue is a queue of Ints.
|
||||
type IntQueue struct {
|
||||
items []int
|
||||
}
|
||||
|
||||
func NewIntQueue() *IntQueue {
|
||||
return &IntQueue{items: make([]int, 0)}
|
||||
}
|
||||
func (q *IntQueue) Push(item int) {
|
||||
q.items = append(q.items, item)
|
||||
}
|
||||
func (q *IntQueue) Pop() int {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
return item
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package slice
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
|
||||
"github.com/cheekybits/genny/generic"
|
||||
)
|
||||
|
||||
type MyType generic.Type
|
||||
|
||||
func EnsureSlice(objectOrSlice interface{}) []MyType {
|
||||
log.Printf("%v", reflect.TypeOf(objectOrSlice))
|
||||
switch obj := objectOrSlice.(type) {
|
||||
case []MyType:
|
||||
log.Println(" returning it untouched")
|
||||
return obj
|
||||
case MyType, *MyType:
|
||||
log.Println(" wrapping in slice")
|
||||
return []MyType{obj}
|
||||
default:
|
||||
panic("ensure slice needs MyType or []MyType")
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package slice
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnsureSlice(t *testing.T) {
|
||||
|
||||
myType := new(MyType)
|
||||
slice := EnsureSlice(myType)
|
||||
if assert.NotNil(t, slice) {
|
||||
assert.Equal(t, slice[0], myType)
|
||||
}
|
||||
|
||||
slice = EnsureSlice(slice)
|
||||
log.Printf("%#v", slice[0])
|
||||
if assert.NotNil(t, slice) {
|
||||
assert.Equal(t, slice[0], myType)
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package unexported
|
||||
|
||||
type myType struct{}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package unexported
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cheekybits/genny/generic"
|
||||
)
|
||||
|
||||
type secret generic.Type
|
||||
|
||||
func secretInspect(s secret) string {
|
||||
return fmt.Sprintf("%#v", s)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// This file was automatically generated by genny.
|
||||
// Any changes will be lost if this file is regenerated.
|
||||
// see https://github.com/cheekybits/genny
|
||||
|
||||
package unexported
|
||||
|
||||
import "fmt"
|
||||
|
||||
func myTypeInspect(s *myType) string {
|
||||
return fmt.Sprintf("%#v", s)
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package parse
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
typeSep = " "
|
||||
keyValueSep = "="
|
||||
valuesSep = ","
|
||||
builtins = "BUILTINS"
|
||||
numbers = "NUMBERS"
|
||||
)
|
||||
|
||||
// TypeSet turns a type string into a []map[string]string
|
||||
// that can be given to parse.Generics for it to do its magic.
|
||||
//
|
||||
// Acceptable args are:
|
||||
//
|
||||
// Person=man
|
||||
// Person=man Animal=dog
|
||||
// Person=man Animal=dog Animal2=cat
|
||||
// Person=man,woman Animal=dog,cat
|
||||
// Person=man,woman,child Animal=dog,cat Place=london,paris
|
||||
func TypeSet(arg string) ([]map[string]string, error) {
|
||||
|
||||
types := make(map[string][]string)
|
||||
var keys []string
|
||||
for _, pair := range strings.Split(arg, typeSep) {
|
||||
segs := strings.Split(pair, keyValueSep)
|
||||
if len(segs) != 2 {
|
||||
return nil, &errBadTypeArgs{Arg: arg, Message: "Generic=Specific expected"}
|
||||
}
|
||||
key := segs[0]
|
||||
keys = append(keys, key)
|
||||
types[key] = make([]string, 0)
|
||||
for _, t := range strings.Split(segs[1], valuesSep) {
|
||||
if t == builtins {
|
||||
types[key] = append(types[key], Builtins...)
|
||||
} else if t == numbers {
|
||||
types[key] = append(types[key], Numbers...)
|
||||
} else {
|
||||
types[key] = append(types[key], t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursors := make(map[string]int)
|
||||
for _, key := range keys {
|
||||
cursors[key] = 0
|
||||
}
|
||||
|
||||
outChan := make(chan map[string]string)
|
||||
go func() {
|
||||
buildTypeSet(keys, 0, cursors, types, outChan)
|
||||
close(outChan)
|
||||
}()
|
||||
|
||||
var typeSets []map[string]string
|
||||
for typeSet := range outChan {
|
||||
typeSets = append(typeSets, typeSet)
|
||||
}
|
||||
|
||||
return typeSets, nil
|
||||
|
||||
}
|
||||
|
||||
func buildTypeSet(keys []string, keyI int, cursors map[string]int, types map[string][]string, out chan<- map[string]string) {
|
||||
key := keys[keyI]
|
||||
for cursors[key] < len(types[key]) {
|
||||
if keyI < len(keys)-1 {
|
||||
buildTypeSet(keys, keyI+1, copycursors(cursors), types, out)
|
||||
} else {
|
||||
// build the typeset for this combination
|
||||
ts := make(map[string]string)
|
||||
for k, vals := range types {
|
||||
ts[k] = vals[cursors[k]]
|
||||
}
|
||||
out <- ts
|
||||
}
|
||||
cursors[key]++
|
||||
}
|
||||
}
|
||||
|
||||
func copycursors(source map[string]int) map[string]int {
|
||||
copy := make(map[string]int)
|
||||
for k, v := range source {
|
||||
copy[k] = v
|
||||
}
|
||||
return copy
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package parse_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cheekybits/genny/parse"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArgsToTypeset(t *testing.T) {
|
||||
|
||||
args := "Person=man,woman Animal=dog,cat Place=london,paris"
|
||||
ts, err := parse.TypeSet(args)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
if assert.Equal(t, 8, len(ts)) {
|
||||
|
||||
assert.Equal(t, ts[0]["Person"], "man")
|
||||
assert.Equal(t, ts[0]["Animal"], "dog")
|
||||
assert.Equal(t, ts[0]["Place"], "london")
|
||||
|
||||
assert.Equal(t, ts[1]["Person"], "man")
|
||||
assert.Equal(t, ts[1]["Animal"], "dog")
|
||||
assert.Equal(t, ts[1]["Place"], "paris")
|
||||
|
||||
assert.Equal(t, ts[2]["Person"], "man")
|
||||
assert.Equal(t, ts[2]["Animal"], "cat")
|
||||
assert.Equal(t, ts[2]["Place"], "london")
|
||||
|
||||
assert.Equal(t, ts[3]["Person"], "man")
|
||||
assert.Equal(t, ts[3]["Animal"], "cat")
|
||||
assert.Equal(t, ts[3]["Place"], "paris")
|
||||
|
||||
assert.Equal(t, ts[4]["Person"], "woman")
|
||||
assert.Equal(t, ts[4]["Animal"], "dog")
|
||||
assert.Equal(t, ts[4]["Place"], "london")
|
||||
|
||||
assert.Equal(t, ts[5]["Person"], "woman")
|
||||
assert.Equal(t, ts[5]["Animal"], "dog")
|
||||
assert.Equal(t, ts[5]["Place"], "paris")
|
||||
|
||||
assert.Equal(t, ts[6]["Person"], "woman")
|
||||
assert.Equal(t, ts[6]["Animal"], "cat")
|
||||
assert.Equal(t, ts[6]["Place"], "london")
|
||||
|
||||
assert.Equal(t, ts[7]["Person"], "woman")
|
||||
assert.Equal(t, ts[7]["Animal"], "cat")
|
||||
assert.Equal(t, ts[7]["Place"], "paris")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ts, err = parse.TypeSet("Person=man Animal=dog Place=london")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 1, len(ts))
|
||||
}
|
||||
ts, err = parse.TypeSet("Person=1,2,3,4,5 Animal=1,2,3,4,5 Place=1,2,3,4,5")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 125, len(ts))
|
||||
}
|
||||
ts, err = parse.TypeSet("Person=1 Animal=1,2,3,4,5 Place=1,2")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 10, len(ts))
|
||||
}
|
||||
|
||||
ts, err = parse.TypeSet("Person=interface{} Animal=interface{} Place=interface{}")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 1, len(ts))
|
||||
assert.Equal(t, ts[0]["Animal"], "interface{}")
|
||||
assert.Equal(t, ts[0]["Person"], "interface{}")
|
||||
assert.Equal(t, ts[0]["Place"], "interface{}")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user