Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 3 additions & 1 deletion src/main/scala/eu/neverblink/jelly/cli/App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import org.apache.jena.sys.JenaSystem
/** Main entrypoint.
*/
object App extends CommandsEntryPoint:

// Initialize Jena now to avoid race conditions later
JenaSystem.init()

protected[cli] var debugMode = false

override def enableCompletionsCommand: Boolean = true

override def enableCompleteCommand: Boolean = true

override def progName: String = "jelly-cli"

override def commands: Seq[Command[?]] = Seq(
FoolAround,
Version,
RdfFromJelly,
)
58 changes: 58 additions & 0 deletions src/main/scala/eu/neverblink/jelly/cli/ErrorHandler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package eu.neverblink.jelly.cli

case class InputFileNotFound(file: String)
extends CriticalException(s"Input file $file does not exist.")
case class InputFileInaccessible(file: String)
extends CriticalException(s"Not enough permissions to read input file $file.")
case class OutputFileCannotBeCreated(file: String)
extends CriticalException(
s"Not enough permission to create output file $file in this directory.",
)
case class JellyDeserializationError(message: String)
extends ParsingError(s"Jelly deserialization error: $message.")
case class JellySerializationError(message: String)
extends ParsingError(s"Jelly serialization error: $message.")
case class JellyTranscodingError(message: String)
extends ParsingError(s"Jelly transcoding error: $message.")
class ParsingError(message: String) extends CriticalException(s"Parsing error: $message.")
case class InputOutputTranslationLossy(format: String)
extends NonCriticalException(
"Input file cannot be fully translated to output format. The translation will be lossy.",
)
case class ExitException(code: Int) extends CriticalException(s"Exiting with code $code.")

class CriticalException(message: String) extends Exception(message)
class NonCriticalException(message: String) extends Exception(message)

/** Handle exceptions. Critical exceptions will exit the application with code 1. Non-critical
* exceptions will be printed to std err.
*/

object ErrorHandler:

def handle(command: JellyCommand[?], t: Throwable): Unit =
t match
case e: ParsingError =>
command.printLine(f"${e.getMessage}", toStderr = true)
printStackTrace(command, e)
command.exit(1)
case e: CriticalException =>
command.printLine(f"${e.getMessage}", toStderr = true)
command.exit(1)
case e: NonCriticalException =>
command.printLine(f"${e.getMessage}", toStderr = true)
case e: Throwable =>
command.printLine("Unknown error", toStderr = true)
printStackTrace(command, e)
command.exit(1)

/** Print out stack trace or debugging information
* @param command
* @param t
*/
private def printStackTrace(
command: JellyCommand[?],
t: Throwable,
): Unit =
if App.debugMode then t.printStackTrace(command.err)
else command.printLine("If needed, run with --debug to see the stack trace.", toStderr = true)
3 changes: 0 additions & 3 deletions src/main/scala/eu/neverblink/jelly/cli/ExitError.scala

This file was deleted.

41 changes: 34 additions & 7 deletions src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ import caseapp.*
import java.io.{ByteArrayOutputStream, OutputStream, PrintStream}
import scala.compiletime.uninitialized

abstract class JellyCommand[T: {Parser, Help}] extends Command[T]:
case class JellyOptions(
@HelpMessage("Add to run command in debug mode") debug: Boolean = false,
)

trait HasJellyOptions:
@Recurse
val common: JellyOptions

abstract class JellyCommand[T <: HasJellyOptions: {Parser, Help}] extends Command[T]:
private var isTest = false
private var out = System.out
private var err = System.err
protected[cli] var out = System.out
protected[cli] var err = System.err
private var osOut: ByteArrayOutputStream = uninitialized
private var osErr: ByteArrayOutputStream = uninitialized

Expand All @@ -27,6 +35,21 @@ abstract class JellyCommand[T: {Parser, Help}] extends Command[T]:
out = System.out
err = System.err

/** Check and set all the options repeating for every Jelly command
* @param options
* @param remainingArgs
*/
def setUpGeneralArgs(options: T, remainingArgs: RemainingArgs): Unit =
if options.common.debug then App.debugMode = true

/** Override to have custom error handling for Jelly commands
*/
override def main(progName: String, args: Array[String]): Unit =
try super.main(progName, args)
catch
case e: Throwable =>
ErrorHandler.handle(this, e)

