├── .gitignore ├── README.md ├── chapter10 ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── preownedkittens │ │ │ ├── LogicJavaTest.java │ │ │ └── sbt │ │ │ └── JUnitListener.java │ │ └── scala │ │ ├── LogicSpec.scala │ │ └── LogicSpecification.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ ├── DatabaseMigrationTesting.scala │ ├── ScalastyleReport.scala │ ├── UberJarRunner.scala │ ├── build.properties │ ├── db.scala │ ├── derby.scala │ ├── plugins.sbt │ └── scalastyle-report.html ├── release.sbt └── website │ ├── build.sbt │ ├── dbtest.sbt │ └── src │ ├── it │ ├── resources │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ ├── main │ ├── resources │ │ ├── app │ │ │ └── views │ │ │ │ ├── kittens.scaml │ │ │ │ ├── layouts │ │ │ │ └── default.scaml │ │ │ │ ├── select_attribute.scaml │ │ │ │ └── selected.scaml │ │ ├── application.conf │ │ ├── evolutions │ │ │ └── default │ │ │ │ ├── 1.sql │ │ │ │ └── 2.sql │ │ ├── org │ │ │ └── preownedkittens │ │ │ │ └── database │ │ │ │ ├── Attribute.option.scaml │ │ │ │ └── Kitten.list.scaml │ │ └── public │ │ │ ├── images │ │ │ └── favicon.png │ │ │ └── stylesheets │ │ │ └── main.css │ └── scala │ │ ├── Global.scala │ │ ├── ScalateIntegration.scala │ │ └── database.scala │ └── sql │ ├── create_users_table.sql │ └── show_stats.sql ├── chapter11 ├── depend-plugin │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ └── main │ │ └── scala │ │ └── DependPlugin.scala ├── depend-test │ ├── build.sbt │ └── project │ │ ├── build.properties │ │ └── plugins.sbt ├── scalastyle-plugin │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── HelloPlugin.scala │ │ │ └── ScalastylePlugin.scala │ │ └── sbt-test │ │ └── config │ │ └── does-not-exist │ │ ├── build.sbt │ │ ├── project │ │ └── plugins.sbt │ │ └── test └── scalastyle-test │ ├── build.sbt │ ├── project │ ├── build.properties │ └── plugins.sbt │ ├── scalastyle_config.xml │ └── src │ └── main │ └── scala │ ├── Bar.scala │ ├── Code1.scala │ └── Foo.scala ├── chapter12 ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── logic.scala │ │ │ └── main.scala │ │ ├── test │ │ ├── java │ │ │ └── org │ │ │ │ └── preownedkittens │ │ │ │ ├── LogicJavaTest.java │ │ │ │ └── sbt │ │ │ │ └── JUnitListener.java │ │ └── scala │ │ │ ├── LogicSpec.scala │ │ │ └── LogicSpecification.scala │ │ └── universal │ │ └── conf │ │ └── analytics.properties ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ ├── DatabaseMigrationTesting.scala │ ├── ScalastyleReport.scala │ ├── UberJarRunner.scala │ ├── build.properties │ ├── db.scala │ ├── derby.scala │ ├── plugins.sbt │ └── scalastyle-report.html ├── release.sbt ├── scalastyle-config.xml ├── src │ └── main │ │ └── scala │ │ └── PreownedKittenMain.scala └── website │ ├── build.sbt │ ├── dbtest.sbt │ └── src │ ├── it │ ├── resources │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ ├── main │ ├── resources │ │ ├── app │ │ │ └── views │ │ │ │ ├── kittens.scaml │ │ │ │ ├── layouts │ │ │ │ └── default.scaml │ │ │ │ ├── select_attribute.scaml │ │ │ │ └── selected.scaml │ │ ├── application.conf │ │ ├── evolutions │ │ │ └── default │ │ │ │ ├── 1.sql │ │ │ │ └── 2.sql │ │ ├── org │ │ │ └── preownedkittens │ │ │ │ └── database │ │ │ │ ├── Attribute.option.scaml │ │ │ │ └── Kitten.list.scaml │ │ └── public │ │ │ ├── images │ │ │ └── favicon.png │ │ │ └── stylesheets │ │ │ └── main.css │ └── scala │ │ ├── Global.scala │ │ ├── ScalateIntegration.scala │ │ └── database.scala │ └── sql │ ├── create_users_table.sql │ └── show_stats.sql ├── chapter2 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ └── scala │ │ ├── PreownedKittenMain.scala │ │ ├── logic.scala │ │ └── models.scala │ └── test │ └── scala │ └── LogicSpec.scala ├── chapter3 ├── analytics │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ └── scala │ │ └── LogicSpec.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── PreownedKittenMain.scala ├── chapter4 ├── analytics │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ └── scala │ │ └── LogicSpec.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── PreownedKittenMain.scala ├── chapter5 ├── .gitignore ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── preownedkittens │ │ │ ├── LogicJavaTest.java │ │ │ └── sbt │ │ │ └── JUnitListener.java │ │ └── scala │ │ ├── LogicSpec.scala │ │ └── LogicSpecification.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ └── build.properties └── website │ ├── build.sbt │ └── src │ ├── it │ ├── resources │ │ ├── chromedriver │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ └── main │ ├── resources │ ├── app │ │ └── views │ │ │ ├── kittens.scaml │ │ │ ├── layouts │ │ │ └── default.scaml │ │ │ ├── select_attribute.scaml │ │ │ └── selected.scaml │ ├── application.conf │ ├── evolutions │ │ └── default │ │ │ └── 1.sql │ ├── org │ │ └── preownedkittens │ │ │ └── database │ │ │ ├── Attribute.option.scaml │ │ │ └── Kitten.list.scaml │ └── public │ │ ├── images │ │ └── favicon.png │ │ └── stylesheets │ │ └── main.css │ └── scala │ ├── Global.scala │ ├── ScalateIntegration.scala │ └── database.scala ├── chapter6 ├── .gitignore ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── preownedkittens │ │ │ ├── LogicJavaTest.java │ │ │ └── sbt │ │ │ └── JUnitListener.java │ │ └── scala │ │ ├── LogicSpec.scala │ │ └── LogicSpecification.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ ├── UberJarRunner.scala │ └── build.properties └── website │ ├── build.sbt │ └── src │ ├── it │ ├── resources │ │ ├── chromedriver │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ └── main │ ├── resources │ ├── app │ │ └── views │ │ │ ├── kittens.scaml │ │ │ ├── layouts │ │ │ └── default.scaml │ │ │ ├── select_attribute.scaml │ │ │ └── selected.scaml │ ├── application.conf │ ├── evolutions │ │ └── default │ │ │ └── 1.sql │ ├── org │ │ └── preownedkittens │ │ │ └── database │ │ │ ├── Attribute.option.scaml │ │ │ └── Kitten.list.scaml │ └── public │ │ ├── images │ │ └── favicon.png │ │ └── stylesheets │ │ └── main.css │ ├── scala │ ├── Global.scala │ ├── ScalateIntegration.scala │ └── database.scala │ └── uber │ ├── META-INF │ └── MANIFEST.MF │ ├── play.plugins │ └── reference.conf ├── chapter7 ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── preownedkittens │ │ │ ├── LogicJavaTest.java │ │ │ └── sbt │ │ │ └── JUnitListener.java │ │ └── scala │ │ ├── LogicSpec.scala │ │ └── LogicSpecification.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ ├── DatabaseMigrationTesting.scala │ ├── UberJarRunner.scala │ ├── build.properties │ ├── db.scala │ └── derby.scala └── website │ ├── build.sbt │ ├── dbtest.sbt │ └── src │ ├── it │ ├── resources │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ ├── main │ ├── resources │ │ ├── app │ │ │ └── views │ │ │ │ ├── kittens.scaml │ │ │ │ ├── layouts │ │ │ │ └── default.scaml │ │ │ │ ├── select_attribute.scaml │ │ │ │ └── selected.scaml │ │ ├── application.conf │ │ ├── evolutions │ │ │ └── default │ │ │ │ ├── 1.sql │ │ │ │ └── 2.sql │ │ ├── org │ │ │ └── preownedkittens │ │ │ │ └── database │ │ │ │ ├── Attribute.option.scaml │ │ │ │ └── Kitten.list.scaml │ │ └── public │ │ │ ├── images │ │ │ └── favicon.png │ │ │ └── stylesheets │ │ │ └── main.css │ └── scala │ │ ├── Global.scala │ │ ├── ScalateIntegration.scala │ │ └── database.scala │ └── sql │ ├── create_users_table.sql │ └── show_stats.sql ├── chapter8 ├── .gitignore ├── analytics │ ├── build.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ └── logic.scala │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── preownedkittens │ │ │ ├── LogicJavaTest.java │ │ │ └── sbt │ │ │ └── JUnitListener.java │ │ └── scala │ │ ├── LogicSpec.scala │ │ └── LogicSpecification.scala ├── build.sbt ├── common │ └── src │ │ └── main │ │ └── scala │ │ ├── PreownedKittenMain.scala │ │ └── models.scala ├── project │ ├── DatabaseMigrationTesting.scala │ ├── ScalastyleReport.scala │ ├── UberJarRunner.scala │ ├── build.properties │ ├── db.scala │ ├── derby.scala │ ├── plugins.sbt │ └── scalastyle-report.html ├── scalastyle-config.xml └── website │ ├── build.sbt │ ├── dbtest.sbt │ └── src │ ├── it │ ├── resources │ │ ├── chromedriver │ │ └── chromedriver.exe │ └── scala │ │ └── SeleniumSpec.scala │ ├── main │ ├── resources │ │ ├── app │ │ │ └── views │ │ │ │ ├── kittens.scaml │ │ │ │ ├── layouts │ │ │ │ └── default.scaml │ │ │ │ ├── select_attribute.scaml │ │ │ │ └── selected.scaml │ │ ├── application.conf │ │ ├── evolutions │ │ │ └── default │ │ │ │ ├── 1.sql │ │ │ │ └── 2.sql │ │ ├── org │ │ │ └── preownedkittens │ │ │ │ └── database │ │ │ │ ├── Attribute.option.scaml │ │ │ │ └── Kitten.list.scaml │ │ └── public │ │ │ ├── images │ │ │ └── favicon.png │ │ │ └── stylesheets │ │ │ └── main.css │ └── scala │ │ ├── Global.scala │ │ ├── ScalateIntegration.scala │ │ └── database.scala │ └── sql │ ├── create_users_table.sql │ └── show_stats.sql └── chapter9 ├── bad-dep-plugin ├── build.sbt └── project │ └── build.properties ├── bad-spaces ├── build.sbt └── project │ └── build.properties └── settings-vs-tasks ├── build.sbt └── project └── build.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | logs 3 | *.log 4 | *.markdown 5 | .idea 6 | .idea_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sbt in Action Example code 2 | This project contains the example code for sbt in Action. The code takes the form of a series of sbt builds for a sophisticated website called preowned-kittens.com that handles the resale of pet kittens. The website attempts to find ideal owners by matching buyer survey questions to known statistics of happy pet owners, and behavior characteristics of the kittens. 3 | 4 | The project is split into directories by chapter, and then optionally by section. The final build is located under a directory called `final` for those who wish to see/use the completed product. 5 | 6 | While the website and analysis code itself is rather simplistic and silly, the sbt build should be self-explantory (to those who know sbt) and well documented. 7 | 8 | If you're following along in the book, feel free to run your `sbt` directly against the chapter / section directory you're currently reading, and try things out. 9 | 10 | 11 | ## MIT License 12 | Copyright (c) 2012-2013 Josh Suereth, Matthew Farwell 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /chapter10/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | // specs2 libraries. 2 | 3 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 4 | 5 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 6 | 7 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 8 | 9 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 10 | 11 | fork in Test := true 12 | 13 | // scalacheck 14 | 15 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 16 | 17 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 18 | 19 | // junit 20 | 21 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 22 | 23 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 26 | 27 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 28 | -------------------------------------------------------------------------------- /chapter10/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter10/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter10/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter10/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter10/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | 7 | object LogicSpecification extends Properties("Logic") { 8 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 9 | "Alien","Rough","Tom","Sad","Overweight") 10 | 11 | val genKitten: Gen[Kitten] = for { 12 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 13 | } yield Kitten(1, attributes) 14 | 15 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 16 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 17 | } yield BuyerPreferences(attributes)) 18 | 19 | def matches(x: String, a: Kitten) = 20 | if (a.attributes.contains(x)) 1.0 else 0.0 21 | 22 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 23 | if (b.attributes.size == 0) true 24 | else { 25 | val num = b.attributes.map{matches(_, a)}.sum 26 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /chapter10/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtGit.git 2 | 3 | name := "preowned-kittens" 4 | 5 | // Custom keys for this build. 6 | 7 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 8 | 9 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 10 | 11 | git.baseVersion := "0.1" 12 | 13 | val scalastyleReport = taskKey[File]("creates a report from Scalastyle") 14 | 15 | // Common settings/definitions for the build 16 | 17 | def PreownedKittenProject(name: String): Project = ( 18 | Project(name, file(name)) 19 | .settings( Defaults.itSettings : _*) 20 | .settings(org.scalastyle.sbt.ScalastylePlugin.Settings: _*) 21 | .settings(versionWithGit:_*) 22 | .settings( 23 | organization := "com.preownedkittens", 24 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 25 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 26 | resolvers ++= Seq( 27 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 28 | "teamon.eu Repo" at "http://repo.teamon.eu/" 29 | ), 30 | exportJars := true, 31 | scalastyleReport := { 32 | val result = org.scalastyle.sbt.PluginKeys.scalastyle.toTask("").value 33 | val file = ScalastyleReport.report(target.value / "html-test-report", 34 | "scalastyle-report.html", 35 | (baseDirectory in ThisBuild).value / "project/scalastyle-report.html", 36 | target.value / "scalastyle-result.xml") 37 | println("created report " + file.getAbsolutePath) 38 | file 39 | }, 40 | org.scalastyle.sbt.PluginKeys.config := { 41 | (baseDirectory in ThisBuild).value / "scalastyle-config.xml" 42 | } 43 | ) 44 | .configs(IntegrationTest) 45 | ) 46 | 47 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 48 | 49 | 50 | // Projects in this build 51 | 52 | lazy val common = ( 53 | PreownedKittenProject("common") 54 | settings( 55 | makeVersionProperties := { 56 | val propFile = (resourceManaged in Compile).value / "version.properties" 57 | val content = "version=%s" format (gitHeadCommitSha.value) 58 | IO.write(propFile, content) 59 | Seq(propFile) 60 | }, 61 | resourceGenerators in Compile <+= makeVersionProperties 62 | ) 63 | ) 64 | 65 | val analytics = ( 66 | PreownedKittenProject("analytics") 67 | dependsOn(common) 68 | settings() 69 | ) 70 | 71 | val website = ( 72 | PreownedKittenProject("website") 73 | dependsOn(common, analytics) 74 | settings() 75 | ) 76 | -------------------------------------------------------------------------------- /chapter10/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter10/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter10/project/ScalastyleReport.scala: -------------------------------------------------------------------------------- 1 | 2 | import java.io._ 3 | import scala.xml._ 4 | import org.apache.velocity.VelocityContext 5 | import org.apache.velocity.app.Velocity 6 | import scala.collection.convert.WrapAsJava._ 7 | import java.util.HashMap 8 | 9 | object ScalastyleReport { 10 | case class ScalastyleError(name: String, line: String, level: String, message: String) 11 | 12 | def report(outputDir: File, outputFile: String, templateFile: File, reportXml: File): File = { 13 | // get text contents of an attribute 14 | def attr(node: Node, name: String) = (node \\ ("@" + name)).text 15 | 16 | val xml = XML.loadFile(reportXml) 17 | 18 | // get scalastyle errors from XML 19 | val errors = asJavaCollection((xml \\ "checkstyle" \\ "file").map(f => { 20 | val name = attr(f, "name") 21 | (f \\ "error").map { e => 22 | val line = attr(e, "line") 23 | val severity = attr(e, "severity") 24 | val message = attr(e, "message") 25 | ScalastyleError(name, line, severity, message) 26 | } 27 | }).flatten) 28 | 29 | sbt.IO.createDirectory(outputDir) 30 | 31 | val context = new HashMap[String, Any]() 32 | context.put("results", errors) 33 | 34 | val sw = new StringWriter() 35 | val template = sbt.IO.read(templateFile) 36 | Velocity.evaluate(new VelocityContext(context), sw, "velocity", template) 37 | 38 | val reportFile = new File(outputDir, outputFile) 39 | sbt.IO.write(reportFile, sw.toString()) 40 | reportFile 41 | } 42 | } -------------------------------------------------------------------------------- /chapter10/project/UberJarRunner.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | trait UberJarRunner { 5 | def start(): Unit 6 | def stop(): Unit 7 | } 8 | 9 | class MyUberJarRunner(uberJar: File) extends UberJarRunner { 10 | var p: Option[Process] = None 11 | def start(): Unit = { 12 | p = Some(Fork.java.fork(ForkOptions(), 13 | Seq("-cp", uberJar.getAbsolutePath, "play.core.server.NettyServer"))) 14 | } 15 | def stop(): Unit = p foreach (_.destroy()) 16 | } -------------------------------------------------------------------------------- /chapter10/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter10/project/db.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | 5 | /** This helper represents how we will execute database statements. */ 6 | trait DatabaseHelper { 7 | def runQuery(sql: String, log: Logger): Unit 8 | def tables: List[String] 9 | } 10 | 11 | 12 | object DatabaseHelper { 13 | 14 | import complete.DefaultParsers._ 15 | import complete.{TokenCompletions, Completions, Parser, Completion} 16 | def localFile(base: File): Parser[File] = { 17 | val completions = TokenCompletions.fixed { (seen, level) => 18 | val fileNames = for { 19 | file <- IO.listFiles(base) 20 | name <- IO.relativize(base, file).toSeq 21 | if name startsWith seen 22 | } yield Completion.token(seen, name drop seen.length) 23 | 24 | Completions.strict(fileNames.toSet) 25 | } 26 | val fileName: Parser[String] = 27 | token(NotFileSeparator.* map (_.mkString), completions) 28 | fileName map (n => new File(base, n)) 29 | } 30 | 31 | //TODO - Ok now for the recursive crazy parser... 32 | 33 | 34 | // TODO - Platform specific, or ignore that junk? 35 | val NotFileSeparator = charClass(x => x != java.io.File.separatorChar, "non-file-separator character") 36 | 37 | 38 | val sqlScriptFileDirectory = settingKey[File]("Directory for SQL scripts") 39 | val sqlFileParser: State => Parser[File] = { state => 40 | val extracted = Project extract state 41 | val bd = extracted get sqlScriptFileDirectory 42 | Space ~> localFile(bd) 43 | } 44 | val dbRunScriptFile = inputKey[Unit]("Runs SQL scripts from a directory.") 45 | val dbHelper = taskKey[DatabaseHelper]("") 46 | 47 | val dbSettings: Seq[Setting[_]] = Seq( 48 | dbRunScriptFile := { 49 | val file = sqlFileParser.parsed 50 | val sql = IO.read(file) 51 | val db = dbHelper.value 52 | val log = streams.value.log 53 | val statements = sql split ";" map (_.replaceAll("[\r\n]", "")) map (_.trim) 54 | for { 55 | stmt <- statements 56 | if !stmt.isEmpty 57 | _ = log.info(s"Executing [$stmt]...") 58 | } db.runQuery(stmt, log) 59 | }, 60 | sqlScriptFileDirectory := sourceDirectory.value / "sql" 61 | ) 62 | 63 | } -------------------------------------------------------------------------------- /chapter10/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.4.0") 2 | 3 | resolvers += "sonatype-releases" at "https://oss.sonatype.org/content/repositories/releases/" 4 | 5 | addSbtPlugin("org.scala-sbt.plugins" % "sbt-onejar" % "0.8") 6 | 7 | libraryDependencies ++= Seq( 8 | "org.apache.velocity" % "velocity" % "1.7" 9 | ) 10 | 11 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.6.2") 12 | -------------------------------------------------------------------------------- /chapter10/project/scalastyle-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scalastyle Results 5 | 28 | 29 | 30 |

Scalastyle Results

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | #foreach( ${error} in ${results} ) 39 | 40 | 41 | 42 | 43 | 44 | 45 | #end 46 |
NameLevelMessageLine
${error.name()}${error.level()}${error.message()}${error.line()}
47 | 48 | -------------------------------------------------------------------------------- /chapter10/release.sbt: -------------------------------------------------------------------------------- 1 | import complete.DefaultParsers._ 2 | import complete.Parser 3 | 4 | val checkNoLocalChanges = taskKey[Unit]("checks to see if we have local git changes. Fails if we do.") 5 | 6 | checkNoLocalChanges := { 7 | val dir = baseDirectory.value 8 | val changes = Process("git diff-index --name-only HEAD --", dir) !! streams.value.log 9 | if(!changes.isEmpty) { 10 | val changeMsg = changes.split("[\r\n]+").mkString(" - ","\n - ","\n") 11 | sys.error("Git changes were found: \n" + changeMsg) 12 | } 13 | } 14 | 15 | val integrationTests = taskKey[Unit]("runs integration tests.") 16 | 17 | integrationTests := streams.value.log.info("Integration tests successful") 18 | 19 | def releaseParser(state: State): Parser[String] = { 20 | val version = (Digit ~ chars(".0123456789").*) map { 21 | case (first, rest) => (first +: rest).mkString 22 | } 23 | val complete = (chars("v") ~ token(version, "")) map { 24 | case (v, num) => v + num 25 | } 26 | Space ~> complete 27 | } 28 | 29 | def releaseAction(state: State, version: String): State = { 30 | "checkNoLocalChanges" :: 31 | "clean" :: 32 | ("all test it:test" :: 33 | s"git tag ${version}" :: 34 | "reload" :: 35 | "publish" :: 36 | state) 37 | } 38 | 39 | val releaseHelp = Help( 40 | Seq( 41 | "release" -> "Runs the release script for a given version number" 42 | ), 43 | Map( 44 | "release" -> 45 | """|Runs our release script. This will: 46 | |1. Run all the tests. 47 | |2. Tag the git repo with the version number. 48 | |3. Reload the build with the new version number from the git tag 49 | |4. publish all the artifacts""".stripMargin 50 | ) 51 | ) 52 | 53 | val releaseCommand = Command("release", releaseHelp)(releaseParser)(releaseAction) 54 | 55 | commands += releaseCommand 56 | -------------------------------------------------------------------------------- /chapter10/website/dbtest.sbt: -------------------------------------------------------------------------------- 1 | // Database testing build settings. 2 | 3 | val dbLocation = settingKey[File]("The location of the testing database.") 4 | 5 | dbLocation := target.value / "database" 6 | 7 | val dbHelper = taskKey[DatabaseHelper]("typesafehub/reactive-platform-service") 8 | 9 | dbHelper := derby((fullClasspath in Compile).value, dbLocation.value) 10 | 11 | val dbListTables = taskKey[List[String]]("Prints out all available tables in the database.") 12 | 13 | dbListTables := dbHelper.value.tables 14 | 15 | 16 | val dbQuery = inputKey[Unit]("Runs a query against the database and prints the result") 17 | 18 | val queryParser = { 19 | import complete.DefaultParsers._ 20 | token(any.* map (_.mkString), "") 21 | } 22 | 23 | dbQuery := { 24 | val query = queryParser.parsed 25 | val db = dbHelper.value 26 | val log = streams.value.log 27 | db.runQuery(query, log) 28 | } 29 | 30 | val dbEvolutionTest = inputKey[Unit]("Tests a database evolution") 31 | 32 | DatabaseEvolutionTesting.evolutionsDirectoryDefaultSetting 33 | 34 | dbEvolutionTest := { 35 | //val cmd = DatabaseEvolutionTesting.parser.parsed 36 | //val db = dbHelper.value 37 | //val log = streams.value.log 38 | //DatabaseEvolutionTesting.runCommand(cmd, db, log) 39 | val cmd = DatabaseEvolutionTesting.oldParser.parsed 40 | println(cmd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /chapter10/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter10/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter10/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter10/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter10/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter10/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter10/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import controllers.Scalate 4 | import play.api.mvc.{Controller, Action, RequestHeader} 5 | import play.api.GlobalSettings 6 | import play.core.StaticApplication 7 | import play.navigator.PlayNavigator 8 | import play.api.data._ 9 | import play.api.data.Forms._ 10 | import org.preownedkittens.database._ 11 | 12 | object Routes extends PlayNavigator { 13 | val index = GET on root to redirect("kittens") 14 | val kittens = GET on "kittens" to { () => Application.kittens } 15 | val selected = POST on "selected" to { () => Application.selected } 16 | val purchase = POST on "purchase" to { () => Application.purchase } 17 | } 18 | 19 | object Application extends Controller { 20 | val kittenSelectForm = Form[SelectKitten]( 21 | mapping( 22 | "select1" -> nonEmptyText, 23 | "select2" -> nonEmptyText, 24 | "select3" -> nonEmptyText 25 | )(SelectKitten.apply)(SelectKitten.unapply) 26 | ) 27 | 28 | def kittens = Action { 29 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 30 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 31 | } 32 | 33 | def purchase = TODO 34 | 35 | def selected = Action { request => 36 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 37 | 38 | body.map { map => 39 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 40 | }.getOrElse{ 41 | BadRequest("Expecting form url encoded body") 42 | } 43 | } 44 | 45 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 46 | import org.preownedkittens.Logic._ 47 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 48 | 49 | val kittensWithLikelihood = Kitten.all().map{ k => 50 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 51 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 52 | 53 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 54 | } 55 | 56 | } 57 | 58 | object Global extends App with GlobalSettings { 59 | new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 60 | override def onRouteRequest(request: RequestHeader) = Routes.onRouteRequest(request) 61 | override def onHandlerNotFound(request: RequestHeader) = Routes.onHandlerNotFound(request) 62 | } 63 | -------------------------------------------------------------------------------- /chapter10/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter10/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter10/website/src/sql/create_users_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users (name VARCHAR(255), id INT); 2 | 3 | INSERT INTO users VALUES ('josh', 1); 4 | 5 | INSERT INTO users VALUES ('jimmy', 2); 6 | -------------------------------------------------------------------------------- /chapter10/website/src/sql/show_stats.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM users 2 | -------------------------------------------------------------------------------- /chapter11/depend-plugin/build.sbt: -------------------------------------------------------------------------------- 1 | version := "1.0" 2 | 3 | sbtPlugin := true 4 | 5 | organization := "org.preownedkittens.sbt" 6 | 7 | name := "depend-plugin" 8 | 9 | addSbtPlugin("org.preownedkittens.sbt" % "scalastyle-plugin" % "1.0") 10 | 11 | -------------------------------------------------------------------------------- /chapter11/depend-plugin/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter11/depend-plugin/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter11/depend-plugin/src/main/scala/DependPlugin.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.sbt 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | import complete.DefaultParsers._ 6 | 7 | object DependPlugin extends sbt.AutoPlugin { 8 | import autoImport._ 9 | 10 | override def projectSettings = Seq( 11 | extTask := { 12 | val args: Seq[String] = spaceDelimited("").parsed 13 | streams.value.log.info("Hello depend " + args.mkString(",")) 14 | } 15 | ) 16 | 17 | override def trigger = Plugins.allRequirements 18 | 19 | override def requires = ScalastylePlugin 20 | 21 | object autoImport { 22 | lazy val extTask = InputKey[Unit]("depend", "Prints hello.") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter11/depend-test/build.sbt: -------------------------------------------------------------------------------- 1 | version := "1.0" 2 | 3 | organization := "org.preownedkittens" 4 | 5 | name := "depend-test" 6 | 7 | val root = Project("root", file(".")).enablePlugins(ScalastylePlugin) 8 | -------------------------------------------------------------------------------- /chapter11/depend-test/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter11/depend-test/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.preownedkittens.sbt" % "scalastyle-plugin" % "1.0") 2 | 3 | addSbtPlugin("org.preownedkittens.sbt" % "depend-plugin" % "1.0") 4 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/build.sbt: -------------------------------------------------------------------------------- 1 | version := "1.0" 2 | 3 | sbtPlugin := true 4 | 5 | organization := "org.preownedkittens.sbt" 6 | 7 | name := "scalastyle-plugin" 8 | 9 | libraryDependencies ++= Seq("org.scalastyle" %% "scalastyle" % "0.5.0") 10 | 11 | ScriptedPlugin.scriptedSettings 12 | 13 | scriptedLaunchOpts <++= version apply { version => 14 | Seq("-Xmx1024M", "-XX:MaxPermSize=256M", "-Dplugin.version=" + version) 15 | } 16 | 17 | scriptedBufferLog := false 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies <+= (sbtVersion) { sv => 2 | "org.scala-sbt" % "scripted-plugin" % sv 3 | } 4 | 5 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/src/main/scala/HelloPlugin.scala: -------------------------------------------------------------------------------- 1 | 2 | package org.preownedkittens.sbt 3 | 4 | import sbt._ 5 | import sbt.Keys._ 6 | import complete.DefaultParsers._ 7 | 8 | object HelloPlugin extends sbt.AutoPlugin { 9 | lazy val hello = inputKey[Unit]("Prints Hello world.") 10 | lazy val helloKey = SettingKey[String]("default message for hello") 11 | 12 | override def projectSettings = Seq( 13 | hello := { 14 | val args: Seq[String] = spaceDelimited("").parsed 15 | val sourceDir = (scalaSource in Compile).value 16 | streams.value.log.info("Hello " + helloKey.value + " " + args.mkString(",") + " " + sourceDir.getAbsolutePath) 17 | }, 18 | helloKey := "default message" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/src/sbt-test/config/does-not-exist/build.sbt: -------------------------------------------------------------------------------- 1 | org.preownedkittens.sbt.ScalastylePlugin.projectSettings 2 | 3 | org.preownedkittens.sbt.ScalastylePlugin.scalastyleConfig := file("does-not-exist.xml") 4 | 5 | -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/src/sbt-test/config/does-not-exist/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += "Local Maven Repository" at "file:///dev/repo" 2 | 3 | { 4 | val pluginVersion = System.getProperty("plugin.version") 5 | if(pluginVersion == null) 6 | throw new RuntimeException("""|The system property 'plugin.version' is not defined. 7 | |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) 8 | else addSbtPlugin("org.preownedkittens.sbt" % "scalastyle-plugin" % pluginVersion) 9 | } -------------------------------------------------------------------------------- /chapter11/scalastyle-plugin/src/sbt-test/config/does-not-exist/test: -------------------------------------------------------------------------------- 1 | # test that a missing config file causes error 2 | > clean 3 | -> scalastyle 4 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/build.sbt: -------------------------------------------------------------------------------- 1 | import org.preownedkittens.sbt.ScalastylePlugin._ 2 | 3 | version := "1.0" 4 | 5 | organization := "org.preownedkittens" 6 | 7 | name := "sbt-test" 8 | 9 | org.preownedkittens.sbt.HelloPlugin.projectSettings 10 | 11 | org.preownedkittens.sbt.HelloPlugin.helloKey := "new message" 12 | 13 | org.preownedkittens.sbt.ScalastylePlugin.projectSettings 14 | 15 | scalastyleConfig in Test := file("test.xml") 16 | 17 | scalastyleConfig in Compile := file("scalastyle_config.xml") 18 | 19 | incremental := true 20 | 21 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.preownedkittens.sbt" % "scalastyle-plugin" % "1.0") 2 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/src/main/scala/Bar.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Bar { 4 | def bar() = 55 5 | } 6 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/src/main/scala/Code1.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Bar { 4 | def bar() = 55 5 | } 6 | -------------------------------------------------------------------------------- /chapter11/scalastyle-test/src/main/scala/Foo.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Foo { 4 | def foo() = 55 5 | } 6 | -------------------------------------------------------------------------------- /chapter12/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtNativePackager._ 2 | import NativePackagerKeys._ 3 | 4 | //-------- 5 | // Testing 6 | //-------- 7 | 8 | // specs2 libraries. 9 | 10 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 11 | 12 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 13 | 14 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 15 | 16 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 17 | 18 | // Workaround conflicting definitions in Play 2.2.x 19 | _root_.sbt.Keys.fork in Test := true 20 | 21 | // scalacheck 22 | 23 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 26 | 27 | // junit 28 | 29 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 30 | 31 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 32 | 33 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 34 | 35 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 36 | 37 | 38 | //----------- 39 | // Packaging 40 | //----------- 41 | 42 | mainClass := Some("org.preownedkittens.Analytics") 43 | 44 | packageArchetype.java_server 45 | 46 | bashScriptExtraDefines += """addJava "-Danalytics.properties=${app_home}/../conf/analytics.properties"""" 47 | 48 | batScriptExtraDefines += """set _JAVA_OPTS=%_JAVA_OPTS% -Danalytics.properties=%ANALYTICS_HOME%\\conf\\analytics.properties""" 49 | 50 | maintainer := "Josh Suereth " 51 | 52 | packageSummary := "Analytics server for prewoned-kittens.com" 53 | 54 | packageDescription := """Contains the analytics of kitten-owner compatibilities.""" 55 | 56 | debianPackageDependencies in Debian ++= Seq("java7-runtime-headless", "bash") -------------------------------------------------------------------------------- /chapter12/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter12/analytics/src/main/scala/main.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import java.io._ 4 | import java.util.Properties 5 | 6 | object Analytics { 7 | def main(args: Array[String]): Unit = { 8 | val propsFile = new File(sys.props("analytics.properties")) 9 | val props = new Properties 10 | val in = new FileInputStream(propsFile) 11 | try props.load(in) 12 | finally in.close() 13 | println("Running analytics....") 14 | println(s"* database: ${props.get("database.url")}") 15 | } 16 | } -------------------------------------------------------------------------------- /chapter12/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter12/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter12/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter12/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | import org.scalacheck.Gen._ 7 | import org.scalacheck.Arbitrary._ 8 | 9 | object LogicSpecification extends Properties("Logic") { 10 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 11 | "Alien","Rough","Tom","Sad","Overweight") 12 | 13 | val genKitten: Gen[Kitten] = for { 14 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 15 | } yield Kitten(1, attributes) 16 | 17 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 18 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 19 | } yield BuyerPreferences(attributes)) 20 | 21 | def matches(x: String, a: Kitten) = 22 | if (a.attributes.contains(x)) 1.0 else 0.0 23 | 24 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 25 | if (b.attributes.size == 0) true 26 | else { 27 | val num = b.attributes.map{matches(_, a)}.sum 28 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /chapter12/analytics/src/universal/conf/analytics.properties: -------------------------------------------------------------------------------- 1 | database.url=jdbc:derby:/tmp/dummy.db;create=true 2 | 3 | -------------------------------------------------------------------------------- /chapter12/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | // Custom keys for this build. 4 | 5 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 6 | 7 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 8 | 9 | git.baseVersion := "0.1" 10 | 11 | val scalastyleReport = taskKey[File]("creates a report from Scalastyle") 12 | 13 | // Common settings/definitions for the build 14 | def PreownedKittenProject(name: String): Project = ( 15 | Project(name, file(name)) 16 | .settings( Defaults.itSettings : _*) 17 | .settings(org.scalastyle.sbt.ScalastylePlugin.Settings: _*) 18 | .settings(versionWithGit:_*) 19 | .settings( 20 | organization := "com.preownedkittens", 21 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 22 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 23 | resolvers ++= Seq( 24 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 25 | "teamon.eu Repo" at "http://repo.teamon.eu/" 26 | ), 27 | exportJars := true, 28 | scalastyleReport := { 29 | val result = org.scalastyle.sbt.PluginKeys.scalastyle.toTask("").value 30 | val file = ScalastyleReport.report(target.value / "html-test-report", 31 | "scalastyle-report.html", 32 | (baseDirectory in ThisBuild).value / "project/scalastyle-report.html", 33 | target.value / "scalastyle-result.xml") 34 | println("created report " + file.getAbsolutePath) 35 | file 36 | }, 37 | org.scalastyle.sbt.PluginKeys.config := { 38 | (baseDirectory in ThisBuild).value / "scalastyle-config.xml" 39 | } 40 | ) 41 | .configs(IntegrationTest) 42 | ) 43 | 44 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 45 | 46 | 47 | // Projects in this build 48 | 49 | lazy val common = ( 50 | PreownedKittenProject("common") 51 | settings( 52 | makeVersionProperties := { 53 | val propFile = (resourceManaged in Compile).value / "version.properties" 54 | val content = "version=%s" format (gitHeadCommitSha.value) 55 | IO.write(propFile, content) 56 | Seq(propFile) 57 | }, 58 | resourceGenerators in Compile <+= makeVersionProperties 59 | ) 60 | ) 61 | 62 | val analytics = ( 63 | PreownedKittenProject("analytics") 64 | dependsOn(common) 65 | settings() 66 | ) 67 | 68 | val website = ( 69 | PreownedKittenProject("website") 70 | dependsOn(common, analytics) 71 | settings() 72 | ) 73 | -------------------------------------------------------------------------------- /chapter12/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter12/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter12/project/ScalastyleReport.scala: -------------------------------------------------------------------------------- 1 | 2 | import java.io._ 3 | import scala.xml._ 4 | import org.apache.velocity.VelocityContext 5 | import org.apache.velocity.app.Velocity 6 | import scala.collection.convert.WrapAsJava._ 7 | import java.util.HashMap 8 | import java.util.Collection 9 | 10 | object ScalastyleReport { 11 | case class ScalastyleError(name: String, line: String, level: String, message: String) 12 | 13 | def report(outputDir: File, outputFile: String, templateFile: File, reportXml: File): File = { 14 | // get text contents of an attribute 15 | def attr(node: Node, name: String) = (node \\ ("@" + name)).text 16 | 17 | val xml = XML.loadFile(reportXml) 18 | 19 | // get scalastyle errors from XML 20 | val errors = asJavaCollection((xml \\ "checkstyle" \\ "file").map(f => { 21 | val name = attr(f, "name") 22 | (f \\ "error").map { e => 23 | val line = attr(e, "line") 24 | val severity = attr(e, "severity") 25 | val message = attr(e, "message") 26 | ScalastyleError(name, line, severity, message) 27 | } 28 | }).flatten) 29 | 30 | sbt.IO.createDirectory(outputDir) 31 | 32 | val context = new HashMap[String, Any]() 33 | context.put("results", errors) 34 | 35 | val sw = new StringWriter() 36 | val template = sbt.IO.read(templateFile) 37 | Velocity.evaluate(new VelocityContext(context), sw, "velocity", template) 38 | 39 | val reportFile = new File(outputDir, outputFile) 40 | sbt.IO.write(reportFile, sw.toString()) 41 | reportFile 42 | } 43 | } -------------------------------------------------------------------------------- /chapter12/project/UberJarRunner.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | trait UberJarRunner { 5 | def start(): Unit 6 | def stop(): Unit 7 | } 8 | 9 | class MyUberJarRunner(uberJar: File) extends UberJarRunner { 10 | var p: Option[Process] = None 11 | def start(): Unit = { 12 | p = Some(Fork.java.fork(ForkOptions(), 13 | Seq("-jar", uberJar.getAbsolutePath))) 14 | } 15 | def stop(): Unit = p foreach (_.destroy()) 16 | } -------------------------------------------------------------------------------- /chapter12/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter12/project/db.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | 5 | /** This helper represents how we will execute database statements. */ 6 | trait DatabaseHelper { 7 | def runQuery(sql: String, log: Logger): Unit 8 | def tables: List[String] 9 | } 10 | 11 | 12 | object DatabaseHelper { 13 | 14 | import complete.DefaultParsers._ 15 | import complete.{TokenCompletions, Completions, Parser, Completion} 16 | def localFile(base: File): Parser[File] = { 17 | val completions = TokenCompletions.fixed { (seen, level) => 18 | val fileNames = for { 19 | file <- IO.listFiles(base) 20 | name <- IO.relativize(base, file).toSeq 21 | if name startsWith seen 22 | } yield Completion.token(seen, name drop seen.length) 23 | 24 | Completions.strict(fileNames.toSet) 25 | } 26 | val fileName: Parser[String] = 27 | token(NotFileSeparator.* map (_.mkString), completions) 28 | fileName map (n => new File(base, n)) 29 | } 30 | 31 | //TODO - Ok now for the recursive crazy parser... 32 | 33 | 34 | // TODO - Platform specific, or ignore that junk? 35 | val NotFileSeparator = charClass(x => x != java.io.File.separatorChar, "non-file-separator character") 36 | 37 | 38 | val sqlScriptFileDirectory = settingKey[File]("Directory for SQL scripts") 39 | val sqlFileParser: State => Parser[File] = { state => 40 | val extracted = Project extract state 41 | val bd = extracted get sqlScriptFileDirectory 42 | Space ~> localFile(bd) 43 | } 44 | val dbRunScriptFile = inputKey[Unit]("Runs SQL scripts from a directory.") 45 | val dbHelper = taskKey[DatabaseHelper]("") 46 | 47 | val dbSettings: Seq[Setting[_]] = Seq( 48 | dbRunScriptFile := { 49 | val file = sqlFileParser.parsed 50 | val sql = IO.read(file) 51 | val db = dbHelper.value 52 | val log = streams.value.log 53 | val statements = sql split ";" map (_.replaceAll("[\r\n]", "")) map (_.trim) 54 | for { 55 | stmt <- statements 56 | if !stmt.isEmpty 57 | _ = log.info(s"Executing [$stmt]...") 58 | } db.runQuery(stmt, log) 59 | }, 60 | sqlScriptFileDirectory := sourceDirectory.value / "sql" 61 | ) 62 | 63 | } -------------------------------------------------------------------------------- /chapter12/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.4.0") 2 | 3 | resolvers += "Typesafe Public Repo" at "http://repo.typesafe.com/typesafe/releases" 4 | 5 | resolvers += "sonatype-releases" at "https://oss.sonatype.org/content/repositories/releases/" 6 | 7 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.2.3") 8 | 9 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") 10 | 11 | libraryDependencies ++= Seq( 12 | "org.apache.velocity" % "velocity" % "1.7" 13 | ) 14 | 15 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.6.2") 16 | 17 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.1") 18 | 19 | -------------------------------------------------------------------------------- /chapter12/project/scalastyle-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scalastyle Results 5 | 28 | 29 | 30 |

