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)
}
}
}