<file_sep>package main

import (
	"go/format"
	"io"
	"log"
	"os"

	"github.com/cheekybits/genny/parse"
)

func gennyGen(filename, pkgName string, typesets []map[string]string, out io.Writer) error {

	var output []byte
	var err error

	var file *os.File
	file, err = os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	output, err = parse.Generics(filename, "outputFilename.go", pkgName, "", file, typesets)
	if err != nil {
		return err
	}

	fmtOutput, err := format.Source([]byte(output))
	if err != nil {
		log.Fatal(err)
	}
	out.Write(fmtOutput)
	return nil
}
<file_sep>package clefs

import (
	"reflect"
	"sort"

	"github.com/ikasamt/goslicer/typeslicer"
)

type Anything struct { //generic.Type
} //generic.Type
type Anythings []Anything

// First is ..
func (anythings Anythings) First() (Anything, bool) {
	if len(anythings) == 0 {
		return Anything{}, false
	}
	return anythings[0], true
}

// Where is ..
func (anythings Anythings) Where(f func(anything Anything) bool) (result Anythings) {
	for _, a := range anythings {
		if f(a) {
			result = append(result, a)
		}
	}
	return
}

// Count is ..
func (anythings Anythings) Count() (counter int) {
	return len(anythings)
}

// CountIf is ..
func (anythings Anythings) CountIf(f func(anything Anything) bool) (counter int) {
	for _, a := range anythings {
		if f(a) {
			counter++
		}
	}
	return
}

// Select is ..
func (anythings Anythings) Select(fieldName string) (result typeslicer.InterfaceSlice) {
	for _, a := range anythings {
		i := reflect.ValueOf(a).FieldByName(fieldName).Interface()
		result = append(result, i)
	}
	return
}

// SortBy is ..
func (anythings Anythings) SortBy(sortFunc func(Anything, Anything) bool) (result Anythings) {
	f := func(i, j int) bool {
		a := anythings[i]
		b := anythings[j]
		return sortFunc(a, b)
	}

	tmp := make(Anythings, len(anythings))
	copy(tmp, anythings)
	sort.Slice(tmp, f)
	return tmp
}

// DistinctBy is ..
func (anythings Anythings) DistinctBy(f func(anything Anything) interface{}) (result Anythings) {
	tmp := map[interface{}]Anything{}
	for _, a := range anythings {
		tmp[f(a)] = a
	}
	for _, t := range tmp {
		result = append(result, t)
	}
	return
}

func (anythings Anythings) Mapper(f func(anything Anything) Anything) (result Anythings) {
	for _, a := range anythings {
		result = append(result, f(a))
	}
	return
}
<file_sep>package example

type Something struct { //generic.Type
	ID int //generic.Type
} //generic.Type

func (any Anything) WhySomething(ID int, some Something) bool {
	return any.ID == some.ID
}
<file_sep>module github.com/ikasamt/zapp-jam

go 1.14

require (
	github.com/cheekybits/genny v1.0.0
	github.com/ikasamt/goslicer v1.0.4 // indirect
	golang.org/x/mod v0.3.0 // indirect
	golang.org/x/tools v0.0.0-20200618155944-c7475b9d7fb2 // indirect
)
<file_sep>package app

// +jam /clefs/example/is.go
// +jam /clefs/example/some.go User Item
// +jam ValidatePresenceOf Name
// +jam Setter Name
type Company struct {
	ID   int
	Name string
}

// +jam /clefs/example/is.go
// +jam /clefs/example/diff.go
// +jam ValidatePresenceOf Name,CompanyID
type Item struct {
	ID        int
	Name      string
	CompanyID int
}
<file_sep>package main

import (
	"bytes"
	"go/build"
	"go/format"
	"strings"

	// "bytes"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
)

const (
	exitCode      = 0
	prefixAutoGen = "zzz-"
	buildTag      = `+jam`
	validationSep = `,`
)

