func (g *GenWriter) WriteHeader(w io.Writer, t typewriter.Type) { err := g.ensureValidation(t) if err != nil { panic(err) } m, exists := g.models[t.String()] if !exists { return } s := `// See http://clipperhouse.github.io/gen for documentation ` w.Write([]byte(s)) if includeSortSupport(m.methods) { s := `// Sort implementation is a modification of http://golang.org/pkg/sort/#Sort // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found at http://golang.org/LICENSE. ` w.Write([]byte(s)) } return }
// genwriter prepares models for later use in the .Validate() method. It must be called prior. func (g *GenWriter) ensureValidation(t typewriter.Type) error { if !g.validated[t.String()] { return fmt.Errorf("Type '%s' has not been previously validated. TypeWriter.Validate() must be called on all types before using them in subsequent methods.", t.String()) } return nil }
func (c *ContainerWriter) Validate(t typewriter.Type) (bool, error) { tag, found, err := t.Tags.ByName("containers") if !found || err != nil { return false, err } // must include at least one item that we recognize any := false for _, item := range tag.Items { if templates.Contains(item) { // found one, move on any = true break } } if !any { // not an error, but irrelevant return false, nil } c.tagsByType[t.String()] = tag return true, nil }
func (c ContainerWriter) Validate(t typewriter.Type) (bool, error) { tag, found, err := t.Tags.ByName("containers") if found && err == nil { c.tagsByType[t.String()] = tag } return found, err }
func (g *GenWriter) Imports(t typewriter.Type) (result []typewriter.ImportSpec) { err := g.ensureValidation(t) if err != nil { panic(err) } m, exists := g.models[t.String()] if !exists { return } imports := make(map[string]bool) methodRequiresErrors := map[string]bool{ "First": true, "Single": true, "Max": true, "Min": true, "MaxBy": true, "MinBy": true, "Average": true, } methodRequiresSort := map[string]bool{ "Sort": true, } for _, s := range m.methods { if methodRequiresErrors[s] { imports["errors"] = true } if methodRequiresSort[s] { imports["sort"] = true } } for _, p := range m.projections { if methodRequiresErrors[p.Method] { imports["errors"] = true } if methodRequiresSort[p.Method] { imports["sort"] = true } } for s := range imports { result = append(result, typewriter.ImportSpec{ Path: s, }) } return }
func (c *ContainerWriter) WriteHeader(w io.Writer, t typewriter.Type) { tag := c.tagsByType[t.String()] // validated above s := `// See http://clipperhouse.github.io/gen for documentation ` w.Write([]byte(s)) var list, ring, set bool for _, s := range tag.Items { if s == "List" { list = true } if s == "Ring" { ring = true } if s == "Set" { set = true } } if list { license := `// List is a modification of http://golang.org/pkg/container/list/ ` w.Write([]byte(license)) } if ring { license := `// Ring is a modification of http://golang.org/pkg/container/ring/ ` w.Write([]byte(license)) } if list || ring { license := `// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found at http://golang.org/LICENSE ` w.Write([]byte(license)) } if set { license := `// Set is a modification of https://github.com/deckarep/golang-set // The MIT License (MIT) // Copyright (c) 2013 Ralph Caraveo ([email protected]) ` w.Write([]byte(license)) } return }
func (g *GenWriter) WriteBody(w io.Writer, t typewriter.Type) { err := g.ensureValidation(t) if err != nil { panic(err) } m, exists := g.models[t.String()] if !exists { return } tmpl, _ := standardTemplates.Get("plural") if err := tmpl.Execute(w, m); err != nil { panic(err) } for _, s := range m.methods { tmpl, _ := standardTemplates.Get(s) // already validated above err := tmpl.Execute(w, m) if err != nil { panic(err) } } for _, p := range m.projections { tmpl, _ := projectionTemplates.Get(p.Method) // already validated above err := tmpl.Execute(w, p) if err != nil { panic(err) } } if includeSortInterface(m.methods) { tmpl, _ := standardTemplates.Get("sortInterface") // already validated above err := tmpl.Execute(w, m) if err != nil { panic(err) } } if includeSortSupport(m.methods) { tmpl, _ := standardTemplates.Get("sortSupport") // already validated above err := tmpl.Execute(w, m) if err != nil { panic(err) } } }
func (c *ContainerWriter) WriteBody(w io.Writer, t typewriter.Type) { tag := c.tagsByType[t.String()] // validated above for _, s := range tag.Items { tmpl, err := templates.Get(s) // validate above to avoid err check here? if err != nil { continue } err = tmpl.Execute(w, t) if err != nil { fmt.Println(err) continue } } return }
// Validates that the tag on the gen type has correctly instructed this // typewriter to generate code. If the first return value is false, then // none of the write methods are called. func (tw *SimpleTypewriter) Validate(twt typewriter.Type) (bool, error) { tag, found, err := twt.Tags.ByName(plural(tw.name)) if !found || err != nil { return false, err } items := tw.itemsToGenerate(tag.Items) fmt.Printf("%s: items for %s: %v\n", tw.Name(), twt.String(), items) if len(items) == 0 { // not an error, but irrelevant to generation return false, nil } tw.itemsForTypeName[twt.String()] = items return true, nil }
func (f *fooWriter) WriteBody(w io.Writer, t typewriter.Type) { w.Write([]byte(fmt.Sprintf(`func pointless%s(){ fmt.Println("pointless!") }`, t.LocalName()))) return }
func TestValidate(t *testing.T) { g := NewGenWriter() pkg := typewriter.NewPackage("dummy", "SomePackage") typ := typewriter.Type{ Package: pkg, Name: "SomeType", Tags: typewriter.Tags{}, } if g.validated[typ.String()] { t.Errorf("type should not show having been validated yet") } if err := g.ensureValidation(typ); err == nil { t.Errorf("ensure validation should return err prior to validation") } valid, err := g.Validate(typ) if !valid || err != nil { t.Errorf("type should be valid") } if !g.validated[typ.String()] { t.Errorf("type should show having been validated") } if err := g.ensureValidation(typ); err != nil { t.Errorf("ensure validation should not return err after validation") } if _, ok := g.models[typ.String()]; !ok { t.Errorf("type should appear in g.models") } if m := g.models[typ.String()]; len(m.methods) == 0 { t.Errorf("model without tags should have methods") } if m := g.models[typ.String()]; len(m.projections) != 0 { t.Errorf("model without tags should have no projections") } typ2 := typewriter.Type{ Package: pkg, Name: "SomeType2", Tags: typewriter.Tags{ typewriter.Tag{ Name: "projections", Items: []string{"int", "string"}, }, }, } valid2, err2 := g.Validate(typ2) if !valid2 || err2 != nil { t.Errorf("type should be valid") } if m := g.models[typ2.String()]; len(m.projections) == 0 { t.Errorf("model with projections tag should have projections") } typ3 := typewriter.Type{ Package: pkg, Name: "SomeType3", Tags: typewriter.Tags{ typewriter.Tag{ Name: "projections", Items: []string{"int", "Foo"}, }, }, } valid3, err3 := g.Validate(typ3) if valid3 { t.Errorf("type with unknown projection type should be invalid") } if err3 == nil { t.Errorf("type with unknown projection should return error") } if !strings.Contains(err3.Error(), "Foo") { t.Errorf("type with unknown projection type should mention the unknown projection type; got %v", err3) } if !strings.Contains(err3.Error(), typ3.Name) { t.Errorf("type with unknown projection type should mention the type on which it was declared; got %v", err3) } if m := g.models[typ2.String()]; len(m.projections) == 0 { t.Errorf("model with projections tag should have projections") } typ4 := typewriter.Type{ Package: pkg, Name: "SomeType4", Tags: typewriter.Tags{ typewriter.Tag{ Name: "methods", Items: []string{"All", "Foo"}, }, }, } valid4, err4 := g.Validate(typ4) if valid4 { t.Errorf("type with unknown method should be invalid") } if err4 == nil { t.Errorf("type with unknown method should return error") } if !strings.Contains(err4.Error(), "Foo") { t.Errorf("type with unknown method should mention the unknown projection type; got %v", err4) } if !strings.Contains(err4.Error(), typ4.Name) { t.Errorf("type with unknown method should mention the type on which it was declared; got %v", err4) } }
// This business exists because I overload the methods tag to specify both standard and projection methods. // Kind of a mess, but for the end user, arguably simpler. And arguably not. func evaluateTags(t typewriter.Type) (standardMethods, projectionMethods []string, err error) { var nilMethods, nilProjections bool methods, found, methodsErr := t.Tags.ByName("methods") if methodsErr != nil { err = methodsErr return } nilMethods = !found // non-existent methods tag is different than empty _, found, projectionsErr := t.Tags.ByName("projections") if projectionsErr != nil { err = projectionsErr return } nilProjections = !found if nilMethods || methods.Negated { // default to all standardMethods = standardTemplates.GetAllKeys() if !nilProjections { projectionMethods = projectionTemplates.GetAllKeys() } } if !nilMethods { // categorize subsetted methods as standard or projection std := make([]string, 0) prj := make([]string, 0) // collect unknowns for err later unknown := make([]string, 0) for _, m := range methods.Items { isStd := standardTemplates.Contains(m) if isStd { std = append(std, m) } // only consider projection methods in presence of projected types isPrj := !nilProjections && projectionTemplates.Contains(m) if isPrj { prj = append(prj, m) } if !isStd && !isPrj { unknown = append(unknown, m) } } if methods.Negated { standardMethods = remove(standardMethods, std...) projectionMethods = remove(projectionMethods, prj...) } else { standardMethods = std projectionMethods = prj } if len(unknown) > 0 { err = fmt.Errorf("method(s) %v on type %s are unknown", unknown, t.String()) return } } return }
func TestEvaluateTags(t *testing.T) { typ := typewriter.Type{ Name: "TestType", Package: typewriter.NewPackage("dummy", "TestPackage"), } typ.Tags = typewriter.Tags{} standardMethods1, projectionMethods1, err1 := evaluateTags(typ) if err1 != nil { t.Errorf("empty methods should be ok, instead got '%v'", err1) } if len(standardMethods1) != len(standardTemplates.GetAllKeys()) { t.Errorf("standard methods should default to all") } if len(projectionMethods1) != 0 { t.Errorf("projection methods without projected type should be none, instead got %v", projectionMethods1) } typ.Tags = typewriter.Tags{ { Name: "methods", Items: []string{"Count", "Where"}, }, } standardMethods2, projectionMethods2, err2 := evaluateTags(typ) if err2 != nil { t.Errorf("empty methods should be ok, instead got %v", err2) } if len(standardMethods2) != 2 { t.Errorf("standard methods should be parsed") } if len(projectionMethods2) != 0 { t.Errorf("projection methods without projected typs should be none") } typ.Tags = typewriter.Tags{ { Name: "methods", Items: []string{"Count", "Unknown"}, }, } standardMethods3, projectionMethods3, err3 := evaluateTags(typ) if err3 == nil { t.Errorf("unknown method should be error") } if len(standardMethods3) != 1 { t.Errorf("standard methods (except unknown) should be 1, got %v", len(standardMethods3)) } if len(projectionMethods3) != 0 { t.Errorf("projection methods without projected types should be none") } typ.Tags = typewriter.Tags{ { Name: "projections", Items: []string{"SomeType"}, }, } standardMethods4, projectionMethods4, err4 := evaluateTags(typ) if err4 != nil { t.Errorf("projected types without subsetted methods should be ok, instead got: '%v'", err4) } if len(standardMethods4) != len(standardTemplates.GetAllKeys()) { t.Errorf("standard methods should default to all") } if len(projectionMethods4) != len(projectionTemplates.GetAllKeys()) { t.Errorf("projection methods should default to all in presence of projected types") } typ.Tags = typewriter.Tags{ { Name: "methods", Items: []string{"GroupBy"}, }, { Name: "projections", Items: []string{"SomeType"}, }, } standardMethods5, projectionMethods5, err5 := evaluateTags(typ) if err5 != nil { t.Errorf("projected types with subsetted methods should be ok, instead got: '%v'", err5) } if len(standardMethods5) != 0 { t.Errorf("standard methods should be none") } if len(projectionMethods5) != 1 { t.Errorf("projection methods should be subsetted") } typ.Tags = typewriter.Tags{ { Name: "methods", }, } standardMethods6, projectionMethods6, err6 := evaluateTags(typ) if err6 != nil { t.Errorf("empty subsetted methods should be ok, instead got: '%v'", err6) } if len(standardMethods6) != 0 { t.Errorf("standard methods should be empty when the tag is empty") } if len(projectionMethods6) != 0 { t.Errorf("projection methods should be none") } typ.Tags = typewriter.Tags{ { Name: "methods", Items: []string{"Sort", "Any"}, Negated: true, }, } standardMethods7, projectionMethods7, err7 := evaluateTags(typ) if err7 != nil { t.Errorf("subsetted methods should be ok, instead got: '%v'", err7) } expected7 := []string{"All", "Count", "Distinct", "DistinctBy", "Each", "First", "IsSorted", "IsSortedBy", "IsSortedByDesc", "IsSortedDesc", "Max", "MaxBy", "Min", "MinBy", "Single", "SortBy", "SortByDesc", "SortDesc", "Where"} if !sliceEqual(standardMethods7, expected7) { t.Errorf("standard methods should be negatively subsetted, expected %v, got %v", expected7, standardMethods7) } if len(projectionMethods7) != 0 { t.Errorf("projection methods should be none") } typ.Tags = typewriter.Tags{ { Name: "methods", Items: []string{"Sort", "Where", "GroupBy"}, Negated: true, }, { Name: "projections", Items: []string{"int"}, }, } standardMethods8, projectionMethods8, err8 := evaluateTags(typ) if err8 != nil { t.Errorf("subsetted methods should be ok, instead got: '%v'", err8) } expectedStd8 := []string{"All", "Any", "Count", "Distinct", "DistinctBy", "Each", "First", "IsSorted", "IsSortedBy", "IsSortedByDesc", "IsSortedDesc", "Max", "MaxBy", "Min", "MinBy", "Single", "SortBy", "SortByDesc", "SortDesc"} if !sliceEqual(standardMethods8, expectedStd8) { t.Errorf("standard methods should be negatively subsetted, expected %v, got %v", expectedStd8, standardMethods8) } expectedPrj8 := []string{"Aggregate", "Average", "Max", "Min", "Select", "Sum"} if !sliceEqual(projectionMethods8, expectedPrj8) { t.Errorf("projection methods should be negatively subsetted, expected %v, got %v", expectedPrj8, projectionMethods8) } }
func (g *GenWriter) Validate(t typewriter.Type) (bool, error) { standardMethods, projectionMethods, err := evaluateTags(t) if err != nil { return false, err } // filter methods applicable to type for _, s := range standardMethods { tmpl, ok := standardTemplates[s] if !ok { err = fmt.Errorf("unknown method %v on type %s", s, t.String()) return false, err } if !tmpl.ApplicableTo(t) { standardMethods = remove(standardMethods, s) } } projectionTag, found, err := t.Tags.ByName("projections") if err != nil { return false, err } m := model{ Type: t, methods: standardMethods, } if found { for _, s := range projectionTag.Items { projectionType, err := t.Package.Eval(s) if err != nil { return false, fmt.Errorf("unable to identify type %s, projected on %s (%s)", s, t.Name, err) } for _, pm := range projectionMethods { tmpl, ok := projectionTemplates[pm] if !ok { return false, fmt.Errorf("unknown projection method %v", pm) } if tmpl.ApplicableTo(projectionType) { m.projections = append(m.projections, Projection{ Method: pm, Type: s, Parent: &m, }) } } } } g.validated[t.String()] = true // only add to models if we are going to do something with it if len(m.methods) > 0 || len(m.projections) > 0 { g.models[t.String()] = m } return true, nil }
// Write the actual body. func (tw *SimpleTypewriter) WriteBody(wrt io.Writer, twt typewriter.Type) { for _, item := range tw.itemsForTypeName[twt.String()] { tw.writeTemplateBody(item, wrt, twt) } return }
// Write headersForItemType for the generated file. This would include licensing, credits // or anything else that you wanted to put in there. This can depend upon the // items selected. func (tw *SimpleTypewriter) WriteHeader(wrt io.Writer, twt typewriter.Type) { for _, item := range tw.itemsForTypeName[twt.String()] { tw.writeHeaderForItem(item, wrt) } }