Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/JellyCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ trait HasJellyOptions:
val common: JellyOptions

abstract class JellyCommand[T <: HasJellyOptions: {Parser, Help}] extends Command[T]:

private var isTest = false
private var isDebug = false
final protected[cli] var out = System.out
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package eu.neverblink.jelly.cli.command.rdf

import com.google.protobuf.InvalidProtocolBufferException
import org.apache.jena.riot.RiotException
import eu.neverblink.jelly.cli.*
import caseapp.*

import scala.reflect.TypeTest
import eu.ostrzyciel.jelly.core.{RdfProtoDeserializationError, RdfProtoSerializationError}

import java.io.{InputStream, OutputStream}

/** This abstract class is responsible for the common logic in both RDF parsing commands
*/
abstract class RdfCommand[T <: HasJellyOptions: {Parser, Help}, F <: RdfFormat](using
tt: TypeTest[RdfFormat, F],
) extends JellyCommand[T]:

override final def group = "rdf"

/** What is the default action if no formats specified */
val defaultAction: (InputStream, OutputStream) => Unit

/** The print util responsible for handling the specific formats etc the command requires */
lazy val printUtil: RdfCommandPrintUtil[F]

/** The method responsible for matching the format to a given action */
def matchToAction(option: F): Option[(InputStream, OutputStream) => Unit]

/** This method takes care of proper error handling and takes care of the parameter priorities in
* matching the input to a given format conversion
*
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
* @param format
* Option[String]
* @param fileName
* Option[String]
* @throws JellyDeserializationError
* @throws JenaRiotException
* @throws InvalidJellyFile
*/
def parseFormatArgs(
inputStream: InputStream,
outputStream: OutputStream,
format: Option[String],
fileName: Option[String],
): Unit =
try {
val explicitFormat = if (format.isDefined) RdfFormat.find(format.get) else None
val implicitFormat =
if (fileName.isDefined) RdfFormat.inferFormat(fileName.get) else None
(explicitFormat, implicitFormat) match {
case (Some(f: F), _) =>
matchToAction(f).get(inputStream, outputStream)
// If format explicitely defined but does not match any available actions or formats, we throw an error
case (_, _) if format.isDefined =>
throw InvalidFormatSpecified(format.get, printUtil.validFormatsString)
case (_, Some(f: F)) =>
matchToAction(f).get(inputStream, outputStream)
// If format not explicitely defined but implicitely not understandable we default to this
case (_, _) => defaultAction(inputStream, outputStream)
}
} catch
case e: RiotException =>
throw JenaRiotException(e)
case e: InvalidProtocolBufferException =>
throw InvalidJellyFile(e)
case e: RdfProtoDeserializationError =>
throw JellyDeserializationError(e.getMessage)
case e: RdfProtoSerializationError =>
throw JellySerializationError(e.getMessage)
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package eu.neverblink.jelly.cli.command.rdf

trait RdfCommandPrintUtil:
val validFormats: List[RdfFormatOption]
val defaultFormat: RdfFormatOption
import scala.reflect.TypeTest

trait RdfCommandPrintUtil[F <: RdfFormat](using tt: TypeTest[RdfFormat, F]):
val defaultFormat: RdfFormat

lazy val validFormats: List[RdfFormat] = RdfFormat.all.collect { case x: F => x }

/** Prints the available RDF formats to the user.
*/
lazy val validFormatsString: String =
validFormats.map(RdfFormatOption.optionString).mkString(", ")
validFormats.map(RdfFormat.optionString).mkString(", ")

lazy val helpMsg: String =
f"Possible values: ${validFormatsString}. Default format: ${defaultFormat.fullName}"
69 changes: 69 additions & 0 deletions src/main/scala/eu/neverblink/jelly/cli/command/rdf/RdfFormat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package eu.neverblink.jelly.cli.command.rdf

import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage
import org.apache.jena.riot.{Lang, RDFLanguages}

sealed trait RdfFormat:
val fullName: String
val cliOptions: List[String]

object RdfFormat:

sealed trait Writeable extends RdfFormat

sealed trait Jena extends RdfFormat:
val jenaLang: Lang

object Jena:
sealed trait Writeable extends Jena, RdfFormat.Writeable
sealed trait Readable extends Jena

case object NQuads extends RdfFormat.Jena.Writeable, RdfFormat.Jena.Readable:
override val fullName: String = "N-Quads"
override val cliOptions: List[String] = List("nq", "nquads")
override val jenaLang: Lang = RDFLanguages.NQUADS

case object NTriples extends RdfFormat.Jena.Writeable, RdfFormat.Jena.Readable:
override val fullName: String = "N-Triples"
override val cliOptions: List[String] = List("nt", "ntriples")
override val jenaLang: Lang = RDFLanguages.NTRIPLES

// We do not ever want to write or read from Jelly to Jelly
// So better not have it as Writeable or Readable, just mark that it's integrated into Jena
case object JellyBinary extends RdfFormat.Jena:
override val fullName: String = "Jelly binary format"
override val cliOptions: List[String] = List("jelly")
override val jenaLang: Lang = JellyLanguage.JELLY

case object JellyText extends RdfFormat, RdfFormat.Writeable:
override val fullName: String = "Jelly text format"
override val cliOptions: List[String] = List("jelly-text")
val extension = ".jelly.txt"

