Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/scala/eu/neverblink/jelly/cli/App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ object App extends CommandsEntryPoint:
override def commands: Seq[Command[?]] = Seq(
Version,
RdfFromJelly,
RdfToJelly,
)
69 changes: 53 additions & 16 deletions src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package eu.neverblink.jelly.cli

import caseapp.*
import eu.neverblink.jelly.cli.util.IoUtil

import java.io.{ByteArrayOutputStream, OutputStream, PrintStream}
import java.io.{ByteArrayOutputStream, InputStream, OutputStream, PrintStream}
import scala.compiletime.uninitialized

case class JellyOptions(
Expand Down Expand Up @@ -76,15 +77,24 @@ abstract class JellyCommand[T <: HasJellyOptions: {Parser, Help}] extends Comman
osOut.reset()
osErr.reset()
App.main(args.toArray)
(osOut.toString, osErr.toString)
(osOut.toString("UTF-8"), osErr.toString("UTF-8"))

final def getOutContent: String =
if isTest then
out.flush()
val s = osOut.toString
osOut.reset()
s
else throw new IllegalStateException("Not in test mode")
private def validateTestMode(): Unit =
if !isTest then throw new IllegalStateException("Not in test mode")

final def getOutString: String =
validateTestMode()
out.flush()
val s = osOut.toString
osOut.reset()
s

final def getOutBytes: Array[Byte] =
validateTestMode()
out.flush()
val b = osOut.toByteArray
osOut.reset()
b

final def getOutStream: OutputStream =
if isTest then osOut
Expand All @@ -94,13 +104,40 @@ abstract class JellyCommand[T <: HasJellyOptions: {Parser, Help}] extends Comman
if isTest then osOut
else System.out

final def getErrContent: String =
if isTest then
err.flush()
val s = osErr.toString
osErr.reset()
s
else throw new IllegalStateException("Not in test mode")
final def getErrString: String =
validateTestMode()
err.flush()
val s = osErr.toString
osErr.reset()
s

final def getErrBytes: Array[Byte] =
validateTestMode()
err.flush()
val b = osErr.toByteArray
osErr.reset()
b

/** This method matches the input and output options to the correct file or standard input/output
* @param inputOption
* @param outputOption
* @return
*/
final def matchIOStreams(
inputOption: Option[String],
outputOption: Option[String],
): (InputStream, OutputStream) =
val inputStream = inputOption match {
case Some(fileName: String) =>
IoUtil.inputStream(fileName)
case _ => System.in
}
val outputStream = outputOption match {
case Some(fileName: String) =>
IoUtil.outputStream(fileName)
case None => getStdOut
}
(inputStream, outputStream)

@throws[ExitException]
final override def exit(code: Int): Nothing =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package eu.neverblink.jelly.cli.command.rdf

import eu.neverblink.jelly.cli.command.rdf.RdfFormatOption.JellyBinary

trait RdfCommandPrintUtil:
val validFormats: List[RdfFormatOption]
val validFormats: List[RdfFormatOption] =
RdfFormatOption.values.filterNot(_ == JellyBinary).toList
val defaultFormat: RdfFormatOption

/** Prints the available RDF formats to the user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import caseapp.*
import com.google.protobuf.InvalidProtocolBufferException
import eu.neverblink.jelly.cli.*
import eu.neverblink.jelly.cli.command.rdf.RdfFormatOption.*
import eu.neverblink.jelly.cli.util.IoUtil
import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage
import eu.ostrzyciel.jelly.core.proto.v1.RdfStreamFrame
import eu.ostrzyciel.jelly.core.{IoUtils, RdfProtoDeserializationError}
Expand All @@ -13,10 +12,6 @@ import org.apache.jena.riot.{RDFLanguages, RDFParser, RiotException}
import java.io.{InputStream, OutputStream}

object RdfFromJellyPrint extends RdfCommandPrintUtil:
// We exclude JellyBinary because translating JellyBinary to JellyBinary makes no sense
override val validFormats: List[RdfFormatOption] =
RdfFormatOption.values.filterNot(_ == JellyBinary).toList

override val defaultFormat: RdfFormatOption = NQuads

case class RdfFromJellyOptions(
Expand All @@ -38,16 +33,8 @@ object RdfFromJelly extends JellyCommand[RdfFromJellyOptions]:
)

override def doRun(options: RdfFromJellyOptions, remainingArgs: RemainingArgs): Unit =
val inputStream = remainingArgs.remaining.headOption match {
case Some(fileName: String) =>
IoUtil.inputStream(fileName)
case _ => System.in
}
val outputStream = options.outputFile match {
case Some(fileName: String) =>
IoUtil.outputStream(fileName)
case None => getStdOut
}
val (inputStream, outputStream) =
this.matchIOStreams(remainingArgs.remaining.headOption, options.outputFile)
doConversion(inputStream, outputStream, options.outputFormat)

/** This method takes care of proper error handling and matches the desired output format to the
Expand All @@ -58,8 +45,8 @@ object RdfFromJelly extends JellyCommand[RdfFromJellyOptions]:
* @param outputStream
* OutputStream
* @throws JellyDeserializationError
* @throws ParsingError
* @throws InvalidFormatSpecified
* @throws JenaRiotException
* @throws InvalidJellyFile
*/
private def doConversion(
inputStream: InputStream,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package eu.neverblink.jelly.cli.command.rdf
import caseapp.*
import com.google.protobuf.InvalidProtocolBufferException
import eu.neverblink.jelly.cli.*
import eu.neverblink.jelly.cli.command.rdf.RdfFormatOption.*
import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage
import eu.ostrzyciel.jelly.core.RdfProtoSerializationError
import org.apache.jena.riot.system.StreamRDFWriter
import org.apache.jena.riot.{RDFLanguages, RDFParser, RiotException}

import java.io.{InputStream, OutputStream}

object RdfToJellyPrint extends RdfCommandPrintUtil:
override val defaultFormat: RdfFormatOption = JellyBinary

case class RdfToJellyOptions(
@Recurse
common: JellyOptions = JellyOptions(),
@ExtraName("to") outputFile: Option[String] = None,
@ValueDescription("Input format.")
@HelpMessage(
RdfToJellyPrint.helpMsg,
)
@ExtraName("in-format") inputFormat: Option[String] = None,
) extends HasJellyOptions

object RdfToJelly extends JellyCommand[RdfToJellyOptions]:
override def group = "rdf"

override def names: List[List[String]] = List(
List("rdf", "to-jelly"),
)

override def doRun(options: RdfToJellyOptions, remainingArgs: RemainingArgs): Unit =
val (inputStream, outputStream) =
matchIOStreams(remainingArgs.remaining.headOption, options.outputFile)
doConversion(inputStream, outputStream, options.inputFormat)

/** This method takes care of proper error handling and matches the desired output format to the
* correct conversion
*
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
* @throws JellySerializationError
* @throws JenaRiotException
* @throws InvalidJellyFile
*/
private def doConversion(
inputStream: InputStream,
outputStream: OutputStream,
format: Option[String],
): Unit =
try {
format match {
case Some(f: String) =>
RdfFormatOption.find(f) match
case Some(NQuads) => nQuadToJelly(inputStream, outputStream)
case _ =>
throw InvalidFormatSpecified(
f,
RdfToJellyPrint.validFormatsString,
) // if anything else, it's an invalid option
case None =>
nQuadToJelly(inputStream, outputStream) // default option if no parameter supplied
}
} catch
case e: RdfProtoSerializationError =>
throw JellySerializationError(e.getMessage)
case e: RiotException =>
throw JenaRiotException(e)
case e: InvalidProtocolBufferException =>
throw InvalidJellyFile(e)

/** This method reads the NQuad file, rewrites it to Jelly and writes it to some output stream
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
*/
private def nQuadToJelly(inputStream: InputStream, outputStream: OutputStream): Unit =
val jellyWriter = StreamRDFWriter.getWriterStream(outputStream, JellyLanguage.JELLY)
RDFParser.source(inputStream).lang(RDFLanguages.NQUADS).parse(jellyWriter)
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
"a file to file" in {
val jellyFile = DataGenHelper.generateJellyFile(3)
val nQuadString = DataGenHelper.generateNQuadString(3)
val outputFile = DataGenHelper.generateOutputFile(RDFLanguages.NQUADS)
val outputFile = DataGenHelper.generateFile(RDFLanguages.NQUADS)
val (out, err) =
RdfFromJelly.runTestCommand(
List("rdf", "from-jelly", jellyFile, "--to", outputFile),
Expand All @@ -55,7 +55,7 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
}
"an input stream to file" in {
DataGenHelper.generateJellyInputStream(3)
val outputFile = DataGenHelper.generateOutputFile(RDFLanguages.NQUADS)
val outputFile = DataGenHelper.generateFile(RDFLanguages.NQUADS)
val nQuadString = DataGenHelper.generateNQuadString(3)
val (out, err) =
RdfFromJelly.runTestCommand(List("rdf", "from-jelly", "--to", outputFile))
Expand Down Expand Up @@ -107,7 +107,7 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
RdfFromJelly.runTestCommand(List("rdf", "from-jelly", nonExist))
}
val msg = InputFileNotFound(nonExist).getMessage
RdfFromJelly.getErrContent should include(msg)
RdfFromJelly.getErrString should include(msg)
exception.code should be(1)
}
"input file is not accessible" in {
Expand All @@ -123,14 +123,14 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
RdfFromJelly.runTestCommand(List("rdf", "from-jelly", jellyFile))
}
val msg = InputFileInaccessible(jellyFile).getMessage
RdfFromJelly.getErrContent should include(msg)
RdfFromJelly.getErrString should include(msg)
exception.code should be(1)
}
"output file cannot be created" in {
val jellyFile = DataGenHelper.generateJellyFile(3)
val unreachableDir = DataGenHelper.makeTestDir()
Paths.get(unreachableDir).toFile.setWritable(false)
val quadFile = DataGenHelper.generateOutputFile()
val quadFile = DataGenHelper.generateFile()
val exception =
intercept[ExitException] {

Expand All @@ -139,12 +139,13 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
)
}
val msg = OutputFileCannotBeCreated(quadFile).getMessage
RdfFromJelly.getErrContent should include(msg)
Paths.get(unreachableDir).toFile.setWritable(true)
RdfFromJelly.getErrString should include(msg)
exception.code should be(1)
}
"parsing error occurs" in {
"deserializing error occurs" in {
val jellyFile = DataGenHelper.generateJellyFile(3)
val quadFile = DataGenHelper.generateOutputFile()
val quadFile = DataGenHelper.generateFile()
RdfFromJelly.runTestCommand(
List("rdf", "from-jelly", jellyFile, "--to", quadFile),
)
Expand All @@ -155,14 +156,14 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
)
}
val msg = InvalidJellyFile(new InvalidProtocolBufferException("")).getMessage
val errContent = RdfFromJelly.getErrContent
val errContent = RdfFromJelly.getErrString
errContent should include(msg)
errContent should include("Run with --debug to see the complete stack trace.")
exception.code should be(1)
}
"parsing error occurs with debug set" in {
val jellyFile = DataGenHelper.generateJellyFile(3)
val quadFile = DataGenHelper.generateOutputFile()
val quadFile = DataGenHelper.generateFile()
RdfFromJelly.runTestCommand(
List("rdf", "from-jelly", jellyFile, "--to", quadFile),
)
Expand All @@ -173,22 +174,22 @@ class RdfFromJellySpec extends AnyWordSpec with Matchers with CleanUpAfterTest:
)
}
val msg = InvalidJellyFile(new InvalidProtocolBufferException("")).getMessage
val errContent = RdfFromJelly.getErrContent
val errContent = RdfFromJelly.getErrString
errContent should include(msg)
errContent should include("eu.neverblink.jelly.cli.InvalidJellyFile")
exception.code should be(1)
}
"invalid output format supplied" in {
val jellyFile = DataGenHelper.generateJellyFile(3)
val quadFile = DataGenHelper.generateOutputFile()
val quadFile = DataGenHelper.generateFile()
val exception =
intercept[ExitException] {
RdfFromJelly.runTestCommand(
List("rdf", "from-jelly", jellyFile, "--to", quadFile, "--out-format", "invalid"),
)
}
val msg = InvalidFormatSpecified("invalid", RdfFromJellyPrint.validFormatsString)
RdfFromJelly.getErrContent should include(msg.getMessage)
RdfFromJelly.getErrString should include(msg.getMessage)
exception.code should be(1)
}
}
Expand Down
Loading