diff --git a/build.sbt b/build.sbt index 3838904..286164b 100644 --- a/build.sbt +++ b/build.sbt @@ -27,6 +27,7 @@ libraryDependencies += "io.spray" %% "spray-json" % "1.3.3" 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" debianPackageDependencies := Seq("java8-runtime-headless") 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 b487a03..47f75c5 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 @@ -26,6 +26,7 @@ package de.upb.cs.swt.delphi.cli 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 = "", silent: Boolean = false, list : Boolean = false, mode: String = "", @@ -36,5 +37,6 @@ case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://d opts: List[String] = List()) { lazy val consoleOutput = new ConsoleOutput(this) + lazy val csvOutput = new CsvOutput(this) } diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala new file mode 100644 index 0000000..6b3e30b --- /dev/null +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala @@ -0,0 +1,69 @@ +// 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 java.io.{BufferedWriter, FileWriter} + +import de.upb.cs.swt.delphi.cli.artifacts.Result +import au.com.bytecode.opencsv.CSVWriter + +import scala.collection.JavaConverters._ + +/** + * Export search and retrieve results to .csv file. + * + * @author Lisa Nguyen Quang Do + * @author Ben Hermann + * + */ + +class CsvOutput(config: Config) { + + def exportResult(value: Any): Unit = { + printToCsv( + value match { + case results : + Seq[Result] if results.headOption.getOrElse(Seq.empty[Array[String]]).isInstanceOf[Result] => resultsToCsv(results) + case _ => Seq.empty[Array[String]] + } + ) + } + + def printToCsv(table : Seq[Array[String]]): Unit = { + val outputFile = new BufferedWriter(new FileWriter(config.csv, /* append = */false)) + val csvWriter = new CSVWriter(outputFile) + csvWriter.writeAll(seqAsJavaList(table)) + outputFile.close() + } + + def resultsToCsv(results : Seq[Result]) : Seq[Array[String]] = { + val headOption = results.headOption.getOrElse() + if (!headOption.isInstanceOf[Result]) { + Seq.empty[Array[String]] + } else { + val fieldNames = headOption.asInstanceOf[Result].fieldNames() + val tableHeader : Array[String] = + fieldNames.+:("discovered at").+:("version").+:("groupId").+:("artifactId").+:("source").+:("Id").toArray + results.map { + e => { + Array(e.id, e.metadata.source, e.metadata.artifactId, e.metadata.groupId, e.metadata.version, + e.metadata.discovered).++(fieldNames.map(f => e.metricResults(f).toString)) + } + }.+:(tableHeader) + } + } +} 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 c7c45c6..64e4d14 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 @@ -32,7 +32,6 @@ object DelphiCLI extends App { implicit val system = ActorSystem() - val cliParser = { new scopt.OptionParser[Config]("delphi-cli") { head("Delphi Command Line Tool", s"(${BuildInfo.version})") @@ -55,7 +54,8 @@ object DelphiCLI extends App { .children( arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"), 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") + "with the filepath given in place of the ID"), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)") ) cmd("search").action((s, c) => c.copy(mode = "search")) @@ -63,7 +63,8 @@ object DelphiCLI extends App { .children( arg[String]("query").action((x,c) => c.copy(query = x)).text("The query to be used."), 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[Unit](name="list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)") ) } } diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/artifacts/SearchResult.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/artifacts/SearchResult.scala index ee656bd..19d927c 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/artifacts/SearchResult.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/artifacts/SearchResult.scala @@ -18,21 +18,23 @@ package de.upb.cs.swt.delphi.cli.artifacts import spray.json.DefaultJsonProtocol -case class RetrieveResult(val id: String, - val metadata: ArtifactMetadata, - val metricResults: Map[String, Int]) { +trait Result{ + val id: String + val metadata: ArtifactMetadata + val metricResults: Map[String, Int] + def toMavenIdentifier() : String = s"${metadata.groupId}:${metadata.artifactId}:${metadata.version}" def fieldNames() : List[String] = metricResults.keys.toList.sorted } -case class SearchResult(val id: String, - val metadata: ArtifactMetadata, - val metricResults: Map[String, Int]) { - def toMavenIdentifier() : String = s"${metadata.groupId}:${metadata.artifactId}:${metadata.version}" +case class SearchResult(id: String, + metadata: ArtifactMetadata, + metricResults: Map[String, Int]) extends Result - def fieldNames() : List[String] = metricResults.keys.toList.sorted -} +case class RetrieveResult(id: String, + metadata: ArtifactMetadata, + metricResults: Map[String, Int]) extends Result case class ArtifactMetadata(val artifactId: String, val source: String, 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 02cce23..30b9e22 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 @@ -80,4 +80,6 @@ trait Command { 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 7f4865c..817aa00 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 @@ -23,6 +23,7 @@ import akka.stream.ActorMaterializer import de.upb.cs.swt.delphi.cli.Config 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 scala.concurrent.Await @@ -62,7 +63,9 @@ object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonPro result.map(s => { if (config.raw) { reportResult(config)(s) - } else { + } + + if (!config.raw || !config.csv.equals("")) { val unmarshalledFuture = Unmarshal(s).to[List[RetrieveResult]] unmarshalledFuture.transform { @@ -71,6 +74,11 @@ object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonPro success(config)(s"Found ${unmarshalled.size} item(s).") reportResult(config)(unmarshalled) + if(!config.csv.equals("")) { + exportResult(config)(unmarshalled) + information(config)("Results written to file '" + config.csv + "'") + } + Success(unmarshalled) } case Failure(e) => { 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 f804b8c..55aed07 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 @@ -29,6 +29,7 @@ import akka.util.ByteString 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 de.upb.cs.swt.delphi.cli.commands.RetrieveCommand.information import spray.json.DefaultJsonProtocol import scala.concurrent.duration._ @@ -77,7 +78,9 @@ object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProto if (config.raw || result.equals("")) { reportResult(config)(result) - } else { + } + + if(!(config.raw || result.equals("")) || !config.csv.equals("")) { val unmarshalledFuture = Unmarshal(result).to[List[SearchResult]] val processFuture = unmarshalledFuture.transform { @@ -108,6 +111,11 @@ object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProto reportResult(config)(results) information(config)(f"Query took $queryRuntime%.2fs.") + + if(!config.csv.equals("")) { + exportResult(config)(results) + information(config)("Results written to file '" + config.csv + "'") + } } case class Query(query: String,