diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..cb82408 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,33 @@ +name: Code checks +on: + pull_request: + branches: [ "main" ] +jobs: + scalafmt-lint: + name: Scalafmt lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: jrouly/scalafmt-native-action@v4 + + scalafix-lint: + name: Scalafix lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: Setup SBT + uses: sbt/setup-sbt@v1 + + - run: sbt 'scalafixAll --check' \ No newline at end of file diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 0000000..4235cba --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,7 @@ +rules = [ + RemoveUnused + ExplicitResultTypes + ProcedureSyntax + RedundantSyntax +] +ExplicitResultTypes.memberVisibility = [Public] \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..6eb6d6f --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,7 @@ +version = 3.8.6 +runner.dialect = scala36 +maxColumn = 100 +align.preset=none +rewrite.trailingCommas.style = always +newlines.selectChains.style = keep +newlines.selectChains.classicKeepAfterFirstBreak = true \ No newline at end of file diff --git a/README.md b/README.md index 08195d3..e91e46a 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,8 @@ $ ./jelly-cli --help - The binary will be available at `./target/graalvm-native-image/jelly-cli`. Alternatively, you can use the utility with your JVM (no ahead-of-time compilation), by running `sbt run`. + +## Developer notes + +Run `sbt fixAll` before committing. Your code should be formatted and free of warnings. +The CI checks will not pass if this is not the case. diff --git a/build.sbt b/build.sbt index 21d0d67..762b13a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,12 +1,14 @@ ThisBuild / version := "0.1.0-SNAPSHOT" ThisBuild / scalaVersion := "3.6.4" - +ThisBuild / autoAPIMappings := true +ThisBuild / semanticdbEnabled := true resolvers += "Sonatype OSS Snapshots" at "https://s01.oss.sonatype.org/content/repositories/snapshots" lazy val jenaV = "5.3.0" lazy val jellyV = "2.8.0+14-4181e89a-SNAPSHOT" +addCommandAlias("fixAll", "scalafixAll; scalafmtAll") def isDevBuild: Boolean = sys.env.get("DEV_BUILD").exists(s => s != "0" && s != "false") @@ -25,6 +27,14 @@ lazy val root = (project in file(".")) "com.github.alexarchambault" %% "case-app" % "2.1.0-M30", "org.scalatest" %% "scalatest" % "3.2.19" % Test, ), + scalacOptions ++= Seq( + "-Wunused:imports", + "-Werror", + "-feature", + "-deprecation", + "-unchecked", + "-explain", + ), buildInfoKeys := Seq[BuildInfoKey]( version, scalaVersion, diff --git a/project/plugins.sbt b/project/plugins.sbt index 9abd225..412ab3f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.3") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.1") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4") \ No newline at end of file diff --git a/src/main/scala/eu/neverblink/jelly/cli/App.scala b/src/main/scala/eu/neverblink/jelly/cli/App.scala index 7973cf6..ef394a5 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/App.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/App.scala @@ -3,9 +3,8 @@ package eu.neverblink.jelly.cli import caseapp.* import eu.neverblink.jelly.cli.command.* -/** - * Main entrypoint. - */ +/** Main entrypoint. + */ object App extends CommandsEntryPoint: override def progName: String = "jelly-cli" diff --git a/src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala b/src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala index 118b818..9bb527e 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/JellyCommand.scala @@ -9,17 +9,17 @@ import scala.compiletime.uninitialized object JellyCommand: val emptyRemainingArgs: RemainingArgs = RemainingArgs(Seq.empty, Seq.empty) -abstract class JellyCommand[T : {Parser, Help}] extends Command[T]: +abstract class JellyCommand[T: {Parser, Help}] extends Command[T]: private var isTest = false private var out = System.out private var err = System.err private var osOut: ByteArrayOutputStream = uninitialized private var osErr: ByteArrayOutputStream = uninitialized - /** - * Enable the "test mode" which captures stdout, stderr, exit code, and so on. - * @param test true to enable, false to disable - */ + /** Enable the "test mode" which captures stdout, stderr, exit code, and so on. + * @param test + * true to enable, false to disable + */ def testMode(test: Boolean): Unit = this.isTest = test if test then @@ -47,13 +47,16 @@ abstract class JellyCommand[T : {Parser, Help}] extends Command[T]: s else throw new IllegalStateException("Not in test mode") - /** - * Run the command in test mode, capturing stdout and stderr. - * @param options the command options - * @param remainingArgs the remaining arguments - * @throws ExitError if the command exits - * @return (stdout, stderr) - */ + /** Run the command in test mode, capturing stdout and stderr. + * @param options + * the command options + * @param remainingArgs + * the remaining arguments + * @throws ExitError + * if the command exits + * @return + * (stdout, stderr) + */ @throws[ExitError] def runTest(options: T, remainingArgs: RemainingArgs = emptyRemainingArgs): (String, String) = if !isTest then testMode(true) diff --git a/src/main/scala/eu/neverblink/jelly/cli/command/FoolAround.scala b/src/main/scala/eu/neverblink/jelly/cli/command/FoolAround.scala index 8425f8b..9f1b2f2 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/command/FoolAround.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/command/FoolAround.scala @@ -4,13 +4,12 @@ import caseapp.* import eu.neverblink.jelly.cli.JellyCommand case class FoolAroundOptions( - @HelpMessage("What to say") - say: String = "Hello, World!" + @HelpMessage("What to say") + say: String = "Hello, World!", ) -/** - * "foo-bar" kind of command. - */ +/** "foo-bar" kind of command. + */ object FoolAround extends JellyCommand[FoolAroundOptions]: // https://alexarchambault.github.io/case-app/commands/ override def names: List[List[String]] = List( diff --git a/src/main/scala/eu/neverblink/jelly/cli/command/RDFFromJelly.scala b/src/main/scala/eu/neverblink/jelly/cli/command/RDFFromJelly.scala new file mode 100644 index 0000000..78ea3d3 --- /dev/null +++ b/src/main/scala/eu/neverblink/jelly/cli/command/RDFFromJelly.scala @@ -0,0 +1,23 @@ +package eu.neverblink.jelly.cli.command +import caseapp.* +import eu.neverblink.jelly.cli.JellyCommand + +case class FromJellyRDFOptions() + +object RDFFromJelly extends JellyCommand[FromJellyRDFOptions]: + override def names: List[List[String]] = List( + List("rdf-from-jelly"), + ) + + override def run(options: FromJellyRDFOptions, remainingArgs: RemainingArgs): Unit = + println("rdf-from-jelly") + println(options) + println(remainingArgs) + println("rdf-from-jelly") + + /* + * This method will be used to validate the passing RDF stream + */ + def validate(): Unit = + println("rdf-from-jelly validate") + println("rdf-from-jelly validate") diff --git a/src/main/scala/eu/neverblink/jelly/cli/command/Version.scala b/src/main/scala/eu/neverblink/jelly/cli/command/Version.scala index 5c265ae..0d99d41 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/command/Version.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/command/Version.scala @@ -16,8 +16,7 @@ object Version extends JellyCommand[VersionOptions]: .find(_.startsWith("org.apache.jena:jena-core:")).get.split(":")(2) val jellyV = BuildInfo.libraryDependencies .find(_.startsWith("eu.ostrzyciel.jelly:jelly-jena:")).get.split(":")(2) - printLine( - f"""jelly-cli ${BuildInfo.version} + printLine(f"""jelly-cli ${BuildInfo.version} |---------------------------------------------- |Jelly-JVM $jellyV |Apache Jena $jenaV diff --git a/src/test/scala/eu/neverblink/jelly/cli/command/RDFFromJellySpec.scala b/src/test/scala/eu/neverblink/jelly/cli/command/RDFFromJellySpec.scala new file mode 100644 index 0000000..4ef9c96 --- /dev/null +++ b/src/test/scala/eu/neverblink/jelly/cli/command/RDFFromJellySpec.scala @@ -0,0 +1,12 @@ +package eu.neverblink.jelly.cli.command + +import eu.neverblink.jelly.cli.command.helpers.DataGenHelper +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +class RDFFromJellySpec extends AnyWordSpec, Matchers: + "rdf-from-jelly command" should { + "be able to convert a jelly stream to ntriples" in { + val jellyStream = DataGenHelper.generateJelly(3) + } + } diff --git a/src/test/scala/eu/neverblink/jelly/cli/command/VersionSpec.scala b/src/test/scala/eu/neverblink/jelly/cli/command/VersionSpec.scala index fc4f9f9..3059933 100644 --- a/src/test/scala/eu/neverblink/jelly/cli/command/VersionSpec.scala +++ b/src/test/scala/eu/neverblink/jelly/cli/command/VersionSpec.scala @@ -7,8 +7,8 @@ class VersionSpec extends AnyWordSpec, Matchers: "version command" should { "print something" in { val (out, err) = Version.runTest(VersionOptions()) - out should startWith ("jelly-cli") - out should include ("Jelly-JVM") - out should include ("Apache Jena") + out should startWith("jelly-cli") + out should include("Jelly-JVM") + out should include("Apache Jena") } } diff --git a/src/test/scala/eu/neverblink/jelly/cli/command/helpers/DataGenHelper.scala b/src/test/scala/eu/neverblink/jelly/cli/command/helpers/DataGenHelper.scala new file mode 100644 index 0000000..6c1e187 --- /dev/null +++ b/src/test/scala/eu/neverblink/jelly/cli/command/helpers/DataGenHelper.scala @@ -0,0 +1,45 @@ +package eu.neverblink.jelly.cli.command.helpers + +import eu.ostrzyciel.jelly.convert.jena.riot.JellyLanguage +import org.apache.commons.io.output.ByteArrayOutputStream +import org.apache.jena.rdf.model.{Model, ModelFactory, ResourceFactory} +import org.apache.jena.riot.RDFDataMgr + +import scala.util.Using + +/* + * This class will be used to generate test data + */ +object DataGenHelper: + + /* + * This method generates a triple model with nTriples + * @param nTriples number of triples to generate + * @return Model + */ + def generateTripleModel(nTriples: Int): Model = + val model = ModelFactory.createDefaultModel() + val subStr = "http://example.org/subject" + val predStr = "http://example.org/predicate" + val objStr = "http://example.org/object" + val tripleList = (1 to nTriples).map { i => + val sub = ResourceFactory.createResource(s"$subStr/$i") + val pred = ResourceFactory.createProperty(s"$predStr/$i") + val obj = ResourceFactory.createResource(s"$objStr/$i") + val stat = ResourceFactory.createStatement(sub, pred, obj) + model.add(stat) + } + model + + /* + * This method generates a Jelly stream with nTriples + * @param nTriples number of triples to generate + */ + def generateJelly(nTriple: Int): ByteArrayOutputStream = + val model = generateTripleModel(nTriple) + val newStream = ByteArrayOutputStream() + // TODO: Add tests for different variants of Jelly (small strict etc) + Using.resource(newStream) { stream => + RDFDataMgr.write(stream, model, JellyLanguage.JELLY) + } + newStream