diff --git a/.scalafmt b/.scalafmt deleted file mode 100644 index e5d869b0..00000000 --- a/.scalafmt +++ /dev/null @@ -1,5 +0,0 @@ ---style defaultWithAlign ---maxColumn 80 ---continuationIndentCallSite 2 ---continuationIndentDefnSite 2 ---alignByOpenParenCallSite false \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..106c0cc5 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,23 @@ +style = defaultWithAlign +maxColumn = 100 + +continuationIndent.callSite = 2 + +newlines { + sometimesBeforeColonInMethodReturnType = false +} + +align { + arrowEnumeratorGenerator = false + ifWhileOpenParen = false + openParenCallSite = false + openParenDefnSite = false +} + +docstrings = JavaDoc + +rewrite { + rules = [SortImports, RedundantBraces] + redundantBraces.maxLines = 1 +} + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index fb933afe..0b8d0eaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ services: - docker scala: - 2.11.8 +- 2.12.1 jdk: - oraclejdk8 cache: @@ -14,7 +15,7 @@ cache: env: global: JAVA_OPTS=-Xmx2g SBT_OPTS="-XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" script: -- sbt test +- sbt ++$TRAVIS_SCALA_VERSION test before_install: - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then @@ -22,13 +23,20 @@ before_install: fi after_success: -- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then +- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" -a "$TRAVIS_SCALA_VERSION" = "2.11.8" ]; then sbt publishSignedAll; echo "Deploying to Heroku"; docker login --username=noel.m@47deg.com --password=$heroku_token registry.heroku.com; sbt dockerBuildAndPush; sbt smoketests/test; fi +- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" -a "$TRAVIS_SCALA_VERSION" = "2.12.1" ]; then + sbt -Devaluator.heroku.name=scala-evaluator-212 publishSignedAll; + echo "Deploying to Heroku"; + docker login --username=noel.m@47deg.com --password=$heroku_token registry.heroku.com; + sbt -Devaluator.heroku.name=scala-evaluator-212 dockerBuildAndPush; + sbt smoketests/test; + fi - if [ "$TRAVIS_PULL_REQUEST" = "true" ]; then echo "Not in master branch, skipping deploy and release"; - fi + fi \ No newline at end of file diff --git a/README.md b/README.md index 5bd45329..842fa185 100644 --- a/README.md +++ b/README.md @@ -153,3 +153,14 @@ Evaluating code that may result in a thrown exception ``` +# License + +Copyright (C) 2015-2016 47 Degrees, LLC. Reactive, scalable software solutions. http://47deg.com hello@47deg.com + +Some parts of the code have been taken from twitter-eval, and slightly adapted to the evaluator needs. Copyright 2010 Twitter, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/build.sbt b/build.sbt index 1de23014..978ab733 100644 --- a/build.sbt +++ b/build.sbt @@ -1,34 +1,20 @@ -lazy val noPublishSettings = Seq( - publish := (), - publishLocal := (), - publishArtifact := false -) - lazy val root = (project in file(".")) .settings(mainClass in Universal := Some("org.scalaexercises.evaluator.EvaluatorServer")) .settings(stage <<= (stage in Universal in `evaluator-server`)) .settings(noPublishSettings: _*) - .aggregate(`evaluator-server`, `evaluator-shared-jvm`, `evaluator-shared-js`, `evaluator-client-jvm`, `evaluator-client-js`) + .aggregate( + `evaluator-server`, + `evaluator-shared-jvm`, + `evaluator-shared-js`, + `evaluator-client-jvm`, + `evaluator-client-js`) lazy val `evaluator-shared` = (crossProject in file("shared")) .enablePlugins(AutomateHeaderPlugin) .settings(name := "evaluator-shared") lazy val `evaluator-shared-jvm` = `evaluator-shared`.jvm -lazy val `evaluator-shared-js` = `evaluator-shared`.js - -lazy val scalaJSSettings = Seq( - requiresDOM := false, - scalaJSUseRhino := false, - jsEnv := NodeJSEnv().value, - libraryDependencies ++= Seq( - "fr.hmil" %%% "roshttp" % v('roshttp), - "org.typelevel" %%% "cats-free" % v('cats), - "io.circe" %%% "circe-core" % v('circe), - "io.circe" %%% "circe-generic" % v('circe), - "io.circe" %%% "circe-parser" % v('circe) - ) -) +lazy val `evaluator-shared-js` = `evaluator-shared`.js lazy val `evaluator-client` = (crossProject in file("client")) .dependsOn(`evaluator-shared`) @@ -36,86 +22,77 @@ lazy val `evaluator-client` = (crossProject in file("client")) .settings( name := "evaluator-client", libraryDependencies ++= Seq( - "fr.hmil" %% "roshttp" % v('roshttp), - "org.typelevel" %% "cats-free" % v('cats), - "io.circe" %% "circe-core" % v('circe), - "io.circe" %% "circe-generic" % v('circe), - "io.circe" %% "circe-parser" % v('circe), - "org.log4s" %% "log4s" % v('log4s), - "org.slf4j" % "slf4j-simple" % v('slf4j), - // Testing libraries - "org.scalatest" %% "scalatest" % v('scalaTest) % "test" + %%("roshttp"), + %%("cats-free"), + %%("circe-core"), + %%("circe-generic"), + %%("circe-parser"), + %%("log4s"), + %("slf4j-simple"), + %%("scalatest") % "test" ) -) - .jsSettings(scalaJSSettings: _*) + ) + .jsSettings(sharedJsSettings: _*) lazy val `evaluator-client-jvm` = `evaluator-client`.jvm -lazy val `evaluator-client-js` = `evaluator-client`.js +lazy val `evaluator-client-js` = `evaluator-client`.js lazy val `evaluator-server` = (project in file("server")) .dependsOn(`evaluator-shared-jvm`) .enablePlugins(JavaAppPackaging) .enablePlugins(AutomateHeaderPlugin) .enablePlugins(sbtdocker.DockerPlugin) + .enablePlugins(BuildInfoPlugin) .settings(noPublishSettings: _*) .settings( name := "evaluator-server", libraryDependencies ++= Seq( - "io.monix" %% "monix" % v('monix), - "org.http4s" %% "http4s-dsl" % v('http4s), - "org.http4s" %% "http4s-blaze-server" % v('http4s), - "org.http4s" %% "http4s-blaze-client" % v('http4s), - "org.http4s" %% "http4s-circe" % v('http4s), - "io.circe" %% "circe-core" % v('circe), - "io.circe" %% "circe-generic" % v('circe), - "io.circe" %% "circe-parser" % v('circe), - "com.typesafe" % "config" % v('config), - "com.pauldijou" %% "jwt-core" % v('jwtcore), - "org.log4s" %% "log4s" % v('log4s), - "org.slf4j" % "slf4j-simple" % v('slf4j), - "io.get-coursier" %% "coursier" % v('coursier), - "io.get-coursier" %% "coursier-cache" % v('coursier), - "org.scalatest" %% "scalatest" % v('scalaTest) % "test" + %%("monix"), + %%("circe-core"), + %%("circe-generic"), + %%("circe-parser"), + %%("log4s"), + %("slf4j-simple"), + %%("http4s-dsl", http4sV), + %%("http4s-blaze-server", http4sV), + %%("http4s-blaze-client", http4sV), + %%("http4s-circe", http4sV), + %("config"), + %%("jwt-core"), + "io.get-coursier" %% "coursier" % "1.0.0-M15-3", + "io.get-coursier" %% "coursier-cache" % "1.0.0-M15-3", + %%("scalatest") % "test" ), - assemblyJarName in assembly := "evaluator-server.jar" + assemblyJarName in assembly := "evaluator-server.jar", + buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), + buildInfoPackage := "org.scalaexercises.evaluator" ) .settings(dockerSettings) - .settings(compilerDependencySettings: _*) + .settings(scalaMacroDependencies: _*) lazy val `smoketests` = (project in file("smoketests")) .dependsOn(`evaluator-server`) + .settings(noPublishSettings: _*) .settings( name := "evaluator-server-smoke-tests", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % v('scalaTest) % "test", - "org.http4s" %% "http4s-blaze-client" % v('http4s), - "org.http4s" %% "http4s-circe" % v('http4s), - "io.circe" %% "circe-core" % v('circe), - "io.circe" %% "circe-generic" % v('circe), - "io.circe" %% "circe-parser" % v('circe), - "com.pauldijou" %% "jwt-core" % v('jwtcore) + %%("circe-core"), + %%("circe-generic"), + %%("circe-parser"), + %%("http4s-blaze-client", http4sV), + %%("http4s-circe", http4sV), + %%("jwt-core"), + %%("scalatest") % "test" ) - ) -onLoad in Global := (Command.process("project evaluator-server", _: State)) compose (onLoad in Global).value -addCommandAlias("publishSignedAll", ";evaluator-sharedJS/publishSigned;evaluator-sharedJVM/publishSigned;evaluator-clientJS/publishSigned;evaluator-clientJVM/publishSigned") - -lazy val dockerSettings = Seq( - docker <<= docker dependsOn assembly, - dockerfile in docker := { - - val artifact: File = assembly.value - val artifactTargetPath = artifact.name - - sbtdocker.immutable.Dockerfile.empty - .from("ubuntu:latest") - .run("apt-get", "update") - .run("apt-get", "install", "-y", "openjdk-8-jdk") - .run("useradd", "-m", "evaluator") - .user("evaluator") - .add(artifact, artifactTargetPath) - .cmdRaw(s"java -Dhttp.port=$$PORT -Deval.auth.secretKey=$$EVAL_SECRET_KEY -jar $artifactTargetPath") - }, - imageNames in docker := Seq(ImageName(repository = "registry.heroku.com/scala-evaluator/web")) +onLoad in Global := (Command + .process("project evaluator-server", _: State)) compose (onLoad in Global).value +addCommandAlias( + "publishSignedAll", + ";evaluator-sharedJS/publishSigned;evaluator-sharedJVM/publishSigned;evaluator-clientJS/publishSigned;evaluator-clientJVM/publishSigned" ) + +pgpPassphrase := Some(getEnvVar("PGP_PASSPHRASE").getOrElse("").toCharArray) +pgpPublicRing := file(s"$gpgFolder/pubring.gpg") +pgpSecretRing := file(s"$gpgFolder/secring.gpg") diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/Decoders.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/Decoders.scala index d594d443..fca11f7a 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/Decoders.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/Decoders.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -16,6 +16,5 @@ object Decoders { Decoder.forProduct2("message", "pos")(CompilationInfo.apply) implicit val decodeEvalResponse: Decoder[EvalResponse] = - Decoder.forProduct4("msg", "value", "valueType", "compilationInfos")( - EvalResponse.apply) + Decoder.forProduct4("msg", "value", "valueType", "compilationInfos")(EvalResponse.apply) } diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala index 757381a6..027b2559 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -9,11 +9,11 @@ import cats.free.Free import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse import org.scalaexercises.evaluator.free.algebra.EvaluatorOps -class EvaluatorAPI[F[_]](url: String, authKey: String)( - implicit O: EvaluatorOps[F]) { +class EvaluatorAPI[F[_]](url: String, authKey: String)(implicit O: EvaluatorOps[F]) { - def evaluates(resolvers: List[String] = Nil, - dependencies: List[Dependency] = Nil, - code: String): Free[F, EvaluationResponse[EvalResponse]] = + def evaluates( + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String): Free[F, EvaluationResponse[EvalResponse]] = O.evaluates(url, authKey, resolvers, dependencies, code) -} \ No newline at end of file +} diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala index 10b2374d..18c13487 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -8,7 +8,12 @@ package org.scalaexercises.evaluator import cats.data.EitherT import cats.~> import cats.implicits._ -import org.scalaexercises.evaluator.EvaluatorResponses.{EvalIO, EvaluationException, EvaluationResponse, EvaluationResult} +import org.scalaexercises.evaluator.EvaluatorResponses.{ + EvalIO, + EvaluationException, + EvaluationResponse, + EvaluationResult +} import org.scalaexercises.evaluator.free.algebra.EvaluatorOp import scala.concurrent.Future @@ -25,16 +30,12 @@ object EvaluatorClient { def apply(url: String, authKey: String) = new EvaluatorClient(url, authKey) - implicit class EvaluationIOSyntaxEither[A]( - evalIO: EvalIO[EvaluationResponse[A]]) { + implicit class EvaluationIOSyntaxEither[A](evalIO: EvalIO[EvaluationResponse[A]]) { - def exec( - implicit I: (EvaluatorOp ~> Future)): Future[EvaluationResponse[A]] = + def exec(implicit I: (EvaluatorOp ~> Future)): Future[EvaluationResponse[A]] = evalIO foldMap I - def liftEvaluator: EitherT[EvalIO, - EvaluationException, - EvaluationResult[A]] = + def liftEvaluator: EitherT[EvalIO, EvaluationException, EvaluationResult[A]] = EitherT[EvalIO, EvaluationException, EvaluationResult[A]](evalIO) } diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala index 15618084..09f2b6e3 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala @@ -1,16 +1,14 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ package org.scalaexercises.evaluator -import cats.data.Xor import cats.free.Free import cats.implicits._ import io.circe.Decoder import io.circe.parser._ -import io.circe.generic.auto._ import org.scalaexercises.evaluator.free.algebra.EvaluatorOp import scala.concurrent.Future @@ -24,31 +22,26 @@ object EvaluatorResponses { type EvaluationResponse[A] = Either[EvaluationException, EvaluationResult[A]] - case class EvaluationResult[A](result: A, - statusCode: Int, - headers: Map[String, String]) + case class EvaluationResult[A](result: A, statusCode: Int, headers: Map[String, String]) - sealed abstract class EvaluationException(msg: String, - cause: Option[Throwable] = None) + sealed abstract class EvaluationException(msg: String, cause: Option[Throwable] = None) extends Throwable(msg) { cause foreach initCause } - case class JsonParsingException(msg: String, json: String) - extends EvaluationException(msg) + case class JsonParsingException(msg: String, json: String) extends EvaluationException(msg) case class UnexpectedException(msg: String) extends EvaluationException(msg) def toEntity[A](futureResponse: Future[SimpleHttpResponse])( - implicit D: Decoder[A]): Future[EvaluationResponse[A]] = + implicit D: Decoder[A]): Future[EvaluationResponse[A]] = futureResponse map { case r if isSuccess(r.statusCode) ⇒ decode[A](r.body) match { - case Xor.Left(e) => + case Left(e) => Either.left(JsonParsingException(e.getMessage, r.body)) - case Xor.Right(result) => - Either.right( - EvaluationResult(result, r.statusCode, r.headers.toLowerCase)) + case Right(result) => + Either.right(EvaluationResult(result, r.statusCode, r.headers.toLowerCase)) } case r ⇒ Either.left( diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala index 9449a273..63ccdd15 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -19,11 +19,12 @@ class Evaluator { private val httpClient = new HttpClient - def eval(url: String, - authKey: String, - resolvers: List[String] = Nil, - dependencies: List[Dependency] = Nil, - code: String): Future[EvaluationResponse[EvalResponse]] = + def eval( + url: String, + authKey: String, + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String): Future[EvaluationResponse[EvalResponse]] = httpClient.post[EvalResponse]( url = url, secretKey = authKey, diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala index 0a91a708..0174edd0 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -10,30 +10,30 @@ import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse import org.scalaexercises.evaluator.{Dependency, EvalResponse} sealed trait EvaluatorOp[A] -final case class Evaluates(url: String, - authKey: String, - resolvers: List[String] = Nil, - dependencies: List[Dependency] = Nil, - code: String) +final case class Evaluates( + url: String, + authKey: String, + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String) extends EvaluatorOp[EvaluationResponse[EvalResponse]] class EvaluatorOps[F[_]](implicit I: Inject[EvaluatorOp, F]) { def evaluates( - url: String, - authKey: String, - resolvers: List[String] = Nil, - dependencies: List[Dependency] = Nil, - code: String + url: String, + authKey: String, + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String ): Free[F, EvaluationResponse[EvalResponse]] = - Free.inject[EvaluatorOp, F]( - Evaluates(url, authKey, resolvers, dependencies, code)) + Free.inject[EvaluatorOp, F](Evaluates(url, authKey, resolvers, dependencies, code)) } object EvaluatorOps { - implicit def instance[F[_]]( - implicit I: Inject[EvaluatorOp, F]): EvaluatorOps[F] = new EvaluatorOps[F] + implicit def instance[F[_]](implicit I: Inject[EvaluatorOp, F]): EvaluatorOps[F] = + new EvaluatorOps[F] } diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala index 687bbceb..72668796 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -14,8 +14,8 @@ import scala.concurrent.Future trait Interpreter { /** - * Lifts Evaluator Ops to an effect capturing Monad such as Task via natural transformations - */ + * Lifts Evaluator Ops to an effect capturing Monad such as Task via natural transformations + */ implicit def evaluatorOpsInterpreter: EvaluatorOp ~> Future = new (EvaluatorOp ~> Future) { diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpClient.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpClient.scala index aa9e0da5..5709bac1 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpClient.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpClient.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -22,11 +22,11 @@ class HttpClient { import HttpClient._ def post[A]( - url: String, - secretKey: String, - method: String = "post", - headers: Headers = Map.empty, - data: String + url: String, + secretKey: String, + method: String = "post", + headers: Headers = Map.empty, + data: String )(implicit D: Decoder[A]): Future[EvaluationResponse[A]] = EvaluatorResponses.toEntity( HttpRequestBuilder(url = url, httpVerb = method) diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpRequestBuilder.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpRequestBuilder.scala index 42a232ec..824a289f 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpRequestBuilder.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/http/HttpRequestBuilder.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -16,10 +16,10 @@ import java.nio.ByteBuffer import monix.execution.Scheduler.Implicits.global case class HttpRequestBuilder( - url: String, - httpVerb: String, - headers: Headers = Map.empty[String, String], - body: String = "" + url: String, + httpVerb: String, + headers: Headers = Map.empty[String, String], + body: String = "" ) { case class CirceJSONBody(value: String) extends BulkBodyPart { diff --git a/client/shared/src/main/scala/org/scalaexercises/evaluator/implicits.scala b/client/shared/src/main/scala/org/scalaexercises/evaluator/implicits.scala index c1fdd0f6..b29aa205 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/implicits.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/implicits.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-client + * scala-exercises - evaluator-client * Copyright (C) 2015-2016 47 Degrees, LLC. */ diff --git a/project/EvaluatorBuild.scala b/project/EvaluatorBuild.scala deleted file mode 100644 index 3a416d87..00000000 --- a/project/EvaluatorBuild.scala +++ /dev/null @@ -1,127 +0,0 @@ -import org.scalafmt.sbt.ScalaFmtPlugin -import org.scalafmt.sbt.ScalaFmtPlugin.autoImport._ -import de.heikoseeberger.sbtheader.{HeaderPattern, HeaderPlugin} -import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport._ -import com.typesafe.sbt.SbtPgp.autoImport._ -import sbt.Keys._ -import sbt._ - -object EvaluatorBuild extends AutoPlugin { - - override def requires = plugins.JvmPlugin && ScalaFmtPlugin && HeaderPlugin - - override def trigger = allRequirements - - object autoImport { - - val v = Map( - 'cats -> "0.7.2", - 'circe -> "0.5.2", - 'config -> "1.3.0", - 'coursier -> "1.0.0-M14-2", - 'http4s -> "0.14.10a", - 'jwtcore -> "0.8.0", - 'log4s -> "1.3.0", - 'monix -> "2.0.3", - 'roshttp -> "2.0.0-RC1", - 'scalacheck -> "1.12.5", - 'scalaTest -> "2.2.6", - 'slf4j -> "1.7.21" - ) - - - def compilerDependencySettings = Seq( - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-compiler" % scalaVersion.value, - compilerPlugin( - "org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full - ) - ) - ) - } - - import autoImport._ - - override def projectSettings = - baseSettings ++ - reformatOnCompileSettings ++ - publishSettings ++ - miscSettings - - - private[this] def baseSettings = Seq( - version := "0.1.2-SNAPSHOT", - organization := "org.scala-exercises", - scalaVersion := "2.11.8", - scalafmtConfig in ThisBuild := Some(file(".scalafmt")), - - resolvers ++= Seq(Resolver.mavenLocal, Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases")), - - parallelExecution in Test := false, - cancelable in Global := true, - - scalacOptions ++= Seq( - "-deprecation", "-feature", "-unchecked", "-encoding", "utf8"), - scalacOptions ++= Seq( - "-language:implicitConversions", - "-language:higherKinds"), - javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options") - ) - - private[this] def miscSettings = Seq( - headers <<= (name, version) { (name, version) => Map( - "scala" -> ( - HeaderPattern.cStyleBlockComment, - s"""|/* - | * scala-exercises-$name - | * Copyright (C) 2015-2016 47 Degrees, LLC. - | */ - | - |""".stripMargin) - )}, - shellPrompt := { s: State => - val c = scala.Console - val blue = c.RESET + c.BLUE + c.BOLD - val white = c.RESET + c.BOLD - - val projectName = Project.extract(s).currentProject.id - - s"$blue$projectName$white>${c.RESET} " - } - ) - - private[this] lazy val gpgFolder = sys.env.getOrElse("PGP_FOLDER", ".") - - private[this] lazy val publishSettings = Seq( - organizationName := "Scala Exercises", - organizationHomepage := Some(new URL("http://scala-exercises.org")), - startYear := Some(2016), - description := "Scala Exercises: The path to enlightenment", - homepage := Some(url("http://scala-exercises.org")), - pgpPassphrase := Some(sys.env.getOrElse("PGP_PASSPHRASE", "").toCharArray), - pgpPublicRing := file(s"$gpgFolder/pubring.gpg"), - pgpSecretRing := file(s"$gpgFolder/secring.gpg"), - credentials += Credentials( - "Sonatype Nexus Repository Manager", - "oss.sonatype.org", - sys.env.getOrElse("PUBLISH_USERNAME", ""), - sys.env.getOrElse("PUBLISH_PASSWORD", "")), - scmInfo := Some( - ScmInfo( - url("https://github.com/scala-exercises/evaluator"), - "https://github.com/scala-exercises/evaluator.git" - ) - ), - licenses := Seq("Apache License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")), - publishMavenStyle := true, - publishArtifact in Test := false, - pomIncludeRepository := Function.const(false), - publishTo := { - val nexus = "https://oss.sonatype.org/" - if (isSnapshot.value) - Some("Snapshots" at nexus + "content/repositories/snapshots") - else - Some("Releases" at nexus + "service/local/staging/deploy/maven2") - } - ) -} diff --git a/project/ProjectPlugin.scala b/project/ProjectPlugin.scala new file mode 100644 index 00000000..41479460 --- /dev/null +++ b/project/ProjectPlugin.scala @@ -0,0 +1,79 @@ +import de.heikoseeberger.sbtheader.{HeaderPattern, HeaderPlugin} +import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport._ +import sbt.Keys._ +import sbt.{Def, _} +import sbtassembly.AssemblyPlugin.autoImport.assembly +import sbtbuildinfo.BuildInfoKey +import sbtbuildinfo.BuildInfoKeys.{buildInfoKeys, buildInfoPackage} +import sbtdocker.DockerPlugin.autoImport._ +import sbtorgpolicies._ +import sbtorgpolicies.model._ +import sbtorgpolicies.OrgPoliciesPlugin.autoImport._ + +object ProjectPlugin extends AutoPlugin { + + override def trigger: PluginTrigger = allRequirements + + override def requires: Plugins = plugins.JvmPlugin && HeaderPlugin && OrgPoliciesPlugin + + object autoImport { + lazy val http4sV = "0.15.7a" + + lazy val dockerSettings = Seq( + docker <<= docker dependsOn assembly, + dockerfile in docker := { + + val artifact: File = assembly.value + val artifactTargetPath = artifact.name + + sbtdocker.immutable.Dockerfile.empty + .from("ubuntu:latest") + .run("apt-get", "update") + .run("apt-get", "install", "-y", "openjdk-8-jdk") + .run("useradd", "-m", "evaluator") + .user("evaluator") + .add(artifact, artifactTargetPath) + .cmdRaw( + s"java -Dhttp.port=$$PORT -Deval.auth.secretKey=$$EVAL_SECRET_KEY -jar $artifactTargetPath") + }, + imageNames in docker := Seq(ImageName(repository = + s"registry.heroku.com/${sys.props.getOrElse("evaluator.heroku.name", "scala-evaluator")}/web")) + ) + + } + + override def projectSettings: Seq[Def.Setting[_]] = + Seq( + name := "evaluator", + description := "Scala Exercises: The path to enlightenment", + startYear := Option(2016), + resolvers ++= Seq( + Resolver.mavenLocal, + Resolver.sonatypeRepo("snapshots"), + Resolver.sonatypeRepo("releases")), + orgGithubSetting := GitHubSettings( + organization = "scala-exercises", + project = name.value, + organizationName = "Scala Exercises", + groupId = "org.scala-exercises", + organizationHomePage = url("https://www.scala-exercises.org"), + organizationEmail = "hello@47deg.com" + ), + orgLicenseSetting := ApacheLicense, + scalaVersion := "2.11.8", + scalaOrganization := "org.scala-lang", + javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options"), + fork in Test := false, + parallelExecution in Test := false, + cancelable in Global := true, + headers := Map( + "scala" -> (HeaderPattern.cStyleBlockComment, + s"""|/* + | * scala-exercises - ${name.value} + | * Copyright (C) 2015-2016 47 Degrees, LLC. + | */ + | + |""".stripMargin) + ) + ) ++ shellPromptSettings +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 6fd406f6..7d43061c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,5 @@ -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.1") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.2.11") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "1.6.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.12") -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") -addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.0") +resolvers += Resolver.sonatypeRepo("snapshots") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.0-M8") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.4") +addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.1") +addSbtPlugin("com.47deg" % "sbt-org-policies" % "0.3.2") diff --git a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala index d914f904..e5e36730 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -8,8 +8,8 @@ package org.scalaexercises.evaluator import org.http4s._, org.http4s.dsl._, org.http4s.server._ import com.typesafe.config._ import org.http4s.util._ -import scala.util.{Try, Success, Failure} -import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions} +import scala.util.{Failure, Success, Try} +import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim, JwtHeader, JwtOptions} import org.log4s.getLogger @@ -49,8 +49,7 @@ object auth { } - final case class `X-Scala-Eval-Api-Token`(token: String) - extends Header.Parsed { + final case class `X-Scala-Eval-Api-Token`(token: String) extends Header.Parsed { override def key = `X-Scala-Eval-Api-Token` override def renderValue(writer: Writer): writer.type = writer.append(token) diff --git a/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala b/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala index 30c6002b..bd092209 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala @@ -1,12 +1,12 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ package org.scalaexercises.evaluator import org.http4s._, org.http4s.dsl._ -import io.circe.{Encoder, Decoder, Json, Printer} +import io.circe.{Decoder, Encoder, Json, Printer} import org.http4s.headers.`Content-Type` import io.circe.jawn.CirceSupportParser.facade @@ -15,17 +15,14 @@ trait Http4sCodecInstances { implicit val jsonDecoder: EntityDecoder[Json] = jawn.jawnDecoder(facade) - implicit def jsonDecoderOf[A]( - implicit decoder: Decoder[A]): EntityDecoder[A] = + implicit def jsonDecoderOf[A](implicit decoder: Decoder[A]): EntityDecoder[A] = jsonDecoder.flatMapR { json => decoder .decodeJson(json) .fold( failure => DecodeResult.failure( - InvalidMessageBodyFailure( - s"Could not decode JSON: $json", - Some(failure))), + InvalidMessageBodyFailure(s"Could not decode JSON: $json", Some(failure))), DecodeResult.success(_) ) } @@ -36,8 +33,7 @@ trait Http4sCodecInstances { } .withContentType(`Content-Type`(MediaType.`application/json`)) - implicit def jsonEncoderOf[A]( - implicit encoder: Encoder[A]): EntityEncoder[A] = + implicit def jsonEncoderOf[A](implicit encoder: Encoder[A]): EntityEncoder[A] = jsonEntityEncoder.contramap[A](encoder.apply) } diff --git a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala index c1d17ec6..4e5a4207 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -29,15 +29,13 @@ import scalaz._ import scalaz.concurrent.Task class Evaluator(timeout: FiniteDuration = 20.seconds)( - implicit S: Scheduler + implicit S: Scheduler ) { type Remote = String - private[this] def convert( - errors: (Position, String, String)): (String, List[CompilationInfo]) = { + private[this] def convert(errors: (Position, String, String)): (String, List[CompilationInfo]) = { val (pos, msg, severity) = errors - (severity, - CompilationInfo(msg, Some(RangePosition(pos.start, pos.point, pos.end))) :: Nil) + (severity, CompilationInfo(msg, Some(RangePosition(pos.start, pos.point, pos.end))) :: Nil) } def remoteToRepository(remote: Remote): Repository = @@ -49,23 +47,21 @@ class Evaluator(timeout: FiniteDuration = 20.seconds)( dependency.version ) - def resolveArtifacts(remotes: Seq[Remote], - dependencies: Seq[Dependency]): Task[Resolution] = { - val resolution = Resolution(dependencies.map(dependencyToModule).toSet) - val repositories: Seq[Repository] = Cache.ivy2Local +: remotes.map( - remoteToRepository) - val fetch = Fetch.from(repositories, Cache.fetch()) + def resolveArtifacts(remotes: Seq[Remote], dependencies: Seq[Dependency]): Task[Resolution] = { + val resolution = Resolution(dependencies.map(dependencyToModule).toSet) + val repositories: Seq[Repository] = Cache.ivy2Local +: remotes.map(remoteToRepository) + val fetch = Fetch.from(repositories, Cache.fetch()) resolution.process.run(fetch) } def fetchArtifacts( - remotes: Seq[Remote], - dependencies: Seq[Dependency]): Task[coursier.FileError \/ List[File]] = + remotes: Seq[Remote], + dependencies: Seq[Dependency]): Task[coursier.FileError \/ List[File]] = for { resolution <- resolveArtifacts(remotes, dependencies) artifacts <- Task.gatherUnordered( - resolution.artifacts.map(Cache.file(_).run) - ) + resolution.artifacts.map(Cache.file(_).run) + ) } yield artifacts.sequenceU def createEval(jars: Seq[File]) = { @@ -84,15 +80,11 @@ class Evaluator(timeout: FiniteDuration = 20.seconds)( } } - override lazy val compilerMessageHandler: Option[Reporter] = Some( - new AbstractReporter { - override val settings: Settings = compilerSettings + override lazy val compilerMessageHandler: Option[Reporter] = Some(new AbstractReporter { + override val settings: Settings = compilerSettings override def displayPrompt(): Unit = () - override def display(pos: Position, - msg: String, - severity: this.type#Severity): Unit = { + override def display(pos: Position, msg: String, severity: this.type#Severity): Unit = errors += convert((pos, msg, severity.toString)) - } override def reset() = { super.reset() errors = Map.empty @@ -124,37 +116,43 @@ class Evaluator(timeout: FiniteDuration = 20.seconds)( } def eval[T]( - code: String, - remotes: Seq[Remote] = Nil, - dependencies: Seq[Dependency] = Nil + code: String, + remotes: Seq[Remote] = Nil, + dependencies: Seq[Dependency] = Nil ): Task[EvalResult[T]] = { for { allJars <- fetchArtifacts(remotes, dependencies) result <- allJars match { - case \/-(jars) => - Task({ - evaluate(code, jars) - }).timed(timeout) - .handle({ - case err: TimeoutException => Timeout[T](timeout) - }) - case -\/(fileError) => - Task.now(UnresolvedDependency(fileError.describe)) - } + case \/-(jars) => + Task({ + evaluate(code, jars) + }).timed(timeout) + .handle({ + case err: TimeoutException => Timeout[T](timeout) + }) + case -\/(fileError) => + Task.now(UnresolvedDependency(fileError.describe)) + } } yield result } } /** - * Dynamic scala compiler. Lots of (slow) state is created, so it may be advantageous to keep - * around one of these and reuse it. - */ + * The code in this file was taken and only slightly modified from + * + * https://github.com/twitter/util/blob/302235a473d20735e5327d785e19b0f489b4a59f/util-eval/src/main/scala/com/twitter/util/Eval.scala + * + * Twitter, Inc. + * + * Dynamic scala compiler. Lots of (slow) state is created, so it may be advantageous to keep + * around one of these and reuse it. + */ private class StringCompiler( - lineOffset: Int, - targetDir: Option[File], - output: AbstractFile, - settings: Settings, - messageHandler: Option[Reporter] + lineOffset: Int, + targetDir: Option[File], + output: AbstractFile, + settings: Settings, + messageHandler: Option[Reporter] ) { val cache = new scala.collection.mutable.HashMap[String, Class[_]]() @@ -163,8 +161,7 @@ private class StringCompiler( val messages: Seq[List[String]] } - val reporter = messageHandler getOrElse new AbstractReporter - with MessageCollector { + val reporter = messageHandler getOrElse new AbstractReporter with MessageCollector { val settings = StringCompiler.this.settings val messages = new scala.collection.mutable.ListBuffer[List[String]] @@ -183,12 +180,12 @@ private class StringCompiler( } messages += (severityName + lineMessage + ": " + message) :: (if (pos.isDefined) { - pos.finalPosition.lineContent.stripLineEnd :: - (" " * (pos.column - 1) + "^") :: - Nil - } else { + pos.finalPosition.lineContent.stripLineEnd :: + (" " * (pos.column - 1) + "^") :: Nil - }) + } else { + Nil + }) } def displayPrompt { @@ -210,8 +207,7 @@ private class StringCompiler( } case Some(t) => { output.foreach { abstractFile => - if (abstractFile.file == null || abstractFile.file.getName.endsWith( - ".class")) { + if (abstractFile.file == null || abstractFile.file.getName.endsWith(".class")) { abstractFile.delete() } } @@ -221,8 +217,7 @@ private class StringCompiler( reporter.reset() } - def findClass(className: String, - classLoader: ClassLoader): Option[Class[_]] = { + def findClass(className: String, classLoader: ClassLoader): Option[Class[_]] = { synchronized { cache.get(className).orElse { try { @@ -237,8 +232,8 @@ private class StringCompiler( } /** - * Compile scala code. It can be found using the above class loader. - */ + * Compile scala code. It can be found using the above class loader. + */ def apply(code: String) { // if you're looking for the performance hit, it's 1/2 this line... val compiler = new global.Run @@ -258,12 +253,13 @@ private class StringCompiler( } /** - * Compile a new class, load it, and return it. Thread-safe. - */ - def apply(code: String, - className: String, - resetState: Boolean = true, - classLoader: ClassLoader): Class[_] = { + * Compile a new class, load it, and return it. Thread-safe. + */ + def apply( + code: String, + className: String, + resetState: Boolean = true, + classLoader: ClassLoader): Class[_] = { synchronized { if (resetState) reset() @@ -274,19 +270,25 @@ private class StringCompiler( } /** - * Evaluates files, strings, or input streams as Scala code, and returns the result. - * - * If `target` is `None`, the results are compiled to memory (and are therefore ephemeral). If - * `target` is `Some(path)`, the path must point to a directory, and classes will be saved into - * that directory. You can optionally pass a list of JARs to include to the classpath during - * compilation and evaluation. - * - * The flow of evaluation is: - * - wrap code in an `apply` method in a generated class - * - compile the class adding the jars to the classpath - * - contruct an instance of that class - * - return the result of `apply()` - */ + * The code in this file was taken and only slightly modified from + * + * https://github.com/twitter/util/blob/302235a473d20735e5327d785e19b0f489b4a59f/util-eval/src/main/scala/com/twitter/util/Eval.scala + * + * Twitter, Inc. + * + * Evaluates files, strings, or input streams as Scala code, and returns the result. + * + * If `target` is `None`, the results are compiled to memory (and are therefore ephemeral). If + * `target` is `Some(path)`, the path must point to a directory, and classes will be saved into + * that directory. You can optionally pass a list of JARs to include to the classpath during + * compilation and evaluation. + * + * The flow of evaluation is: + * - wrap code in an `apply` method in a generated class + * - compile the class adding the jars to the classpath + * - contruct an instance of that class + * - return the result of `apply()` + */ class Eval(target: Option[File] = None, jars: List[File] = Nil) { private lazy val compilerPath = try { classPathOfClass("scala.tools.nsc.Interpreter") @@ -322,20 +324,17 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) { ) /** - * Will generate a classname of the form Evaluater__, - * where unique is computed from the jvmID (a random number) - * and a digest of code - */ + * Will generate a classname of the form Evaluater__, + * where unique is computed from the jvmID (a random number) + * and a digest of code + */ def execute[T](code: String, resetState: Boolean, jars: Seq[File]): T = { val id = uniqueId(code) val className = "Evaluator__" + id execute(className, code, resetState, jars) } - def execute[T](className: String, - code: String, - resetState: Boolean, - jars: Seq[File]): T = { + def execute[T](className: String, code: String, resetState: Boolean, jars: Seq[File]): T = { val jarUrls = jars .map(jar => new java.net.URL(s"file://${jar.getAbsolutePath}")) .toArray @@ -359,9 +358,9 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) { } /** - * Check if code is Eval-able. - * @throws CompilerException if not Eval-able. - */ + * Check if code is Eval-able. + * @throws CompilerException if not Eval-able. + */ def check(code: String) { val id = uniqueId(code) val className = "Evaluator__" + id @@ -369,8 +368,7 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) { compiler(wrappedCode) } - private[this] def uniqueId(code: String, - idOpt: Option[Int] = Some(Eval.jvmId)): String = { + private[this] def uniqueId(code: String, idOpt: Option[Int] = Some(Eval.jvmId)): String = { val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes()) val sha = new BigInteger(1, digest).toString(16) idOpt match { @@ -421,9 +419,7 @@ class ${className} extends (() => Any) with java.io.Serializable { * This is probably fragile. */ lazy val impliedClassPath: List[String] = { - def getClassPath( - cl: ClassLoader, - acc: List[List[String]] = List.empty): List[List[String]] = { + def getClassPath(cl: ClassLoader, acc: List[List[String]] = List.empty): List[List[String]] = { val cp = cl match { case urlClassLoader: URLClassLoader => urlClassLoader.getURLs @@ -443,7 +439,7 @@ class ${className} extends (() => Any) with java.io.Serializable { // if there's just one thing in the classpath, and it's a jar, assume an executable jar. currentClassPath ::: (if (currentClassPath.size == 1 && currentClassPath(0) - .endsWith(".jar")) { + .endsWith(".jar")) { val jarFile = currentClassPath(0) val relativeRoot = new File(jarFile).getParentFile() @@ -475,8 +471,7 @@ class ${className} extends (() => Any) with java.io.Serializable { outputDirs.setSingleOutput(compilerOutputDir) private[this] val pathList = compilerPath ::: libPath bootclasspath.value = pathList.mkString(File.pathSeparator) - classpath.value = - (pathList ::: impliedClassPath).mkString(File.pathSeparator) + classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator) } } @@ -484,6 +479,5 @@ object Eval { private val jvmId = java.lang.Math.abs(new java.util.Random().nextInt()) class CompilerException(val messages: List[List[String]]) - extends Exception( - "Compiler exception " + messages.map(_.mkString("\n")).mkString("\n")) + extends Exception("Compiler exception " + messages.map(_.mkString("\n")).mkString("\n")) } diff --git a/server/src/main/scala/org/scalaexercises/evaluator/services.scala b/server/src/main/scala/org/scalaexercises/evaluator/services.scala index bd42b9ee..db575d40 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/services.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/services.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -30,10 +30,9 @@ object services { Header("Vary", "Origin,Access-Control-Request-Methods"), Header("Access-Control-Allow-Methods", "POST"), Header("Access-Control-Allow-Origin", "*"), - Header( - "Access-Control-Allow-Headers", - "x-scala-eval-api-token, Content-Type"), - Header("Access-Control-Max-Age", 1.day.toSeconds.toString())) + Header("Access-Control-Allow-Headers", "x-scala-eval-api-token, Content-Type"), + Header("Access-Control-Max-Age", 1.day.toSeconds.toString()) + ) def evalService = auth(HttpService { @@ -58,11 +57,7 @@ object services { case Timeout(_) => EvalResponse(`Timeout Exceded`, None, None, Map.empty) case UnresolvedDependency(msg) => - EvalResponse( - `Unresolved Dependency` + " : " + msg, - None, - None, - Map.empty) + EvalResponse(`Unresolved Dependency` + " : " + msg, None, None, Map.empty) case EvalRuntimeError(cis, runtimeError) => EvalResponse( `Runtime Error`, @@ -72,11 +67,7 @@ object services { case CompilationError(cis) => EvalResponse(`Compilation Error`, None, None, cis) case GeneralError(err) => - EvalResponse( - `Unforeseen Exception`, - None, - None, - Map.empty) + EvalResponse(`Unforeseen Exception`, None, None, Map.empty) } Ok(response.asJson) } @@ -110,7 +101,7 @@ object EvaluatorServer extends App { val ip = Option(System.getenv("HOST")).getOrElse("0.0.0.0") val port = (Option(System.getenv("PORT")) orElse - Option(System.getProperty("http.port"))).map(_.toInt).getOrElse(8080) + Option(System.getProperty("http.port"))).map(_.toInt).getOrElse(8080) logger.info(s"Initializing Evaluator at $ip:$port") diff --git a/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala b/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala index a021830c..270cac69 100644 --- a/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala +++ b/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -12,6 +12,7 @@ import io.circe.syntax._ import org.http4s.dsl._ import org.http4s.headers._ import org.http4s.{Status => HttpStatus, _} +import org.scalaexercises.evaluator.helper._ import org.scalatest._ import pdi.jwt.{Jwt, JwtAlgorithm} import scodec.bits.ByteVector @@ -25,16 +26,12 @@ class EvalEndpointSpec extends FunSpec with Matchers { import codecs._ import services._ - val sonatypeReleases = "https://oss.sonatype.org/content/repositories/releases/" :: Nil + val validToken: String = + Jwt.encode("""{"user": "scala-exercises"}""", auth.secretKey, JwtAlgorithm.HS256) - val validToken = Jwt.encode( - """{"user": "scala-exercises"}""", - auth.secretKey, - JwtAlgorithm.HS256) + val invalidToken: String = java.util.UUID.randomUUID.toString - val invalidToken = java.util.UUID.randomUUID.toString - - def serve(evalRequest: EvalRequest, authHeader: Header) = + def serve(evalRequest: EvalRequest, authHeader: Header): Response = evalService .run( Request( @@ -46,17 +43,17 @@ class EvalEndpointSpec extends FunSpec with Matchers { ) ) ).putHeaders(authHeader)) - .run + .unsafePerformSync def verifyEvalResponse( - response: Response, - expectedStatus: HttpStatus, - expectedValue: Option[String] = None, - expectedMessage: String - ) = { + response: Response, + expectedStatus: HttpStatus, + expectedValue: Option[String] = None, + expectedMessage: String + ): Assertion = { response.status should be(expectedStatus) - val evalResponse = response.as[EvalResponse].run + val evalResponse = response.as[EvalResponse].unsafePerformSync evalResponse.value should be(expectedValue) evalResponse.msg should be(expectedMessage) } @@ -65,7 +62,10 @@ class EvalEndpointSpec extends FunSpec with Matchers { it("can evaluate simple expressions") { verifyEvalResponse( response = serve( - EvalRequest(code = "{ 41 + 1 }"), + EvalRequest( + code = "{ 41 + 1 }", + resolvers = commonResolvers, + dependencies = scalaDependencies(Scala211)), `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), @@ -76,7 +76,10 @@ class EvalEndpointSpec extends FunSpec with Matchers { it("fails with a timeout when takes longer than the configured timeout") { verifyEvalResponse( response = serve( - EvalRequest(code = "{ while(true) {}; 123 }"), + EvalRequest( + code = "{ while(true) {}; 123 }", + resolvers = commonResolvers, + dependencies = scalaDependencies(Scala211)), `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = None, @@ -89,10 +92,12 @@ class EvalEndpointSpec extends FunSpec with Matchers { response = serve( EvalRequest( code = "{import cats._; Eval.now(42).value}", - resolvers = sonatypeReleases, - dependencies = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil + resolvers = commonResolvers, + dependencies = List(Dependency("org.typelevel", "cats_2.11", "0.6.0")) ++ scalaDependencies( + Scala211) ), - `X-Scala-Eval-Api-Token`(validToken)), + `X-Scala-Eval-Api-Token`(validToken) + ), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), expectedMessage = `ok` @@ -101,21 +106,19 @@ class EvalEndpointSpec extends FunSpec with Matchers { it("can load different versions of a dependency across evaluations") { val code = "{import cats._; Eval.now(42).value}" - val resolvers = sonatypeReleases + val resolvers = commonResolvers List("0.6.0", "0.4.1") foreach { version => verifyEvalResponse( - response = - serve( - EvalRequest( - code = code, - resolvers = resolvers, - dependencies = Dependency( - "org.typelevel", - "cats_2.11", - version) :: Nil - ), - `X-Scala-Eval-Api-Token`(validToken)), + response = serve( + EvalRequest( + code = code, + resolvers = resolvers, + dependencies = List(Dependency("org.typelevel", "cats_2.11", "0.6.0")) ++ scalaDependencies( + Scala211) + ), + `X-Scala-Eval-Api-Token`(validToken) + ), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), expectedMessage = `ok` @@ -125,17 +128,19 @@ class EvalEndpointSpec extends FunSpec with Matchers { } it("can run code from the exercises content") { + verifyEvalResponse( response = serve( EvalRequest( - code = "{import stdlib._; Asserts.scalaTestAsserts(true)}", - resolvers = sonatypeReleases, - dependencies = Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - "0.2.0") :: Nil + code = exerciseContentCode(true), + resolvers = commonResolvers, + dependencies = List(Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion)) ++ scalaDependencies(Scala211) ), - `X-Scala-Eval-Api-Token`(validToken)), + `X-Scala-Eval-Api-Token`(validToken) + ), expectedStatus = HttpStatus.Ok, expectedValue = Some("()"), expectedMessage = `ok` @@ -146,14 +151,15 @@ class EvalEndpointSpec extends FunSpec with Matchers { verifyEvalResponse( response = serve( EvalRequest( - code = "{import stdlib._; Asserts.scalaTestAsserts(false)}", - resolvers = sonatypeReleases, - dependencies = Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - "0.2.0") :: Nil + code = exerciseContentCode(false), + resolvers = commonResolvers, + dependencies = List(Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion)) ++ scalaDependencies(Scala211) ), - `X-Scala-Eval-Api-Token`(validToken)), + `X-Scala-Eval-Api-Token`(validToken) + ), expectedStatus = HttpStatus.Ok, expectedValue = Some("true was not false"), expectedMessage = `Runtime Error` @@ -167,8 +173,7 @@ class EvalEndpointSpec extends FunSpec with Matchers { resolvers = Nil, dependencies = Nil ), - `X-Scala-Eval-Api-Token`(invalidToken)).status should be( - HttpStatus.Unauthorized) + `X-Scala-Eval-Api-Token`(invalidToken)).status should be(HttpStatus.Unauthorized) } it("rejects requests with missing tokens") { diff --git a/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala b/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala index d643ec9a..8b86b6e4 100644 --- a/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala +++ b/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala @@ -1,13 +1,13 @@ /* - * scala-exercises-evaluator-server + * scala-exercises - evaluator-server * Copyright (C) 2015-2016 47 Degrees, LLC. */ package org.scalaexercises.evaluator import monix.execution.Scheduler +import org.scalaexercises.evaluator.helper._ import org.scalatest._ -import org.scalatest.exceptions.TestFailedException import scala.concurrent.duration._ import scala.language.postfixOps @@ -16,11 +16,21 @@ class EvaluatorSpec extends FunSpec with Matchers { implicit val scheduler: Scheduler = Scheduler.io("exercises-spec") val evaluator = new Evaluator(20 seconds) - val remotes = "https://oss.sonatype.org/content/repositories/releases/" :: Nil - describe("evaluation") { - it("can evaluate simple expressions") { - val result: EvalResult[Int] = evaluator.eval("{ 41 + 1 }").run + it("can evaluate simple expressions, for Scala 2.11") { + val result: EvalResult[Int] = evaluator + .eval("{ 41 + 1 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala211)) + .unsafePerformSync + + result should matchPattern { + case EvalSuccess(_, 42, _) ⇒ + } + } + + it("can evaluate simple expressions, for Scala 2.12") { + val result: EvalResult[Int] = evaluator + .eval("{ 41 + 1 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala212)) + .unsafePerformSync result should matchPattern { case EvalSuccess(_, 42, _) ⇒ @@ -28,41 +38,87 @@ class EvaluatorSpec extends FunSpec with Matchers { } it("fails with a timeout when takes longer than the configured timeout") { - val result: EvalResult[Int] = - evaluator.eval("{ while(true) {}; 123 }").run + val result: EvalResult[Int] = evaluator + .eval( + "{ while(true) {}; 123 }", + remotes = commonResolvers, + dependencies = scalaDependencies(Scala211)) + .unsafePerformSync result should matchPattern { case Timeout(_) ⇒ } } - describe("can load dependencies for an evaluation") { - val code = "{import cats._; Eval.now(42).value}" + it("can load dependencies for an evaluation") { + val code = """ +import cats.data.Xor - val dependencies = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil +Xor.Right(42).toOption.get + """ + val remotes = + List("https://oss.sonatype.org/content/repositories/releases/") + val dependencies = List( + Dependency("org.typelevel", "cats_2.11", "0.6.0") + ) val result: EvalResult[Int] = evaluator - .eval(code, remotes = remotes, dependencies = dependencies) - .run + .eval( + code, + remotes = remotes, + dependencies = dependencies + ) + .unsafePerformSync result should matchPattern { case EvalSuccess(_, 42, _) => } } - describe("can load different versions of a dependency across evaluations") { - val code = "{import cats._; Eval.now(42).value}" + it( + s"can load binary incompatible dependencies for an evaluation, for scala ${BuildInfo.scalaVersion}") { - val dependencies1 = Dependency("org.typelevel", "cats_2.11", "0.4.1") :: Nil + val result: EvalResult[Int] = evaluator + .eval( + fetchCode, + remotes = commonResolvers, + dependencies = fetchLibraryDependencies(toScalaVersion(BuildInfo.scalaVersion)) + ) + .unsafePerformSync - val dependencies2 = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil + result should matchPattern { + case EvalSuccess(_, _, _) => + } + } + + it("can load different versions of a dependency across evaluations") { + val code = """ +import cats._ +Eval.now(42).value + """ + val remotes = + List("https://oss.sonatype.org/content/repositories/releases/") + val dependencies1 = List( + Dependency("org.typelevel", "cats_2.11", "0.4.1") + ) ++ scalaDependencies(Scala211) + val dependencies2 = List( + Dependency("org.typelevel", "cats_2.11", "0.6.0") + ) ++ scalaDependencies(Scala211) val result1: EvalResult[Int] = evaluator - .eval(code, remotes = remotes, dependencies = dependencies1) - .run + .eval( + code, + remotes = remotes, + dependencies = dependencies1 + ) + .unsafePerformSync val result2: EvalResult[Int] = evaluator - .eval(code, remotes = remotes, dependencies = dependencies2) - .run + .eval( + code, + remotes = remotes, + dependencies = dependencies2 + ) + .unsafePerformSync result1 should matchPattern { case EvalSuccess(_, 42, _) => @@ -72,40 +128,40 @@ class EvaluatorSpec extends FunSpec with Matchers { } } - describe("can run code from the exercises content") { - val code = "{import stdlib._; Asserts.scalaTestAsserts(true)}" - - val dependencies = Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - "0.2.0") :: Nil + it("can run code from the exercises content") { + val code = exerciseContentCode(true) + val dependencies = List( + Dependency("org.scala-exercises", "exercises-stdlib_2.11", exercisesVersion) + ) ++ scalaDependencies(Scala211) val result: EvalResult[Unit] = evaluator - .eval(code, remotes = remotes, dependencies = dependencies) - .run + .eval( + code, + remotes = commonResolvers, + dependencies = dependencies + ) + .unsafePerformSync result should matchPattern { case EvalSuccess(_, (), _) => } } - describe("captures exceptions when running the exercises content") { - val code = "{import stdlib._; Asserts.scalaTestAsserts(false)}" + it("captures exceptions when running the exercises content") { - val dependencies = Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - "0.2.0") :: Nil + val dependencies = List( + Dependency("org.scala-exercises", "exercises-stdlib_2.11", exercisesVersion) + ) ++ scalaDependencies(Scala211) val result: EvalResult[Unit] = evaluator - .eval(code, remotes = remotes, dependencies = dependencies) - .run - - result should matchPattern { - case EvalRuntimeError( - _, - Some(RuntimeError(err: TestFailedException, _))) => - } + .eval( + exerciseContentCode(false), + remotes = commonResolvers, + dependencies = dependencies + ) + .unsafePerformSync + + result shouldBe a[EvalRuntimeError[_]] } describe("can run code from the exercises content") { @@ -115,12 +171,11 @@ class EvaluatorSpec extends FunSpec with Matchers { val result: EvalResult[Unit] = evaluator .eval(code, remotes = remotes, dependencies = dependencies) - .run + .unsafePerformSync result should matchPattern { case EvalSuccess(_, 42, _) => } } - } } diff --git a/server/src/test/scala/org/scalaexercises/evaluator/helper.scala b/server/src/test/scala/org/scalaexercises/evaluator/helper.scala new file mode 100644 index 00000000..cbf1daf2 --- /dev/null +++ b/server/src/test/scala/org/scalaexercises/evaluator/helper.scala @@ -0,0 +1,129 @@ +/* + * scala-exercises - evaluator-server + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator + +object helper { + + val remotes: List[String] = "https://oss.sonatype.org/content/repositories/releases/" :: Nil + val exercisesVersion: String = "0.4.1-SNAPSHOT" + + sealed abstract class ScalaVersion(val version: String) + case object Scala211 extends ScalaVersion("2.11.8") + case object Scala212 extends ScalaVersion("2.12.1") + + def toScalaVersion(v: String): ScalaVersion = v match { + case version if version.startsWith("2.11") => Scala211 + case version if version.startsWith("2.12") => Scala212 + case _ => throw new IllegalArgumentException("Unknown Scala Version") + } + + val commonResolvers = List( + "https://oss.sonatype.org/content/repositories/snapshots", + "https://oss.sonatype.org/content/repositories/public", + "http://repo1.maven.org/maven2" + ) + + def scalaDependencies(scala: ScalaVersion): List[Dependency] = List( + Dependency("org.scala-lang", s"scala-library", s"${scala.version}"), + Dependency("org.scala-lang", s"scala-api", s"${scala.version}"), + Dependency("org.scala-lang", s"scala-reflect", s"${scala.version}"), + Dependency("org.scala-lang", s"scala-compiler", s"${scala.version}"), + Dependency("org.scala-lang", "scala-xml", s"${scala.version}") + ) + + def fetchLibraryDependencies(scala: ScalaVersion): List[Dependency] = { + val sv = scala.version + List( + Dependency("com.fortysevendeg", s"fetch_${sv.substring(0, 4)}", "0.4.0"), + Dependency("com.fortysevendeg", s"fetch-monix_${sv.substring(0, 4)}", "0.4.0") + ) ++ scalaDependencies(scala) + } + + def exerciseContentCode(assertCheck: Boolean) = + s""" +import org.scalaexercises.content._ +import stdlib.Asserts +import stdlib.Asserts._ +import stdlib._ + +Asserts.scalaTestAsserts($assertCheck) + """ + + val fetchCode = + """ +type UserId = Int +case class User(id: UserId, username: String) + +def latency[A](result: A, msg: String) = { + val id = Thread.currentThread.getId + println(s"~~> [$id] $msg") + Thread.sleep(100) + println(s"<~~ [$id] $msg") + result +} + +import cats.data.NonEmptyList +import cats.instances.list._ + +import fetch._ + +val userDatabase: Map[UserId, User] = Map( + 1 -> User(1, "@one"), + 2 -> User(2, "@two"), + 3 -> User(3, "@three"), + 4 -> User(4, "@four") +) + +implicit object UserSource extends DataSource[UserId, User]{ + override def name = "User" + + override def fetchOne(id: UserId): Query[Option[User]] = { + Query.sync({ + latency(userDatabase.get(id), s"One User $id") + }) + } + override def fetchMany(ids: NonEmptyList[UserId]): Query[Map[UserId, User]] = { + Query.sync({ + latency(userDatabase.filterKeys(ids.toList.contains), s"Many Users $ids") + }) + } +} + +def getUser(id: UserId): Fetch[User] = Fetch(id) // or, more explicitly: Fetch(id)(UserSource) + +implicit object UnbatchedSource extends DataSource[Int, Int]{ + override def name = "Unbatched" + + override def fetchOne(id: Int): Query[Option[Int]] = { + Query.sync(Option(id)) + } + override def fetchMany(ids: NonEmptyList[Int]): Query[Map[Int, Int]] = { + batchingNotSupported(ids) + } +} + +val fetchUser: Fetch[User] = getUser(1) + +import cats.Id +import fetch.unsafe.implicits._ +import fetch.syntax._ + +fetchUser.runA[Id] + +val fetchTwoUsers: Fetch[(User, User)] = for { + aUser <- getUser(1) + anotherUser <- getUser(aUser.id + 1) +} yield (aUser, anotherUser) + +fetchTwoUsers.runA[Id] + +import cats.syntax.cartesian._ + +val fetchProduct: Fetch[(User, User)] = getUser(1).product(getUser(2)) + +fetchProduct.runA[Id] + """ +} diff --git a/shared/shared/src/main/scala/org/scalaexercises/evaluator/types.scala b/shared/shared/src/main/scala/org/scalaexercises/evaluator/types.scala index 7abdb336..df14ff11 100644 --- a/shared/shared/src/main/scala/org/scalaexercises/evaluator/types.scala +++ b/shared/shared/src/main/scala/org/scalaexercises/evaluator/types.scala @@ -1,5 +1,5 @@ /* - * scala-exercises-evaluator-shared + * scala-exercises - evaluator-shared * Copyright (C) 2015-2016 47 Degrees, LLC. */ @@ -21,37 +21,32 @@ object EvalResult { import org.scalaexercises.evaluator.EvalResult._ -final case class EvalSuccess[A](compilationInfos: CI, - result: A, - consoleOutput: String) +final case class EvalSuccess[A](compilationInfos: CI, result: A, consoleOutput: String) extends EvalResult[A] final case class Timeout[A](duration: FiniteDuration) extends EvalResult[A] -final case class UnresolvedDependency[A](explanation: String) - extends EvalResult[A] +final case class UnresolvedDependency[A](explanation: String) extends EvalResult[A] -final case class EvalRuntimeError[A](compilationInfos: CI, - runtimeError: Option[RuntimeError]) +final case class EvalRuntimeError[A](compilationInfos: CI, runtimeError: Option[RuntimeError]) extends EvalResult[A] -final case class CompilationError[A](compilationInfos: CI) - extends EvalResult[A] +final case class CompilationError[A](compilationInfos: CI) extends EvalResult[A] final case class GeneralError[A](stack: Throwable) extends EvalResult[A] -final case class Dependency(groupId: String, - artifactId: String, - version: String) +final case class Dependency(groupId: String, artifactId: String, version: String) -final case class EvalRequest(resolvers: List[String] = Nil, - dependencies: List[Dependency] = Nil, - code: String) +final case class EvalRequest( + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String) -final case class EvalResponse(msg: String, - value: Option[String] = None, - valueType: Option[String] = None, - compilationInfos: CI = Map.empty) +final case class EvalResponse( + msg: String, + value: Option[String] = None, + valueType: Option[String] = None, + compilationInfos: CI = Map.empty) object EvalResponse { diff --git a/version.sbt b/version.sbt new file mode 100644 index 00000000..03a8b079 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "0.2.0-SNAPSHOT"