-
-
Save deanwampler/161702 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* http://gist.github.com/161702. | |
* Start with this Groovy example, http://gist.github.com/160492 | |
* and Scala variants suggested by others, http://gist.github.com/161159 | |
* and http://gist.github.com/162389 | |
* | |
* Here is what I came up with, but it's somewhat different. The others assume | |
* that Cucumber will parse the regex's and invoke the anonymous function | |
* specified with the Given declaration. The problem is that it's difficult to | |
* define a generic function signature. | |
* | |
* Instead, I assume that Scala will be used parse strings with the regexs, using | |
* the Given.eval(...) function defined below. Then, the anonymous function | |
* specified by the user is actually a PartialFunction. "PFs" are "defined" over | |
* a subset of the domain of the single argument's type. For example, a PF that | |
* takes an Int might only be defined for positive even Ints. Usually, PFs are | |
* written as case expressions; the surrounding "match" clause is supplied by | |
* the compiler. In this case, I've assumed that all the PFs will take a Product | |
* argument, which is the parent type of ProductN types, for N = 1 - 22, which | |
* are the parents of correspondingTupleN types. Hence, my examples use | |
* case (x1, x2, ..., xN) => ... | |
* expressions. | |
*/ | |
import scala.util.matching._ | |
import scala.util.matching.Regex._ | |
import scala.collection.mutable._ | |
/** | |
* Constructor takes two argument lists, each with a single argument. This makes | |
* the syntax nice, as in the examples below. | |
*/ | |
class Given(name: String)(body: PartialFunction[Product,Unit]) { | |
val regex = name.r | |
/** | |
* See if the regex matches. If so, see if the ParialFunction applies. If so, | |
* call it on the groups extracted from the string by the regex. | |
* The Regex and MatchIterator APIs don't work like normal Scala collections, | |
* etc., so we have to hack around... | |
*/ | |
def apply(str: String): Boolean = { | |
val iter = regex.findAllIn(str) | |
if (iter.hasNext == false) return false | |
val values = toTuple(iter) | |
if (body.isDefinedAt(values)) { body(values); true } else false | |
} | |
/** | |
* There's no convenient way to map an arbitrarily-sized collection to a | |
* tuple (up to Tuple22, of course), so we do it "manually". (We use tuples to | |
* provide the convenient syntax used in the examples below.) | |
*/ | |
protected def toTuple(iter: MatchIterator) = { | |
val list = iter.subgroups | |
list.size match { | |
case 0 => Tuple1(iter.matched) // No capture groups; return whole string. | |
case 1 => Tuple1(list(0)) | |
case 2 => Tuple2(list(0), list(1)) | |
case 3 => Tuple3(list(0), list(1), list(2)) | |
case 4 => Tuple4(list(0), list(1), list(2), list(3)) | |
case n => throw new RuntimeException( | |
n + " > 4 arguments not yet supported, but you could add them easily...") | |
} | |
} | |
} | |
/** | |
* "Companion object", used to track the defined Givens and find the right one for | |
* a particular input string, using "eval". Also provides the typical "apply" | |
* factory method for creating Given instances. | |
*/ | |
object Given { | |
val givens = new ListBuffer[Given] | |
def apply(name: String)(body: PartialFunction[Product,Unit]): Given = { | |
givens prepend new Given(name)(body) | |
givens first // return new Given | |
} | |
/** | |
* Tests if any Givens match the string and if found, invokes the Given's | |
* "action" implicitly. | |
*/ | |
def eval(test: String) = givens.exists(g => g(test)) || | |
{ println("Nothing matched string: " + test); false } | |
} | |
// Example #1 | |
Given("I ate (\\d+) cukes for (\\w+) and I feel (.+)") { | |
case (cukes, meal, state) => | |
println("You ate " + cukes + " cukes during " + meal + " and you feel " + state) | |
} | |
// Example #2 | |
Given("I have (\\d+) cukes in my (.+)") { | |
case (cukes, where) => println("You have " + cukes + " cukes in your " + where) | |
} | |
// Example #3 | |
// Use "Tuple1(x)" rather than "(x)", because the latter is ambiguious; is it a | |
// tuple or just parentheses around an expression? | |
Given("my name is (.+)") { | |
case Tuple1(name) => println("Your name is " + name) | |
} | |
// Example #4 | |
// No capture groups, so will match the whole string verbatim. | |
Given("This is a fixed string") { | |
case Tuple1(str) => println("The whole string is: " + str) | |
} | |
// Try the three options: | |
Given.eval("I ate 7 cukes for lunch and I feel bad") | |
Given.eval("I have 5 cukes in my body") | |
Given.eval("I have 6 cukes in my other body") | |
Given.eval("my name is Dean") | |
Given.eval("my name is Aslak") | |
Given.eval("my name is ") | |
Given.eval("This is a fixed string") | |
// Output: | |
// => You ate 7 cukes during lunch and you feel bad | |
// => You have 5 cukes in your body | |
// => You have 6 cukes in your other body | |
// => Your name is Dean | |
// => Your name is Aslak | |
// => Nothing matched string: my name is | |
// => The whole string is: This is a fixed string |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment