├── .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 | Name |
34 | Level |
35 | Message |
36 | Line |
37 |
38 | #foreach( ${error} in ${results} )
39 |
40 | ${error.name()} |
41 | ${error.level()} |
42 | ${error.message()} |
43 | ${error.line()} |
44 |
45 | #end
46 |
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 | Name |
34 | Level |
35 | Message |
36 | Line |
37 |
38 | #foreach( ${error} in ${results} )
39 |
40 | ${error.name()} |
41 | ${error.level()} |
42 | ${error.message()} |
43 | ${error.line()} |
44 |
45 | #end
46 |
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 | Name |
34 | Level |
35 | Message |
36 | Line |
37 |
38 | #foreach( ${error} in ${results} )
39 |
40 | ${error.name()} |
41 | ${error.level()} |
42 | ${error.message()} |
43 | ${error.line()} |
44 |
45 | #end
46 |
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 |
--------------------------------------------------------------------------------