├── .gitignore ├── collection ├── each.go ├── get.go ├── find.go ├── filter.go ├── find_test.go ├── get_test.go ├── uniq.go ├── filter_test.go ├── each_test.go ├── maps.go ├── collection.go ├── maps_test.go ├── uniq_test.go ├── array.go ├── sort.go ├── collection_test.go └── array_test.go ├── .travis.yml ├── cmd ├── cmd.go ├── create.go ├── generate.go └── update.go ├── gen ├── gen_method.go ├── types.go ├── config_test.go ├── replace.go ├── config.go ├── ast_test.go ├── ast.go └── gen.go ├── main.go ├── LICENSE └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | .idea 4 | collections 5 | grizzly.json -------------------------------------------------------------------------------- /collection/each.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) ForEach(callback func(item *Model)) { 4 | for _, i := range c.Items { 5 | callback(i) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /collection/get.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) Get(index int) (model *Model) { 4 | if index >= 0 && len(c.Items) > index { 5 | return c.Items[index] 6 | } 7 | 8 | return model 9 | } 10 | -------------------------------------------------------------------------------- /collection/find.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) Find(callback SearchCallback) *Model { 4 | for _, v := range c.Items { 5 | if callback(v) == true { 6 | return v 7 | } 8 | } 9 | 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8 4 | - tip 5 | 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - go test -race -coverprofile=coverage.txt -covermode=atomic ./collection 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /collection/filter.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) Filter(callback SearchCallback) *Collection { 4 | var newItems []*Model 5 | 6 | for _, v := range c.Items { 7 | if callback(v) == true { 8 | newItems = append(newItems, v) 9 | } 10 | } 11 | 12 | return &Collection{Items: newItems} 13 | } 14 | -------------------------------------------------------------------------------- /collection/find_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_Find(t *testing.T) { 10 | item := GetCollection().Find(func(item *Model) bool { 11 | return item.GrizzlyId == 3 12 | }) 13 | 14 | assert.Equal(t, item.GrizzlyId, 3) 15 | } 16 | -------------------------------------------------------------------------------- /collection/get_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_Get(t *testing.T) { 10 | item := GetCollection().Get(1) 11 | 12 | assert.Equal(t, item.GrizzlyId, 2) 13 | 14 | item = GetCollection().Get(20) 15 | 16 | assert.Nil(t, item) 17 | 18 | item = GetCollection().Get(-1) 19 | 20 | assert.Nil(t, item) 21 | } 22 | -------------------------------------------------------------------------------- /collection/uniq.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) UniqByGrizzlyId() *Collection { 4 | collection := &Collection{} 5 | 6 | for _, item := range c.Items { 7 | searchItem := collection.Find(func(model *Model) bool { 8 | return model.GrizzlyId == item.GrizzlyId 9 | }) 10 | 11 | if searchItem == nil { 12 | collection = collection.Push(item) 13 | } 14 | } 15 | 16 | return collection 17 | } 18 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | func Init() { 10 | app := cli.NewApp() 11 | 12 | app.Name = "grizzly" 13 | app.Usage = "codegen for golang" 14 | app.UsageText = "grizzly command" 15 | app.Version = "0.1.0" 16 | 17 | app.Commands = []cli.Command{ 18 | CreateCommand(), 19 | UpdateCommand(), 20 | GenerateCommand(), 21 | } 22 | 23 | app.Run(os.Args) 24 | } 25 | -------------------------------------------------------------------------------- /collection/filter_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_Filter(t *testing.T) { 10 | collection := GetCollection().Filter(func(item *Model) bool { 11 | return item.GrizzlyId > 2 12 | }) 13 | 14 | assert.Len(t, collection.Items, 2) 15 | assert.Equal(t, collection.Items[0].GrizzlyId, 3) 16 | assert.Equal(t, collection.Items[1].GrizzlyId, 4) 17 | } 18 | -------------------------------------------------------------------------------- /collection/each_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_ForEach(t *testing.T) { 10 | var iterationCount int 11 | 12 | collection := GetCollection() 13 | 14 | collection.ForEach(func(item *Model) { 15 | assert.Equal(t, collection.Get(iterationCount).GrizzlyId, item.GrizzlyId) 16 | iterationCount += 1 17 | }) 18 | 19 | assert.Equal(t, iterationCount, 4) 20 | } 21 | -------------------------------------------------------------------------------- /collection/maps.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) MapToInt(callback func(item *Model) int) []int { 4 | items := []int{} 5 | 6 | for _, v := range c.Items { 7 | items = append(items, callback(v)) 8 | } 9 | 10 | return items 11 | } 12 | 13 | func (c *Collection) MapToString(callback func(item *Model) string) []string { 14 | items := []string{} 15 | 16 | for _, v := range c.Items { 17 | items = append(items, callback(v)) 18 | } 19 | 20 | return items 21 | } 22 | -------------------------------------------------------------------------------- /gen/gen_method.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path/filepath" 7 | ) 8 | 9 | func GetMethod(collectionDir string, method string) (result []byte, err error) { 10 | methodPath := filepath.Join(collectionDir, method+".go") 11 | 12 | if CheckExistFile(methodPath) { 13 | return result, fmt.Errorf("method %s is not exist", method) 14 | } 15 | 16 | result, err = ioutil.ReadFile(methodPath) 17 | 18 | if err != nil { 19 | return result, err 20 | } 21 | 22 | return result, err 23 | } 24 | -------------------------------------------------------------------------------- /collection/collection.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | type Model struct { 4 | GrizzlyId int 5 | GrizzlyName string 6 | } 7 | 8 | type Collection struct { 9 | Items []*Model 10 | } 11 | 12 | type SearchCallback func(item *Model) bool 13 | 14 | //grizzly:replaceName New{{.Name}}Collection 15 | func NewCollection(items []*Model) *Collection { 16 | var collection Collection 17 | 18 | collection.Items = items 19 | 20 | return &collection 21 | } 22 | 23 | //grizzly:replaceName NewEmpty{{.Name}}Collection 24 | func NewEmptyCollection() *Collection { 25 | return &Collection{} 26 | } 27 | -------------------------------------------------------------------------------- /collection/maps_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_MapToInt(t *testing.T) { 10 | items := GetCollection().MapToInt(func(item *Model) int { 11 | return item.GrizzlyId 12 | }) 13 | 14 | assert.Equal(t, items, []int{1, 2, 3, 4}) 15 | } 16 | 17 | func TestCollection_MapToString(t *testing.T) { 18 | items := GetCollection().MapToString(func(item *Model) string { 19 | return item.GrizzlyName 20 | }) 21 | 22 | assert.Equal(t, items, []string{"item1", "item2", "item3", "item4"}) 23 | } 24 | -------------------------------------------------------------------------------- /collection/uniq_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_UniqByGrizzlyId(t *testing.T) { 10 | collection := NewCollection([]*Model{ 11 | &Model{GrizzlyId: 1, GrizzlyName: "item1"}, 12 | &Model{GrizzlyId: 2, GrizzlyName: "item2"}, 13 | &Model{GrizzlyId: 2, GrizzlyName: "item3"}, 14 | &Model{GrizzlyId: 4, GrizzlyName: "item4"}, 15 | }).UniqByGrizzlyId() 16 | 17 | assert.Len(t, collection.Items, 3) 18 | assert.Equal(t, collection.Get(1).GrizzlyId, 2) 19 | assert.Equal(t, collection.Get(2).GrizzlyId, 4) 20 | } 21 | -------------------------------------------------------------------------------- /collection/array.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | func (c *Collection) Push(item *Model) *Collection { 4 | newItems := append(c.Items, item) 5 | 6 | return &Collection{Items: newItems} 7 | } 8 | 9 | func (c *Collection) Shift() *Model { 10 | item := c.Items[0] 11 | c.Items = c.Items[1:] 12 | 13 | return item 14 | } 15 | 16 | func (c *Collection) Pop() *Model { 17 | item := c.Items[len(c.Items)-1] 18 | c.Items = c.Items[:len(c.Items)-1] 19 | 20 | return item 21 | } 22 | 23 | func (c *Collection) Unshift(item *Model) *Collection { 24 | newItems := append([]*Model{item}, c.Items...) 25 | 26 | return &Collection{Items: newItems} 27 | } 28 | 29 | func (c *Collection) Len() int { 30 | return len(c.Items) 31 | } 32 | -------------------------------------------------------------------------------- /collection/sort.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import "sort" 4 | 5 | type byGrizzlyIdAsc []*Model 6 | 7 | func (a byGrizzlyIdAsc) Len() int { return len(a) } 8 | func (a byGrizzlyIdAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 9 | func (a byGrizzlyIdAsc) Less(i, j int) bool { return a[i].GrizzlyId < a[j].GrizzlyId } 10 | 11 | type byGrizzlyIdDesc []*Model 12 | 13 | func (a byGrizzlyIdDesc) Len() int { return len(a) } 14 | func (a byGrizzlyIdDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 15 | func (a byGrizzlyIdDesc) Less(i, j int) bool { return a[i].GrizzlyId > a[j].GrizzlyId } 16 | 17 | func (c *Collection) SortByGrizzlyId(mode string) *Collection { 18 | collection := &Collection{Items: c.Items} 19 | 20 | if mode == "desc" { 21 | sort.Sort(byGrizzlyIdDesc(collection.Items)) 22 | } else { 23 | sort.Sort(byGrizzlyIdAsc(collection.Items)) 24 | } 25 | 26 | return collection 27 | } 28 | -------------------------------------------------------------------------------- /collection/collection_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func GetCollection() *Collection { 10 | return NewCollection([]*Model{ 11 | &Model{GrizzlyId: 1, GrizzlyName: "item1"}, 12 | &Model{GrizzlyId: 2, GrizzlyName: "item2"}, 13 | &Model{GrizzlyId: 3, GrizzlyName: "item3"}, 14 | &Model{GrizzlyId: 4, GrizzlyName: "item4"}, 15 | }) 16 | } 17 | 18 | func TestNewCollection(t *testing.T) { 19 | var first = Model{GrizzlyId: 1, GrizzlyName: "first"} 20 | var second = Model{GrizzlyId: 2, GrizzlyName: "second"} 21 | 22 | expected := &Collection{Items: []*Model{&first, &second}} 23 | collection := NewCollection([]*Model{&first, &second}) 24 | 25 | assert.Equal(t, expected, collection) 26 | } 27 | 28 | func TestNewEmptyCollection(t *testing.T) { 29 | collection := NewEmptyCollection() 30 | 31 | assert.Len(t, collection.Items, 0) 32 | } 33 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Generation of collection code from a config or cli 3 | Example: 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "test/collections" 9 | ) 10 | 11 | func main() { 12 | users := collections.NewUserCollection([]*collections.User{ 13 | {Id: 1, Name: "John", Age: 20}, 14 | {Id: 2, Name: "Tom", Age: 22}, 15 | {Id: 3, Name: "Billy", Age: 19}, 16 | {Id: 4, Name: "Mister X", Age: 30}, 17 | }) 18 | 19 | youngUsers := users.Filter(func (user *collections.User) bool { 20 | return user.Age < 30 21 | }) 22 | 23 | Tom := youngUsers.Find(func (user *collections.User) bool { 24 | return user.Name == "Tom" 25 | }) 26 | 27 | youngUsersIds := youngUsers.MapToInt(func (user *collections.User) int { 28 | return user.Id 29 | }) 30 | 31 | fmt.Println(Tom, youngUsersIds) 32 | } 33 | */ 34 | 35 | package main 36 | 37 | import ( 38 | "github.com/matroskin13/grizzly/cmd" 39 | ) 40 | 41 | func main() { 42 | cmd.Init() 43 | } 44 | -------------------------------------------------------------------------------- /gen/types.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import "strings" 4 | 5 | type GrizzlyType struct { 6 | Name string 7 | Value string 8 | IsPointer bool 9 | IsPrimitive bool 10 | } 11 | 12 | var PrimitiveTypes = []string{ 13 | "int", 14 | "int8", 15 | "int16", 16 | "int32", 17 | "int64", 18 | "uint", 19 | "uint8", 20 | "uint16", 21 | "uint32", 22 | "uint64", 23 | "uintptr", 24 | "string", 25 | "bool", 26 | "float64", 27 | "float32", 28 | } 29 | 30 | func GenerateTypes(configTypes map[string]string) []GrizzlyType { 31 | var types []GrizzlyType 32 | 33 | for key, value := range configTypes { 34 | var customType = GrizzlyType{ 35 | Name: key, 36 | Value: value, 37 | } 38 | 39 | if strings.Contains(value, "*") { 40 | customType.IsPointer = true 41 | customType.Value = value[1:] 42 | } 43 | 44 | for _, primitive := range PrimitiveTypes { 45 | if primitive == value { 46 | customType.IsPrimitive = true 47 | } 48 | } 49 | 50 | types = append(types, customType) 51 | } 52 | 53 | return types 54 | } 55 | -------------------------------------------------------------------------------- /gen/config_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetConfigByCode(t *testing.T) { 10 | code := ` 11 | package test 12 | 13 | import ( 14 | "fmt" 15 | ) 16 | 17 | //grizzly:generate 18 | type User struct { 19 | Id int 20 | Email string 21 | } 22 | 23 | //grizzly:generate 24 | type Car struct { 25 | Brand string 26 | Model string 27 | } 28 | 29 | func GoGo() { 30 | fmt.Println("start") 31 | } 32 | ` 33 | 34 | expectedConfig := &GrizzlyConfig{ 35 | Collections: []GrizzlyConfigCollection{ 36 | { 37 | Name: "User", 38 | Types: map[string]string{ 39 | "Id": "int", 40 | "Email": "string", 41 | }, 42 | Package: "test", 43 | Methods: GetDefaultMethods(), 44 | }, 45 | 46 | { 47 | Name: "Car", 48 | Types: map[string]string{ 49 | "Brand": "string", 50 | "Model": "string", 51 | }, 52 | Package: "test", 53 | Methods: GetDefaultMethods(), 54 | }, 55 | }, 56 | } 57 | 58 | config, err := GetConfigByCode([]byte(code)) 59 | 60 | assert.Nil(t, err) 61 | assert.Equal(t, expectedConfig, config) 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Valentin Dubrovsky 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 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/urfave/cli" 7 | 8 | "github.com/matroskin13/grizzly/gen" 9 | ) 10 | 11 | func CreateCommand() cli.Command { 12 | return cli.Command{ 13 | Name: "create", 14 | Aliases: []string{"c"}, 15 | Usage: "create model and collection by name", 16 | Action: createAction, 17 | } 18 | } 19 | 20 | func createAction(c *cli.Context) (err error) { 21 | types := map[string]string{} 22 | modelName := strings.ToLower(c.Args().First()) 23 | 24 | for _, blob := range c.Args().Tail() { 25 | blobTypes := strings.Split(blob, ":") 26 | 27 | types[blobTypes[0]] = blobTypes[1] 28 | } 29 | 30 | if modelName == "" { 31 | return cli.NewExitError("model name is empty", 0) 32 | } 33 | 34 | collectionConfig := gen.GrizzlyConfigCollection{ 35 | Types: types, 36 | Name: modelName, 37 | Methods: gen.GetDefaultMethods(), 38 | } 39 | 40 | code, err := gen.GenCollectionCode(collectionConfig, false) 41 | 42 | if err != nil { 43 | return cli.NewExitError(err, 0) 44 | } 45 | 46 | err = gen.CreateCollection(modelName, string(code), false, "") 47 | 48 | if err != nil { 49 | return cli.NewExitError(err, 0) 50 | } 51 | 52 | return err 53 | } 54 | -------------------------------------------------------------------------------- /gen/replace.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func ReplaceSearchCallback(code []byte, modelName string) []byte { 9 | rSearchCallback, _ := regexp.Compile("SearchCallback") 10 | result := rSearchCallback.ReplaceAll(code, []byte("SearchCallback"+strings.Title(modelName))) 11 | 12 | return result 13 | } 14 | 15 | // Remove package name 16 | func RemovePackage(code []byte) []byte { 17 | rPackage, _ := regexp.Compile("package collection") 18 | result := rPackage.ReplaceAll(code, make([]byte, 0)) 19 | 20 | return result 21 | } 22 | 23 | func ReplaceGrizzlyId(code []byte, customType string) []byte { 24 | gICollections, _ := regexp.Compile("GrizzlyId") 25 | result := gICollections.ReplaceAll(code, []byte(strings.Title(customType))) 26 | 27 | return result 28 | } 29 | 30 | func ReplaceImports(code []byte) []byte { 31 | rICollections, _ := regexp.Compile("(import \"sort\")") 32 | result := rICollections.ReplaceAll(code, []byte("")) 33 | 34 | return result 35 | } 36 | 37 | func InjectImports(code []byte, imports []string) (result []byte) { 38 | result = code[:] 39 | 40 | for _, i := range imports { 41 | result = append([]byte("\nimport \""+i+"\"\n"), code...) 42 | } 43 | 44 | return result 45 | } 46 | -------------------------------------------------------------------------------- /collection/array_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCollection_Push(t *testing.T) { 10 | collection := GetCollection().Push(&Model{GrizzlyId: 10, GrizzlyName: "test"}) 11 | 12 | assert.Len(t, collection.Items, 5) 13 | assert.Equal(t, collection.Get(4).GrizzlyId, 10) 14 | } 15 | 16 | func TestCollection_Pop(t *testing.T) { 17 | collection := GetCollection() 18 | item := collection.Pop() 19 | 20 | assert.Equal(t, item.GrizzlyId, 4) 21 | assert.Len(t, collection.Items, 3) 22 | assert.Equal(t, collection.Get(2).GrizzlyId, 3) 23 | } 24 | 25 | func TestCollection_Unshift(t *testing.T) { 26 | collection := GetCollection().Unshift(&Model{GrizzlyId: 10, GrizzlyName: "test"}) 27 | 28 | assert.Len(t, collection.Items, 5) 29 | assert.Equal(t, collection.Get(0).GrizzlyId, 10) 30 | } 31 | 32 | func TestCollection_Shift(t *testing.T) { 33 | collection := GetCollection() 34 | item := collection.Shift() 35 | 36 | assert.Equal(t, item.GrizzlyId, 1) 37 | assert.Len(t, collection.Items, 3) 38 | assert.Equal(t, collection.Get(2).GrizzlyId, 4) 39 | } 40 | 41 | func TestByGrizzlyIdAsc_Len(t *testing.T) { 42 | collection := GetCollection() 43 | 44 | assert.Equal(t, collection.Len(), 4) 45 | 46 | collection.Shift() 47 | 48 | assert.Equal(t, collection.Len(), 3) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/generate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/urfave/cli" 10 | 11 | "github.com/matroskin13/grizzly/gen" 12 | ) 13 | 14 | func GenerateCommand() cli.Command { 15 | return cli.Command{ 16 | Name: "generate", 17 | Aliases: []string{"g"}, 18 | Usage: "generate collection from file", 19 | Action: generateAction, 20 | } 21 | } 22 | 23 | func generateAction(c *cli.Context) (err error) { 24 | pwd, _ := os.Getwd() 25 | generateFileName := c.Args().First() 26 | generateFilePath := filepath.Join(pwd, generateFileName) 27 | 28 | file, err := ioutil.ReadFile(generateFilePath) 29 | 30 | if err != nil { 31 | return cli.NewExitError(err, 0) 32 | } 33 | 34 | config, err := gen.GetConfigByCode(file) 35 | 36 | if err != nil { 37 | return cli.NewExitError(err, 0) 38 | } 39 | 40 | for _, collection := range config.Collections { 41 | if len(collection.Methods) == 0 { 42 | collection.Methods = gen.GetDefaultMethods() 43 | } 44 | 45 | code, err := gen.GenCollectionCode(collection, true) 46 | 47 | if err != nil { 48 | return cli.NewExitError(err, 0) 49 | } 50 | 51 | err = gen.CreateCollection(strings.ToLower(collection.Name)+"_collection", code, true, pwd) 52 | 53 | if err != nil { 54 | return cli.NewExitError(err, 0) 55 | } 56 | } 57 | 58 | return err 59 | } 60 | -------------------------------------------------------------------------------- /cmd/update.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/urfave/cli" 11 | 12 | "github.com/matroskin13/grizzly/gen" 13 | ) 14 | 15 | func UpdateCommand() cli.Command { 16 | return cli.Command{ 17 | Name: "update", 18 | Aliases: []string{"u"}, 19 | Usage: "update collections by config", 20 | Action: updateAction, 21 | } 22 | } 23 | 24 | func updateAction(c *cli.Context) (err error) { 25 | var config gen.GrizzlyConfig 26 | 27 | currentPath, _ := os.Getwd() 28 | fullPwd := filepath.Join(currentPath, "grizzly.json") 29 | 30 | bytes, err := ioutil.ReadFile(fullPwd) 31 | 32 | if err != nil { 33 | return cli.NewExitError(err, 0) 34 | } 35 | 36 | err = json.Unmarshal(bytes, &config) 37 | 38 | if err != nil { 39 | return cli.NewExitError("config is not readable", 0) 40 | } 41 | 42 | for _, collection := range config.Collections { 43 | collection.Package = "collections" 44 | collection.Name = strings.Title(collection.Name) 45 | 46 | if collection.Name == "" { 47 | return cli.NewExitError("collection name is empty", 0) 48 | } 49 | 50 | if len(collection.Methods) == 0 { 51 | collection.Methods = gen.GetDefaultMethods() 52 | } 53 | 54 | code, err := gen.GenCollectionCode(collection, false) 55 | 56 | if err != nil { 57 | return cli.NewExitError(err, 0) 58 | } 59 | 60 | err = gen.CreateCollection(strings.ToLower(collection.Name), code, true, "") 61 | 62 | if err != nil { 63 | return cli.NewExitError(err, 0) 64 | } 65 | } 66 | 67 | return err 68 | } 69 | -------------------------------------------------------------------------------- /gen/config.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | ) 8 | 9 | func grizzlyComment(doc *ast.CommentGroup) bool { 10 | if doc == nil { 11 | return false 12 | } 13 | 14 | for _, comment := range doc.List { 15 | if comment.Text == "//grizzly:generate" { 16 | return true 17 | } 18 | } 19 | 20 | return false 21 | } 22 | 23 | type GrizzlyConfigCollection struct { 24 | Name string `json:"name"` 25 | Types map[string]string `json:"types"` 26 | Methods []string 27 | Package string 28 | } 29 | 30 | type GrizzlyConfig struct { 31 | Collections []GrizzlyConfigCollection `json:"collections"` 32 | } 33 | 34 | // Create config from GO code 35 | func GetConfigByCode(code []byte) (*GrizzlyConfig, error) { 36 | var config GrizzlyConfig 37 | 38 | fset := token.NewFileSet() 39 | 40 | f, err := parser.ParseFile(fset, "main.go", string(code), parser.ParseComments) 41 | 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | ast.Inspect(f, func(n ast.Node) bool { 47 | x, ok := n.(*ast.GenDecl) 48 | 49 | if ok && x.Tok == token.TYPE && x.Doc != nil { 50 | _, isExist := GetGrizzlyCommand(x.Doc)[CommandGenerate] 51 | 52 | if isExist { 53 | itemConfig := GrizzlyConfigCollection{ 54 | Types: map[string]string{}, 55 | Package: f.Name.Name, 56 | Methods: GetDefaultMethods(), 57 | } 58 | 59 | ast.Inspect(x, func(n ast.Node) bool { 60 | switch x := n.(type) { 61 | case *ast.Field: 62 | switch y := x.Type.(type) { 63 | case *ast.Ident: 64 | itemConfig.Types[x.Names[0].Name] = y.Name 65 | } 66 | case *ast.Ident: 67 | if itemConfig.Name == "" { 68 | itemConfig.Name = x.Name 69 | } 70 | } 71 | 72 | return true 73 | }) 74 | 75 | config.Collections = append(config.Collections, itemConfig) 76 | } 77 | } 78 | 79 | return true 80 | }) 81 | 82 | return &config, nil 83 | } 84 | -------------------------------------------------------------------------------- /gen/ast_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "go/token" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var code = `package test 15 | 16 | //grizzly:replaceName Get{{.Name}} 17 | func GetChicken() {} 18 | 19 | //grizzly:replaceName GetGet{{.Name}} 20 | func GetPig() {} 21 | ` 22 | 23 | var expectedCode = `package test 24 | 25 | //grizzly:replaceName Get{{.Name}} 26 | func GetTest() {} 27 | 28 | //grizzly:replaceName GetGet{{.Name}} 29 | func GetGetTest() {} 30 | ` 31 | 32 | func HelperGetAst(code []byte) (*ast.File, func() string) { 33 | fset := token.NewFileSet() 34 | f, _ := parser.ParseFile(fset, "test.go", code, parser.ParseComments) 35 | 36 | return f, func() string { 37 | var buf bytes.Buffer 38 | 39 | format.Node(&buf, fset, f) 40 | 41 | return buf.String() 42 | } 43 | } 44 | 45 | func TestGetGrizzlyCommand(t *testing.T) { 46 | testAst, _ := HelperGetAst([]byte(code)) 47 | fun, _ := testAst.Decls[0].(*ast.FuncDecl) 48 | 49 | commands := GetGrizzlyCommand(fun.Doc) 50 | 51 | expected := map[string]GrizzlyCommand{ 52 | "grizzly:replaceName": GrizzlyCommand{ 53 | Command: "grizzly:replaceName", 54 | Action: "Get{{.Name}}", 55 | }, 56 | } 57 | 58 | assert.Equal(t, expected, commands) 59 | } 60 | 61 | func TestApplyCommands(t *testing.T) { 62 | config := &GrizzlyConfigCollection{Name: "Test"} 63 | testAst, finish := HelperGetAst([]byte(code)) 64 | 65 | ApplyCommands(testAst, config) 66 | 67 | result := finish() 68 | 69 | assert.Equal(t, expectedCode, result) 70 | } 71 | 72 | var code1 = `package test 73 | 74 | type Model struct{} 75 | type Collection struct{} 76 | 77 | func GetModel(arg *Model) *Model { 78 | return &Model{} 79 | } 80 | 81 | func GetCollection(arg *TestCollection) *Collection { 82 | return &Collection{} 83 | } 84 | ` 85 | 86 | var expectedCode1 = `package test 87 | 88 | type Test struct{} 89 | type TestCollection struct{} 90 | 91 | func GetModel(arg *Test) *Test { 92 | return &Test{} 93 | } 94 | 95 | func GetCollection(arg *TestCollection) *TestCollection { 96 | return &TestCollection{} 97 | } 98 | ` 99 | 100 | func TestSwapTypes(t *testing.T) { 101 | testAst, finish := HelperGetAst([]byte(code1)) 102 | config := &GrizzlyConfigCollection{Name: "Test"} 103 | 104 | SwapTypes(testAst, config) 105 | 106 | result := finish() 107 | 108 | assert.Equal(t, expectedCode1, result) 109 | } 110 | 111 | var code2 = `package test 112 | 113 | type Test struct { 114 | One string 115 | } 116 | ` 117 | 118 | var expectedCode2 = `package test 119 | 120 | type Test struct { 121 | Id int 122 | } 123 | ` 124 | 125 | func TestInjectTypes(t *testing.T) { 126 | testAst, finish := HelperGetAst([]byte(code2)) 127 | config := &GrizzlyConfigCollection{ 128 | Name: "Test", 129 | Types: map[string]string{ 130 | "Id": "int", 131 | }, 132 | } 133 | 134 | InjectTypes(testAst, config) 135 | 136 | result := finish() 137 | 138 | assert.Equal(t, expectedCode2, result) 139 | } 140 | -------------------------------------------------------------------------------- /gen/ast.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "go/token" 9 | "strings" 10 | "text/template" 11 | ) 12 | 13 | // Return grizzly commands by comments of code. Example: 14 | // 15 | // //grizzly:replaceName GetGrizzly{{.Name}} 16 | // func GetGrizzly() {} 17 | func GetGrizzlyCommand(doc *ast.CommentGroup) map[string]GrizzlyCommand { 18 | commands := make(map[string]GrizzlyCommand) 19 | 20 | for _, comment := range doc.List { 21 | if strings.Contains(comment.Text, "//grizzly:") { 22 | var command GrizzlyCommand 23 | 24 | arr := strings.Split(comment.Text, " ") 25 | 26 | command.Command = arr[0][2:] 27 | 28 | if len(arr) > 1 { 29 | command.Action = arr[1] 30 | } 31 | 32 | commands[command.Command] = command 33 | } 34 | } 35 | 36 | return commands 37 | } 38 | 39 | func GenCode(config *GrizzlyConfigCollection, code []byte, isSimple bool) []byte { 40 | fset := token.NewFileSet() 41 | 42 | f, _ := parser.ParseFile(fset, "main.go", code, parser.ParseComments) 43 | 44 | ApplyCommands(f, config) 45 | SwapTypes(f, config) 46 | 47 | if isSimple == false { 48 | InjectTypes(f, config) 49 | } else { 50 | RemoveType(f, config) 51 | } 52 | 53 | var buf bytes.Buffer 54 | 55 | format.Node(&buf, fset, f) 56 | 57 | return buf.Bytes() 58 | } 59 | 60 | func RemoveType(node *ast.File, config *GrizzlyConfigCollection) { 61 | for key, decl := range node.Decls { 62 | if x, ok := decl.(*ast.GenDecl); ok && x.Tok == token.TYPE { 63 | if tSpec, ok := x.Specs[0].(*ast.TypeSpec); ok { 64 | if _, ok := tSpec.Type.(*ast.StructType); tSpec.Name.Name == config.Name && ok { 65 | copy(node.Decls[key:], node.Decls[key+1:]) 66 | node.Decls = node.Decls[:len(node.Decls)-1] 67 | 68 | return 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | func ApplyCommands(node *ast.File, config *GrizzlyConfigCollection) *ast.File { 76 | ast.Inspect(node, func(n ast.Node) bool { 77 | switch x := n.(type) { 78 | case *ast.FuncDecl: 79 | commands := make(map[string]GrizzlyCommand) 80 | 81 | if x.Doc != nil { 82 | commands = GetGrizzlyCommand(x.Doc) 83 | } 84 | 85 | if command, ok := commands[CommandReplaceName]; ok { 86 | var buf bytes.Buffer 87 | 88 | tmp, _ := template.New(CommandReplaceName).Parse(command.Action) 89 | 90 | tmp.Execute(&buf, config) 91 | 92 | x.Name.Name = buf.String() 93 | } 94 | } 95 | 96 | return true 97 | }) 98 | 99 | return node 100 | } 101 | 102 | // Replaces grizzly Model and Collection 103 | func SwapTypes(node *ast.File, config *GrizzlyConfigCollection) { 104 | ast.Inspect(node, func(n ast.Node) bool { 105 | switch x := n.(type) { 106 | case *ast.Ident: 107 | if x.Name == GrizzlyCollection { 108 | x.Name = config.Name + "Collection" 109 | } 110 | 111 | if x.Name == GrizzlyModel { 112 | x.Name = config.Name 113 | } 114 | } 115 | 116 | return true 117 | }) 118 | } 119 | 120 | func InjectTypes(node *ast.File, config *GrizzlyConfigCollection) { 121 | ast.Inspect(node, func(n ast.Node) bool { 122 | if x, ok := n.(*ast.GenDecl); ok && x.Tok == token.TYPE { 123 | if tSpec, ok := x.Specs[0].(*ast.TypeSpec); ok { 124 | if sType, ok := tSpec.Type.(*ast.StructType); tSpec.Name.Name == config.Name && ok { 125 | sType.Fields.List = []*ast.Field{} 126 | 127 | for key, customType := range config.Types { 128 | typeName := strings.Title(key) 129 | 130 | sType.Fields.List = append(sType.Fields.List, &ast.Field{ 131 | Names: []*ast.Ident{ast.NewIdent(typeName)}, 132 | Type: ast.NewIdent(customType), 133 | }) 134 | } 135 | } 136 | } 137 | } 138 | 139 | return true 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /gen/gen.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | const GithubRepo = "github.com/matroskin13/grizzly" 12 | 13 | const ( 14 | MethodFind = "find" 15 | MethodFilter = "filter" 16 | MethodMaps = "maps" 17 | MethodArray = "array" 18 | MethodGet = "get" 19 | MethodUniq = "uniq" 20 | MethodSort = "sort" 21 | MethodEach = "each" 22 | ) 23 | 24 | const ( 25 | GrizzlyCollection = "Collection" 26 | GrizzlyModel = "Model" 27 | 28 | CommandReplaceName = "grizzly:replaceName" 29 | CommandGenerate = "grizzly:generate" 30 | ) 31 | 32 | type GrizzlyCommand struct { 33 | Command string 34 | Action string 35 | } 36 | 37 | func GetDefaultMethods() []string { 38 | return []string{ 39 | MethodFind, 40 | MethodFilter, 41 | MethodMaps, 42 | MethodArray, 43 | MethodGet, 44 | MethodUniq, 45 | MethodSort, 46 | MethodEach, 47 | } 48 | } 49 | 50 | func IsPropertyMethod(method string) bool { 51 | for _, propertyMethod := range []string{MethodUniq, MethodSort} { 52 | if propertyMethod == method { 53 | return true 54 | } 55 | } 56 | 57 | return false 58 | } 59 | 60 | func GetImportsByMethods(methods []string) (imports []string) { 61 | for _, method := range methods { 62 | switch method { 63 | case MethodSort: 64 | imports = append(imports, "sort") 65 | } 66 | } 67 | 68 | return imports 69 | } 70 | 71 | func GetCollectionDir(isDev bool) (string, error) { 72 | goPaths := strings.Split(os.Getenv("GOPATH"), ":") 73 | 74 | if isDev { 75 | return "./collection", nil 76 | } 77 | 78 | for _, path := range goPaths { 79 | grizzlyPath := filepath.Join(path, "src", GithubRepo) 80 | 81 | if !CheckExistDir(grizzlyPath) { 82 | return filepath.Join(grizzlyPath, "collection"), nil 83 | } 84 | } 85 | 86 | return "", errors.New("grizzly repo is not defined") 87 | } 88 | 89 | func GetCollectionCode() (result []byte, err error) { 90 | collectionDir, err := GetCollectionDir(false) 91 | 92 | if err != nil { 93 | return result, err 94 | } 95 | 96 | collectionPath := filepath.Join(collectionDir, "collection.go") 97 | 98 | result, err = ioutil.ReadFile(collectionPath) 99 | 100 | if err != nil { 101 | return result, err 102 | } 103 | 104 | code := RemovePackage(result) 105 | 106 | return code, err 107 | } 108 | 109 | func CheckExistDir(path string) bool { 110 | _, err := os.Stat(path) 111 | 112 | if err == nil || os.IsNotExist(err) { 113 | return false 114 | } else { 115 | return true 116 | } 117 | } 118 | 119 | func CheckExistFile(path string) bool { 120 | stat, _ := os.Stat(path) 121 | 122 | if stat == nil { 123 | return false 124 | } else { 125 | return true 126 | } 127 | } 128 | 129 | func CreateCollection(modelName string, code string, isUpdate bool, savePath string) error { 130 | var collectionPath string 131 | 132 | pwd, _ := os.Getwd() 133 | 134 | if savePath == "" { 135 | collectionPath = filepath.Join(pwd, "collections") 136 | } else { 137 | collectionPath = savePath 138 | } 139 | 140 | filePath := filepath.Join(collectionPath, modelName+".go") 141 | 142 | if !CheckExistDir(collectionPath) { 143 | os.Mkdir(collectionPath, os.ModePerm) 144 | } 145 | 146 | if !CheckExistFile(filePath) || isUpdate { 147 | err := ioutil.WriteFile(filePath, []byte(code), 0666) 148 | 149 | if err != nil { 150 | return err 151 | } 152 | } else { 153 | return errors.New("collection is alredy exist") 154 | } 155 | 156 | return nil 157 | } 158 | 159 | func GetMethodsCode(methods []string, types []GrizzlyType) (result []byte, err error) { 160 | collectionDir, err := GetCollectionDir(false) 161 | 162 | if err != nil { 163 | return result, err 164 | } 165 | 166 | for _, v := range methods { 167 | methodPath := filepath.Join(collectionDir, v+".go") 168 | 169 | bytes, err := ioutil.ReadFile(methodPath) 170 | 171 | if err != nil { 172 | return result, err 173 | } 174 | 175 | if IsPropertyMethod(v) { 176 | for _, customType := range types { 177 | if customType.IsPrimitive { 178 | result = append(result, ReplaceGrizzlyId(bytes, customType.Name)...) 179 | } 180 | } 181 | } else { 182 | result = append(result, bytes...) 183 | } 184 | } 185 | 186 | result = RemovePackage(result) 187 | result = ReplaceImports(result) 188 | 189 | return result, err 190 | } 191 | 192 | func GenCollectionCode(config GrizzlyConfigCollection, isSimple bool) (result string, err error) { 193 | code, err := GetCollectionCode() 194 | types := GenerateTypes(config.Types) 195 | 196 | if err != nil { 197 | return result, err 198 | } 199 | 200 | methodCode, err := GetMethodsCode(config.Methods, types) 201 | 202 | code = append(code, methodCode...) 203 | code = InjectImports(code, GetImportsByMethods(config.Methods)) 204 | code = append([]byte("package "+config.Package), code...) 205 | 206 | code = GenCode(&config, code, isSimple) 207 | 208 | if err != nil { 209 | return result, err 210 | } 211 | 212 | code = ReplaceSearchCallback(code, config.Name) 213 | 214 | return string(code), err 215 | } 216 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Grizzly 2 | 3 | [![Build Status](https://travis-ci.org/matroskin13/grizzly.svg?branch=master)](https://travis-ci.org/matroskin13/grizzly) 4 | [![codecov](https://codecov.io/gh/matroskin13/grizzly/branch/master/graph/badge.svg)](https://codecov.io/gh/matroskin13/grizzly) 5 | 6 | Grizzly allows you to use collections in GO without generics. With Grizzly you can use the methods Map, Filter, Find, etc. 7 | 8 | ## Usage with generation 9 | 10 | Install Grizzly: 11 | 12 | ```bash 13 | $ go get github.com/matroskin13/grizzly 14 | ``` 15 | 16 | And update your working file: 17 | 18 | ```go 19 | //go:generate grizzly generate main.go 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | ) 26 | 27 | //grizzly:generate 28 | type User struct { 29 | Id int 30 | Name string 31 | Age int 32 | } 33 | 34 | func main() { 35 | users := NewUserCollection([]*User{ 36 | {Id: 1, Name: "John", Age: 20}, 37 | {Id: 2, Name: "Tom", Age: 22}, 38 | {Id: 3, Name: "Billy", Age: 20}, 39 | {Id: 4, Name: "Mister X", Age: 30}, 40 | }) 41 | 42 | youngUsers := users.Filter(func (user *User) bool { 43 | return user.Age < 30 44 | }) 45 | 46 | youngUsersIds := youngUsers.MapToInt(func (user *User) int { 47 | return user.Id 48 | }) 49 | 50 | fmt.Println("young users ids", youngUsersIds) 51 | } 52 | ``` 53 | 54 | And run go generate: 55 | 56 | ```bash 57 | $ go generate 58 | ``` 59 | 60 | ## Generate from config 61 | 62 | Create file grizzly.json in your root directory 63 | 64 | ```json 65 | { 66 | "collections": [ 67 | { 68 | "name": "user", 69 | "types": { 70 | "id": "int", 71 | "name": "string", 72 | "age": "int" 73 | } 74 | }, 75 | { 76 | "name": "city", 77 | "types": { 78 | "cityId": "int" 79 | } 80 | } 81 | ] 82 | } 83 | ``` 84 | 85 | And run Grizzly 86 | 87 | ```bash 88 | $ grizzly update 89 | ``` 90 | 91 | Now you can use the collection code: 92 | 93 | ```go 94 | package test 95 | 96 | import ( 97 | "fmt" 98 | "test/collections" 99 | ) 100 | 101 | func main() { 102 | users := collections.NewUserCollection([]*collections.User{ 103 | {Id: 1, Name: "John", Age: 20}, 104 | {Id: 2, Name: "Tom", Age: 22}, 105 | {Id: 3, Name: "Billy", Age: 20}, 106 | {Id: 4, Name: "Mister X", Age: 30}, 107 | }) 108 | 109 | city := collections.NewCitiesCollection([]*collections.Cities{ 110 | {CityId: 1}, 111 | }).Find(func (city *collections.Cities) bool { 112 | return true 113 | }) 114 | 115 | youngUsers := users.Filter(func (user *collections.User) bool { 116 | return user.Age < 30 117 | }) 118 | 119 | Tom := youngUsers.Find(func (user *collections.User) bool { 120 | return user.Name == "Tom" 121 | }) 122 | 123 | youngUsersIds := youngUsers.MapToInt(func (user *collections.User) int { 124 | return user.Id 125 | }) 126 | 127 | uniqAges := users.UniqByAge() 128 | sortedAges := users.SortByAge("asc").MapToInt(func (user *collections.User) int { 129 | return user.Age 130 | }) 131 | 132 | fmt.Println("tom", Tom) 133 | fmt.Println("young users ids", youngUsersIds) 134 | fmt.Println("first city", city) 135 | fmt.Println("uniq ages", uniqAges) 136 | fmt.Println("sorted ages", sortedAges) 137 | } 138 | ``` 139 | 140 | You can also specify the required methods: 141 | 142 | ```json 143 | { 144 | "name": "User", 145 | "types": { 146 | "id": "int", 147 | "name": "string", 148 | "age": "int" 149 | }, 150 | "methods": ["find", "filter"] 151 | } 152 | ``` 153 | 154 | List of default methods: "find", "filter", "maps", "array", "get", "uniq", "sort" 155 | 156 | ## Methods of collection 157 | 158 | The following methods will be available for the collection: 159 | 160 | grizzly.json 161 | 162 | ```json 163 | { 164 | "collections": [ 165 | { 166 | "name": "user", 167 | "types": { 168 | "id": "int", 169 | "name": "string" 170 | } 171 | } 172 | ] 173 | } 174 | ``` 175 | 176 | by go generate 177 | 178 | ```go 179 | //grizzly:generate 180 | type User struct { 181 | Id int 182 | Name string 183 | } 184 | ``` 185 | 186 | ```go 187 | func NewUserCollection(items []*User) *UserCollection 188 | func NewEmptyUserCollection() *UserCollection 189 | 190 | func (c *UserCollection) Len() int 191 | 192 | func (c *UserCollection) ForEach(callback func(item *User)) 193 | 194 | func (c *UserCollection) Filter(callback SearchCallbackUser) *UserCollection 195 | 196 | func (c *UserCollection) Find(callback SearchCallbackUser) *User 197 | 198 | func (c *UserCollection) Get(index int) (model *User) 199 | 200 | func (c *UserCollection) MapToInt(callback func(item *User) int) []int 201 | 202 | func (c *UserCollection) MapToString(callback func(item *User) string) []string 203 | 204 | func (c *UserCollection) Pop() *User 205 | 206 | func (c *UserCollection) Push(item *User) *UserCollection 207 | 208 | func (c *UserCollection) Shift() *User 209 | 210 | func (c *UserCollection) SortById(mode string) *UserCollection 211 | 212 | func (c *UserCollection) SortByName(mode string) *UserCollection 213 | 214 | func (c *UserCollection) UniqById() *UserCollection 215 | 216 | func (c *UserCollection) UniqByName() *UserCollection 217 | 218 | func (c *UserCollection) Unshift(item *User) *UserCollection 219 | 220 | func (c *UserCollection) ForEach(callback func(item *User)) 221 | ``` 222 | --------------------------------------------------------------------------------