/** Runs the command in test mode from the outside app parsing level
* @param args
* the command line arguments
Expand All @@ -38,29 +61,33 @@ abstract class JellyCommand[T: {Parser, Help}] extends Command[T]:
App.main(args.toArray)
(osOut.toString, osErr.toString)

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

def getOutStream: OutputStream =
if isTest then osOut
else System.out

protected def getStdOut: OutputStream =
if isTest then osOut
else System.out

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

@throws[ExitError]
@throws[ExitException]
override def exit(code: Int): Nothing =
if isTest then throw ExitError(code)
if isTest then throw ExitException(code)
else super.exit(code)

override def printLine(line: String, toStderr: Boolean): Unit =
Expand Down
20 changes: 0 additions & 20 deletions src/main/scala/eu/neverblink/jelly/cli/command/FoolAround.scala

This file was deleted.

6 changes: 5 additions & 1 deletion src/main/scala/eu/neverblink/jelly/cli/command/Version.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package eu.neverblink.jelly.cli.command
import caseapp.*
import eu.neverblink.jelly.cli.*

case class VersionOptions()
case class VersionOptions(
@Recurse
common: JellyOptions = JellyOptions(),
) extends HasJellyOptions

object Version extends JellyCommand[VersionOptions]:
override def names: List[List[String]] = List(
Expand All @@ -12,6 +15,7 @@ object Version extends JellyCommand[VersionOptions]:
)

override def run(options: VersionOptions, remainingArgs: RemainingArgs): Unit =
super.setUpGeneralArgs(options, remainingArgs)
val jenaV = BuildInfo.libraryDependencies
.find(_.startsWith("org.apache.jena:jena-core:")).get.split(":")(2)
val jellyV = BuildInfo.libraryDependencies
Expand Down
35 changes: 35 additions & 0 deletions src/main/scala/eu/neverblink/jelly/cli/command/rdf/Ops.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package eu.neverblink.jelly.cli.command.rdf

import eu.neverblink.jelly.cli.{InputFileInaccessible, InputFileNotFound, OutputFileCannotBeCreated}

import java.io.{File, FileInputStream, FileOutputStream}

/** Object for small, repeating operations with proper error handling
*/
object Ops:

/** Read input file and return FileInputStream
* @param fileName
* @throws InputFileNotFound
* @throws InputFileInaccessible
* @return
* FileInputStream
*/
def readInputFile(fileName: String): FileInputStream =
val file = File(fileName)
if !file.exists then throw InputFileNotFound(fileName)
if !file.canRead then throw InputFileInaccessible(fileName)
FileInputStream(file)

/** Create output stream with extra error handling. If the file exists, it will append to it.
* @param fileName
* @throws OutputFileExists
* @return
* FileOutputStream
*/
def createOutputStream(fileName: String): FileOutputStream =
val file = File(fileName)
val suppFile = file.getParentFile
val parentFile = if (suppFile != null) suppFile else File(".")
if !parentFile.canWrite then throw OutputFileCannotBeCreated(fileName)
FileOutputStream(file, true)
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package eu.neverblink.jelly.cli.command.rdf
import caseapp.*
import eu.neverblink.jelly.cli.JellyCommand
import eu.ostrzyciel.jelly.convert.jena.riot.{JellyLanguage, JellySubsystemLifecycle}
import eu.neverblink.jelly.cli.{
HasJellyOptions,
JellyCommand,
JellyDeserializationError,
JellyOptions,
ParsingError,
}
import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage
import eu.ostrzyciel.jelly.core.RdfProtoDeserializationError
import org.apache.jena.riot.system.StreamRDFWriter
import org.apache.jena.riot.{RDFLanguages, RDFParser}

import java.io.{File, FileInputStream, FileOutputStream, InputStream, OutputStream}
import java.io.{InputStream, OutputStream}

case class RdfFromJellyOptions(
@Recurse
common: JellyOptions = JellyOptions(),
@ExtraName("to") outputFile: Option[String] = None,
)
) extends HasJellyOptions

object RdfFromJelly extends JellyCommand[RdfFromJellyOptions]:
override def group = "rdf"
Expand All @@ -19,25 +28,33 @@ object RdfFromJelly extends JellyCommand[RdfFromJellyOptions]:
)

override def run(options: RdfFromJellyOptions, remainingArgs: RemainingArgs): Unit =
super.setUpGeneralArgs(options, remainingArgs)
val inputStream = remainingArgs.remaining.headOption match {
case Some(fileName: String) =>
FileInputStream(File(fileName))
Ops.readInputFile(fileName)
case _ => System.in
}
val outputStream = options.outputFile match {
case Some(fileName: String) =>
FileOutputStream(fileName)
Ops.createOutputStream(fileName)
case None => getStdOut
}
doConversion(inputStream, outputStream)

/*
This method reads the Jelly file, rewrite it to NQuads and writes it to some output stream
* @param inputStream InputStream
* @param outputStream OutputStream
*/
/** This method reads the Jelly file, rewrites it to NQuads and writes it to some output stream
* @param inputStream
* InputStream
* @param outputStream
* OutputStream
* @throws JellyDeserializationError
* @throws ParsingError
*/
private def doConversion(inputStream: InputStream, outputStream: OutputStream): Unit =
val mod = JellySubsystemLifecycle()
mod.start()
val nQuadWriter = StreamRDFWriter.getWriterStream(outputStream, RDFLanguages.NQUADS)
RDFParser.source(inputStream).lang(JellyLanguage.JELLY).parse(nQuadWriter)
try {
val nQuadWriter = StreamRDFWriter.getWriterStream(outputStream, RDFLanguages.NQUADS)
RDFParser.source(inputStream).lang(JellyLanguage.JELLY).parse(nQuadWriter)
} catch
case e: RdfProtoDeserializationError =>
throw JellyDeserializationError(e.getMessage)
case e: Exception =>
throw ParsingError(e.getMessage)
Loading