private val rdfFormats: List[RdfFormat] = List(NQuads, NTriples, JellyBinary, JellyText)

def all: List[RdfFormat] = rdfFormats

/** Returns a string representation of the option for the user.
*/
def optionString(option: RdfFormat): String =
f"${option.cliOptions.map(s => f"\"${s}\"").mkString(", ")} for ${option.fullName}"

/** Finds the appropriate RdfFormat based on supplied option string.
*/
def find(cliOption: String): Option[RdfFormat] =
rdfFormats.find(_.cliOptions.contains(cliOption))

/** Infers the format based on the file name.
*/
def inferFormat(fileName: String): Option[RdfFormat] =
val jenaImpl = RdfFormat.all.collect({ case x: RdfFormat.Jena => x })
val guessType = RDFLanguages.guessContentType(fileName)
val formatGuessed = jenaImpl.collectFirst({
case x if x.jenaLang.getContentType == guessType => x
})
formatGuessed match {
case Some(f: RdfFormat.Jena) => formatGuessed
case _ if fileName.endsWith(JellyText.extension) => Some(RdfFormat.JellyText)
case _ => None
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
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.neverblink.jelly.cli.command.rdf.RdfFormat.*
import eu.neverblink.jelly.cli.command.rdf.RdfFormat.Jena.*
import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage
import eu.ostrzyciel.jelly.core.proto.v1.RdfStreamFrame
import eu.ostrzyciel.jelly.core.{IoUtils, RdfProtoDeserializationError}
import eu.ostrzyciel.jelly.core.IoUtils
import org.apache.jena.riot.system.StreamRDFWriter
import org.apache.jena.riot.{RDFLanguages, RDFParser, RiotException}
import org.apache.jena.riot.{Lang, RDFParser}

import java.io.{InputStream, OutputStream}

object RdfFromJellyPrint extends RdfCommandPrintUtil:
override val validFormats: List[RdfFormatOption] = List(JellyText, NQuads)
override val defaultFormat: RdfFormatOption = NQuads
object RdfFromJellyPrint extends RdfCommandPrintUtil[RdfFormat.Writeable]:
override val defaultFormat: RdfFormat = RdfFormat.NQuads

case class RdfFromJellyOptions(
@Recurse
Expand All @@ -26,64 +25,44 @@ case class RdfFromJellyOptions(
@ExtraName("out-format") outputFormat: Option[String] = None,
) extends HasJellyOptions

object RdfFromJelly extends JellyCommand[RdfFromJellyOptions]:
override def group = "rdf"
object RdfFromJelly extends RdfCommand[RdfFromJellyOptions, RdfFormat.Writeable]:

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

lazy val printUtil: RdfCommandPrintUtil[RdfFormat.Writeable] = RdfFromJellyPrint

val defaultAction: (InputStream, OutputStream) => Unit =
jellyToLang(RdfFormat.NQuads.jenaLang, _, _)

override def doRun(options: RdfFromJellyOptions, remainingArgs: RemainingArgs): Unit =
val (inputStream, outputStream) =
this.getIoStreamsFromOptions(remainingArgs.remaining.headOption, options.outputFile)
doConversion(inputStream, outputStream, options.outputFormat)
parseFormatArgs(inputStream, outputStream, options.outputFormat, options.outputFile)

override def matchToAction(
option: RdfFormat.Writeable,
): Option[(InputStream, OutputStream) => Unit] =
option match
case j: RdfFormat.Jena.Writeable => Some(jellyToLang(j.jenaLang, _, _))
case RdfFormat.JellyText => Some(jellyBinaryToText)

/** This method takes care of proper error handling and matches the desired output format to the
* correct conversion
*
/** This method reads the Jelly file, rewrites it to specified format and writes it to some output
* stream
* @param jenaLang
* Language that jelly should be converted to
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
* @throws JellyDeserializationError
* @throws JenaRiotException
* @throws InvalidJellyFile
*/
private def doConversion(
private def jellyToLang(
jenaLang: Lang,
inputStream: InputStream,
outputStream: OutputStream,
format: Option[String],
): Unit =
try {
format match {
case Some(f: String) =>
RdfFormatOption.find(f) match
case Some(JellyText) => jellyBinaryToText(inputStream, outputStream)
case Some(NQuads) => jellyToNQuad(inputStream, outputStream)
case _ =>
throw InvalidFormatSpecified(
f,
RdfFromJellyPrint.validFormatsString,
) // if anything else, it's an invalid option
case None =>
jellyToNQuad(inputStream, outputStream) // default option if no parameter supplied
}
} catch
case e: RdfProtoDeserializationError =>
throw JellyDeserializationError(e.getMessage)
case e: RiotException =>
throw JenaRiotException(e)
case e: InvalidProtocolBufferException =>
throw InvalidJellyFile(e)

/** This method reads the Jelly file, rewrites it to NQuads and writes it to some output stream
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
*/
private def jellyToNQuad(inputStream: InputStream, outputStream: OutputStream): Unit =
val nQuadWriter = StreamRDFWriter.getWriterStream(outputStream, RDFLanguages.NQUADS)
val nQuadWriter = StreamRDFWriter.getWriterStream(outputStream, jenaLang)
RDFParser.source(inputStream).lang(JellyLanguage.JELLY).parse(nQuadWriter)

/** This method reads the Jelly file, rewrites it to Jelly text and writes it to some output
Expand Down
Loading