From dadd1221ec7c1301b3cc2dfc178dba2091e1f9b8 Mon Sep 17 00:00:00 2001 From: Rocky Madden Date: Sat, 6 Oct 2012 23:19:20 -0600 Subject: Created repository. --- .../stringmetric/cli/OptionMapUtility.scala | 55 ++++++++ .../stringmetric/cli/command/Command.scala | 34 +++++ .../cli/command/jaroWinklerMetric.scala | 52 +++++++ .../stringmetric/cli/command/package.scala | 6 + .../org/hashtree/stringmetric/cli/package.scala | 6 + .../stringmetric/cli/OptionMapUtilitySpec.scala | 151 +++++++++++++++++++++ .../cli/command/jaroWinklerMetricSpec.scala | 39 ++++++ 7 files changed, 343 insertions(+) create mode 100755 cli/source/core/scala/org/hashtree/stringmetric/cli/OptionMapUtility.scala create mode 100755 cli/source/core/scala/org/hashtree/stringmetric/cli/command/Command.scala create mode 100755 cli/source/core/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetric.scala create mode 100755 cli/source/core/scala/org/hashtree/stringmetric/cli/command/package.scala create mode 100755 cli/source/core/scala/org/hashtree/stringmetric/cli/package.scala create mode 100755 cli/source/test/scala/org/hashtree/stringmetric/cli/OptionMapUtilitySpec.scala create mode 100755 cli/source/test/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetricSpec.scala (limited to 'cli/source') diff --git a/cli/source/core/scala/org/hashtree/stringmetric/cli/OptionMapUtility.scala b/cli/source/core/scala/org/hashtree/stringmetric/cli/OptionMapUtility.scala new file mode 100755 index 0000000..926ba8b --- /dev/null +++ b/cli/source/core/scala/org/hashtree/stringmetric/cli/OptionMapUtility.scala @@ -0,0 +1,55 @@ +package org.hashtree.stringmetric.cli + +import scala.collection.immutable.HashMap + +/** Utility standalone for OptionMap based operations. */ +object OptionMapUtility { + def toOptionMap(arguments: Array[String]): OptionMap = { + toOptionMap(arguments.toList) + } + + def toOptionMap(arguments: List[String]): OptionMap = { + next(new HashMap[Symbol, String](), arguments) + } + + private[this] def next(optionMap: OptionMap, arguments: List[String]): OptionMap = { + val double = """^(--[a-zA-Z0-9]+)(\=[a-zA-Z0-9\.\-\_]+)?""".r + val single = """^(-[a-zA-Z0-9]+)(\=[a-zA-Z0-9\.\-\_]+)?""".r + val less = """([a-zA-Z0-9\/\-\_\$\.]+)""".r + + arguments match { + // Empty List, return OptionMap. + case Nil => optionMap + // Double dash options, without value. + case double(name, null) :: tail => { + next(optionMap + (Symbol(name.tail.tail) -> ""), tail) + } + // Double dash options, with value. + case double(name, value) :: tail => { + next(optionMap + (Symbol(name.tail.tail) -> value.tail), tail) + } + // Single dash options, without value. + case single(name, null) :: tail => { + next(optionMap + (Symbol(name.tail) -> ""), tail) + } + // Single dash options, with value. Value is discarded. + case single(name, value) :: tail => { + next(optionMap + (Symbol(name.tail) -> ""), tail) + } + // Dashless options. + case less(value) :: tail if value.head != '-' => { + if (optionMap.contains('dashless)) { + val dashless = optionMap('dashless) + " " + value.trim + + next((optionMap - 'dashless) + ('dashless -> dashless), tail) + } else { + next(optionMap + ('dashless -> value.trim), tail) + } + } + // Invalid option, ignore. + case _ :: tail => { + next(optionMap, tail) + } + } + } +} \ No newline at end of file diff --git a/cli/source/core/scala/org/hashtree/stringmetric/cli/command/Command.scala b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/Command.scala new file mode 100755 index 0000000..bac7786 --- /dev/null +++ b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/Command.scala @@ -0,0 +1,34 @@ +package org.hashtree.stringmetric.cli.command + +import org.hashtree.stringmetric.cli.OptionMap + +/** Defines the traits and provides basic implementations of a command. Commands are always implemented as objects. */ +trait Command { + def main(args: Array[String]): Unit + + def help(): Unit + + final def error(error: Throwable)(implicit options: OptionMap): Unit = { + if (! isUnitTest(options)) { + println(error.getMessage) + sys.exit(1) + } else { + throw error + } + } + + def execute(options: OptionMap): Unit + + final def exit(implicit options: OptionMap): Unit = { + if (! isUnitTest(options)) sys.exit(0) + } + + protected[this] def isUnitTest(options: OptionMap): Boolean = { + (options.contains('ut) || (options.contains('unitTest) && options.get('unitTest) != "false")) + } + + protected[this] def isDebug(options: OptionMap): Boolean = { + (options.contains('d) || (options.contains('debug) && options.get('debug) != "false")) + } +} + diff --git a/cli/source/core/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetric.scala b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetric.scala new file mode 100755 index 0000000..63ab69c --- /dev/null +++ b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetric.scala @@ -0,0 +1,52 @@ +package org.hashtree.stringmetric.cli.command + +import org.hashtree.stringmetric.JaroWinklerMetric +import org.hashtree.stringmetric.cli._ +import org.hashtree.stringmetric.cli.command._ + +/** + * The jaroWinklerMetric [[org.hashtree.stringmetric.cli.command.Command]]. Compares two strings to calculate the + * Jaro-Winkler distance. + */ +object jaroWinklerMetric extends Command { + override def main(args: Array[String]): Unit = { + val options = OptionMapUtility.toOptionMap(args) + + try { + // Help. + if (options.contains('h) || options.contains('help)) { + help() + exit(options) + // Execute. + } else if (options.contains('dashless) && options('dashless).count(_ == ' ') == 1) { + execute(options) + exit(options) + // Invalid syntax. + } else { + throw new IllegalArgumentException("Expected valid syntax. See --help.") + } + } catch { + case e => error(e)(options) + } + } + + override def help(): Unit = { + val ls = sys.props("line.separator") + val tab = " " + + println( + "Compares two strings to calculate the Jaro-Winkler distance." + ls + ls + + "Syntax:" + ls + + tab + "jaroWinklerMetric [Options] string1 string2..." + ls + ls + + "Options:" + ls + + tab + "-h, --help" + ls + + tab + tab + "Outputs description, syntax, and options." + ) + } + + override def execute(options: OptionMap): Unit = { + val strings = options('dashless).split(" ") + + println(JaroWinklerMetric.compare(strings(0), strings(1)).toString) + } +} \ No newline at end of file diff --git a/cli/source/core/scala/org/hashtree/stringmetric/cli/command/package.scala b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/package.scala new file mode 100755 index 0000000..b0610ba --- /dev/null +++ b/cli/source/core/scala/org/hashtree/stringmetric/cli/command/package.scala @@ -0,0 +1,6 @@ +package org.hashtree.stringmetric.cli + +/** Provides core command functionality. */ +package object command { + implicit val optionMap: OptionMap = OptionMapUtility.toOptionMap(Array("--unitTest=false")) +} \ No newline at end of file diff --git a/cli/source/core/scala/org/hashtree/stringmetric/cli/package.scala b/cli/source/core/scala/org/hashtree/stringmetric/cli/package.scala new file mode 100755 index 0000000..a8c1c01 --- /dev/null +++ b/cli/source/core/scala/org/hashtree/stringmetric/cli/package.scala @@ -0,0 +1,6 @@ +package org.hashtree.stringmetric + +/** Provides core CLI functionality. */ +package object cli { + type OptionMap = Map[Symbol, String] +} \ No newline at end of file diff --git a/cli/source/test/scala/org/hashtree/stringmetric/cli/OptionMapUtilitySpec.scala b/cli/source/test/scala/org/hashtree/stringmetric/cli/OptionMapUtilitySpec.scala new file mode 100755 index 0000000..a5a8eb1 --- /dev/null +++ b/cli/source/test/scala/org/hashtree/stringmetric/cli/OptionMapUtilitySpec.scala @@ -0,0 +1,151 @@ +package org.hashtree.stringmetric.cli + +import org.hashtree.stringmetric.ScalaTest +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner + +@RunWith(classOf[JUnitRunner]) +final class OptionMapUtilitySpec extends ScalaTest { + "OptionMapUtility" should provide { + "overloaded toOptionMap method" when passed { + "Array with a single valid double dashed option" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("--help")) + + options('help) should equal ("") + } + } + "List with a single valid double dashed option" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(List("--help")) + + options('help) should equal ("") + } + } + "Array with a multiple valid double dashed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("--help", "--test=test")) + + options('help) should equal ("") + options('test) should equal ("test") + } + } + "List with a multiple valid double dashed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(List("--help", "--test=test")) + + options('help) should equal ("") + options('test) should equal ("test") + } + } + "Array with invalid double dashed options" should returns { + "empty Map" in { + val options = OptionMapUtility.toOptionMap(Array("--help#", "--test%=test")) + + options.keysIterator.length should be (0) + } + } + "List with invalid double dashed options" should returns { + "empty Map" in { + val options = OptionMapUtility.toOptionMap(List("--help#", "--test%=test")) + + options.keysIterator.length should be (0) + } + } + "Array with a single valid single dashed option" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("-h")) + + options('h) should equal ("") + } + } + "List with a single valid single dashed option" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(List("-h")) + + options('h) should equal ("") + } + } + "Array with multiple valid single dashed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("-h", "-i")) + + options('h) should equal ("") + options('i) should equal ("") + } + } + "List with multiple valid single dashed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(List("-h", "-i")) + + options('h) should equal ("") + options('i) should equal ("") + } + } + "Array with an invalid single dashed options" should returns { + "empty Map" in { + val options = OptionMapUtility.toOptionMap(Array("-h-i", "-i#gloo")) + + options.keysIterator.length should be (0) + } + } + "List with an invalid single dashed options" should returns { + "empty Map" in { + val options = OptionMapUtility.toOptionMap(List("-h-i", "-i#gloo")) + + options.keysIterator.length should be (0) + } + } + "Array with a single nameless option" should returns { + "single key populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("filename0")) + + options('dashless).count(_ == ' ') should be (0) + } + } + "List with a single nameless option" should returns { + "single key populated Map" in { + val options = OptionMapUtility.toOptionMap(List("filename0")) + + options('dashless).count(_ == ' ') should be (0) + } + } + "Array with multiple single nameless options" should returns { + "single key populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("filename0", "filename1", "filename2")) + + options('dashless).count(_ == ' ') should be (2) + } + } + "List with multiple single nameless options" should returns { + "single key populated Map" in { + val options = OptionMapUtility.toOptionMap(List("filename0", "filename1", "filename2")) + + options('dashless).count(_ == ' ') should be (2) + } + } + "Array with mixed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(Array("-q", "--help", "--test=test", "-go", "filename0", "filename1", "filename2")) + + options('q) should equal ("") + options('help) should equal ("") + options('test) should equal ("test") + options('go) should equal ("") + options('dashless).count(_ == ' ') should be (2) + } + } + "List with mixed options" should returns { + "populated Map" in { + val options = OptionMapUtility.toOptionMap(List("-q", "--help", "--test=test", "-go", "filename0", "filename1", "filename2")) + + options('q) should equal ("") + options('help) should equal ("") + options('test) should equal ("test") + options('go) should equal ("") + options('dashless).count(_ == ' ') should be (2) + } + } + } + } +} \ No newline at end of file diff --git a/cli/source/test/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetricSpec.scala b/cli/source/test/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetricSpec.scala new file mode 100755 index 0000000..071454a --- /dev/null +++ b/cli/source/test/scala/org/hashtree/stringmetric/cli/command/jaroWinklerMetricSpec.scala @@ -0,0 +1,39 @@ +package org.hashtree.stringmetric.cli.command + +import org.hashtree.stringmetric.ScalaTest +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner + +@RunWith(classOf[JUnitRunner]) +final class jaroWinklerMetricSpec extends ScalaTest { + "jaroWinklerMetric" should provide { + "main method" when passed { + "valid dashless arguments" should executes { + "print the distance" in { + val out = new java.io.ByteArrayOutputStream() + + Console.withOut(out)( + jaroWinklerMetric.main(Array("--unitTest", "--debug", "abc", "abc")) + ) + + out.toString should equal ("1.0\n") + out.reset() + + Console.withOut(out)( + jaroWinklerMetric.main(Array("--unitTest", "--debug", "abc", "xyz")) + ) + + out.toString should equal ("0.0\n") + out.reset() + } + } + "no dashless arguments" should throws { + "IllegalArgumentException" in { + evaluating { + jaroWinklerMetric.main(Array("--unitTest", "--debug")) + } should produce [IllegalArgumentException] + } + } + } + } +} \ No newline at end of file -- cgit v1.2.3