diff --git a/build.sbt b/build.sbt index 286164b..e7b264f 100644 --- a/build.sbt +++ b/build.sbt @@ -28,6 +28,13 @@ libraryDependencies += "de.vandermeer" % "asciitable" % "0.3.2" libraryDependencies += "com.lihaoyi" %% "fansi" % "0.2.5" libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value libraryDependencies += "au.com.bytecode" % "opencsv" % "2.4" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test" + +libraryDependencies ++= Seq( + "com.softwaremill.sttp" %% "core" % "1.5.4", + "com.softwaremill.sttp" %% "spray-json" % "1.5.4" +) + debianPackageDependencies := Seq("java8-runtime-headless") @@ -38,11 +45,23 @@ lazy val cli = (project in file(".")). enablePlugins(BuildInfoPlugin). enablePlugins(DebianPlugin). enablePlugins(WindowsPlugin). - + enablePlugins(GraalVMNativeImagePlugin). + settings( + graalVMNativeImageOptions ++= Seq( + "--enable-https", + "--enable-http", + "--enable-all-security-services", + "--allow-incomplete-classpath", + "--enable-url-protocols=http,https" + ) + ). + enablePlugins(JDKPackagerPlugin). settings( buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), - buildInfoPackage := "de.upb.cs.swt.delphi.cli" + buildInfoPackage := "de.upb.cs.swt.delphi.cli", ) scalastyleConfig := baseDirectory.value / "project" / "scalastyle-config.xml" trapExit := false +fork := true +connectInput := true diff --git a/project/build.properties b/project/build.properties index 210243d..7609b47 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.1.1 +sbt.version = 1.2.8 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 705f861..86636a4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ // build management and packaging addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15") // coverage addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala index cc5b016..fcdb9dc 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala @@ -23,7 +23,7 @@ package de.upb.cs.swt.delphi.cli * @param verbose Marker if logging should be verbose * @param mode The command to be run */ -case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://delphi.cs.uni-paderborn.de/api/"), +case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://delphi.cs.uni-paderborn.de/api"), verbose: Boolean = false, raw: Boolean = false, csv: String = "", diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala index 1c5be43..6148ef6 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala @@ -16,81 +16,87 @@ package de.upb.cs.swt.delphi.cli -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import de.upb.cs.swt.delphi.cli.commands.{RetrieveCommand, SearchCommand, TestCommand} - -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, ExecutionContext} - +import com.softwaremill.sttp._ +import de.upb.cs.swt.delphi.cli.commands._ /** * The application class for the Delphi command line interface */ -object DelphiCLI extends App { +object DelphiCLI { + + + def main(args: Array[String]): Unit = { - implicit val system = ActorSystem() + def getEnvOrElse(envVar: String, defaultPath: String) = sys.env.getOrElse(envVar, defaultPath) - val cliParser = { - new scopt.OptionParser[Config]("delphi-cli") { - head("Delphi Command Line Tool", s"(${BuildInfo.version})") + val javaLibPath = getEnvOrElse("JAVA_LIB_PATH", "/usr/lib/jvm/default-java/lib/") - version("version").text("Prints the version of the command line tool.") + val trustStorePath = getEnvOrElse("JAVA_TRUSTSTORE", "/usr/lib/jvm/default-java/lib/security/cacerts") - help("help").text("Prints this help text.") - override def showUsageOnError = true + System.setProperty("java.library.path", javaLibPath) + System.setProperty("javax.net.ssl.trustStore", trustStorePath) - opt[String]("server").action( (x,c) => c.copy(server = x)).text("The url to the Delphi server") - opt[Unit] (name = "raw").action((_,c) => c.copy(raw = true)).text("Output the raw results") - opt[Unit] (name = "silent").action((_,c) => c.copy(silent = true)).text("Suppress non-result output") + cliParser.parse(args, Config()) match { + case Some(c) => - checkConfig(c => if (c.server.isEmpty()) failure("Option server is required.") else success) - cmd("test").action((_,c) => c.copy(mode = "test")) + implicit val config: Config = c + implicit val backend: SttpBackend[Id, Nothing] = HttpURLConnectionBackend() - cmd("retrieve").action((s,c) => c.copy(mode = "retrieve")) - .text("Retrieve a project's description, specified by ID.") - .children( - arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"), - opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), - opt[Unit]('f', "file").action((_, c) => c.copy(opts = List("file"))).text("Use to load the ID from file, " + - "with the filepath given in place of the ID") - ) + if (!config.silent) cliParser.showHeader() - cmd("search").action((s, c) => c.copy(mode = "search")) - .text("Search artifact using a query.") - .children( - arg[String]("query").action((x,c) => c.copy(query = x)).text("The query to be used."), - opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), - opt[Int]("limit").action((x, c) => c.copy(limit = Some(x))).text("The maximal number of results returned."), - opt[Unit](name="list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"), - opt[Int]("timeout").action((x, c) => c.copy(timeout = Some(x))).text("Timeout in seconds.") - ) + config.mode match { + case "test" => TestCommand.execute + case "retrieve" => RetrieveCommand.execute + case "search" => SearchCommand.execute + case x => config.consoleOutput.outputError(s"Unknown command: $x") + } + + + case None => } + } + private def cliParser = { + val parser = { + new scopt.OptionParser[Config]("delphi-cli") { + head("Delphi Command Line Tool", s"(${BuildInfo.version})") - cliParser.parse(args, Config()) match { - case Some(config) => - if (!config.silent) cliParser.showHeader() - config.mode match { - case "test" => TestCommand.execute(config) - case "retrieve" => RetrieveCommand.execute(config) - case "search" => SearchCommand.execute(config) - case x => config.consoleOutput.outputError(s"Unknown command: $x") - } + version("version").text("Prints the version of the command line tool.") - case None => - } + help("help").text("Prints this help text.") + + override def showUsageOnError = true + opt[String]("server").action((x, c) => c.copy(server = x)).text("The url to the Delphi server") + opt[Unit](name = "raw").action((_, c) => c.copy(raw = true)).text("Output the raw results") + opt[Unit](name = "silent").action((_, c) => c.copy(silent = true)).text("Suppress non-result output") - val poolShutdown = Http().shutdownAllConnectionPools() - Await.result(poolShutdown, Duration.Inf) + checkConfig(c => if (c.server.isEmpty()) failure("Option server is required.") else success) - implicit val ec: ExecutionContext = system.dispatcher - val terminationFuture = system.terminate() + cmd("test").action((_, c) => c.copy(mode = "test")) - terminationFuture.onComplete { - sys.exit(0) + cmd("retrieve").action((s, c) => c.copy(mode = "retrieve")) + .text("Retrieve a project's description, specified by ID.") + .children( + arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), + opt[Unit]('f', "file").action((_, c) => c.copy(opts = List("file"))).text("Use to load the ID from file, " + + "with the filepath given in place of the ID") + ) + + cmd("search").action((s, c) => c.copy(mode = "search")) + .text("Search artifact using a query.") + .children( + arg[String]("query").action((x, c) => c.copy(query = x)).text("The query to be used."), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), + opt[Int]("limit").action((x, c) => c.copy(limit = Some(x))).text("The maximal number of results returned."), + opt[Unit](name = "list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"), + opt[Int]("timeout").action((x, c) => c.copy(timeout = Some(x))).text("Timeout in seconds.") + ) + } + } + parser } -} +} \ No newline at end of file diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/Command.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/Command.scala index 30b9e22..d3bc8bf 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/Command.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/Command.scala @@ -16,18 +16,9 @@ package de.upb.cs.swt.delphi.cli.commands -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.model.Uri.Query -import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes, Uri} -import akka.stream.ActorMaterializer -import akka.util.ByteString +import com.softwaremill.sttp._ import de.upb.cs.swt.delphi.cli.Config -import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ -import scala.util.{Failure, Success} - /** * Represents the implementation of a command of the CLI */ @@ -35,49 +26,43 @@ trait Command { /** * Executes the command implementation + * * @param config The current configuration for the command */ - def execute(config: Config)(implicit system : ActorSystem): Unit + def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = {} + /** - * Implements a common request type using currying to avoid code duplication - * @param target The endpoint to perform a Get request on - * @param config The current configuration for the command + * Http GET request template + * + * @param target Sub url in delphi server + * @param parameters Query params + * @return GET response */ - protected def executeGet(target: String, parameters: Map[String, String] = Map())(config: Config, system : ActorSystem) : Option[String] = { - implicit val sys : ActorSystem = system - implicit val materializer = ActorMaterializer() - implicit val executionContext = sys.dispatcher - - val uri = Uri(config.server) - config.consoleOutput.outputInformation(s"Contacting server ${uri}...") - - val responseFuture = Http().singleRequest(HttpRequest(uri = uri.withPath(uri.path + target).withQuery(Query(parameters)))) - - responseFuture.onComplete { - case Failure(_) => error(config)(s"Could not reach server ${config.server}.") - case _ => + protected def executeGet(paths: Seq[String], parameters: Map[String, String] = Map()) + (implicit config: Config, backend: SttpBackend[Id, Nothing]): Option[String] = { + val serverUrl = uri"${config.server}" + val oldPath = serverUrl.path + val reqUrl = serverUrl.path(oldPath ++ paths).params(parameters) + val request = sttp.get(reqUrl) + config.consoleOutput.outputInformation(s"Sending request ${request.uri}") + val response = request.send() + response.body match { + case Left(value) => + error.apply(s"Request failed:\n $value") + None + case Right(value) => + Some(value) } - - val result = Await.result(responseFuture, 30 seconds) - val resultString = result match { - case HttpResponse(StatusCodes.OK, headers, entity, _) => - entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body => - Some(body.utf8String) - } - case resp @ HttpResponse(code, _, _, _) => { - error(config)("Artifact not found.") - resp.discardEntityBytes() - Future(None) - } - } - - Await.result(resultString, Duration.Inf) } + protected def information(implicit config: Config): String => Unit = config.consoleOutput.outputInformation _ + protected def reportResult(implicit config: Config): Any => Unit = config.consoleOutput.outputResult _ + protected def error(implicit config: Config): String => Unit = config.consoleOutput.outputError _ + protected def success(implicit config: Config): String => Unit = config.consoleOutput.outputSuccess _ protected def exportResult(implicit config: Config): Any => Unit = config.csvOutput.exportResult _ diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/RetrieveCommand.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/RetrieveCommand.scala index 817aa00..93a64df 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/RetrieveCommand.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/RetrieveCommand.scala @@ -16,31 +16,22 @@ package de.upb.cs.swt.delphi.cli.commands -import akka.actor.ActorSystem -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import akka.http.scaladsl.unmarshalling.Unmarshal -import akka.stream.ActorMaterializer -import de.upb.cs.swt.delphi.cli.Config +import com.softwaremill.sttp.{Id, SttpBackend} +import de.upb.cs.swt.delphi.cli._ import de.upb.cs.swt.delphi.cli.artifacts.RetrieveResult import de.upb.cs.swt.delphi.cli.artifacts.SearchResultJson._ -import de.upb.cs.swt.delphi.cli.commands.SearchCommand.information -import spray.json.DefaultJsonProtocol +import spray.json._ -import scala.concurrent.Await -import scala.concurrent.duration.Duration import scala.io.Source -import scala.util.{Failure, Success} /** * The implementation of the retrieve command. * Retrieves the contents of the file at the endpoint specified by the config file, and prints them to stdout */ -object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonProtocol { +object RetrieveCommand extends Command { - override def execute(config: Config)(implicit system: ActorSystem): Unit = { - implicit val ec = system.dispatcher - implicit val materializer = ActorMaterializer() + override def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = { //Checks whether the ID should be loaded from a file or not, and either returns the first line // of the given file if it is, or the specified ID otherwise @@ -56,35 +47,27 @@ object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonPro } val result = executeGet( - s"/retrieve/$checkTarget", + Seq("retrieve", checkTarget), Map("pretty" -> "") - )(config, system) + ) - result.map(s => { + result.foreach(s => { if (config.raw) { - reportResult(config)(s) + reportResult.apply(s) } - if (!config.raw || !config.csv.equals("")) { - val unmarshalledFuture = Unmarshal(s).to[List[RetrieveResult]] - unmarshalledFuture.transform { - case Success(unmarshalled) => { - val unmarshalled = Await.result(unmarshalledFuture, Duration.Inf) - success(config)(s"Found ${unmarshalled.size} item(s).") - reportResult(config)(unmarshalled) + //TODO: Direct convertTo[List[RetrieveResult]] not working ??? + - if(!config.csv.equals("")) { - exportResult(config)(unmarshalled) - information(config)("Results written to file '" + config.csv + "'") - } + val jsonArr = s.parseJson.asInstanceOf[JsArray].elements + val retrieveResults = jsonArr.map(r => r.convertTo[RetrieveResult]) - Success(unmarshalled) - } - case Failure(e) => { - error(config)(s) - Failure(e) - } + success.apply(s"Found ${retrieveResults.size} item(s).") + reportResult.apply(retrieveResults) + if (!config.csv.equals("")) { + exportResult.apply(retrieveResults) + information.apply("Results written to file '" + config.csv + "'") } } }) diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala index 24b6683..edb5c66 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala @@ -16,124 +16,101 @@ package de.upb.cs.swt.delphi.cli.commands -import java.util.concurrent.{TimeUnit, TimeoutException} - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import akka.http.scaladsl.marshalling.Marshal -import akka.http.scaladsl.model._ -import akka.http.scaladsl.unmarshalling.Unmarshal -import akka.stream.ActorMaterializer -import akka.util.ByteString +import java.util.concurrent.TimeUnit + +import com.softwaremill.sttp._ +import com.softwaremill.sttp.sprayJson._ import de.upb.cs.swt.delphi.cli.Config import de.upb.cs.swt.delphi.cli.artifacts.SearchResult import de.upb.cs.swt.delphi.cli.artifacts.SearchResultJson._ -import spray.json.DefaultJsonProtocol +import spray.json._ import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContextExecutor, Future} -import scala.util.{Failure, Success, Try} -object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProtocol { +object SearchCommand extends Command with DefaultJsonProtocol{ - val searchTimeout: Int = 10 + val searchTimeout = 10.seconds + val timeoutCode = 408 /** * Executes the command implementation * * @param config The current configuration for the command */ - override def execute(config: Config)(implicit system: ActorSystem): Unit = { - implicit val ec = system.dispatcher - implicit val materializer = ActorMaterializer() + override def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = { def query = config.query - information(config)(s"Searching for artifacts matching ${'"'}$query${'"'}.") - val start = System.nanoTime() + information.apply(s"Searching for artifacts matching ${'"'}$query${'"'}.") - implicit val queryFormat = jsonFormat2(Query) - val baseUri = Uri(config.server) - val prettyParam = Map("pretty" -> "") - val searchUri = baseUri.withPath(baseUri.path + "/search").withQuery(akka.http.scaladsl.model.Uri.Query(prettyParam)) - val responseFuture = Marshal(Query(query, config.limit)).to[RequestEntity] flatMap { entity => - Http().singleRequest(HttpRequest(uri = searchUri, method = HttpMethods.POST, entity = entity)) - } - Try(Await.result(responseFuture, Duration(config.timeout.getOrElse(searchTimeout) + " seconds"))). - map(response => parseResponse(response, config, start)(ec, materializer)). - recover { - case e : TimeoutException => { - error(config)("The query timed out after " + (System.nanoTime() - start).nanos.toUnit(TimeUnit.SECONDS) + - " seconds. To set a longer timeout, use the --timeout option.") - Failure(e) - } - } + val queryParams = Map("pretty" -> "") + val queryPayload: Query = Query(query,config.limit) + val searchUri = uri"${config.server}/search?$queryParams" + + val request = sttp.body(queryPayload.toJson).post(searchUri) + + val (res, time) = processRequest(request) + res.foreach(processResults(_, time)) } - private def parseResponse(response: HttpResponse, config: Config, start: Long) - (implicit ec: ExecutionContextExecutor, materializer: ActorMaterializer): Unit = { + private def processRequest(req: Request[String, Nothing]) + (implicit config: Config, + backend: SttpBackend[Id, Nothing]): (Option[String], FiniteDuration) = { + val start = System.nanoTime() + val res: Id[Response[String]] = req.readTimeout(searchTimeout).send() + val end = System.nanoTime() + val took = (end - start).nanos - val resultFuture: Future[String] = response match { - case HttpResponse(StatusCodes.OK, headers, entity, _) => - entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body => - body.utf8String - } - case resp@HttpResponse(code, _, _, _) => { - error(config)("Request failed, response code: " + code) - resp.discardEntityBytes() - Future("") - } - } + if (res.code == timeoutCode) { - val result = Await.result(resultFuture, Duration.Inf) + error.apply(s"The query timed out after ${took.toSeconds} seconds. " + + "To set a longer timeout, use the --timeout option.") + } + val resStr = res.body match { + case Left(v) => + error.apply(s"Search request failed \n $v") + None + case Right(v) => + Some(v) + } + (resStr, took) + } - val took = (System.nanoTime() - start).nanos.toUnit(TimeUnit.SECONDS) + private def processResults(res: String, queryRuntime: FiniteDuration)(implicit config: Config) = { - if (config.raw || result.equals("")) { - reportResult(config)(result) + if (config.raw || res.equals("")) { + reportResult.apply(res) + } + if (!(config.raw || res.equals("")) || !config.csv.equals("")) { + val jsonArr = res.parseJson.asInstanceOf[JsArray].elements + val retrieveResults = jsonArr.map(r => r.convertTo[SearchResult]).toList + onProperSearchResults(retrieveResults) } - if(!(config.raw || result.equals("")) || !config.csv.equals("")) { - val unmarshalledFuture = Unmarshal(result).to[List[SearchResult]] + def onProperSearchResults(sr: List[SearchResult]) = { - val processFuture = unmarshalledFuture.transform { - case Success(unmarshalled) => { - processResults(config, unmarshalled, took) - Success(unmarshalled) - } - case Failure(e) => { - error(config)(result) - Failure(e) + val capMessage = { + config.limit match { + case Some(limit) if (limit <= sr.size) + => s"Results may be capped by result limit set to $limit." + case None if (sr.size >= 50) + => "Results may be capped by default limit of 50 returned results. Use --limit to extend the result set." + case _ + => "" } } - } - } - private def processResults(config: Config, results: List[SearchResult], queryRuntime: Double) = { - val capMessage = { - config.limit match { - case Some(limit) if (limit <= results.size) - => s"Results may be capped by result limit set to $limit." - case None if (results.size >= 50) - => "Results may be capped by default limit of 50 returned results. Use --limit to extend the result set." - case _ - => "" - } - } - success(config)(s"Found ${results.size} item(s). $capMessage") - reportResult(config)(results) + success.apply(s"Found ${sr.size} item(s). $capMessage") + reportResult.apply(sr) - information(config)(f"Query took $queryRuntime%.2fs.") + information.apply(f"Query took ${queryRuntime.toUnit(TimeUnit.SECONDS)}%.2fs.") - if(!config.csv.equals("")) { - exportResult(config)(results) - information(config)("Results written to file '" + config.csv + "'") + if (!config.csv.equals("")) { + exportResult.apply(sr) + information.apply("Results written to file '" + config.csv + "'") + } } } - case class Query(query: String, - limit: Option[Int] = None) - } diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/TestCommand.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/TestCommand.scala index 2589bea..2f11b3e 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/TestCommand.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/TestCommand.scala @@ -16,7 +16,7 @@ package de.upb.cs.swt.delphi.cli.commands -import akka.actor.ActorSystem +import com.softwaremill.sttp.{Id, SttpBackend} import de.upb.cs.swt.delphi.cli.Config /** @@ -24,10 +24,11 @@ import de.upb.cs.swt.delphi.cli.Config * Tries to connect to the Delphi server and reports on the results of the version call. */ object TestCommand extends Command { - override def execute(config: Config)(implicit system : ActorSystem): Unit = executeGet( - "/version" - )(config, system).map(s => { - success(config)("Successfully contacted Delphi server. ") - information(config)("Server version: " + s) - }) + override def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = { + executeGet(Seq("version")) + .foreach(s => { + success.apply("Successfully contacted Delphi server. ") + information.apply("Server version: " + s) + }) + } } diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/package.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/package.scala new file mode 100644 index 0000000..8975631 --- /dev/null +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/package.scala @@ -0,0 +1,28 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// 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. +package de.upb.cs.swt.delphi.cli + +import spray.json._ + +package object commands extends DefaultJsonProtocol { + + + case class Query(query: String, + limit: Option[Int] = None) + + implicit val queryFormat = jsonFormat2(Query) + +}