var GoPath string
var ZappJamSrcRoot string

const SemanticVersion = `v0.6.8`

func init() {
	GoPath = os.Getenv("GOPATH")
	if GoPath == "" {
		GoPath = build.Default.GOPATH
	}
	ZappJamSrcRoot = filepath.Join(GoPath, `/pkg/mod/github.com/ikasamt/zapp-jam@`+SemanticVersion)
	log.Println(ZappJamSrcRoot)
}

var parseFlags = func() []string {
	flag.Parse()
	return flag.Args()
}

func jamMain() {
	packagePaths := parseFlags()
	if len(packagePaths) == 0 {
		return
	}
	packagePath := packagePaths[0]

	packageName, task, validations := ParseDir(packagePath)
	log.Println(task)
	log.Println(fmt.Sprintf("# %s", packageName))
	for inFn, typesets := range task {
		_, fn := filepath.Split(inFn)
		outFn := filepath.Join(packagePath, fmt.Sprintf("%s%s", prefixAutoGen, fn))
		log.Println(fmt.Sprintf("%s -> %s", inFn, outFn))
		out, err := os.Create(outFn)
		defer out.Close()
		if err != nil {
			log.Println(err)
			return
		}
		gennyGen(inFn, packageName, typesets, out)
	}

	if len(validations) != 0 {
		for funcName, validation := range validations {
			switch funcName {
			case `Ngram`:
				var buf bytes.Buffer
				buf.WriteString("package " + packageName)
				buf.WriteString("\n\n// Auto-generated DO NOT EDIT!")
				buf.WriteString("\n")
				buf.WriteString("\n\nimport (")
				buf.WriteString("\"github.com/ikasamt/zapp/zapp\"")
				buf.WriteString("\n)")
				buf.WriteString("\n")
				buf.WriteString("\n")
				for _, value := range validation {
					for structName, fieldsStr := range value {
						fields := strings.Split(fieldsStr, validationSep)
						buf.WriteString("\n")
						fmt.Fprintf(&buf, "\nfunc (x %s) Ngrams() []string {", structName)
						fmt.Fprintf(&buf, "\nngrams := []string{}")
						for _, v := range fields {
							fmt.Fprintf(&buf, "\nngrams = append(ngrams, zapp.SplitNgramsRange(x.%s, 3)...)", v)
						}
						fmt.Fprintf(&buf, "\nreturn ngrams}")
						buf.WriteString("\n")
					}
				}
				buf.WriteString("\n")
				formatted, err := format.Source(buf.Bytes())
				if err != nil {
					log.Printf("%v", err)
					log.Printf("%s", buf.Bytes())
				}

				file := filepath.Join(packagePath, fmt.Sprintf("%s%s", prefixAutoGen, `ngram.go`))
				fh, err := os.Create(file)
				if err != nil {
					log.Printf(`failed to open file %s for writing`, file)
				}
				defer fh.Close()
				fh.Write(formatted)
			case `ValidatePresenceOf`:
				var buf bytes.Buffer
				buf.WriteString("package " + packageName)
				buf.WriteString("\n\n// Auto-generated DO NOT EDIT!")
				buf.WriteString("\n")
				buf.WriteString("\n\nimport (")
				buf.WriteString("  validation \"github.com/go-ozzo/ozzo-validation\"")
				buf.WriteString("\n)")
				buf.WriteString("\n")
				buf.WriteString("\n")
				for _, value := range validation {
					for structName, fieldsStr := range value {
						fields := strings.Split(fieldsStr, validationSep)
						buf.WriteString("\n")
						fmt.Fprintf(&buf, "\nfunc (x %s) Validations() error {", structName)
						fmt.Fprintf(&buf, "\nreturn validation.ValidateStruct(&x,")
						for _, v := range fields {
							fmt.Fprintf(&buf, "\nvalidation.Field(&x.%s, validation.Required),", v)
						}
						fmt.Fprintf(&buf, "\n)")
						fmt.Fprintf(&buf, "\n}")
						buf.WriteString("\n")
					}
				}
				buf.WriteString("\n")
				formatted, err := format.Source(buf.Bytes())
				if err != nil {
					log.Printf("%s", buf.Bytes())
				}

				file := filepath.Join(packagePath, fmt.Sprintf("%s%s", prefixAutoGen, `validate-presence-of.go`))
				fh, err := os.Create(file)
				if err != nil {
					log.Printf(`failed to open file %s for writing`, file)
				}
				defer fh.Close()
				fh.Write(formatted)
			}
		}

	}

}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	jamMain()
	os.Exit(exitCode)
}
<file_sep>package app