Scalastyle Results

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | #foreach( ${error} in ${results} ) 39 | 40 | 41 | 42 | 43 | 44 | 45 | #end 46 |
NameLevelMessageLine
${error.name()}${error.level()}${error.message()}${error.line()}
47 | 48 | -------------------------------------------------------------------------------- /chapter12/release.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtGit._ 2 | import complete.DefaultParsers._ 3 | import complete.Parser 4 | 5 | val checkNoLocalChanges = taskKey[Unit]("checks to see if we have local git changes. Fails if we do.") 6 | 7 | checkNoLocalChanges := { 8 | val dir = baseDirectory.value 9 | val changes = Process("git diff-index --name-only HEAD --", dir) !! streams.value.log 10 | if(!changes.isEmpty) { 11 | val changeMsg = changes.split("[\r\n]+").mkString(" - ","\n - ","\n") 12 | sys.error("Git changes were found: \n" + changeMsg) 13 | } 14 | } 15 | 16 | val integrationTests = taskKey[Unit]("runs integration tests.") 17 | 18 | integrationTests := streams.value.log.info("Integration tests successful") 19 | 20 | def releaseParser(state: State): Parser[String] = { 21 | val version = (Digit ~ chars(".0123456789").*) map { 22 | case (first, rest) => (first +: rest).mkString 23 | } 24 | val complete = (chars("v") ~ token(version, "")) map { 25 | case (v, num) => v + num 26 | } 27 | Space ~> complete 28 | } 29 | 30 | def releaseAction(state: State, version: String): State = { 31 | "checkNoLocalChanges" :: 32 | "clean" :: 33 | ("all test it:test" :: 34 | s"git tag ${version}" :: 35 | "reload" :: 36 | "publish" :: 37 | state) 38 | } 39 | 40 | val releaseHelp = Help( 41 | Seq( 42 | "release" -> "Runs the release script for a given version number" 43 | ), 44 | Map( 45 | "release" -> 46 | """|Runs our release script. This will: 47 | |1. Run all the tests. 48 | |2. Tag the git repo with the version number. 49 | |3. Reload the build with the new version number from the git tag 50 | |4. publish all the artifacts""".stripMargin 51 | ) 52 | ) 53 | 54 | val releaseCommand = Command("release", releaseHelp)(releaseParser)(releaseAction) 55 | 56 | commands += releaseCommand 57 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter12/website/build.sbt: -------------------------------------------------------------------------------- 1 | import AssemblyKeys._ 2 | 3 | // Workaround for onejar plugin not being able to use play's default main method 4 | // since it's not in the built jar. 5 | mainClass := Some("Global") 6 | 7 | // These are dependencies for Play 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.play" %% "play" % "2.2.3", 11 | "eu.teamon" %% "play-navigator" % "0.5.0", 12 | "org.webjars" % "jquery" % "1.9.1", 13 | "com.typesafe.play" %% "anorm" % "2.2.3", 14 | "com.typesafe.play" %% "play-jdbc" % "2.2.3", 15 | "org.fusesource.scalate" %% "scalate-core" % "1.6.1", 16 | "org.apache.derby" % "derby" % "10.10.1.1" 17 | ) 18 | 19 | // scalatest 20 | 21 | // Workaround conflicting definitions in Play 2.2.x 22 | _root_.sbt.Keys.fork in IntegrationTest := true 23 | 24 | libraryDependencies += "org.scalatest" %% "scalatest" % "2.0" % "it" 25 | 26 | libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "2.31.0" % "it" 27 | 28 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "it" 29 | 30 | // TODO - cross platform driver. 31 | javaOptions in IntegrationTest += "-Dwebdriver.chrome.driver=" + (baseDirectory.value / "src/it/resources/chromedriver.exe").getAbsolutePath 32 | 33 | testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-h", (target.value / "html-test-report").getAbsolutePath) 34 | 35 | // ------------------ 36 | // Assembly packaging 37 | // ------------------ 38 | 39 | assemblySettings 40 | 41 | mainClass in assembly := Some("Global") 42 | 43 | mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => 44 | { 45 | case "application.conf" => MergeStrategy.concat 46 | case "reference.conf" => MergeStrategy.concat 47 | case "META-INF/spring.tooling" => MergeStrategy.concat 48 | case "overview.html" => MergeStrategy.rename 49 | case x => old(x) 50 | } 51 | } 52 | 53 | excludedJars in assembly <<= (fullClasspath in assembly) map { cp => 54 | cp filter { f => 55 | (f.data.getName contains "commons-logging") || 56 | (f.data.getName contains "sbt-link") 57 | } 58 | } 59 | 60 | addArtifact(Artifact("website", "assembly"), assembly) 61 | 62 | // ------------------- 63 | // Integration testing 64 | // ------------------- 65 | 66 | val uberJarRunner = taskKey[UberJarRunner]("run the uber jar") 67 | 68 | uberJarRunner := new MyUberJarRunner(assembly.value) 69 | 70 | testOptions in IntegrationTest += Tests.Setup { () => uberJarRunner.value.start() } 71 | 72 | testOptions in IntegrationTest += Tests.Cleanup { _ => uberJarRunner.value.stop() } 73 | -------------------------------------------------------------------------------- /chapter12/website/dbtest.sbt: -------------------------------------------------------------------------------- 1 | // Database testing build settings. 2 | 3 | val dbLocation = settingKey[File]("The location of the testing database.") 4 | 5 | dbLocation := target.value / "database" 6 | 7 | val dbHelper = taskKey[DatabaseHelper]("typesafehub/reactive-platform-service") 8 | 9 | dbHelper := derby((fullClasspath in Compile).value, dbLocation.value) 10 | 11 | val dbListTables = taskKey[List[String]]("Prints out all available tables in the database.") 12 | 13 | dbListTables := dbHelper.value.tables 14 | 15 | 16 | val dbQuery = inputKey[Unit]("Runs a query against the database and prints the result") 17 | 18 | val queryParser = { 19 | import complete.DefaultParsers._ 20 | token(any.* map (_.mkString), "") 21 | } 22 | 23 | dbQuery := { 24 | val query = queryParser.parsed 25 | val db = dbHelper.value 26 | val log = streams.value.log 27 | db.runQuery(query, log) 28 | } 29 | 30 | val dbEvolutionTest = inputKey[Unit]("Tests a database evolution") 31 | 32 | DatabaseEvolutionTesting.evolutionsDirectoryDefaultSetting 33 | 34 | dbEvolutionTest := { 35 | //val cmd = DatabaseEvolutionTesting.parser.parsed 36 | //val db = dbHelper.value 37 | //val log = streams.value.log 38 | //DatabaseEvolutionTesting.runCommand(cmd, db, log) 39 | val cmd = DatabaseEvolutionTesting.oldParser.parsed 40 | println(cmd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /chapter12/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter12/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter12/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter12/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter12/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter12/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter12/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import controllers.Assets 4 | import play.api.libs.json.Json 5 | import play.api.mvc.{Controller, Action, RequestHeader} 6 | import play.api.GlobalSettings 7 | import play.core.StaticApplication 8 | import play.navigator._ 9 | import play.navigator.{PlayNavigator, PlayResourcesController} 10 | import scala.collection.mutable 11 | import play.api._ 12 | import play.api.mvc._ 13 | import play.api.data._ 14 | import play.api.data.Forms._ 15 | import org.preownedkittens.database._ 16 | import controllers.Scalate 17 | 18 | object MyRoutes extends PlayNavigator { 19 | val index = GET on root to redirect("kittens") 20 | val kittens = GET on "kittens" to { () => Application.kittens } 21 | val selected = POST on "selected" to { () => Application.selected } 22 | val purchase = POST on "purchase" to { () => Application.purchase } 23 | } 24 | 25 | object Application extends Controller { 26 | val kittenSelectForm = Form[SelectKitten]( 27 | mapping( 28 | "select1" -> nonEmptyText, 29 | "select2" -> nonEmptyText, 30 | "select3" -> nonEmptyText 31 | )(SelectKitten.apply)(SelectKitten.unapply) 32 | ) 33 | 34 | def kittens = Action { 35 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 36 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 37 | } 38 | 39 | def purchase = TODO 40 | 41 | def selected = Action { request => 42 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 43 | 44 | body.map { map => 45 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 46 | }.getOrElse{ 47 | BadRequest("Expecting form url encoded body") 48 | } 49 | } 50 | 51 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 52 | import org.preownedkittens.Logic._ 53 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 54 | 55 | val kittensWithLikelihood = Kitten.all().map{ k => 56 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 57 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 58 | 59 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 60 | } 61 | 62 | } 63 | 64 | object Global extends GlobalSettings { 65 | // TODO - this was only necessary when we were instantiating play with the global object, 66 | // because onejar REQUIRES the main class to exist in the main jar, yay? 67 | def main(args: Array[String]): Unit = { 68 | play.core.server.NettyServer.main(args) 69 | //new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 70 | } 71 | 72 | import concurrent.Future 73 | override def onRouteRequest(request: RequestHeader) = 74 | MyRoutes.onRouteRequest(request) 75 | override def onHandlerNotFound(request: RequestHeader) = 76 | Future.successful(MyRoutes.onHandlerNotFound(request)) 77 | } 78 | -------------------------------------------------------------------------------- /chapter12/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter12/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter12/website/src/sql/create_users_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users (name VARCHAR(255), id INT); 2 | 3 | INSERT INTO users VALUES ('josh', 1); 4 | 5 | INSERT INTO users VALUES ('jimmy', 2); 6 | -------------------------------------------------------------------------------- /chapter12/website/src/sql/show_stats.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM users 2 | -------------------------------------------------------------------------------- /chapter2/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version := "1.0" 4 | 5 | libraryDependencies += "org.specs2" % "specs2_2.10" % "1.14" % "test" 6 | -------------------------------------------------------------------------------- /chapter2/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | if (nums.length > 0) nums.sum / nums.length else 0.0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Seq[String]) 5 | case class BuyerPreferences(attributes: Seq[String]) 6 | -------------------------------------------------------------------------------- /chapter2/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, List("male", "tabby")) 9 | val prefs = BuyerPreferences(List("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 0% when no attributes match" in { 13 | val tabby = Kitten(1, List("male", "tabby")) 14 | val prefs = BuyerPreferences(List("female", "calico")) 15 | val result = Logic.matchLikelihood(tabby, prefs) 16 | result must beLessThan(0.001) 17 | } 18 | "correctly handle an empty BuyerPreferences" in { 19 | val tabby = Kitten(1, List("male", "tabby")) 20 | val prefs = BuyerPreferences(List()) 21 | val result = Logic.matchLikelihood(tabby, prefs) 22 | result.isNaN mustEqual false 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter3/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.length 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter3/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, List("male", "tabby")) 9 | val prefs = BuyerPreferences(List("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 0% when no attributes match" in { 13 | val tabby = Kitten(1, List("male", "tabby")) 14 | val prefs = BuyerPreferences(List("female", "calico")) 15 | val result = Logic.matchLikelihood(tabby, prefs) 16 | result must beLessThan(0.001) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter3/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | // Common settings/definitions for the build 15 | 16 | def PreownedKittenProject(name: String): Project = ( 17 | Project(name, file(name)) 18 | settings( 19 | libraryDependencies += "org.specs2" % "specs2_2.10" % "1.14" % "test" 20 | ) 21 | ) 22 | 23 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 24 | 25 | 26 | // Projects in this build 27 | 28 | lazy val common = ( 29 | PreownedKittenProject("common") 30 | settings( 31 | makeVersionProperties := { 32 | val propFile = (resourceManaged in Compile).value / "version.properties" 33 | val content = "version=%s" format (gitHeadCommitSha.value) 34 | IO.write(propFile, content) 35 | Seq(propFile) 36 | }, 37 | resourceGenerators in Compile <+= makeVersionProperties 38 | ) 39 | ) 40 | 41 | lazy val analytics = ( 42 | PreownedKittenProject("analytics") 43 | dependsOn(common) 44 | settings() 45 | ) 46 | 47 | lazy val website = ( 48 | PreownedKittenProject("website") 49 | dependsOn(common) 50 | settings() 51 | ) 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /chapter3/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter3/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Seq[String]) 5 | case class BuyerPreferences(attributes: Seq[String]) 6 | -------------------------------------------------------------------------------- /chapter3/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter4/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.length 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter4/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, List("male", "tabby")) 9 | val prefs = BuyerPreferences(List("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 0% when no attributes match" in { 13 | val tabby = Kitten(1, List("male", "tabby")) 14 | val prefs = BuyerPreferences(List("female", "calico")) 15 | val result = Logic.matchLikelihood(tabby, prefs) 16 | result must beLessThan(0.001) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter4/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | // Common settings/definitions for the build 15 | 16 | def PreownedKittenProject(name: String): Project = ( 17 | Project(name, file(name)) 18 | settings( 19 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 20 | ) 21 | ) 22 | 23 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 24 | 25 | 26 | // Projects in this build 27 | 28 | lazy val common = ( 29 | PreownedKittenProject("common") 30 | settings( 31 | makeVersionProperties := { 32 | val propFile = (resourceManaged in Compile).value / "version.properties" 33 | val content = "version=%s" format (gitHeadCommitSha.value) 34 | IO.write(propFile, content) 35 | Seq(propFile) 36 | }, 37 | resourceGenerators in Compile <+= makeVersionProperties 38 | ) 39 | ) 40 | 41 | lazy val analytics = ( 42 | PreownedKittenProject("analytics") 43 | dependsOn(common) 44 | settings() 45 | ) 46 | 47 | lazy val website = ( 48 | PreownedKittenProject("website") 49 | dependsOn(common) 50 | settings() 51 | ) -------------------------------------------------------------------------------- /chapter4/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter4/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Seq[String]) 5 | case class BuyerPreferences(attributes: Seq[String]) 6 | -------------------------------------------------------------------------------- /chapter4/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter5/.gitignore: -------------------------------------------------------------------------------- 1 | chromedriver.log 2 | -------------------------------------------------------------------------------- /chapter5/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | // specs2 libraries. 2 | 3 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 4 | 5 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 6 | 7 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 8 | 9 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 10 | 11 | fork in Test := true 12 | 13 | // scalacheck 14 | 15 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 16 | 17 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 18 | 19 | // junit 20 | 21 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 22 | 23 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 26 | 27 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 28 | -------------------------------------------------------------------------------- /chapter5/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter5/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter5/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter5/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter5/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | 7 | object LogicSpecification extends Properties("Logic") { 8 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 9 | "Alien","Rough","Tom","Sad","Overweight") 10 | 11 | val genKitten: Gen[Kitten] = for { 12 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 13 | } yield Kitten(1, attributes) 14 | 15 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 16 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 17 | } yield BuyerPreferences(attributes)) 18 | 19 | def matches(x: String, a: Kitten) = 20 | if (a.attributes.contains(x)) 1.0 else 0.0 21 | 22 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 23 | if (b.attributes.size == 0) true 24 | else { 25 | val num = b.attributes.map{matches(_, a)}.sum 26 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /chapter5/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | // Common settings/definitions for the build 15 | 16 | def PreownedKittenProject(name: String): Project = ( 17 | Project(name, file(name)) 18 | .settings( Defaults.itSettings : _*) 19 | .settings( 20 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 21 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 22 | resolvers ++= Seq( 23 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 24 | "teamon.eu Repo" at "http://repo.teamon.eu/" 25 | ) 26 | ) 27 | .configs(IntegrationTest) 28 | ) 29 | 30 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 31 | 32 | 33 | // Projects in this build 34 | 35 | lazy val common = ( 36 | PreownedKittenProject("common") 37 | settings( 38 | makeVersionProperties := { 39 | val propFile = (resourceManaged in Compile).value / "version.properties" 40 | val content = "version=%s" format (gitHeadCommitSha.value) 41 | IO.write(propFile, content) 42 | Seq(propFile) 43 | }, 44 | resourceGenerators in Compile <+= makeVersionProperties 45 | ) 46 | ) 47 | 48 | val analytics = ( 49 | PreownedKittenProject("analytics") 50 | dependsOn(common) 51 | settings() 52 | ) 53 | 54 | val website = ( 55 | PreownedKittenProject("website") 56 | dependsOn(common, analytics) 57 | settings() 58 | ) 59 | -------------------------------------------------------------------------------- /chapter5/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter5/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter5/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter5/website/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | // These are dependencies for Play 3 | 4 | libraryDependencies ++= Seq( 5 | "play" %% "play" % "2.1.1", 6 | "eu.teamon" %% "play-navigator" % "0.4.0", 7 | "org.webjars" % "jquery" % "1.9.1", 8 | "play" %% "anorm" % "2.1.1", 9 | "play" %% "play-jdbc" % "2.1.1", 10 | "org.fusesource.scalate" %% "scalate-core" % "1.6.1" 11 | ) 12 | 13 | // scalatest 14 | 15 | fork in IntegrationTest := true 16 | 17 | libraryDependencies += "org.scalatest" %% "scalatest" % "2.0" % "it" 18 | 19 | libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "2.31.0" % "it" 20 | 21 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "it" 22 | 23 | def chromeDriver = if (System.getProperty("os.name").startsWith("Windows")) "chromedriver.exe" else "chromedriver" 24 | 25 | javaOptions in IntegrationTest += "-Dwebdriver.chrome.driver=" + (baseDirectory.value / "src/it/resources" / chromeDriver).getAbsolutePath 26 | 27 | testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-h", (target.value / "html-test-report").getAbsolutePath) 28 | 29 | -------------------------------------------------------------------------------- /chapter5/website/src/it/resources/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter5/website/src/it/resources/chromedriver -------------------------------------------------------------------------------- /chapter5/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter5/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter5/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter5/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter5/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter5/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter5/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import play.api.mvc.{Controller, Action, RequestHeader} 4 | import play.api.GlobalSettings 5 | import play.core.StaticApplication 6 | import play.navigator.PlayNavigator 7 | import play.api.data._ 8 | import play.api.data.Forms._ 9 | import org.preownedkittens.database._ 10 | import controllers.Scalate 11 | 12 | object Routes extends PlayNavigator { 13 | val index = GET on root to redirect("kittens") 14 | val kittens = GET on "kittens" to { () => Application.kittens } 15 | val selected = POST on "selected" to { () => Application.selected } 16 | val purchase = POST on "purchase" to { () => Application.purchase } 17 | } 18 | 19 | object Application extends Controller { 20 | val kittenSelectForm = Form[SelectKitten]( 21 | mapping( 22 | "select1" -> nonEmptyText, 23 | "select2" -> nonEmptyText, 24 | "select3" -> nonEmptyText 25 | )(SelectKitten.apply)(SelectKitten.unapply) 26 | ) 27 | 28 | def kittens = Action { 29 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 30 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 31 | } 32 | 33 | def purchase = TODO 34 | 35 | def selected = Action { request => 36 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 37 | 38 | body.map { map => 39 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 40 | }.getOrElse{ 41 | BadRequest("Expecting form url encoded body") 42 | } 43 | } 44 | 45 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 46 | import org.preownedkittens.Logic._ 47 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 48 | 49 | val kittensWithLikelihood = Kitten.all().map{ k => 50 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 51 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 52 | 53 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 54 | } 55 | 56 | } 57 | 58 | object Global extends App with GlobalSettings { 59 | new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 60 | override def onRouteRequest(request: RequestHeader) = Routes.onRouteRequest(request) 61 | override def onHandlerNotFound(request: RequestHeader) = Routes.onHandlerNotFound(request) 62 | } 63 | -------------------------------------------------------------------------------- /chapter5/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter5/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter6/.gitignore: -------------------------------------------------------------------------------- 1 | chromedriver.log 2 | -------------------------------------------------------------------------------- /chapter6/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | // specs2 libraries. 2 | 3 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 4 | 5 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 6 | 7 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 8 | 9 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 10 | 11 | fork in Test := true 12 | 13 | // scalacheck 14 | 15 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 16 | 17 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 18 | 19 | // junit 20 | 21 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 22 | 23 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 26 | 27 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 28 | -------------------------------------------------------------------------------- /chapter6/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter6/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter6/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter6/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter6/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | 7 | object LogicSpecification extends Properties("Logic") { 8 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 9 | "Alien","Rough","Tom","Sad","Overweight") 10 | 11 | val genKitten: Gen[Kitten] = for { 12 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 13 | } yield Kitten(1, attributes) 14 | 15 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 16 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 17 | } yield BuyerPreferences(attributes)) 18 | 19 | def matches(x: String, a: Kitten) = 20 | if (a.attributes.contains(x)) 1.0 else 0.0 21 | 22 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 23 | if (b.attributes.size == 0) true 24 | else { 25 | val num = b.attributes.map{matches(_, a)}.sum 26 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /chapter6/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | // Common settings/definitions for the build 15 | 16 | def PreownedKittenProject(name: String): Project = ( 17 | Project(name, file(name)) 18 | .settings( Defaults.itSettings : _*) 19 | .settings( 20 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 21 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 22 | resolvers ++= Seq( 23 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 24 | "teamon.eu Repo" at "http://repo.teamon.eu/" 25 | ), 26 | exportJars := true 27 | ) 28 | .configs(IntegrationTest) 29 | .settings( 30 | (test in IntegrationTest) := { 31 | val x = (test in Test).value 32 | (test in IntegrationTest).value 33 | } 34 | ) 35 | ) 36 | 37 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 38 | 39 | 40 | // Projects in this build 41 | 42 | lazy val common = ( 43 | PreownedKittenProject("common") 44 | settings( 45 | makeVersionProperties := { 46 | val propFile = (resourceManaged in Compile).value / "version.properties" 47 | val content = "version=%s" format (gitHeadCommitSha.value) 48 | IO.write(propFile, content) 49 | Seq(propFile) 50 | }, 51 | resourceGenerators in Compile <+= makeVersionProperties 52 | ) 53 | ) 54 | 55 | val analytics = ( 56 | PreownedKittenProject("analytics") 57 | dependsOn(common) 58 | settings() 59 | ) 60 | 61 | val website = ( 62 | PreownedKittenProject("website") 63 | dependsOn(common, analytics) 64 | settings() 65 | ) 66 | -------------------------------------------------------------------------------- /chapter6/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter6/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter6/project/UberJarRunner.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | trait UberJarRunner { 4 | def start(): Unit 5 | def stop(): Unit 6 | } 7 | 8 | class MyUberJarRunner(uberJar: File) extends UberJarRunner { 9 | var p: Option[Process] = None 10 | def start(): Unit = { 11 | p = Some(Fork.java.fork(ForkOptions(), 12 | Seq("-cp", uberJar.getAbsolutePath, "Global"))) 13 | } 14 | def stop(): Unit = p foreach (_.destroy()) 15 | } 16 | -------------------------------------------------------------------------------- /chapter6/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter6/website/src/it/resources/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter6/website/src/it/resources/chromedriver -------------------------------------------------------------------------------- /chapter6/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter6/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter6/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter6/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter6/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter6/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter6/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import controllers.Scalate 4 | import play.api.mvc.{Controller, Action, RequestHeader} 5 | import play.api.GlobalSettings 6 | import play.core.StaticApplication 7 | import play.navigator.PlayNavigator 8 | import play.api.data._ 9 | import play.api.data.Forms._ 10 | import org.preownedkittens.database._ 11 | 12 | 13 | object Routes extends PlayNavigator { 14 | val index = GET on root to redirect("kittens") 15 | val kittens = GET on "kittens" to { () => Application.kittens } 16 | val selected = POST on "selected" to { () => Application.selected } 17 | val purchase = POST on "purchase" to { () => Application.purchase } 18 | } 19 | 20 | object Application extends Controller { 21 | val kittenSelectForm = Form[SelectKitten]( 22 | mapping( 23 | "select1" -> nonEmptyText, 24 | "select2" -> nonEmptyText, 25 | "select3" -> nonEmptyText 26 | )(SelectKitten.apply)(SelectKitten.unapply) 27 | ) 28 | 29 | def kittens = Action { 30 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 31 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 32 | } 33 | 34 | def purchase = TODO 35 | 36 | def selected = Action { request => 37 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 38 | 39 | body.map { map => 40 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 41 | }.getOrElse{ 42 | BadRequest("Expecting form url encoded body") 43 | } 44 | } 45 | 46 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 47 | import org.preownedkittens.Logic._ 48 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 49 | 50 | val kittensWithLikelihood = Kitten.all().map{ k => 51 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 52 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 53 | 54 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 55 | } 56 | 57 | } 58 | 59 | object Global extends App with GlobalSettings { 60 | new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 61 | override def onRouteRequest(request: RequestHeader) = Routes.onRouteRequest(request) 62 | override def onHandlerNotFound(request: RequestHeader) = Routes.onHandlerNotFound(request) 63 | } 64 | -------------------------------------------------------------------------------- /chapter6/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter6/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter6/website/src/main/uber/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: Global 3 | -------------------------------------------------------------------------------- /chapter6/website/src/main/uber/play.plugins: -------------------------------------------------------------------------------- 1 | 200:play.api.db.BoneCPPlugin 2 | 500:play.api.db.evolutions.EvolutionsPlugin 3 | 100:play.api.i18n.MessagesPlugin 4 | 600:play.api.cache.EhCachePlugin 5 | 1000:play.api.libs.concurrent.AkkaPlugin 6 | 10000:play.api.GlobalPlugin 7 | 8 | -------------------------------------------------------------------------------- /chapter7/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | // specs2 libraries. 2 | 3 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 4 | 5 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 6 | 7 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 8 | 9 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 10 | 11 | fork in Test := true 12 | 13 | // scalacheck 14 | 15 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 16 | 17 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 18 | 19 | // junit 20 | 21 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 22 | 23 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 26 | 27 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 28 | -------------------------------------------------------------------------------- /chapter7/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter7/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter7/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter7/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter7/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | 7 | object LogicSpecification extends Properties("Logic") { 8 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 9 | "Alien","Rough","Tom","Sad","Overweight") 10 | 11 | val genKitten: Gen[Kitten] = for { 12 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 13 | } yield Kitten(1, attributes) 14 | 15 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 16 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 17 | } yield BuyerPreferences(attributes)) 18 | 19 | def matches(x: String, a: Kitten) = 20 | if (a.attributes.contains(x)) 1.0 else 0.0 21 | 22 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 23 | if (b.attributes.size == 0) true 24 | else { 25 | val num = b.attributes.map{matches(_, a)}.sum 26 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /chapter7/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | // Common settings/definitions for the build 15 | 16 | def PreownedKittenProject(name: String): Project = ( 17 | Project(name, file(name)) 18 | .settings( Defaults.itSettings : _*) 19 | .settings( 20 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 21 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 22 | resolvers ++= Seq( 23 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 24 | "teamon.eu Repo" at "http://repo.teamon.eu/" 25 | ), 26 | exportJars := true 27 | ) 28 | .configs(IntegrationTest) 29 | ) 30 | 31 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 32 | 33 | 34 | // Projects in this build 35 | 36 | lazy val common = ( 37 | PreownedKittenProject("common") 38 | settings( 39 | makeVersionProperties := { 40 | val propFile = (resourceManaged in Compile).value / "version.properties" 41 | val content = "version=%s" format (gitHeadCommitSha.value) 42 | IO.write(propFile, content) 43 | Seq(propFile) 44 | }, 45 | resourceGenerators in Compile <+= makeVersionProperties 46 | ) 47 | ) 48 | 49 | val analytics = ( 50 | PreownedKittenProject("analytics") 51 | dependsOn(common) 52 | settings() 53 | ) 54 | 55 | val website = ( 56 | PreownedKittenProject("website") 57 | dependsOn(common, analytics) 58 | settings() 59 | ) 60 | -------------------------------------------------------------------------------- /chapter7/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter7/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter7/project/UberJarRunner.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | trait UberJarRunner { 4 | def start(): Unit 5 | def stop(): Unit 6 | } 7 | 8 | class MyUberJarRunner(uberJar: File) extends UberJarRunner { 9 | var p: Option[Process] = None 10 | def start(): Unit = { 11 | p = Some(Fork.java.fork(ForkOptions(), 12 | Seq("-cp", uberJar.getAbsolutePath, "play.core.server.NettyServer"))) 13 | } 14 | def stop(): Unit = p foreach (_.destroy()) 15 | } -------------------------------------------------------------------------------- /chapter7/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter7/project/db.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | 5 | /** This helper represents how we will execute database statements. */ 6 | trait DatabaseHelper { 7 | def runQuery(sql: String, log: Logger): Unit 8 | def tables: List[String] 9 | } 10 | 11 | 12 | object DatabaseHelper { 13 | 14 | import complete.DefaultParsers._ 15 | import complete.{TokenCompletions, Completions, Parser, Completion} 16 | def localFile(base: File): Parser[File] = { 17 | val completions = TokenCompletions.fixed { (seen, level) => 18 | val fileNames = for { 19 | file <- IO.listFiles(base) 20 | name <- IO.relativize(base, file).toSeq 21 | if name startsWith seen 22 | } yield Completion.token(seen, name drop seen.length) 23 | 24 | Completions.strict(fileNames.toSet) 25 | } 26 | val fileName: Parser[String] = 27 | token(NotFileSeparator.* map (_.mkString), completions) 28 | fileName map (n => new File(base, n)) 29 | } 30 | 31 | //TODO - Ok now for the recursive crazy parser... 32 | 33 | 34 | // TODO - Platform specific, or ignore that junk? 35 | val NotFileSeparator = charClass(x => x != java.io.File.separatorChar, "non-file-separator character") 36 | 37 | 38 | val sqlScriptFileDirectory = settingKey[File]("Directory for SQL scripts") 39 | val sqlFileParser: State => Parser[File] = { state => 40 | val extracted = Project extract state 41 | val bd = extracted get sqlScriptFileDirectory 42 | Space ~> localFile(bd) 43 | } 44 | val dbRunScriptFile = inputKey[Unit]("Runs SQL scripts from a directory.") 45 | val dbHelper = taskKey[DatabaseHelper]("") 46 | 47 | val dbSettings: Seq[Setting[_]] = Seq( 48 | dbRunScriptFile := { 49 | val file = sqlFileParser.parsed 50 | val sql = IO.read(file) 51 | val db = dbHelper.value 52 | val log = streams.value.log 53 | val statements = sql split ";" map (_.replaceAll("[\r\n]", "")) map (_.trim) 54 | for { 55 | stmt <- statements 56 | if !stmt.isEmpty 57 | _ = log.info(s"Executing [$stmt]...") 58 | } db.runQuery(stmt, log) 59 | }, 60 | sqlScriptFileDirectory := sourceDirectory.value / "sql" 61 | ) 62 | 63 | } -------------------------------------------------------------------------------- /chapter7/website/dbtest.sbt: -------------------------------------------------------------------------------- 1 | // Database testing build settings. 2 | 3 | val dbLocation = settingKey[File]("The location of the testing database.") 4 | 5 | dbLocation := target.value / "database" 6 | 7 | val dbHelper = taskKey[DatabaseHelper]("typesafehub/reactive-platform-service") 8 | 9 | dbHelper := derby((fullClasspath in Compile).value, dbLocation.value) 10 | 11 | val dbListTables = taskKey[List[String]]("Prints out all available tables in the database.") 12 | 13 | dbListTables := dbHelper.value.tables 14 | 15 | 16 | val dbQuery = inputKey[Unit]("Runs a query against the database and prints the result") 17 | 18 | val queryParser = { 19 | import complete.DefaultParsers._ 20 | token(any.* map (_.mkString), "") 21 | } 22 | 23 | dbQuery := { 24 | val query = queryParser.parsed 25 | val db = dbHelper.value 26 | val log = streams.value.log 27 | db.runQuery(query, log) 28 | } 29 | 30 | val dbEvolutionTest = inputKey[Unit]("Tests a database evolution") 31 | 32 | DatabaseEvolutionTesting.evolutionsDirectoryDefaultSetting 33 | 34 | dbEvolutionTest := { 35 | //val cmd = DatabaseEvolutionTesting.parser.parsed 36 | //val db = dbHelper.value 37 | //val log = streams.value.log 38 | //DatabaseEvolutionTesting.runCommand(cmd, db, log) 39 | val cmd = DatabaseEvolutionTesting.oldParser.parsed 40 | println(cmd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /chapter7/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter7/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter7/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter7/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter7/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter7/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter7/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import controllers.Scalate 4 | import play.api.mvc.{Controller, Action, RequestHeader} 5 | import play.api.GlobalSettings 6 | import play.core.StaticApplication 7 | import play.navigator.PlayNavigator 8 | import play.api.data._ 9 | import play.api.data.Forms._ 10 | import org.preownedkittens.database._ 11 | 12 | object Routes extends PlayNavigator { 13 | val index = GET on root to redirect("kittens") 14 | val kittens = GET on "kittens" to { () => Application.kittens } 15 | val selected = POST on "selected" to { () => Application.selected } 16 | val purchase = POST on "purchase" to { () => Application.purchase } 17 | } 18 | 19 | object Application extends Controller { 20 | val kittenSelectForm = Form[SelectKitten]( 21 | mapping( 22 | "select1" -> nonEmptyText, 23 | "select2" -> nonEmptyText, 24 | "select3" -> nonEmptyText 25 | )(SelectKitten.apply)(SelectKitten.unapply) 26 | ) 27 | 28 | def kittens = Action { 29 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 30 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 31 | } 32 | 33 | def purchase = TODO 34 | 35 | def selected = Action { request => 36 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 37 | 38 | body.map { map => 39 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 40 | }.getOrElse{ 41 | BadRequest("Expecting form url encoded body") 42 | } 43 | } 44 | 45 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 46 | import org.preownedkittens.Logic._ 47 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 48 | 49 | val kittensWithLikelihood = Kitten.all().map{ k => 50 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 51 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 52 | 53 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 54 | } 55 | 56 | } 57 | 58 | object Global extends App with GlobalSettings { 59 | new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 60 | override def onRouteRequest(request: RequestHeader) = Routes.onRouteRequest(request) 61 | override def onHandlerNotFound(request: RequestHeader) = Routes.onHandlerNotFound(request) 62 | } 63 | -------------------------------------------------------------------------------- /chapter7/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter7/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter7/website/src/sql/create_users_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users (name VARCHAR(255), id INT); 2 | 3 | INSERT INTO users VALUES ('josh', 1); 4 | 5 | INSERT INTO users VALUES ('jimmy', 2); 6 | -------------------------------------------------------------------------------- /chapter7/website/src/sql/show_stats.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM users 2 | -------------------------------------------------------------------------------- /chapter8/.gitignore: -------------------------------------------------------------------------------- 1 | chromedriver.log 2 | -------------------------------------------------------------------------------- /chapter8/analytics/build.sbt: -------------------------------------------------------------------------------- 1 | // specs2 libraries. 2 | 3 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test" 4 | 5 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "test" 6 | 7 | testOptions += Tests.Argument(TestFrameworks.Specs2, "html") 8 | 9 | javaOptions in Test += "-Dspecs2.outDir=" + (target.value / "generated/test-reports").getAbsolutePath 10 | 11 | fork in Test := true 12 | 13 | // scalacheck 14 | 15 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" 16 | 17 | testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-s", "500") 18 | 19 | // junit 20 | 21 | libraryDependencies += "junit" % "junit" % "4.11" % "test" 22 | 23 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 24 | 25 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-n", "--run-listener=com.preownedkittens.sbt.JUnitListener") 26 | 27 | javaOptions in Test += "-Djunit.output.file=" + (target.value / "generated/junit.html").getAbsolutePath 28 | -------------------------------------------------------------------------------- /chapter8/analytics/src/main/scala/logic.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object Logic { 4 | /** Determines the match likelihood and returns % match. */ 5 | def matchLikelihood(kitten: Kitten, buyer: BuyerPreferences): Double = { 6 | val matches = buyer.attributes.toList map { attribute => 7 | kitten.attributes contains attribute 8 | } 9 | val nums = matches map { b => if(b) 1.0 else 0.0 } 10 | nums.sum / nums.size 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter8/analytics/src/test/java/org/preownedkittens/LogicJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.junit.*; 4 | import scala.collection.immutable.*; 5 | 6 | public class LogicJavaTest { 7 | @Test 8 | public void testKitten() { 9 | Kitten kitten = new Kitten(1, new HashSet()); 10 | // in chapter 5 we have Assert.assertEquals(1, kitten.attributes().size()); 11 | // but as part of the chapter, we correct it - this test should pass 12 | Assert.assertEquals(0, kitten.attributes().size()); 13 | } 14 | } -------------------------------------------------------------------------------- /chapter8/analytics/src/test/java/org/preownedkittens/sbt/JUnitListener.java: -------------------------------------------------------------------------------- 1 | package com.preownedkittens.sbt; 2 | 3 | import org.junit.*; 4 | import java.io.*; 5 | import org.junit.runner.*; 6 | import org.junit.runner.notification.*; 7 | 8 | public class JUnitListener extends RunListener { 9 | private PrintWriter pw; 10 | private boolean testFailed; 11 | private String outputFile = System.getProperty("junit.output.file"); 12 | 13 | public void testRunStarted(Description description) throws Exception { 14 | pw = new PrintWriter(new FileWriter(outputFile)); 15 | pw.println("JUnit report"); 16 | } 17 | public void testRunFinished(Result result) throws Exception { 18 | pw.println(""); 19 | pw.close(); 20 | 21 | } 22 | public void testStarted(Description description) throws Exception { 23 | pw.print("

