diff --git a/typewriters/genwriter/genwriter.go b/typewriters/genwriter/genwriter.go index fe95659..d578750 100644 --- a/typewriters/genwriter/genwriter.go +++ b/typewriters/genwriter/genwriter.go @@ -42,6 +42,11 @@ func (m model) Plural() (result string) { return } +func (m model) Predicate() (result string) { + result = m.Name + "Predicate" + 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()] { @@ -219,6 +224,11 @@ func (g GenWriter) Write(w io.Writer, t typewriter.Type) { panic(err) } + tmpl, _ = standardTemplates.Get("predicates") + 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) diff --git a/typewriters/genwriter/templates.go b/typewriters/genwriter/templates.go index b61cffe..5ff53b2 100644 --- a/typewriters/genwriter/templates.go +++ b/typewriters/genwriter/templates.go @@ -12,6 +12,33 @@ type {{.Plural}} []{{.Pointer}}{{.Name}} `, }, + "predicates": &typewriter.Template{ + Text: `// {{.Predicate}} is a function that accepts a {{.Pointer}}{{.Name}} and returns a bool. Used with gen methods below. Use this type where you would use func({{.Pointer}}{{.Name}}) bool. +type {{.Predicate}} func(item {{.Pointer}}{{.Name}}) bool + +// And combines two predicates into a new predicate that is satisfied if both of the original predicates are satisfied +func (rcv {{.Predicate}}) And(other {{.Predicate}}) {{.Predicate}} { + return func (item {{.Pointer}}{{.Name}}) bool { + return rcv(item) && other(item) + } +} + +// Or combines two predicates into a new predicate that is satisfied if either of the original predicates is satisfied +func (rcv {{.Predicate}}) Or(other {{.Predicate}}) {{.Predicate}} { + return func (item {{.Pointer}}{{.Name}}) bool { + return rcv(item)|| other(item) + } +} + +// Not inverts a predicate that is satisfied if the original predicates is not satisfied +func (rcv {{.Predicate}}) Not() {{.Predicate}} { + return func (item {{.Pointer}}{{.Name}}) bool { + return !rcv(item) + } +} +`, + }, + "All": &typewriter.Template{ Text: ` // All verifies that all elements of {{.Plural}} return true for the passed func. See: http://clipperhouse.github.io/gen/#All diff --git a/typewriters/genwriter/test/other_gen.go b/typewriters/genwriter/test/other_gen.go index 4042562..86ad4ce 100644 --- a/typewriters/genwriter/test/other_gen.go +++ b/typewriters/genwriter/test/other_gen.go @@ -12,6 +12,30 @@ import ( // Others is a slice of type Other, for use with gen methods below. Use this type where you would use []Other. (This is required because slices cannot be method receivers.) type Others []Other +// OtherPredicate is a function that accepts a Other and returns a bool. Used with gen methods below. Use this type where you would use func(Other) bool. +type OtherPredicate func(item Other) bool + +// And combines two predicates into a new predicate that is satisfied if both of the original predicates are satisfied +func (rcv OtherPredicate) And(other OtherPredicate) OtherPredicate { + return func(item Other) bool { + return rcv(item) && other(item) + } +} + +// Or combines two predicates into a new predicate that is satisfied if either of the original predicates is satisfied +func (rcv OtherPredicate) Or(other OtherPredicate) OtherPredicate { + return func(item Other) bool { + return rcv(item) || other(item) + } +} + +// Not inverts a predicate that is satisfied if the original predicates is not satisfied +func (rcv OtherPredicate) Not() OtherPredicate { + return func(item Other) bool { + return !rcv(item) + } +} + // Max returns the maximum value of Others. In the case of multiple items being equally maximal, the first such element is returned. Returns error if no elements. See: http://clipperhouse.github.io/gen/#Max func (rcv Others) Max() (result Other, err error) { l := len(rcv) diff --git a/typewriters/genwriter/test/thing_gen.go b/typewriters/genwriter/test/thing_gen.go index 584b6da..be504fd 100644 --- a/typewriters/genwriter/test/thing_gen.go +++ b/typewriters/genwriter/test/thing_gen.go @@ -16,6 +16,30 @@ import ( // Things is a slice of type Thing, for use with gen methods below. Use this type where you would use []Thing. (This is required because slices cannot be method receivers.) type Things []Thing +// ThingPredicate is a function that accepts a Thing and returns a bool. Used with gen methods below. Use this type where you would use func(Thing) bool. +type ThingPredicate func(item Thing) bool + +// And combines two predicates into a new predicate that is satisfied if both of the original predicates are satisfied +func (rcv ThingPredicate) And(other ThingPredicate) ThingPredicate { + return func(item Thing) bool { + return rcv(item) && other(item) + } +} + +// Or combines two predicates into a new predicate that is satisfied if either of the original predicates is satisfied +func (rcv ThingPredicate) Or(other ThingPredicate) ThingPredicate { + return func(item Thing) bool { + return rcv(item) || other(item) + } +} + +// Not inverts a predicate that is satisfied if the original predicates is not satisfied +func (rcv ThingPredicate) Not() ThingPredicate { + return func(item Thing) bool { + return !rcv(item) + } +} + // All verifies that all elements of Things return true for the passed func. See: http://clipperhouse.github.io/gen/#All func (rcv Things) All(fn func(Thing) bool) bool { for _, v := range rcv { diff --git a/typewriters/genwriter/test/things_test.go b/typewriters/genwriter/test/things_test.go index cd92979..ad92511 100644 --- a/typewriters/genwriter/test/things_test.go +++ b/typewriters/genwriter/test/things_test.go @@ -4,6 +4,67 @@ import ( "testing" ) +func TestAnd(t *testing.T) { + True := ThingPredicate(func(x Thing) bool { return true }) + False := ThingPredicate(func(x Thing) bool { return false }) + + ff := False.And(False) + ft := False.And(True) + tt := True.And(True) + tf := True.And(False) + + if ff(zero) { + t.Errorf("And FF should not be true") + } + if ft(zero) { + t.Errorf("And FT should not be true") + } + if !tt(zero) { + t.Errorf("And TT should be true") + } + if tf(zero) { + t.Errorf("And TF should not be true") + } +} + +func TestOr(t *testing.T) { + True := ThingPredicate(func(x Thing) bool { return true }) + False := ThingPredicate(func(x Thing) bool { return false }) + + ff := False.Or(False) + ft := False.Or(True) + tt := True.Or(True) + tf := True.Or(False) + + if ff(zero) { + t.Errorf("Or FF should not be true") + } + if !ft(zero) { + t.Errorf("Or FT should be true") + } + if !tt(zero) { + t.Errorf("Or TT should be true") + } + if !tf(zero) { + t.Errorf("Or TF should be true") + } +} + +func TestNot(t *testing.T) { + True := ThingPredicate(func(x Thing) bool { return true }) + False := ThingPredicate(func(x Thing) bool { return false }) + + expectFalse := True.Not() + expectTrue := False.Not() + + if expectFalse(zero) { + t.Errorf("Not True should not be true") + } + if !expectTrue(zero) { + t.Errorf("Not False should be true") + } +} + func TestAll(t *testing.T) { all1 := things.All(func(x Thing) bool { return x.Name == "First"