// +jam /clefs/example/is.go
// +jam ValidatePresenceOf Name,CompanyID
// +jam Ngram Name,CompanyID
type User struct {
	ID        int
	Name      string
	CompanyID int
}
<file_sep>package main

import (
	"testing"
)

func TestParseDir(t *testing.T) {
	pn, task, _ := ParseDir("./example/app")

	if pn != `app` {
		t.Fatalf("failed test %v", pn)
	}

	if task == nil {
		t.Fatalf("failed test %v", task)
	}

}
<file_sep>package example

func (any Anything) Diff(ID int) bool{
	return  any.ID != ID
}
<file_sep>package main

import (
	"go/doc"
	"go/parser"
	"go/token"
	"log"
	"path/filepath"
	"strings"
)

type Task map[string][]map[string]string
type Validation map[string][]map[string]string

func ParseDir(packagePath string) (packageName string, task Task, validation Validation) {
	task = Task{}
	validation = Validation{}

	fset := token.NewFileSet()
	d, err := parser.ParseDir(fset, packagePath, nil, parser.ParseComments)
	if err != nil {
		log.Println(err)
		return
	}

	for pn, f := range d {
		packageName = pn
		p := doc.New(f, "./", 0)
		for _, t := range p.Types {
			doc := t.Doc
			structName := t.Name
			lines := strings.Split(doc, "\n")
			for _, line := range lines {
				if strings.Contains(line, buildTag) {
					tmp := strings.Fields(line)
					switch len(tmp) {
					case 1:
						continue // needs filename
					case 2:
						filename := tmp[1]
						if strings.HasPrefix(filename, `/clefs/`) {
							filename = filepath.Join(ZappJamSrcRoot, filename)
						} else {
							filename = filepath.Join(packagePath, filename)
						}
						task[filename] = append(task[filename], map[string]string{`Anything`: structName})
					default:
						if strings.Contains(tmp[1], `.go`) {
							filename := tmp[1]
							if strings.HasPrefix(filename, `/clefs/`) {
								filename = filepath.Join(ZappJamSrcRoot, filename)
							} else {
								filename = filepath.Join(packagePath, filename)
							}
							args := tmp[2:]
							for _, arg := range args {
								task[filename] = append(task[filename], map[string]string{`Anything`: structName, `Something`: arg})
							}
						} else {
							key := tmp[1]
							validation[key] = append(validation[key], map[string]string{structName: tmp[2]})
						}
					}
				}
			}
		}
	}
	return
}
<file_sep>module github.com/ikasamt/zapp-jam/example/app

go 1.14

require (
	github.com/cheekybits/genny v1.0.0 // indirect
	github.com/ikasamt/zapp-jam v0.6.1 // indirect
	golang.org/x/mod v0.3.0 // indirect
	golang.org/x/tools v0.0.0-20200618031402-d15173dcc7e4 // indirect
)
<file_sep>
build:
	go build

clean:
	rm -rf example/app/zzz-*

example: build clean
	./zapp-jam example/app<file_sep>package example

// Anything is defined as base Struct for genny
type Anything struct { //generic.Type
	ID         int       //generic.Type
} //generic.Type


func (any Anything) Is(ID int) bool{
	return  any.ID == ID
}