From fcc916f3c3077e09de57cbc6d06b3ed0cb98438d Mon Sep 17 00:00:00 2001 From: Lisa Nguyen Date: Wed, 21 Nov 2018 14:10:12 +0100 Subject: [PATCH 1/4] Adding --csv option to search and retrieve (Issue #4 ) --- build.sbt | 1 + .../de/upb/cs/swt/delphi/cli/Config.scala | 2 + .../de/upb/cs/swt/delphi/cli/CsvOutput.scala | 69 +++++++++++++++++++ .../de/upb/cs/swt/delphi/cli/DelphiCLI.scala | 7 +- .../delphi/cli/artifacts/SearchResult.scala | 20 +++--- .../cs/swt/delphi/cli/commands/Command.scala | 2 + .../delphi/cli/commands/RetrieveCommand.scala | 8 ++- .../delphi/cli/commands/SearchCommand.scala | 7 +- 8 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala 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..a11c709 --- /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 de.upb.cs.swt.delphi.cli.commands.SearchCommand.information +import de.vandermeer.asciitable.{AsciiTable, CWC_LongestLine} +import de.vandermeer.skb.interfaces.transformers.textformat.TextAlignment + +import scala.collection.mutable.ListBuffer +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 = { + var table = value match { + case results : Seq[Result] if results.head.isInstanceOf[Result] => resultsToCsv(results) + case _ => { + println("Error: results are in unknown format.") + return + } + } + + val outputFile = new BufferedWriter(new FileWriter(config.csv, /* append = */false)) + val csvWriter = new CSVWriter(outputFile) + csvWriter.writeAll(seqAsJavaList(table)) + outputFile.close() + println("Results written to file '" + config.csv + "'") + } + + def resultsToCsv(results : Seq[Result]) : Seq[Array[String]] = { + if (results.size != 0) { + val fieldNames = results.head.fieldNames() + val tableHeader : Array[String] = (fieldNames.+:("discovered at").+:("version").+:("groupId").+:("artifactId").+:("source").+:("Id")).toArray + val tableBody: Seq[Array[String]] = 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)) + } + } + return tableBody.+:(tableHeader) + } + return Seq.empty[Array[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 c7c45c6..c9868c4 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 @@ -35,7 +35,7 @@ object DelphiCLI extends App { val cliParser = { new scopt.OptionParser[Config]("delphi-cli") { - head("Delphi Command Line Tool", s"(${BuildInfo.version})") + head("Delphi Command Line Tool", s"(Lala)") // ${BuildInfo.version} version("version").text("Prints the version of the command line tool.") @@ -55,7 +55,7 @@ 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..8bc9b6d 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,9 @@ 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) + 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..f0756d8 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 @@ -77,7 +77,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 +110,9 @@ 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) } case class Query(query: String, From 21560a2838d8d8e6dcea16500dcf7db9b78f9902 Mon Sep 17 00:00:00 2001 From: Lisa Nguyen Quang Do Date: Wed, 21 Nov 2018 14:19:22 +0100 Subject: [PATCH 2/4] Fixed the Buildinfo placeholder --- src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c9868c4..3c1f9ca 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 @@ -35,7 +35,7 @@ object DelphiCLI extends App { val cliParser = { new scopt.OptionParser[Config]("delphi-cli") { - head("Delphi Command Line Tool", s"(Lala)") // ${BuildInfo.version} + head("Delphi Command Line Tool", s"(${BuildInfo.version})") version("version").text("Prints the version of the command line tool.") From 20427e6d4f714b498bd46c74f16f9633cedbc141 Mon Sep 17 00:00:00 2001 From: Lisa Nguyen Date: Thu, 22 Nov 2018 01:08:19 +0100 Subject: [PATCH 3/4] Fixing code style issues. --- .../de/upb/cs/swt/delphi/cli/CsvOutput.scala | 37 +++++++++---------- .../de/upb/cs/swt/delphi/cli/DelphiCLI.scala | 4 +- .../delphi/cli/commands/RetrieveCommand.scala | 4 +- .../delphi/cli/commands/SearchCommand.scala | 5 ++- 4 files changed, 27 insertions(+), 23 deletions(-) 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 index a11c709..65b071a 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala @@ -20,11 +20,7 @@ import java.io.{BufferedWriter, FileWriter} import de.upb.cs.swt.delphi.cli.artifacts.Result import au.com.bytecode.opencsv.CSVWriter -import de.upb.cs.swt.delphi.cli.commands.SearchCommand.information -import de.vandermeer.asciitable.{AsciiTable, CWC_LongestLine} -import de.vandermeer.skb.interfaces.transformers.textformat.TextAlignment -import scala.collection.mutable.ListBuffer import scala.collection.JavaConverters._ /** @@ -38,32 +34,35 @@ import scala.collection.JavaConverters._ class CsvOutput(config: Config) { def exportResult(value: Any): Unit = { - var table = value match { - case results : Seq[Result] if results.head.isInstanceOf[Result] => resultsToCsv(results) - case _ => { - println("Error: results are in unknown format.") - return + 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() - println("Results written to file '" + config.csv + "'") } def resultsToCsv(results : Seq[Result]) : Seq[Array[String]] = { - if (results.size != 0) { - val fieldNames = results.head.fieldNames() - val tableHeader : Array[String] = (fieldNames.+:("discovered at").+:("version").+:("groupId").+:("artifactId").+:("source").+:("Id")).toArray - val tableBody: Seq[Array[String]] = results.map { + if (results.isEmpty) { + Seq.empty[Array[String]] + } else { + val fieldNames = results.headOption.get.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)) + 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)) } - } - return tableBody.+:(tableHeader) + }.+:(tableHeader) } - return Seq.empty[Array[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 3c1f9ca..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"), opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)") + "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")) 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 8bc9b6d..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 @@ -74,8 +74,10 @@ object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonPro success(config)(s"Found ${unmarshalled.size} item(s).") reportResult(config)(unmarshalled) - if(!config.csv.equals("")) + if(!config.csv.equals("")) { exportResult(config)(unmarshalled) + information(config)("Results written to file '" + config.csv + "'") + } Success(unmarshalled) } 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 f0756d8..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._ @@ -111,8 +112,10 @@ object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProto information(config)(f"Query took $queryRuntime%.2fs.") - if(!config.csv.equals("")) + if(!config.csv.equals("")) { exportResult(config)(results) + information(config)("Results written to file '" + config.csv + "'") + } } case class Query(query: String, From 72add90723f510a452fd2e1a0406c99311ca87e9 Mon Sep 17 00:00:00 2001 From: Lisa Nguyen Date: Thu, 22 Nov 2018 04:05:29 +0100 Subject: [PATCH 4/4] Fixing last code style issue --- src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 65b071a..6b3e30b 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/CsvOutput.scala @@ -51,10 +51,11 @@ class CsvOutput(config: Config) { } def resultsToCsv(results : Seq[Result]) : Seq[Array[String]] = { - if (results.isEmpty) { - Seq.empty[Array[String]] + val headOption = results.headOption.getOrElse() + if (!headOption.isInstanceOf[Result]) { + Seq.empty[Array[String]] } else { - val fieldNames = results.headOption.get.fieldNames() + val fieldNames = headOption.asInstanceOf[Result].fieldNames() val tableHeader : Array[String] = fieldNames.+:("discovered at").+:("version").+:("groupId").+:("artifactId").+:("source").+:("Id").toArray results.map {