Test " + description.getDisplayName() + " "); 24 | testFailed = false; 25 | 26 | } 27 | public void testFinished(Description description) throws Exception { 28 | if (!testFailed) { 29 | pw.print("OK"); 30 | } 31 | pw.println("

"); 32 | } 33 | public void testFailure(Failure failure) throws Exception { 34 | testFailed = true; 35 | pw.print("FAILED!"); 36 | } 37 | public void testAssumptionFailure(Failure failure) { 38 | pw.print("ASSUMPTION FAILURE"); 39 | } 40 | public void testIgnored(Description description) throws Exception { 41 | pw.print("IGNORED"); 42 | } 43 | } -------------------------------------------------------------------------------- /chapter8/analytics/src/test/scala/LogicSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | object LogicSpec extends Specification { 6 | "The 'matchLikelihood' method" should { 7 | "be 100% when all attributes match" in { 8 | val tabby = Kitten(1, Set("male", "tabby")) 9 | val prefs = BuyerPreferences(Set("male", "tabby")) 10 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 11 | } 12 | "be 100% when all attributes match (with duplicates)" in { 13 | val tabby = Kitten(1, Set("male", "tabby", "male")) 14 | val prefs = BuyerPreferences(Set("male", "tabby", "tabby")) 15 | Logic.matchLikelihood(tabby, prefs) must beGreaterThan(0.999) 16 | } 17 | "be 0% when no attributes match" in { 18 | val tabby = Kitten(1, Set("male", "tabby")) 19 | val prefs = BuyerPreferences(Set("female", "calico")) 20 | val result = Logic.matchLikelihood(tabby, prefs) 21 | result must beLessThan(0.001) 22 | } 23 | "be 66% when two from three attributes match" in { 24 | val tabby = Kitten(1, Set("female", "calico", "thin")) 25 | val prefs = BuyerPreferences(Set("female", "calico", "overweight")) 26 | val result = Logic.matchLikelihood(tabby, prefs) 27 | result must beBetween(0.66, 0.67) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter8/analytics/src/test/scala/LogicSpecification.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop.forAll 5 | import org.scalacheck._ 6 | 7 | object LogicSpecification extends Properties("Logic") { 8 | val allAttributes = Array("Harlequin","Tortoiseshell","Siamese", 9 | "Alien","Rough","Tom","Sad","Overweight") 10 | 11 | val genKitten: Gen[Kitten] = for { 12 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 13 | } yield Kitten(1, attributes) 14 | 15 | val genBuyerPreferences: Gen[BuyerPreferences] = (for { 16 | attributes <- Gen.containerOf[Set,String](Gen.oneOf(allAttributes)) 17 | } yield BuyerPreferences(attributes)) 18 | 19 | def matches(x: String, a: Kitten) = 20 | if (a.attributes.contains(x)) 1.0 else 0.0 21 | 22 | property("matchLikelihood") = forAll(genKitten, genBuyerPreferences)((a: Kitten, b: BuyerPreferences) => { 23 | if (b.attributes.size == 0) true 24 | else { 25 | val num = b.attributes.map{matches(_, a)}.sum 26 | num / b.attributes.size - Logic.matchLikelihood(a, b) < 0.001 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /chapter8/build.sbt: -------------------------------------------------------------------------------- 1 | name := "preowned-kittens" 2 | 3 | version in ThisBuild := "1.0" 4 | 5 | organization in ThisBuild := "com.preownedkittens" 6 | 7 | // Custom keys for this build. 8 | 9 | val gitHeadCommitSha = taskKey[String]("Determines the current git commit SHA") 10 | 11 | val makeVersionProperties = taskKey[Seq[File]]("Creates a version.properties file we can find at runtime.") 12 | 13 | 14 | val scalastyleReport = taskKey[File]("creates a report from Scalastyle") 15 | 16 | // Common settings/definitions for the build 17 | 18 | def PreownedKittenProject(name: String): Project = ( 19 | Project(name, file(name)) 20 | .settings( Defaults.itSettings : _*) 21 | .settings(org.scalastyle.sbt.ScalastylePlugin.Settings: _*) 22 | .settings( 23 | libraryDependencies += "org.specs2" %% "specs2" % "1.14" % "test", 24 | javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6"), 25 | resolvers ++= Seq( 26 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 27 | "teamon.eu Repo" at "http://repo.teamon.eu/" 28 | ), 29 | exportJars := true, 30 | scalastyleReport := { 31 | val result = org.scalastyle.sbt.PluginKeys.scalastyle.toTask("").value 32 | val file = ScalastyleReport.report(target.value / "html-test-report", 33 | "scalastyle-report.html", 34 | (baseDirectory in ThisBuild).value / "project/scalastyle-report.html", 35 | target.value / "scalastyle-result.xml") 36 | println("created report " + file.getAbsolutePath) 37 | file 38 | }, 39 | org.scalastyle.sbt.PluginKeys.config := { 40 | (baseDirectory in ThisBuild).value / "scalastyle-config.xml" 41 | } 42 | ) 43 | .configs(IntegrationTest) 44 | ) 45 | 46 | gitHeadCommitSha in ThisBuild := Process("git rev-parse HEAD").lines.head 47 | 48 | 49 | // Projects in this build 50 | 51 | lazy val common = ( 52 | PreownedKittenProject("common") 53 | settings( 54 | makeVersionProperties := { 55 | val propFile = (resourceManaged in Compile).value / "version.properties" 56 | val content = "version=%s" format (gitHeadCommitSha.value) 57 | IO.write(propFile, content) 58 | Seq(propFile) 59 | }, 60 | resourceGenerators in Compile <+= makeVersionProperties 61 | ) 62 | ) 63 | 64 | val analytics = ( 65 | PreownedKittenProject("analytics") 66 | dependsOn(common) 67 | settings() 68 | ) 69 | 70 | val website = ( 71 | PreownedKittenProject("website") 72 | dependsOn(common, analytics) 73 | settings() 74 | ) 75 | -------------------------------------------------------------------------------- /chapter8/common/src/main/scala/PreownedKittenMain.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | object PreownedKittenMain extends App { 4 | println("Hello, sbt world!") 5 | } 6 | -------------------------------------------------------------------------------- /chapter8/common/src/main/scala/models.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens 2 | 3 | case class Kitten(id: Long, 4 | attributes: Set[String]) 5 | case class BuyerPreferences(attributes: Set[String]) 6 | -------------------------------------------------------------------------------- /chapter8/project/ScalastyleReport.scala: -------------------------------------------------------------------------------- 1 | 2 | import java.io._ 3 | import scala.xml._ 4 | import org.apache.velocity.VelocityContext 5 | import org.apache.velocity.app.Velocity 6 | import scala.collection.convert.WrapAsJava._ 7 | import java.util.HashMap 8 | 9 | object ScalastyleReport { 10 | case class ScalastyleError(name: String, line: String, level: String, message: String) 11 | 12 | def report(outputDir: File, outputFile: String, templateFile: File, reportXml: File): File = { 13 | // get text contents of an attribute 14 | def attr(node: Node, name: String) = (node \\ ("@" + name)).text 15 | 16 | val xml = XML.loadFile(reportXml) 17 | 18 | // get scalastyle errors from XML 19 | val errors = asJavaCollection((xml \\ "checkstyle" \\ "file").map(f => { 20 | val name = attr(f, "name") 21 | (f \\ "error").map { e => 22 | val line = attr(e, "line") 23 | val severity = attr(e, "severity") 24 | val message = attr(e, "message") 25 | ScalastyleError(name, line, severity, message) 26 | } 27 | }).flatten) 28 | 29 | sbt.IO.createDirectory(outputDir) 30 | 31 | val context = new HashMap[String, Any]() 32 | context.put("results", errors) 33 | 34 | val sw = new StringWriter() 35 | val template = sbt.IO.read(templateFile) 36 | Velocity.evaluate(new VelocityContext(context), sw, "velocity", template) 37 | 38 | val reportFile = new File(outputDir, outputFile) 39 | sbt.IO.write(reportFile, sw.toString()) 40 | reportFile 41 | } 42 | } -------------------------------------------------------------------------------- /chapter8/project/UberJarRunner.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | trait UberJarRunner { 5 | def start(): Unit 6 | def stop(): Unit 7 | } 8 | 9 | class MyUberJarRunner(uberJar: File) extends UberJarRunner { 10 | var p: Option[Process] = None 11 | def start(): Unit = { 12 | p = Some(Fork.java.fork(ForkOptions(), 13 | Seq("-jar", uberJar.getAbsolutePath))) 14 | } 15 | def stop(): Unit = p foreach (_.destroy()) 16 | } -------------------------------------------------------------------------------- /chapter8/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter8/project/db.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | 5 | /** This helper represents how we will execute database statements. */ 6 | trait DatabaseHelper { 7 | def runQuery(sql: String, log: Logger): Unit 8 | def tables: List[String] 9 | } 10 | 11 | 12 | object DatabaseHelper { 13 | 14 | import complete.DefaultParsers._ 15 | import complete.{TokenCompletions, Completions, Parser, Completion} 16 | def localFile(base: File): Parser[File] = { 17 | val completions = TokenCompletions.fixed { (seen, level) => 18 | val fileNames = for { 19 | file <- IO.listFiles(base) 20 | name <- IO.relativize(base, file).toSeq 21 | if name startsWith seen 22 | } yield Completion.token(seen, name drop seen.length) 23 | 24 | Completions.strict(fileNames.toSet) 25 | } 26 | val fileName: Parser[String] = 27 | token(NotFileSeparator.* map (_.mkString), completions) 28 | fileName map (n => new File(base, n)) 29 | } 30 | 31 | //TODO - Ok now for the recursive crazy parser... 32 | 33 | 34 | // TODO - Platform specific, or ignore that junk? 35 | val NotFileSeparator = charClass(x => x != java.io.File.separatorChar, "non-file-separator character") 36 | 37 | 38 | val sqlScriptFileDirectory = settingKey[File]("Directory for SQL scripts") 39 | val sqlFileParser: State => Parser[File] = { state => 40 | val extracted = Project extract state 41 | val bd = extracted get sqlScriptFileDirectory 42 | Space ~> localFile(bd) 43 | } 44 | val dbRunScriptFile = inputKey[Unit]("Runs SQL scripts from a directory.") 45 | val dbHelper = taskKey[DatabaseHelper]("") 46 | 47 | val dbSettings: Seq[Setting[_]] = Seq( 48 | dbRunScriptFile := { 49 | val file = sqlFileParser.parsed 50 | val sql = IO.read(file) 51 | val db = dbHelper.value 52 | val log = streams.value.log 53 | val statements = sql split ";" map (_.replaceAll("[\r\n]", "")) map (_.trim) 54 | for { 55 | stmt <- statements 56 | if !stmt.isEmpty 57 | _ = log.info(s"Executing [$stmt]...") 58 | } db.runQuery(stmt, log) 59 | }, 60 | sqlScriptFileDirectory := sourceDirectory.value / "sql" 61 | ) 62 | 63 | } -------------------------------------------------------------------------------- /chapter8/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.4.0") 2 | 3 | resolvers += "sonatype-releases" at "https://oss.sonatype.org/content/repositories/releases/" 4 | 5 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") 6 | 7 | libraryDependencies ++= Seq( 8 | "org.apache.velocity" % "velocity" % "1.7" 9 | ) -------------------------------------------------------------------------------- /chapter8/project/scalastyle-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scalastyle Results 5 | 28 | 29 | 30 |

