Added Add, Or, Not, WithTransform matchers, for composability.
- Allows matchers to be composed into complex expressions that work even with the Eventually() assertion.
- Also makes it easy to create new matchers -- can often write a function that composes a new matcher out of existing ones.
diff --git a/matchers.go b/matchers.go
index 3b39677..872de9e 100644
--- a/matchers.go
+++ b/matchers.go
@@ -343,3 +343,41 @@
 func BeADirectory() types.GomegaMatcher {
 	return &matchers.BeADirectoryMatcher{}
 }
+
+//And succeeds only if all of the given matchers succeed.
+//The matchers are tried in order, and will fail-fast if one doesn't succeed.
+//  Expect("hi").To(And(HaveLen(2), Equal("hi"))
+//
+//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
+func And(ms ...types.GomegaMatcher) types.GomegaMatcher {
+	return &matchers.AndMatcher{Matchers: ms}
+}
+
+//Or succeeds if any of the given matchers succeed.
+//The matchers are tried in order and will return immediately upon the first successful match.
+//  Expect("hi").To(Or(HaveLen(3), HaveLen(2))
+//
+//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
+func Or(ms ...types.GomegaMatcher) types.GomegaMatcher {
+	return &matchers.OrMatcher{Matchers: ms}
+}
+
+//Not negates the given matcher; it succeeds if the given matcher fails.
+//  Expect(1).To(Not(Equal(2))
+//
+//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
+func Not(matcher types.GomegaMatcher) types.GomegaMatcher {
+	return &matchers.NotMatcher{Matcher: matcher}
+}
+
+//WithTransform applies the `transform` to the actual value and matches it against `matcher`.
+//  var plus1 = func(i interface{}) interface{} { return i.(int) + 1 }
+//  Expect(1).To(WithTransform(plus1, Equal(2))
+//
+//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
+func WithTransform(transform func(interface{}) interface{}, matcher types.GomegaMatcher) types.GomegaMatcher {
+	return &matchers.WithTransformMatcher{
+		Transform: transform,
+		Matcher:   matcher,
+	}
+}
diff --git a/matchers/and.go b/matchers/and.go
new file mode 100644
index 0000000..e20fbae
--- /dev/null
+++ b/matchers/and.go
@@ -0,0 +1,34 @@
+package matchers
+
+import (
+	"fmt"
+	"github.com/onsi/gomega/format"
+	"github.com/onsi/gomega/types"
+)
+
+type AndMatcher struct {
+	Matchers []types.GomegaMatcher
+
+	// state
+	firstFailedMatchErrMsg string
+}
+
+func (m *AndMatcher) Match(actual interface{}) (success bool, err error) {
+	for _, matcher := range m.Matchers {
+		success, err := matcher.Match(actual)
+		if !success || err != nil {
+			m.firstFailedMatchErrMsg = matcher.FailureMessage(actual)
+			return false, err
+		}
+	}
+	return true, nil
+}
+
+func (m *AndMatcher) FailureMessage(actual interface{}) (message string) {
+	return m.firstFailedMatchErrMsg
+}
+
+func (m *AndMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	// not the most beautiful list of matchers, but not bad either...
+	return format.Message(actual, fmt.Sprintf("To not satisfy all of these matchers: %s", m.Matchers))
+}
diff --git a/matchers/and_test.go b/matchers/and_test.go
new file mode 100644
index 0000000..c50e800
--- /dev/null
+++ b/matchers/and_test.go
@@ -0,0 +1,61 @@
+package matchers_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/types"
+)
+
+// sample data
+var (
+	// example input
+	input = "hi"
+	// some matchers that succeed against the input
+	true1 = HaveLen(2)
+	true2 = Equal("hi")
+	true3 = MatchRegexp("hi")
+	// some matchers that fail against the input.
+	false1 = HaveLen(1)
+	false2 = Equal("hip")
+	false3 = MatchRegexp("hope")
+)
+
+// verifyFailureMessage expects the matcher to fail with the given input, and verifies the failure message.
+func verifyFailureMessage(m types.GomegaMatcher, input string, expectedFailureMsgFragment string) {
+	Expect(m.Match(input)).To(BeFalse())
+	Expect(m.FailureMessage(input)).To(Equal(
+		"Expected\n    <string>: " + input + "\n" + expectedFailureMsgFragment))
+}
+
+var _ = Describe("AndMatcher", func() {
+	It("works with positive cases", func() {
+		Expect(input).To(And())
+		Expect(input).To(And(true1))
+		Expect(input).To(And(true1, true2))
+		Expect(input).To(And(true1, true2, true3))
+	})
+
+	It("works with negative cases", func() {
+		Expect(input).ToNot(And(false1, false2))
+		Expect(input).ToNot(And(true1, true2, false3))
+		Expect(input).ToNot(And(true1, false2, false3))
+		Expect(input).ToNot(And(false1, true1, true2))
+	})
+
+	Context("failure messages", func() {
+		Context("when match fails", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(And(false1, true1), input, "to have length 1")
+				verifyFailureMessage(And(true1, false2), input, "to equal\n    <string>: hip")
+				verifyFailureMessage(And(true1, true2, false3), input, "to match regular expression\n    <string>: hope")
+			})
+		})
+
+		Context("when match succeeds, but expected it to fail", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(Not(And(true1, true2)), input,
+					`To not satisfy all of these matchers: [%!s(*matchers.HaveLenMatcher=&{2}) %!s(*matchers.EqualMatcher=&{hi})]`)
+			})
+		})
+	})
+})
diff --git a/matchers/not.go b/matchers/not.go
new file mode 100644
index 0000000..5ad0d63
--- /dev/null
+++ b/matchers/not.go
@@ -0,0 +1,23 @@
+package matchers
+
+import "github.com/onsi/gomega/types"
+
+type NotMatcher struct {
+	Matcher types.GomegaMatcher
+}
+
+func (m *NotMatcher) Match(actual interface{}) (bool, error) {
+	success, err := m.Matcher.Match(actual)
+	if err != nil {
+		return false, err
+	}
+	return !success, nil
+}
+
+func (m *NotMatcher) FailureMessage(actual interface{}) (message string) {
+	return m.Matcher.NegatedFailureMessage(actual) // works beautifully
+}
+
+func (m *NotMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return m.Matcher.FailureMessage(actual) // works beautifully
+}
diff --git a/matchers/not_test.go b/matchers/not_test.go
new file mode 100644
index 0000000..e716178
--- /dev/null
+++ b/matchers/not_test.go
@@ -0,0 +1,43 @@
+package matchers_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("NotMatcher", func() {
+	Context("basic examples", func() {
+		It("works", func() {
+			Expect(input).To(Not(false1))
+			Expect(input).To(Not(Not(true2)))
+			Expect(input).ToNot(Not(true3))
+			Expect(input).ToNot(Not(Not(false1)))
+			Expect(input).To(Not(Not(Not(false2))))
+		})
+	})
+
+	Context("De Morgan's laws", func() {
+		It("~(A && B) == ~A || ~B", func() {
+			Expect(input).To(Not(And(false1, false2)))
+			Expect(input).To(Or(Not(false1), Not(false2)))
+		})
+		It("~(A || B) == ~A && ~B", func() {
+			Expect(input).To(Not(Or(false1, false2)))
+			Expect(input).To(And(Not(false1), Not(false2)))
+		})
+	})
+
+	Context("failure messages are opposite of original matchers' failure messages", func() {
+		Context("when match fails", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(Not(HaveLen(2)), input, "not to have length 2")
+			})
+		})
+
+		Context("when match succeeds, but expected it to fail", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(Not(Not(HaveLen(3))), input, "to have length 3")
+			})
+		})
+	})
+})
diff --git a/matchers/or.go b/matchers/or.go
new file mode 100644
index 0000000..228fce6
--- /dev/null
+++ b/matchers/or.go
@@ -0,0 +1,37 @@
+package matchers
+
+import (
+	"fmt"
+	"github.com/onsi/gomega/format"
+	"github.com/onsi/gomega/types"
+)
+
+type OrMatcher struct {
+	Matchers []types.GomegaMatcher
+
+	// state
+	successfulMatcher types.GomegaMatcher
+}
+
+func (m *OrMatcher) Match(actual interface{}) (success bool, err error) {
+	for _, matcher := range m.Matchers {
+		success, err := matcher.Match(actual)
+		if err != nil {
+			return false, err
+		}
+		if success {
+			m.successfulMatcher = matcher
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+func (m *OrMatcher) FailureMessage(actual interface{}) (message string) {
+	// not the most beautiful list of matchers, but not bad either...
+	return format.Message(actual, fmt.Sprintf("To satisfy at least one of these matchers: %s", m.Matchers))
+}
+
+func (m *OrMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return m.successfulMatcher.NegatedFailureMessage(actual)
+}
diff --git a/matchers/or_test.go b/matchers/or_test.go
new file mode 100644
index 0000000..ae6f975
--- /dev/null
+++ b/matchers/or_test.go
@@ -0,0 +1,43 @@
+package matchers_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("OrMatcher", func() {
+	It("works with positive cases", func() {
+		Expect(input).To(Or(true1))
+		Expect(input).To(Or(true1, true2))
+		Expect(input).To(Or(true1, false1))
+		Expect(input).To(Or(false1, true2))
+		Expect(input).To(Or(true1, true2, true3))
+		Expect(input).To(Or(true1, true2, false3))
+		Expect(input).To(Or(true1, false2, true3))
+		Expect(input).To(Or(false1, true2, true3))
+		Expect(input).To(Or(true1, false2, false3))
+		Expect(input).To(Or(false1, false2, true3))
+	})
+
+	It("works with negative cases", func() {
+		Expect(input).ToNot(Or())
+		Expect(input).ToNot(Or(false1))
+		Expect(input).ToNot(Or(false1, false2))
+		Expect(input).ToNot(Or(false1, false2, false3))
+	})
+
+	Context("failure messages", func() {
+		Context("when match fails", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(Or(false1, false2), input,
+					"To satisfy at least one of these matchers: [%!s(*matchers.HaveLenMatcher=&{1}) %!s(*matchers.EqualMatcher=&{hip})]")
+			})
+		})
+
+		Context("when match succeeds, but expected it to fail", func() {
+			It("gives a descriptive message", func() {
+				verifyFailureMessage(Not(Or(true1, true2)), input, `not to have length 2`)
+			})
+		})
+	})
+})
diff --git a/matchers/with_transform.go b/matchers/with_transform.go
new file mode 100644
index 0000000..c7a650a
--- /dev/null
+++ b/matchers/with_transform.go
@@ -0,0 +1,25 @@
+package matchers
+
+import "github.com/onsi/gomega/types"
+
+type WithTransformMatcher struct {
+	// input
+	Transform func(interface{}) interface{}
+	Matcher   types.GomegaMatcher
+
+	// state
+	transformedValue interface{}
+}
+
+func (m *WithTransformMatcher) Match(actual interface{}) (bool, error) {
+	m.transformedValue = m.Transform(actual)
+	return m.Matcher.Match(m.transformedValue)
+}
+
+func (m *WithTransformMatcher) FailureMessage(actual interface{}) (message string) {
+	return m.Matcher.FailureMessage(m.transformedValue)
+}
+
+func (m *WithTransformMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return m.Matcher.NegatedFailureMessage(m.transformedValue)
+}
diff --git a/matchers/with_transform_test.go b/matchers/with_transform_test.go
new file mode 100644
index 0000000..ced53bd
--- /dev/null
+++ b/matchers/with_transform_test.go
@@ -0,0 +1,40 @@
+package matchers_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("WithTransformMatcher", func() {
+
+	var plus1 = func(i interface{}) interface{} { return i.(int) + 1 }
+
+	It("works with positive cases", func() {
+		Expect(1).To(WithTransform(plus1, Equal(2)))
+		Expect(1).To(WithTransform(plus1, WithTransform(plus1, Equal(3))))
+		Expect(1).To(WithTransform(plus1, And(Equal(2), BeNumerically(">", 1))))
+	})
+
+	It("works with negative cases", func() {
+		Expect(1).ToNot(WithTransform(plus1, Equal(3)))
+		Expect(1).ToNot(WithTransform(plus1, WithTransform(plus1, Equal(2))))
+	})
+
+	Context("failure messages", func() {
+		Context("when match fails", func() {
+			It("gives a descriptive message", func() {
+				m := WithTransform(plus1, Equal(3))
+				Expect(m.Match(1)).To(BeFalse())
+				Expect(m.FailureMessage(input)).To(Equal("Expected\n    <int>: 2\nto equal\n    <int>: 3"))
+			})
+		})
+
+		Context("when match succeeds, but expected it to fail", func() {
+			It("gives a descriptive message", func() {
+				m := Not(WithTransform(plus1, Equal(3)))
+				Expect(m.Match(2)).To(BeFalse())
+				Expect(m.FailureMessage(input)).To(Equal("Expected\n    <int>: 3\nnot to equal\n    <int>: 3"))
+			})
+		})
+	})
+})