diff --git a/.scalafmt b/.scalafmt new file mode 100644 index 00000000..e5d869b0 --- /dev/null +++ b/.scalafmt @@ -0,0 +1,5 @@ +--style defaultWithAlign +--maxColumn 80 +--continuationIndentCallSite 2 +--continuationIndentDefnSite 2 +--alignByOpenParenCallSite false \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf deleted file mode 100644 index 106c0cc5..00000000 --- a/.scalafmt.conf +++ /dev/null @@ -1,23 +0,0 @@ -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 0b8d0eaa..fb933afe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ services: - docker scala: - 2.11.8 -- 2.12.1 jdk: - oraclejdk8 cache: @@ -15,7 +14,7 @@ cache: env: global: JAVA_OPTS=-Xmx2g SBT_OPTS="-XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" script: -- sbt ++$TRAVIS_SCALA_VERSION test +- sbt test before_install: - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then @@ -23,20 +22,13 @@ before_install: fi after_success: -- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" -a "$TRAVIS_SCALA_VERSION" = "2.11.8" ]; then +- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; 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 \ No newline at end of file + fi diff --git a/README.md b/README.md index 842fa185..5bd45329 100644 --- a/README.md +++ b/README.md @@ -153,14 +153,3 @@ 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 978ab733..1de23014 100644 --- a/build.sbt +++ b/build.sbt @@ -1,20 +1,34 @@ +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 `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-client` = (crossProject in file("client")) .dependsOn(`evaluator-shared`) @@ -22,77 +36,86 @@ lazy val `evaluator-client` = (crossProject in file("client")) .settings( name := "evaluator-client", libraryDependencies ++= Seq( - %%("roshttp"), - %%("cats-free"), - %%("circe-core"), - %%("circe-generic"), - %%("circe-parser"), - %%("log4s"), - %("slf4j-simple"), - %%("scalatest") % "test" + "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" ) - ) - .jsSettings(sharedJsSettings: _*) +) + .jsSettings(scalaJSSettings: _*) 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( - %%("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" + "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" ), - assemblyJarName in assembly := "evaluator-server.jar", - buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), - buildInfoPackage := "org.scalaexercises.evaluator" + assemblyJarName in assembly := "evaluator-server.jar" ) .settings(dockerSettings) - .settings(scalaMacroDependencies: _*) + .settings(compilerDependencySettings: _*) lazy val `smoketests` = (project in file("smoketests")) .dependsOn(`evaluator-server`) - .settings(noPublishSettings: _*) .settings( name := "evaluator-server-smoke-tests", libraryDependencies ++= Seq( - %%("circe-core"), - %%("circe-generic"), - %%("circe-parser"), - %%("http4s-blaze-client", http4sV), - %%("http4s-circe", http4sV), - %%("jwt-core"), - %%("scalatest") % "test" + "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) ) + ) -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" -) +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") +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")) +) 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 fca11f7a..d594d443 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,5 +16,6 @@ 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 027b2559..757381a6 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 18c13487..10b2374d 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,12 +8,7 @@ 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 @@ -30,12 +25,16 @@ 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 09f2b6e3..15618084 100644 --- a/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala +++ b/client/shared/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala @@ -1,14 +1,16 @@ /* - * 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 @@ -22,26 +24,31 @@ 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 Left(e) => + case Xor.Left(e) => Either.left(JsonParsingException(e.getMessage, r.body)) - case Right(result) => - Either.right(EvaluationResult(result, r.statusCode, r.headers.toLowerCase)) + case Xor.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 63ccdd15..9449a273 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,12 +19,11 @@ 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 0174edd0..0a91a708 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 72668796..687bbceb 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 5709bac1..aa9e0da5 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 824a289f..42a232ec 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 b29aa205..c1fdd0f6 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 new file mode 100644 index 00000000..3a416d87 --- /dev/null +++ b/project/EvaluatorBuild.scala @@ -0,0 +1,127 @@ +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 deleted file mode 100644 index 41479460..00000000 --- a/project/ProjectPlugin.scala +++ /dev/null @@ -1,79 +0,0 @@ -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 7d43061c..6fd406f6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,7 @@ -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") +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") diff --git a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala index e5e36730..d914f904 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.{Failure, Success, Try} -import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim, JwtHeader, JwtOptions} +import scala.util.{Try, Success, Failure} +import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions} import org.log4s.getLogger @@ -49,7 +49,8 @@ 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 bd092209..30c6002b 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.{Decoder, Encoder, Json, Printer} +import io.circe.{Encoder, Decoder, Json, Printer} import org.http4s.headers.`Content-Type` import io.circe.jawn.CirceSupportParser.facade @@ -15,14 +15,17 @@ 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(_) ) } @@ -33,7 +36,8 @@ 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 4e5a4207..c1d17ec6 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,13 +29,15 @@ 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 = @@ -47,21 +49,23 @@ 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]) = { @@ -80,11 +84,15 @@ 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 @@ -116,43 +124,37 @@ 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 } } /** - * 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. - */ + * 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[_]]() @@ -161,7 +163,8 @@ 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]] @@ -180,12 +183,12 @@ private class StringCompiler( } messages += (severityName + lineMessage + ": " + message) :: (if (pos.isDefined) { - pos.finalPosition.lineContent.stripLineEnd :: - (" " * (pos.column - 1) + "^") :: + pos.finalPosition.lineContent.stripLineEnd :: + (" " * (pos.column - 1) + "^") :: + Nil + } else { Nil - } else { - Nil - }) + }) } def displayPrompt { @@ -207,7 +210,8 @@ 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() } } @@ -217,7 +221,8 @@ 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 { @@ -232,8 +237,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 @@ -253,13 +258,12 @@ 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() @@ -270,25 +274,19 @@ private class StringCompiler( } /** - * 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()` - */ + * 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") @@ -324,17 +322,20 @@ 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 @@ -358,9 +359,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 @@ -368,7 +369,8 @@ 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 { @@ -419,7 +421,9 @@ 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 @@ -439,7 +443,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() @@ -471,7 +475,8 @@ 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) } } @@ -479,5 +484,6 @@ 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 db575d40..bd42b9ee 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,9 +30,10 @@ 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 { @@ -57,7 +58,11 @@ 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`, @@ -67,7 +72,11 @@ 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) } @@ -101,7 +110,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 270cac69..a021830c 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,7 +12,6 @@ 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 @@ -26,12 +25,16 @@ class EvalEndpointSpec extends FunSpec with Matchers { import codecs._ import services._ - val validToken: String = - Jwt.encode("""{"user": "scala-exercises"}""", auth.secretKey, JwtAlgorithm.HS256) + val sonatypeReleases = "https://oss.sonatype.org/content/repositories/releases/" :: Nil - val invalidToken: String = java.util.UUID.randomUUID.toString + val validToken = Jwt.encode( + """{"user": "scala-exercises"}""", + auth.secretKey, + JwtAlgorithm.HS256) - def serve(evalRequest: EvalRequest, authHeader: Header): Response = + val invalidToken = java.util.UUID.randomUUID.toString + + def serve(evalRequest: EvalRequest, authHeader: Header) = evalService .run( Request( @@ -43,17 +46,17 @@ class EvalEndpointSpec extends FunSpec with Matchers { ) ) ).putHeaders(authHeader)) - .unsafePerformSync + .run def verifyEvalResponse( - response: Response, - expectedStatus: HttpStatus, - expectedValue: Option[String] = None, - expectedMessage: String - ): Assertion = { + response: Response, + expectedStatus: HttpStatus, + expectedValue: Option[String] = None, + expectedMessage: String + ) = { response.status should be(expectedStatus) - val evalResponse = response.as[EvalResponse].unsafePerformSync + val evalResponse = response.as[EvalResponse].run evalResponse.value should be(expectedValue) evalResponse.msg should be(expectedMessage) } @@ -62,10 +65,7 @@ class EvalEndpointSpec extends FunSpec with Matchers { it("can evaluate simple expressions") { verifyEvalResponse( response = serve( - EvalRequest( - code = "{ 41 + 1 }", - resolvers = commonResolvers, - dependencies = scalaDependencies(Scala211)), + EvalRequest(code = "{ 41 + 1 }"), `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), @@ -76,10 +76,7 @@ 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 }", - resolvers = commonResolvers, - dependencies = scalaDependencies(Scala211)), + EvalRequest(code = "{ while(true) {}; 123 }"), `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = None, @@ -92,12 +89,10 @@ class EvalEndpointSpec extends FunSpec with Matchers { response = serve( EvalRequest( code = "{import cats._; Eval.now(42).value}", - resolvers = commonResolvers, - dependencies = List(Dependency("org.typelevel", "cats_2.11", "0.6.0")) ++ scalaDependencies( - Scala211) + resolvers = sonatypeReleases, + dependencies = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil ), - `X-Scala-Eval-Api-Token`(validToken) - ), + `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), expectedMessage = `ok` @@ -106,19 +101,21 @@ 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 = commonResolvers + val resolvers = sonatypeReleases List("0.6.0", "0.4.1") foreach { version => verifyEvalResponse( - 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) - ), + response = + serve( + EvalRequest( + code = code, + resolvers = resolvers, + dependencies = Dependency( + "org.typelevel", + "cats_2.11", + version) :: Nil + ), + `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("42"), expectedMessage = `ok` @@ -128,19 +125,17 @@ class EvalEndpointSpec extends FunSpec with Matchers { } it("can run code from the exercises content") { - verifyEvalResponse( response = serve( EvalRequest( - code = exerciseContentCode(true), - resolvers = commonResolvers, - dependencies = List(Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - exercisesVersion)) ++ scalaDependencies(Scala211) + code = "{import stdlib._; Asserts.scalaTestAsserts(true)}", + resolvers = sonatypeReleases, + dependencies = Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + "0.2.0") :: Nil ), - `X-Scala-Eval-Api-Token`(validToken) - ), + `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("()"), expectedMessage = `ok` @@ -151,15 +146,14 @@ class EvalEndpointSpec extends FunSpec with Matchers { verifyEvalResponse( response = serve( EvalRequest( - code = exerciseContentCode(false), - resolvers = commonResolvers, - dependencies = List(Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - exercisesVersion)) ++ scalaDependencies(Scala211) + code = "{import stdlib._; Asserts.scalaTestAsserts(false)}", + resolvers = sonatypeReleases, + dependencies = Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + "0.2.0") :: Nil ), - `X-Scala-Eval-Api-Token`(validToken) - ), + `X-Scala-Eval-Api-Token`(validToken)), expectedStatus = HttpStatus.Ok, expectedValue = Some("true was not false"), expectedMessage = `Runtime Error` @@ -173,7 +167,8 @@ 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 8b86b6e4..d643ec9a 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,21 +16,11 @@ class EvaluatorSpec extends FunSpec with Matchers { implicit val scheduler: Scheduler = Scheduler.io("exercises-spec") val evaluator = new Evaluator(20 seconds) - describe("evaluation") { - 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, _) ⇒ - } - } + val remotes = "https://oss.sonatype.org/content/repositories/releases/" :: Nil - it("can evaluate simple expressions, for Scala 2.12") { - val result: EvalResult[Int] = evaluator - .eval("{ 41 + 1 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala212)) - .unsafePerformSync + describe("evaluation") { + it("can evaluate simple expressions") { + val result: EvalResult[Int] = evaluator.eval("{ 41 + 1 }").run result should matchPattern { case EvalSuccess(_, 42, _) ⇒ @@ -38,87 +28,41 @@ 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 }", - remotes = commonResolvers, - dependencies = scalaDependencies(Scala211)) - .unsafePerformSync + val result: EvalResult[Int] = + evaluator.eval("{ while(true) {}; 123 }").run result should matchPattern { case Timeout(_) ⇒ } } - it("can load dependencies for an evaluation") { - val code = """ -import cats.data.Xor + describe("can load dependencies for an evaluation") { + val code = "{import cats._; Eval.now(42).value}" -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 dependencies = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil val result: EvalResult[Int] = evaluator - .eval( - code, - remotes = remotes, - dependencies = dependencies - ) - .unsafePerformSync + .eval(code, remotes = remotes, dependencies = dependencies) + .run result should matchPattern { case EvalSuccess(_, 42, _) => } } - it( - s"can load binary incompatible dependencies for an evaluation, for scala ${BuildInfo.scalaVersion}") { - - val result: EvalResult[Int] = evaluator - .eval( - fetchCode, - remotes = commonResolvers, - dependencies = fetchLibraryDependencies(toScalaVersion(BuildInfo.scalaVersion)) - ) - .unsafePerformSync + describe("can load different versions of a dependency across evaluations") { + val code = "{import cats._; Eval.now(42).value}" - result should matchPattern { - case EvalSuccess(_, _, _) => - } - } + val dependencies1 = Dependency("org.typelevel", "cats_2.11", "0.4.1") :: Nil - 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 dependencies2 = Dependency("org.typelevel", "cats_2.11", "0.6.0") :: Nil val result1: EvalResult[Int] = evaluator - .eval( - code, - remotes = remotes, - dependencies = dependencies1 - ) - .unsafePerformSync + .eval(code, remotes = remotes, dependencies = dependencies1) + .run val result2: EvalResult[Int] = evaluator - .eval( - code, - remotes = remotes, - dependencies = dependencies2 - ) - .unsafePerformSync + .eval(code, remotes = remotes, dependencies = dependencies2) + .run result1 should matchPattern { case EvalSuccess(_, 42, _) => @@ -128,40 +72,40 @@ Eval.now(42).value } } - 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) + 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 val result: EvalResult[Unit] = evaluator - .eval( - code, - remotes = commonResolvers, - dependencies = dependencies - ) - .unsafePerformSync + .eval(code, remotes = remotes, dependencies = dependencies) + .run result should matchPattern { case EvalSuccess(_, (), _) => } } - it("captures exceptions when running the exercises content") { + describe("captures exceptions when running the exercises content") { + val code = "{import stdlib._; Asserts.scalaTestAsserts(false)}" - val dependencies = List( - Dependency("org.scala-exercises", "exercises-stdlib_2.11", exercisesVersion) - ) ++ scalaDependencies(Scala211) + val dependencies = Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + "0.2.0") :: Nil val result: EvalResult[Unit] = evaluator - .eval( - exerciseContentCode(false), - remotes = commonResolvers, - dependencies = dependencies - ) - .unsafePerformSync - - result shouldBe a[EvalRuntimeError[_]] + .eval(code, remotes = remotes, dependencies = dependencies) + .run + + result should matchPattern { + case EvalRuntimeError( + _, + Some(RuntimeError(err: TestFailedException, _))) => + } } describe("can run code from the exercises content") { @@ -171,11 +115,12 @@ Eval.now(42).value val result: EvalResult[Unit] = evaluator .eval(code, remotes = remotes, dependencies = dependencies) - .unsafePerformSync + .run 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 deleted file mode 100644 index cbf1daf2..00000000 --- a/server/src/test/scala/org/scalaexercises/evaluator/helper.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 df14ff11..7abdb336 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,32 +21,37 @@ 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 deleted file mode 100644 index 03a8b079..00000000 --- a/version.sbt +++ /dev/null @@ -1 +0,0 @@ -version in ThisBuild := "0.2.0-SNAPSHOT"