Scalastyle Results

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | #foreach( ${error} in ${results} ) 39 | 40 | 41 | 42 | 43 | 44 | 45 | #end 46 |
NameLevelMessageLine
${error.name()}${error.level()}${error.message()}${error.line()}
47 | 48 | -------------------------------------------------------------------------------- /chapter8/website/build.sbt: -------------------------------------------------------------------------------- 1 | import AssemblyKeys._ 2 | 3 | // These are dependencies for Play 4 | 5 | libraryDependencies ++= Seq( 6 | "play" %% "play" % "2.1.1", 7 | "eu.teamon" %% "play-navigator" % "0.4.0", 8 | "org.webjars" % "jquery" % "1.9.1", 9 | "play" %% "anorm" % "2.1.1", 10 | "play" %% "play-jdbc" % "2.1.1", 11 | "org.fusesource.scalate" %% "scalate-core" % "1.6.1", 12 | "org.apache.derby" % "derby" % "10.10.1.1" 13 | ) 14 | 15 | // scalatest 16 | 17 | fork in IntegrationTest := true 18 | 19 | libraryDependencies += "org.scalatest" %% "scalatest" % "2.0" % "it" 20 | 21 | libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "2.31.0" % "it" 22 | 23 | libraryDependencies += "org.pegdown" % "pegdown" % "1.0.2" % "it" 24 | 25 | def chromeDriver = if (System.getProperty("os.name").startsWith("Windows")) "chromedriver.exe" else "chromedriver" 26 | 27 | javaOptions in IntegrationTest += "-Dwebdriver.chrome.driver=" + (baseDirectory.value / "src/it/resources" / chromeDriver).getAbsolutePath 28 | 29 | testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-h", (target.value / "html-test-report").getAbsolutePath) 30 | 31 | // ------------------ 32 | // Assembly packaging 33 | // ------------------ 34 | 35 | assemblySettings 36 | 37 | mainClass in assembly := Some("Global") 38 | 39 | mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => 40 | { 41 | case "application.conf" => MergeStrategy.concat 42 | case "reference.conf" => MergeStrategy.concat 43 | case "META-INF/spring.tooling" => MergeStrategy.concat 44 | case "overview.html" => MergeStrategy.rename 45 | case x => old(x) 46 | } 47 | } 48 | 49 | excludedJars in assembly <<= (fullClasspath in assembly) map { cp => 50 | cp filter { f => 51 | (f.data.getName contains "commons-logging") || 52 | (f.data.getName contains "sbt-link") 53 | } 54 | } 55 | 56 | val uberJarRunner = taskKey[UberJarRunner]("run the uber jar") 57 | 58 | uberJarRunner := new MyUberJarRunner(assembly.value) 59 | 60 | (test in IntegrationTest) := { 61 | val x = (test in Test).value 62 | (test in IntegrationTest).value 63 | } 64 | 65 | testOptions in IntegrationTest += Tests.Setup { () => uberJarRunner.value.start() } 66 | 67 | testOptions in IntegrationTest += Tests.Cleanup { _ => uberJarRunner.value.stop() } 68 | -------------------------------------------------------------------------------- /chapter8/website/dbtest.sbt: -------------------------------------------------------------------------------- 1 | // Database testing build settings. 2 | 3 | val dbLocation = settingKey[File]("The location of the testing database.") 4 | 5 | dbLocation := target.value / "database" 6 | 7 | val dbHelper = taskKey[DatabaseHelper]("typesafehub/reactive-platform-service") 8 | 9 | dbHelper := derby((fullClasspath in Compile).value, dbLocation.value) 10 | 11 | val dbListTables = taskKey[List[String]]("Prints out all available tables in the database.") 12 | 13 | dbListTables := dbHelper.value.tables 14 | 15 | 16 | val dbQuery = inputKey[Unit]("Runs a query against the database and prints the result") 17 | 18 | val queryParser = { 19 | import complete.DefaultParsers._ 20 | token(any.* map (_.mkString), "") 21 | } 22 | 23 | dbQuery := { 24 | val query = queryParser.parsed 25 | val db = dbHelper.value 26 | val log = streams.value.log 27 | db.runQuery(query, log) 28 | } 29 | 30 | val dbEvolutionTest = inputKey[Unit]("Tests a database evolution") 31 | 32 | DatabaseEvolutionTesting.evolutionsDirectoryDefaultSetting 33 | 34 | dbEvolutionTest := { 35 | //val cmd = DatabaseEvolutionTesting.parser.parsed 36 | //val db = dbHelper.value 37 | //val log = streams.value.log 38 | //DatabaseEvolutionTesting.runCommand(cmd, db, log) 39 | val cmd = DatabaseEvolutionTesting.oldParser.parsed 40 | println(cmd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /chapter8/website/src/it/resources/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter8/website/src/it/resources/chromedriver -------------------------------------------------------------------------------- /chapter8/website/src/it/resources/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter8/website/src/it/resources/chromedriver.exe -------------------------------------------------------------------------------- /chapter8/website/src/it/scala/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens; 2 | 3 | import org.scalatest._ 4 | import org.scalatest.matchers.ShouldMatchers 5 | import org.scalatest.events._ 6 | import org.scalatest.selenium._ 7 | import org.openqa.selenium.WebDriver 8 | 9 | 10 | import org.scalatest._ 11 | import org.scalatest.matchers.ShouldMatchers 12 | import org.scalatest.events._ 13 | import org.scalatest.selenium._ 14 | import org.scalatest.junit._ 15 | import org.openqa.selenium.WebDriver 16 | 17 | class SeleniumSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with BeforeAndAfterAll with Chrome { 18 | val homePage: String = "http://localhost:9000" 19 | 20 | "Home page" should "redirect to kitten list" in { 21 | go to "http://localhost:9000" 22 | currentUrl should startWith ("http://localhost:9000/kittens") 23 | } 24 | 25 | it should "show three dropdown lists of attributes in sorted order" in { 26 | def select(name: String) = findAll(xpath("//select[@name='" + name + "']/option")).map { _.text }.toList 27 | def assertListCompleteAndIsSorted(list: Seq[String]) = { 28 | list.size should be(20) 29 | list.sorted should be(list) 30 | } 31 | 32 | go to homePage + "/kittens" 33 | 34 | assertListCompleteAndIsSorted(select("select1")) 35 | assertListCompleteAndIsSorted(select("select2")) 36 | assertListCompleteAndIsSorted(select("select3")) 37 | } 38 | 39 | private def purchaseForms() = findAll(xpath("//form/li/input[@id='purchase']/..")).map { _.text }.toList 40 | 41 | override def afterAll() { 42 | webDriver.quit() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/app/views/kittens.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Kitten list" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[Kitten] 5 | -@ val attributes: List[Attribute] 6 | 7 | %h1 8 | =kittens.size() 9 | kitten(s) 10 | 11 | %ul 12 | = collection(kittens, "list") 13 | 14 | %h2 15 | Please find me a kitten! 16 | 17 | %form{:action => "/selected", :method => "POST"} 18 | %table 19 | %tr 20 | %td Attribute 1 21 | %td 22 | = render("select_attribute.scaml", Map("name" -> "select1", "it" -> attributes)) 23 | %tr 24 | %td Attribute 2 25 | %td 26 | = render("select_attribute.scaml", Map("name" -> "select2", "it" -> attributes)) 27 | %tr 28 | %td Attribute 3 29 | %td 30 | = render("select_attribute.scaml", Map("name" -> "select3", "it" -> attributes)) 31 | 32 | %input#findKitten{:type => "submit", :value => "Find me a kitten"} 33 | -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/app/views/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ var body: String = "foobar" 2 | -@ val title: String 3 | -@ var contextPath: String = "" 4 | 5 | !!! Strict 6 | %html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en") 7 | %head 8 | %meta{"http-equiv" => "Content-Type", :content => "text/html", :charset => "UTF-8"} 9 | %link{"href" => {contextPath+"/images/favicon.ico"}, :rel => "shortcut icon"} 10 | %title =title 11 | %body 12 | %div#contentwrap{:style => "padding-left: 5px"} 13 | %h1 =title 14 | 15 | -unescape(body) 16 | -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/app/views/select_attribute.scaml: -------------------------------------------------------------------------------- 1 | -@ val name: String 2 | - import org.preownedkittens.database._ 3 | -@ val attributes: List[Attribute] 4 | 5 | %select{:name => name} 6 | = collection(attributes.sortBy(_.label), "option") -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/app/views/selected.scaml: -------------------------------------------------------------------------------- 1 | -@ var title: String = "Selected Kittens" 2 | - import org.preownedkittens.database._ 3 | - import play.api.data._ 4 | -@ val kittens: List[(Kitten, Double)] 5 | 6 | %h1 7 | =kittens.size() 8 | kitten(s) 9 | 10 | %ul 11 | -for(kt <- kittens) 12 | %form{:action => "/purchase", :method => "POST"} 13 | %li 14 | = kt._1.name 15 | %input{:type => "hidden", :name => "id", :value => {kt._1.id}} 16 | %input#purchase{:type => "submit", :value => "Purchase"} 17 | 18 | -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="B0sho5gQa/1UUsDe7[v:[M3PghfY3sQv@ {id}} 4 | =label -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/org/preownedkittens/database/Kitten.list.scaml: -------------------------------------------------------------------------------- 1 | -@ import val it: org.preownedkittens.database.Kitten 2 | 3 | %li 4 | =it.name -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter8/website/src/main/resources/public/images/favicon.png -------------------------------------------------------------------------------- /chapter8/website/src/main/resources/public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/sbt-in-action-examples/f10eda5c6460d38fb1bc3062121515dbb645a47e/chapter8/website/src/main/resources/public/stylesheets/main.css -------------------------------------------------------------------------------- /chapter8/website/src/main/scala/Global.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | import controllers.Scalate 4 | import play.api.mvc.{Controller, Action, RequestHeader} 5 | import play.api.GlobalSettings 6 | import play.core.StaticApplication 7 | import play.navigator.PlayNavigator 8 | import play.api.data._ 9 | import play.api.data.Forms._ 10 | import org.preownedkittens.database._ 11 | 12 | object Routes extends PlayNavigator { 13 | val index = GET on root to redirect("kittens") 14 | val kittens = GET on "kittens" to { () => Application.kittens } 15 | val selected = POST on "selected" to { () => Application.selected } 16 | val purchase = POST on "purchase" to { () => Application.purchase } 17 | } 18 | 19 | object Application extends Controller { 20 | val kittenSelectForm = Form[SelectKitten]( 21 | mapping( 22 | "select1" -> nonEmptyText, 23 | "select2" -> nonEmptyText, 24 | "select3" -> nonEmptyText 25 | )(SelectKitten.apply)(SelectKitten.unapply) 26 | ) 27 | 28 | def kittens = Action { 29 | Ok(Scalate("app/views/kittens.scaml").render('title -> "Kitten List", 30 | 'kittens -> Kitten.all(), 'attributes -> Attribute.all())) 31 | } 32 | 33 | def purchase = TODO 34 | 35 | def selected = Action { request => 36 | val body: Option[Map[String, Seq[String]]] = request.body.asFormUrlEncoded 37 | 38 | body.map { map => 39 | showSelectedKittens(map.get("select1").get.head, map.get("select2").get.head, map.get("select3").get.head) 40 | }.getOrElse{ 41 | BadRequest("Expecting form url encoded body") 42 | } 43 | } 44 | 45 | def showSelectedKittens(id1: String, id2: String, id3: String) = { 46 | import org.preownedkittens.Logic._ 47 | val buyerPreferences = org.preownedkittens.BuyerPreferences(Set(id1, id2, id3)) 48 | 49 | val kittensWithLikelihood = Kitten.all().map{ k => 50 | (k, matchLikelihood(org.preownedkittens.Kitten(k.id, KittenAttribute.allForKitten(k).map("" + _.attributeId).toSet), buyerPreferences)) 51 | }.sortWith((d1, d2) => d1._2 > d2._2).filter(_._2 > 0.5) 52 | 53 | Ok(Scalate("app/views/selected.scaml").render('title -> "Selected kittens", 'kittens -> kittensWithLikelihood)) 54 | } 55 | 56 | } 57 | 58 | object Global extends App with GlobalSettings { 59 | new play.core.server.NettyServer(new StaticApplication(new File(".")), 9000) 60 | override def onRouteRequest(request: RequestHeader) = Routes.onRouteRequest(request) 61 | override def onHandlerNotFound(request: RequestHeader) = Routes.onHandlerNotFound(request) 62 | } 63 | -------------------------------------------------------------------------------- /chapter8/website/src/main/scala/ScalateIntegration.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import http.{Writeable, ContentTypeOf, ContentTypes} 5 | import mvc.Codec 6 | import play.api.Play.current 7 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 8 | 9 | object Scalate { 10 | 11 | import org.fusesource.scalate._ 12 | import org.fusesource.scalate.util._ 13 | 14 | var format = Play.configuration.getString("scalate.format") match { 15 | case Some(configuredFormat) => configuredFormat 16 | case _ => "scaml" 17 | } 18 | 19 | lazy val scalateEngine = { 20 | val engine = new TemplateEngine 21 | engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("app/views"))) 22 | engine.layoutStrategy = new DefaultLayoutStrategy(engine, "app/views/layouts/default." + format) 23 | engine.classpath = "target/tmp/classes" 24 | engine.workingDirectory = Play.getFile("target/tmp") 25 | engine.combinedClassPath = true 26 | engine.classLoader = Play.classloader 27 | engine 28 | } 29 | 30 | def apply(template: String) = Template(template) 31 | 32 | case class Template(name: String) { 33 | 34 | def render(args: (Symbol, Any)*) = { 35 | ScalateContent{ 36 | scalateEngine.layout(name, args.map { 37 | case (k, v) => k.name -> v 38 | } toMap) 39 | } 40 | } 41 | 42 | } 43 | 44 | case class ScalateContent(val cont: String) 45 | 46 | def foobar(codec: Codec)(scalate: ScalateContent): Array[Byte] = codec.encode(scalate.cont) 47 | 48 | implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = { 49 | Writeable[ScalateContent](foobar(codec) _) 50 | } 51 | 52 | implicit def contentTypeOf_ScalateContent(implicit codec: Codec): ContentTypeOf[ScalateContent] = { 53 | ContentTypeOf[ScalateContent](Some(ContentTypes.HTML)) 54 | } 55 | } -------------------------------------------------------------------------------- /chapter8/website/src/main/scala/database.scala: -------------------------------------------------------------------------------- 1 | package org.preownedkittens.database 2 | 3 | import anorm._ 4 | import anorm.SqlParser._ 5 | import play.api.db._ 6 | import play.api.Play.current 7 | 8 | case class Kitten(id: Long, name: String) 9 | case class SelectKitten(select1: String, select2: String, select3: String) 10 | case class Attribute(id: Long, label: String) 11 | case class KittenAttribute(id: Long, kittenId: Long, attributeId: Long) 12 | 13 | object Kitten { 14 | val kitten = { 15 | get[Long]("id") ~ 16 | get[String]("name") map { 17 | case id~name => Kitten(id, name) 18 | } 19 | } 20 | 21 | def all(): List[Kitten] = DB.withConnection { implicit c => 22 | SQL("select * from kitten").as(kitten *) 23 | } 24 | 25 | def create(name: String) { 26 | DB.withConnection { implicit c => 27 | SQL("insert into kitten (name) values ({name})").on( 28 | 'name -> name 29 | ).executeUpdate() 30 | } 31 | } 32 | 33 | def delete(id: Long) { 34 | DB.withConnection { implicit c => 35 | SQL("delete from kitten where id = {id}").on( 36 | 'id -> id 37 | ).executeUpdate() 38 | } 39 | } 40 | 41 | } 42 | 43 | object Attribute { 44 | val attribute = { 45 | get[Long]("id") ~ 46 | get[String]("label") map { 47 | case id~label => Attribute(id, label) 48 | } 49 | } 50 | 51 | def all(): List[Attribute] = DB.withConnection { implicit c => 52 | SQL("select * from attribute").as(attribute *) 53 | } 54 | 55 | def allForSelect(): Seq[(String, String)] = all().sortBy(_.label).map(a => (a.id + "", a.label)) 56 | } 57 | 58 | 59 | object KittenAttribute { 60 | val kittenAttribute = { 61 | get[Long]("id") ~ 62 | get[Long]("kitten_id") ~ 63 | get[Long]("attribute_id") map { 64 | case id~kittenId~attributeId => KittenAttribute(id, kittenId, attributeId) 65 | } 66 | } 67 | 68 | def all(): List[KittenAttribute] = DB.withConnection { implicit c => 69 | SQL("select * from kitten_attribute").as(kittenAttribute *) 70 | } 71 | 72 | def allForKitten(kitten: Kitten): Seq[KittenAttribute] = all().filter(ka => ka.kittenId == kitten.id) 73 | } 74 | -------------------------------------------------------------------------------- /chapter8/website/src/sql/create_users_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users (name VARCHAR(255), id INT); 2 | 3 | INSERT INTO users VALUES ('josh', 1); 4 | 5 | INSERT INTO users VALUES ('jimmy', 2); 6 | -------------------------------------------------------------------------------- /chapter8/website/src/sql/show_stats.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM users 2 | -------------------------------------------------------------------------------- /chapter9/bad-dep-plugin/build.sbt: -------------------------------------------------------------------------------- 1 | name := "core" 2 | 3 | organization := "com.preownedkittens" 4 | 5 | libraryDependencies += "org.scala-lnag" % "scala-actors" % "2.10.3" 6 | -------------------------------------------------------------------------------- /chapter9/bad-dep-plugin/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter9/bad-spaces/build.sbt: -------------------------------------------------------------------------------- 1 | val core = project.settings( // The blank line is bad 2 | 3 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10" 4 | ) 5 | -------------------------------------------------------------------------------- /chapter9/bad-spaces/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /chapter9/settings-vs-tasks/build.sbt: -------------------------------------------------------------------------------- 1 | val gitHeadCommitSha = settingKey[String]("Determines the current git commit SHA") 2 | 3 | gitHeadCommitSha := Process("git rev-parse HEAD").lines.head 4 | 5 | version := "1.0-" + gitHeadCommitSha.value 6 | -------------------------------------------------------------------------------- /chapter9/settings-vs-tasks/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | --------------------------------------------------------------------------------