diff --git a/build.sbt b/build.sbt index 3a1faf1..78708ff 100644 --- a/build.sbt +++ b/build.sbt @@ -2,139 +2,159 @@ import scala.sys.process._ Global / onChangedBuildSource := ReloadOnSourceChanges -name := "scala-csv" +ThisBuild / version := "2.0.0" -version := "2.0.0" +ThisBuild / scalaVersion := "2.13.15" -scalaVersion := "2.13.15" +val defaultCrossScalaVersions = Seq("2.12.20", "2.11.12", "2.10.7", "2.13.15", "3.3.4") +val nativeScalaCrossVersions = Seq("2.12.20", "2.13.15", "3.3.4") -crossScalaVersions := Seq("2.12.20", "2.11.12", "2.10.7", "2.13.15", "3.3.4") +lazy val scalaCsv = crossProject(JVMPlatform, NativePlatform).crossType(CrossType.Pure).in(file(".")).settings( + name := "scala-csv", -TaskKey[Unit]("checkScalariform") := { - val diff = "git diff".!! - if(diff.nonEmpty){ - sys.error("Working directory is dirty!\n" + diff) - } -} + crossScalaVersions := defaultCrossScalaVersions, + + TaskKey[Unit]("checkScalariform") := { + val diff = "git diff".!! + if(diff.nonEmpty){ + sys.error("Working directory is dirty!\n" + diff) + } + }, -organization := "com.github.tototoshi" + organization := "com.github.tototoshi", -libraryDependencies ++= { - Seq( + libraryDependencies ++= Seq( "org.scalatest" %% "scalatest-funspec" % "3.2.19" % Test, "org.scalatest" %% "scalatest-shouldmatchers" % "3.2.19" % Test, if (scalaVersion.value.startsWith("2.")) "org.scalacheck" %% "scalacheck" % "1.14.3" % Test else "org.scalacheck" %% "scalacheck" % "1.18.1" % Test - ) -} - -val enableScalameter = settingKey[Boolean]("") - -enableScalameter := { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, v)) => - 11 <= v && v <= 13 - case _ => - false - } -} - -libraryDependencies ++= { - if (enableScalameter.value) { - Seq("com.storm-enroute" %% "scalameter" % "0.19" % "test") - } else { - Nil + ), + + libraryDependencies ++= { + if (enableScalameter.value) { + Seq("com.storm-enroute" %% "scalameter" % "0.19" % Test) + } else { + Nil + } + }, + + scalacOptions ++= Seq( + "-deprecation", + "-feature", + "-language:implicitConversions" + ), + + scalacOptions ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)){ + case Some((2, v)) if v >= 11 => Seq("-Ywarn-unused") + }.toList.flatten, + + scalacOptions ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)){ + case Some((2, 11 | 12)) => + Seq("-Xsource:3") + case Some((2, 13)) => + Seq("-Xsource:3-cross") + }.toList.flatten, + + Test / sources := { + val s = (Test / sources).value + val exclude = Set("CsvBenchmark.scala") + if (enableScalameter.value) { + s + } else { + s.filterNot(f => exclude(f.getName)) + } + }, + + testFrameworks ++= { + if (enableScalameter.value) { + Seq(new TestFramework("org.scalameter.ScalaMeterFramework")) + } else Nil + }, + + Test / parallelExecution := false, + + logBuffered := false, + + compile / javacOptions += "-Xlint", + + compile / javacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, v)) if v <= 11 => + Seq("-target", "6", "-source", "6") + case _ => + Seq("-target", "8", "-source", "8") + } + }, + + initialCommands := """ + |import com.github.tototoshi.csv._ + """.stripMargin, + + publishMavenStyle := true, + + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (version.value.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + + Test / publishArtifact := false, + + pomExtra := https://github.com/tototoshi/scala-csv + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + git@github.com:tototoshi/scala-csv.git + scm:git:git@github.com:tototoshi/scala-csv.git + + + + tototoshi + Toshiyuki Takahashi + https://tototoshi.github.io + + , + + Compile / unmanagedSourceDirectories += { + val dir = CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, v)) if v <= 12 => + "scala-2.13-" + case _ => + "scala-2.13+" + } + crossProjectBaseDirectory.value / "src" / "main" / dir + }, + Compile / unmanagedSourceDirectories += { + val dir = crossProjectPlatform.value match { + case JVMPlatform => "java" // for binary compatibility with previous JVM publications, to be deprecated + case _ => "scala-readers" + } + crossProjectBaseDirectory.value / "src" / "main" / dir } -} - -scalacOptions ++= Seq( - "-deprecation", - "-feature", - "-language:implicitConversions" +).nativeSettings( + crossScalaVersions := nativeScalaCrossVersions ) -scalacOptions ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)){ - case Some((2, v)) if v >= 11 => Seq("-Ywarn-unused") -}.toList.flatten - -scalacOptions ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)){ - case Some((2, 11 | 12)) => - Seq("-Xsource:3") - case Some((2, 13)) => - Seq("-Xsource:3-cross") -}.toList.flatten - -Test / sources := { - val s = (Test / sources).value - val exclude = Set("CsvBenchmark.scala") - if (enableScalameter.value) { - s - } else { - s.filterNot(f => exclude(f.getName)) +val enableScalameter = Def.setting( + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, v)) => 11 <= v && v <= 13 && crossProjectPlatform.value == JVMPlatform + case _ => false } -} - -testFrameworks += new TestFramework( - "org.scalameter.ScalaMeterFramework" ) -Test / parallelExecution := false - -logBuffered := false - -compile / javacOptions += "-Xlint" - -compile / javacOptions ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, v)) if v <= 11 => - Seq("-target", "6", "-source", "6") - case _ => - Seq("-target", "8", "-source", "8") - } -} - -initialCommands := """ - |import com.github.tototoshi.csv._ - """.stripMargin - -publishMavenStyle := true - -publishTo := { - val nexus = "https://oss.sonatype.org/" - if (version.value.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} - -Test / publishArtifact := false - -pomExtra := https://github.com/tototoshi/scala-csv - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - - - git@github.com:tototoshi/scala-csv.git - scm:git:git@github.com:tototoshi/scala-csv.git - - - - tototoshi - Toshiyuki Takahashi - https://tototoshi.github.io - - - -Compile / unmanagedSourceDirectories += { - val dir = CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, v)) if v <= 12 => - "scala-2.13-" - case _ => - "scala-2.13+" - } - baseDirectory.value / "src" / "main" / dir -} +// an aggregate project to pass commands into platform projects +lazy val root = (project in file(".")) + .aggregate(scalaCsv.jvm, scalaCsv.native) + .settings( + Compile / sources := Seq.empty, + Test / sources := Seq.empty, + artifacts := Seq.empty, + publish / skip := true, + ) diff --git a/project/plugins.sbt b/project/plugins.sbt index e1a92d8..604d89b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,4 +6,8 @@ addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") +addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "1.3.2") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") + libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % "always" diff --git a/src/main/scala-readers/com/github/tototoshi/csv/LineReader.scala b/src/main/scala-readers/com/github/tototoshi/csv/LineReader.scala new file mode 100644 index 0000000..110f602 --- /dev/null +++ b/src/main/scala-readers/com/github/tototoshi/csv/LineReader.scala @@ -0,0 +1,9 @@ +package com.github.tototoshi.csv + +import java.io.Closeable +import java.io.IOException + +trait LineReader extends Closeable { + @throws[IOException] + def readLineWithTerminator(): String +} diff --git a/src/main/scala-readers/com/github/tototoshi/csv/ReaderLineReader.scala b/src/main/scala-readers/com/github/tototoshi/csv/ReaderLineReader.scala new file mode 100644 index 0000000..f8fafdf --- /dev/null +++ b/src/main/scala-readers/com/github/tototoshi/csv/ReaderLineReader.scala @@ -0,0 +1,40 @@ +package com.github.tototoshi.csv + +import java.io.BufferedReader +import java.io.IOException +import java.io.Reader + +class ReaderLineReader(baseReader: Reader) extends LineReader { + private val bufferedReader: BufferedReader = new BufferedReader(baseReader) + + @throws[IOException] + def readLineWithTerminator(): String = { + val sb = new StringBuilder() + var c: Int = 0 + + while (c != -1) { + c = bufferedReader.read() + if (c != -1) sb.append(c.toChar) + if (c == '\n' || c == '\u2028' || c == '\u2029' || c == '\u0085') { + c = -1 + } + if (c == '\r') { + bufferedReader.mark(1) + c = bufferedReader.read() + if (c == '\n') { + sb.append('\n') + } else { + bufferedReader.reset() + } + c = -1 + } + } + if (sb.isEmpty) null else sb.toString() + } + + @throws[IOException] + def close(): Unit = { + bufferedReader.close() + baseReader.close() + } +} diff --git a/src/main/scala-readers/com/github/tototoshi/csv/SourceLineReader.scala b/src/main/scala-readers/com/github/tototoshi/csv/SourceLineReader.scala new file mode 100644 index 0000000..38e1ee9 --- /dev/null +++ b/src/main/scala-readers/com/github/tototoshi/csv/SourceLineReader.scala @@ -0,0 +1,40 @@ +package com.github.tototoshi.csv + +import java.io.IOException +import scala.io.Source + +class SourceLineReader(source: Source) extends LineReader { + + private var buffer: Int = -1 + + @throws[IOException] + def readLineWithTerminator(): String = { + val sb = new StringBuilder() + var c: Int = 0 + + while (c != -1) { + if (buffer != -1) { + c = buffer + buffer = -1 + } else { + c = if (source.hasNext) source.next() else -1 + } + if (c != -1) sb.append(c.toChar) + if (c == '\n' || c == '\u2028' || c == '\u2029' || c == '\u0085') { + c = -1 + } + if (c == '\r') { + buffer = if (source.hasNext) source.next() else -1 + if (buffer == '\n') { + sb.append('\n') + buffer = -1 + } + c = -1 + } + } + if (sb.isEmpty) null else sb.toString() + } + + @throws[IOException] + override def close(): Unit = source.close() +} diff --git a/src/test/scala/com/github/tototoshi/csv/CSVReaderSpec.scala b/src/test/scala/com/github/tototoshi/csv/CSVReaderSpec.scala index b30ac8f..0420b1f 100644 --- a/src/test/scala/com/github/tototoshi/csv/CSVReaderSpec.scala +++ b/src/test/scala/com/github/tototoshi/csv/CSVReaderSpec.scala @@ -312,14 +312,14 @@ class CSVReaderSpec extends AnyFunSpec with Matchers with Using { describe("When the file is empty") { it("returns an empty list") { using(CSVReader.open(new FileReader("src/test/resources/empty.csv"))) { reader => - reader.iteratorWithHeaders should be(Symbol("empty")) + reader.iteratorWithHeaders should be(empty) } } } describe("When the file has only one line") { it("returns an empty list") { using(CSVReader.open(new FileReader("src/test/resources/only-header.csv"))) { reader => - reader.iteratorWithHeaders should be(Symbol("empty")) + reader.iteratorWithHeaders should be(empty) } } } @@ -338,14 +338,14 @@ class CSVReaderSpec extends AnyFunSpec with Matchers with Using { describe("When the file is empty") { it("returns an empty list") { using(CSVReader.open(new FileReader("src/test/resources/empty.csv"))) { reader => - reader.allWithHeaders() should be(Symbol("empty")) + reader.allWithHeaders() should be(empty) } } } describe("When the file has only one line") { it("returns an empty list") { using(CSVReader.open(new FileReader("src/test/resources/only-header.csv"))) { reader => - reader.allWithHeaders() should be(Symbol("empty")) + reader.allWithHeaders() should be(empty) } } }