Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to use kotlinx-io #159

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
9 changes: 4 additions & 5 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -14,11 +14,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 8
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '8'
java-version: '11'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
@@ -29,7 +29,7 @@ jobs:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-
@@ -38,8 +38,7 @@ jobs:

- name: Run checks and generate report
run: |
./gradlew clean check
./gradlew jacocoTestReport
./gradlew clean check koverXmlReport
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v4
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -14,4 +14,7 @@ test.csv
.idea/*

# Ignore yarn.lcok
kotlin-js-store/yarn.lock
kotlin-js-store/yarn.lock

# Kotlin 2.0
.kotlin/
169 changes: 75 additions & 94 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,68 +1,77 @@
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinMultiplatform
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
java
kotlin("multiplatform") version "1.7.21"
id("org.jetbrains.dokka").version("1.7.20")
`maven-publish`
signing
jacoco
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.dokka)
alias(libs.plugins.kover)
alias(libs.plugins.mavenPublish)
alias(libs.plugins.kotest)
}

group = "com.jsoizo"
version = "1.10.0"
version = "2.0.0-dev1"
val projectName = "kotlin-csv"

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.20")
}
}

repositories {
mavenCentral()
}

val dokkaJar = task<Jar>("dokkaJar") {
group = JavaBasePlugin.DOCUMENTATION_GROUP
archiveClassifier.set("javadoc")
}

kotlin {
jvm {
compilations.forEach {
it.kotlinOptions.jvmTarget = "1.8"
}
//https://docs.gradle.org/current/userguide/publishing_maven.html
mavenPublication {
artifact(dokkaJar)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}
js(BOTH) {
js {
browser {
}
nodejs {
}
}
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs {
browser()
nodejs()
}
sourceSets {
commonMain {}
commonMain {
dependencies {
implementation(libs.kotlinx.coroutines.core)
api(libs.kotlinx.io)
}
}
commonTest {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(libs.kotest.framework.engine)
implementation(libs.kotlinx.datetime)
}
}

wasmJsTest {
dependencies {
implementation(kotlin("test-wasm-js"))
}
}

jvm().compilations["main"].defaultSourceSet {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation(libs.kotlinx.coroutines.core)
}
}
jvm().compilations["test"].defaultSourceSet {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation("io.kotest:kotest-runner-junit5:4.6.3")
implementation("io.kotest:kotest-assertions-core:4.6.3")
implementation(libs.bundles.kotest)
}
}
js().compilations["main"].defaultSourceSet {
@@ -81,79 +90,51 @@ tasks.withType<Test>() {
useJUnitPlatform()
}

mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)

publishing {
publications.all {
(this as MavenPublication).pom {
name.set("kotlin-csv")
description.set("Kotlin CSV Reader/Writer")
url.set("https://github.com/jsoizo/kotlin-csv")

organization {
name.set("com.jsoizo")
url.set("https://github.com/jsoizo")
}
licenses {
license {
name.set("Apache License 2.0")
url.set("https://github.com/jsoizo/kotlin-csv/blob/master/LICENSE")
}
}
scm {
url.set("https://github.com/jsoizo/kotlin-csv")
connection.set("scm:git:git://github.com/jsoizo/kotlin-csv.git")
developerConnection.set("https://github.com/jsoizo/kotlin-csv")
}
developers {
developer {
name.set("jsoizo")
}
}
}
}
repositories {
maven {
credentials {
val nexusUsername: String? by project
val nexusPassword: String? by project
username = nexusUsername
password = nexusPassword
}

val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots/")
url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
}
if (project.hasProperty("signing.keyId")) {
signAllPublications()
}
}

signing {
sign(publishing.publications)
}
coordinates(group.toString(), projectName, version.toString())

/////////////////////////////////////////
// Jacoco setting //
/////////////////////////////////////////
jacoco {
toolVersion = "0.8.8"
}
tasks.jacocoTestReport {
val coverageSourceDirs = arrayOf(
"commonMain/src",
"jvmMain/src"
configure(
KotlinMultiplatform(
javadocJar = JavadocJar.Dokka("dokkaHtml"),
sourcesJar = true,
androidVariantsToPublish = listOf("debug", "release"),
)
)
val classFiles = File("${buildDir}/classes/kotlin/jvm/")
.walkBottomUp()
.toSet()
classDirectories.setFrom(classFiles)
sourceDirectories.setFrom(files(coverageSourceDirs))
additionalSourceDirs.setFrom(files(coverageSourceDirs))

executionData
.setFrom(files("${buildDir}/jacoco/jvmTest.exec"))
val repo = "github.com/jsoizo/${projectName}"
val repoHttpUrl = "https://${repo}"
val repoGitUrl = "git://${repo}.git"

reports {
xml.required.set(true)
html.required.set(false)
pom {
name = projectName
description = "Pure Kotlin CSV reader and writer"
inceptionYear = "2019"
url = repoHttpUrl
organization {
name.set("com.jsoizo")
url.set("https://github.com/jsoizo")
}
licenses {
license {
name.set("Apache License 2.0")
url.set("${repoHttpUrl}/blob/master/LICENSE")
}
}
scm {
url.set(repoHttpUrl)
connection.set("scm:git:${repoGitUrl}")
developerConnection.set(repoHttpUrl)
}
developers {
developer {
name.set("jsoizo")
}
}
}
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
kotlin.code.style=official
kotlin.code.style=official
org.gradle.jvmargs=-Xmx4096m
27 changes: 27 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[versions]
kotlin = "2.1.0"
kotlinx-io = "0.6.0"
coroutines = "1.10.1"
maven-publish = "0.30.0"
kover = "0.8.2"
dokka = "2.0.0"
kotest = "5.9.1"

[libraries]
#kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }
kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" }
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-framework-engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" }

[bundles]
kotest = ["kotest-runner-junit5", "kotest-assertions-core"]

[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" }
kotest = { id = "io.kotest.multiplatform", version.ref = "kotest" }
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
19 changes: 12 additions & 7 deletions gradlew
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,10 @@ do
esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -143,12 +140,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi


# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
1 change: 1 addition & 0 deletions gradlew.bat
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

@RequiresOptIn(
message = "This API is experimental. It may be changed in the future without notice.",
110 changes: 110 additions & 0 deletions src/commonMain/kotlin/com/jsoizo/kotlincsv/client/CsvReader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.CsvReaderScope
import com.jsoizo.kotlincsv.dsl.context.ICsvReaderContext
import kotlinx.io.Source
import kotlinx.io.files.Path

interface CsvReader : ICsvReaderContext {
/**
* read csv data as String, and convert into List<List<String>>
*/
fun readAll(data: String): List<List<String>>

/**
* read csv data from a Source, and convert into List<List<String>>.
*/
fun readAll(data: Source): List<List<String>>

/**
* read csv data from a Path, and convert into List<List<String>>
*
* No need to close the [path] when calling this method.
*/
fun readAll(path: Path): List<List<String>>

/**
* read csv data with header, and convert into List<Map<String, String>>
*/
fun readAllWithHeader(data: String): List<Map<String, String>>

/**
* read csv data with a header from a Source, and convert into List<List<String>>.
*/
fun readAllWithHeader(data: Source): List<Map<String, String>>

/**
* read csv data with header, and convert into List<Map<String, String>>
*
* No need to close [path] when calling this method.
*/
fun readAllWithHeader(path: Path): List<Map<String, String>>

/**
* open [source] and execute reading process.
*
* If you want to control read flow precisely, use this method.
* Otherwise, use utility method (e.g. CsvReader.readAll ).
*
* Usage example:
* <pre>
* val data: Sequence<List<String?>> = csvReader().open(source) {
* readAllAsSequence()
* .map { fields -> fields.map { it.trim() } }
* .map { fields -> fields.map { if(it.isBlank()) null else it } }
* }
* </pre>
*/
fun <T> open(source: Source, read: CsvReaderScope.() -> T): T

/**
* open [path] and execute reading process.
*
* If you want to control read flow precisely, use this method.
* Otherwise, use utility method (e.g. CsvReader.readAll ).
*
* Usage example:
* <pre>
* val data: Sequence<List<String?>> = csvReader().open("test.csv") {
* readAllAsSequence()
* .map { fields -> fields.map { it.trim() } }
* .map { fields -> fields.map { if(it.isBlank()) null else it } }
* }
* </pre>
*/
fun <T> open(path: Path, read: CsvReaderScope.() -> T): T

/**
* open [source] and execute reading process on a **suspending** function.
*
* If you want to control read flow precisely, use this method.
* Otherwise, use utility method (e.g. CsvReader.readAll ).
*
* Usage example:
* <pre>
* val data: Sequence<List<String?>> = csvReader().open(source) {
* readAllAsSequence()
* .map { fields -> fields.map { it.trim() } }
* .map { fields -> fields.map { if(it.isBlank()) null else it } }
* }
* </pre>
*/
suspend fun <T> openAsync(source: Source, read: suspend CsvReaderScope.() -> T): T

/**
* open [path] and execute reading process on a **suspending** function.
*
* If you want to control read flow precisely, use this method.
* Otherwise, use utility method (e.g. CsvReader.readAll ).
*
* Usage example:
* <pre>
* val data: Sequence<List<String?>> = csvReader().openAsync("test.csv") {
* readAllAsSequence()
* .map { fields -> fields.map { it.trim() } }
* .map { fields -> fields.map { if(it.isBlank()) null else it } }
* }
* </pre>
*/
suspend fun <T> openAsync(path: Path, read: suspend CsvReaderScope.() -> T): T
}
108 changes: 108 additions & 0 deletions src/commonMain/kotlin/com/jsoizo/kotlincsv/client/CsvReaderImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.CsvReaderScope
import com.jsoizo.kotlincsv.dsl.context.CsvReaderContext
import com.jsoizo.kotlincsv.dsl.context.ICsvReaderContext
import kotlinx.io.Buffer
import kotlinx.io.Source
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem

/**
* CSVReader implementation.
*
* @author gsteckman
*/
internal class CsvReaderImpl(
private val ctx: CsvReaderContext = CsvReaderContext()
) : CsvReader, ICsvReaderContext by ctx {
override fun readAll(data: String): List<List<String>> = data.csvReadAll(ctx)

override fun readAll(data: Source): List<List<String>> = data.csvReadAll(ctx)

override fun readAll(path: Path): List<List<String>> = path.csvReadAll(ctx)

override fun readAllWithHeader(data: String): List<Map<String, String>> =
data.csvReadAllWithHeader(ctx)

override fun readAllWithHeader(data: Source): List<Map<String, String>> = data.csvReadAllWithHeader(ctx)

override fun readAllWithHeader(path: Path): List<Map<String, String>> = path.csvReadAllWithHeader(ctx)

override fun <T> open(source: Source, read: CsvReaderScope.() -> T): T =
SourceCsvReaderScope(ctx, source, ctx.logger).read()

override fun <T> open(path: Path, read: CsvReaderScope.() -> T): T =
SystemFileSystem.source(path).buffered().use {
return open(it, read)
}

override suspend fun <T> openAsync(source: Source, read: suspend CsvReaderScope.() -> T): T =
SourceCsvReaderScope(ctx, source, ctx.logger).read()

override suspend fun <T> openAsync(path: Path, read: suspend CsvReaderScope.() -> T): T =
SystemFileSystem.source(path).buffered().use {
return openAsync(it, read)
}
}

/**
* read csv data as String, and convert into List<List<String>>
*/
fun String.csvReadAll(ctx: CsvReaderContext = CsvReaderContext()): List<List<String>> =
Buffer().apply{ write(encodeToByteArray()) }.use {
it.csvReadAll(ctx)
}

/**
* read csv data with header, and convert into List<Map<String, String>>
*/
fun String.csvReadAllWithHeader(ctx: CsvReaderContext = CsvReaderContext()): List<Map<String, String>> =
Buffer().apply{ write(encodeToByteArray()) }.use {
it.csvReadAllWithHeader(ctx)
}

/**
* read csv data from a Source, and convert into List<List<String>>.
*/
fun Source.csvReadAll(ctx: CsvReaderContext = CsvReaderContext()): List<List<String>> =
SourceCsvReaderScope(ctx, this, ctx.logger).readAllAsSequence().toList()

/**
* read csv data from a Path, and convert into List<List<String>>.
*
* No need to close the Path when calling this method.
*/
fun Path.csvReadAll(ctx: CsvReaderContext = CsvReaderContext()): List<List<String>> =
SystemFileSystem.source(this).buffered().use {
return it.csvReadAll(ctx)
}

/**
* read csv data with a header from a Source, and convert into List<List<String>>.
*/
fun Source.csvReadAllWithHeader(ctx: CsvReaderContext = CsvReaderContext()): List<Map<String, String>> =
SourceCsvReaderScope(ctx, this, ctx.logger).readAllWithHeaderAsSequence().toList()

/**
* read csv data with header, and convert into List<Map<String, String>>
*
* No need to close Path when calling this method.
*/
fun Path.csvReadAllWithHeader(ctx: CsvReaderContext = CsvReaderContext()): List<Map<String, String>> =
SystemFileSystem.source(this).buffered().use {
return it.csvReadAllWithHeader(ctx)
}

/**
* read all csv rows as Sequence
*/
fun Source.csvReadAllAsSequence(fieldsNum: Int? = null, ctx: CsvReaderContext = CsvReaderContext())
: Sequence<List<String>> = SourceCsvReaderScope(ctx, this, ctx.logger).readAllAsSequence(fieldsNum)

/**
* read all csv rows as Sequence with header information
*/
fun Source.csvReadAllWithHeaderAsSequence(ctx: CsvReaderContext = CsvReaderContext())
: Sequence<Map<String, String>> = SourceCsvReaderScope(ctx, this, ctx.logger).readAllWithHeaderAsSequence()
24 changes: 24 additions & 0 deletions src/commonMain/kotlin/com/jsoizo/kotlincsv/client/CsvWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.CsvWriterScope
import com.jsoizo.kotlincsv.dsl.context.ICsvWriterContext
import kotlinx.io.Sink
import kotlinx.io.files.Path

interface CsvWriter : ICsvWriterContext {
fun writeAll(rows: List<List<Any?>>, sink: Sink, append: Boolean = false)

fun writeAll(rows: List<List<Any?>>, path: Path, append: Boolean = false)

suspend fun writeAllAsync(rows: List<List<Any?>>, sink: Sink, append: Boolean = false)

suspend fun writeAllAsync(rows: List<List<Any?>>, path: Path, append: Boolean = false)

fun open(sink: Sink, append: Boolean = false, write: CsvWriterScope.() -> Unit)

fun open(path: Path, append: Boolean = false, write: CsvWriterScope.() -> Unit)

suspend fun openAsync(sink: Sink, append: Boolean = false, write: suspend CsvWriterScope.() -> Unit)

suspend fun openAsync(path: Path, append: Boolean = false, write: suspend CsvWriterScope.() -> Unit)
}
79 changes: 79 additions & 0 deletions src/commonMain/kotlin/com/jsoizo/kotlincsv/client/CsvWriterImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.CsvWriterScope
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext
import com.jsoizo.kotlincsv.dsl.context.ICsvWriterContext
import kotlinx.io.Sink
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem

/**
* CSVWriter implementation, which decides where to write.
*
* @author doyaaaaaken
* @author gsteckman
*/
internal class CsvWriterImpl(private val ctx: CsvWriterContext = CsvWriterContext())
: CsvWriter, ICsvWriterContext by ctx {

override fun open(sink: Sink, append: Boolean, write: CsvWriterScope.() -> Unit) {
SinkCsvWriterScope(ctx, sink).use {
it.write()
}
}

override fun open(path: Path, append: Boolean, write: CsvWriterScope.() -> Unit) {
val sink = SystemFileSystem.sink(path, append).buffered()
open(sink, append, write)
}

override suspend fun openAsync(sink: Sink,
append: Boolean,
write: suspend CsvWriterScope.() -> Unit) {
SinkCsvWriterScope(ctx, sink).useSuspend{
it.write()
}
}

override suspend fun openAsync(path: Path, append: Boolean, write: suspend CsvWriterScope.() -> Unit) {
val sink = SystemFileSystem.sink(path, append).buffered()
openAsync(sink, append, write)
}

/**
* *** ONLY for long-running write case ***
*
* Get and use [SinkCsvWriterScope] directly.
* MUST NOT forget to close [SinkCsvWriterScope] after using it.
*
* Use this method If you want to close file writer manually (i.e. streaming scenario).
*/
@KotlinCsvExperimental
fun openAndGetRawWriter(targetFileName: String, append: Boolean = false): SinkCsvWriterScope {
val sink = SystemFileSystem.sink(Path(targetFileName), append).buffered()
return SinkCsvWriterScope(ctx, sink)
}

/**
* write all rows on assigned target file
*/
override fun writeAll(rows: List<List<Any?>>, sink: Sink, append: Boolean) {
open(sink, append) { writeRows(rows) }
}

override fun writeAll(rows: List<List<Any?>>, path: Path, append: Boolean) {
open(path, append){ writeRows(rows) }
}

/**
* write all rows on assigned target file
*/
override suspend fun writeAllAsync(rows: List<List<Any?>>, sink: Sink, append: Boolean) {
openAsync(sink, append) { writeRows(rows) }
}

override suspend fun writeAllAsync(rows: List<List<Any?>>, path: Path, append: Boolean) {
openAsync(path, append) { writeRows(rows) }
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvWriterContext
import com.github.doyaaaaaken.kotlincsv.dsl.context.WriteQuoteMode
import com.github.doyaaaaaken.kotlincsv.util.Const
import java.io.Closeable
import java.io.Flushable
import java.io.IOException
import java.io.PrintWriter
import com.jsoizo.kotlincsv.dsl.CsvWriterScope
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext
import com.jsoizo.kotlincsv.dsl.context.WriteQuoteMode
import com.jsoizo.kotlincsv.util.Const
import kotlinx.io.Sink
import kotlinx.io.writeString

/**
* CSV Writer class, which controls file I/O flow.
* An implementation of [CsvWriterScope] which writes to a kotlinx.io.Sink. This implementation is not
* thread safe. If [SinkCsvWriterScope] needs to be accessed from multiple threads,
* an additional synchronization is required.
*
* @author doyaaaaaken
* @author gsteckman
*/
class CsvFileWriter internal constructor(
private val ctx: CsvWriterContext,
private val writer: PrintWriter
) : ICsvFileWriter, Closeable, Flushable {
internal class SinkCsvWriterScope internal constructor(private val ctx: CsvWriterContext,
private val writer: Sink) : CsvWriterScope {

private val quoteNeededChars = setOf('\r', '\n', ctx.quote.char, ctx.delimiter)
private val quoteNeededChars = setOf('\r',
'\n',
ctx.quote.char,
ctx.delimiter)

private var hasWroteInitialChar: Boolean = false

@@ -29,10 +32,7 @@ class CsvFileWriter internal constructor(
willWritePreTerminator()
writeNext(row)
willWriteEndTerminator()

if (writer.checkError()) {
throw IOException("Failed to write")
}
writer.flush()
}

/**
@@ -54,9 +54,7 @@ class CsvFileWriter internal constructor(
}
}
willWriteEndTerminator()
if (writer.checkError()) {
throw IOException("Failed to write")
}
writer.flush()
}

/**
@@ -73,9 +71,7 @@ class CsvFileWriter internal constructor(
}

willWriteEndTerminator()
if (writer.checkError()) {
throw IOException("Failed to write")
}
writer.flush()
}

override fun flush() {
@@ -88,7 +84,7 @@ class CsvFileWriter internal constructor(

private fun writeNext(row: List<Any?>) {
if (!hasWroteInitialChar && ctx.prependBOM) {
writer.print(Const.BOM)
writer.writeString(Const.BOM.toString())
}

val rowStr = row.joinToString(ctx.delimiter.toString()) { field ->
@@ -98,7 +94,7 @@ class CsvFileWriter internal constructor(
attachQuote(field.toString())
}
}
writer.print(rowStr)
writer.writeString(rowStr)
hasWroteInitialChar = true
}

@@ -115,7 +111,7 @@ class CsvFileWriter internal constructor(
* write terminator for next line
*/
private fun writeTerminator() {
writer.print(ctx.lineTerminator)
writer.writeString(ctx.lineTerminator)
}

private fun willWriteEndTerminator() {
@@ -156,4 +152,16 @@ class CsvFileWriter internal constructor(
if (shouldQuote) append(ctx.quote.char)
}
}

fun use(block: (SinkCsvWriterScope)->Unit) {
writer.use {
block(this)
}
}

suspend fun useSuspend(block: suspend (SinkCsvWriterScope)->Unit) {
writer.use {
block(this)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext
import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import com.github.doyaaaaaken.kotlincsv.parser.CsvParser
import com.github.doyaaaaaken.kotlincsv.util.CSVAutoRenameFailedException
import com.github.doyaaaaaken.kotlincsv.util.CSVFieldNumDifferentException
import com.github.doyaaaaaken.kotlincsv.util.logger.Logger
import com.github.doyaaaaaken.kotlincsv.util.MalformedCSVException
import com.jsoizo.kotlincsv.dsl.CsvReaderScope
import com.jsoizo.kotlincsv.dsl.context.CsvReaderContext
import com.jsoizo.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.jsoizo.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import com.jsoizo.kotlincsv.parser.CsvParser
import com.jsoizo.kotlincsv.util.CSVAutoRenameFailedException
import com.jsoizo.kotlincsv.util.CSVFieldNumDifferentException
import com.jsoizo.kotlincsv.util.logger.Logger
import com.jsoizo.kotlincsv.util.MalformedCSVException
import kotlinx.io.Source

/**
* CSV Reader class, which controls file I/O flow.
*
* @author doyaaaaaken
*/
class CsvFileReader internal constructor(
internal class SourceCsvReaderScope internal constructor(
private val ctx: CsvReaderContext,
reader: Reader,
source: Source,
private val logger: Logger,
) {

private val reader = BufferedLineReader(reader)
) : CsvReaderScope {
private val reader = SourceLineReader(source)
private var rowNum = 0L

private val parser = CsvParser(ctx.quoteChar, ctx.delimiter, ctx.escapeChar)
@@ -40,7 +41,7 @@ class CsvFileReader internal constructor(
/**
* read all csv rows as Sequence
*/
fun readAllAsSequence(fieldsNum: Int? = null): Sequence<List<String>> {
override fun readAllAsSequence(fieldsNum: Int?): Sequence<List<String>> {
var expectedNumFieldsInRow: Int? = fieldsNum
return generateSequence {
@Suppress("DEPRECATION") readNext()
@@ -60,7 +61,8 @@ class CsvFileReader internal constructor(
throw CSVFieldNumDifferentException(numFieldsInRow, row.size, idx + 1)
}
} else if (numFieldsInRow != row.size) {
if (ctx.skipMissMatchedRow || ctx.insufficientFieldsRowBehaviour == InsufficientFieldsRowBehaviour.IGNORE) {
if (ctx.skipMissMatchedRow ||
ctx.insufficientFieldsRowBehaviour == InsufficientFieldsRowBehaviour.IGNORE) {
skipMismatchedRow(idx, row, numFieldsInRow)
} else if (ctx.insufficientFieldsRowBehaviour == InsufficientFieldsRowBehaviour.EMPTY_STRING) {
val numOfMissingFields = numFieldsInRow - row.size
@@ -86,7 +88,7 @@ class CsvFileReader internal constructor(
/**
* read all csv rows as Sequence with header information
*/
fun readAllWithHeaderAsSequence(): Sequence<Map<String, String>> {
override fun readAllWithHeaderAsSequence(): Sequence<Map<String, String>> {
@Suppress("DEPRECATION")
var headers = readNext() ?: return emptySequence()
if (ctx.autoRenameDuplicateHeaders) {
@@ -98,10 +100,6 @@ class CsvFileReader internal constructor(
return readAllAsSequence(headers.size).map { fields -> headers.zip(fields).toMap() }
}

fun close() {
reader.close()
}

/**
* read next csv row (which may contain multiple lines)
*
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.util.Const
import com.jsoizo.kotlincsv.util.Const
import kotlinx.io.EOFException
import kotlinx.io.Source
import kotlinx.io.readCodePointValue

/**
* buffered reader which can read line with line terminator
* reader from a [Source] which can read line with line terminator.
*/
internal class BufferedLineReader(
private val br: Reader
) {
internal class SourceLineReader(private val br: Source) {
companion object {
private const val BOM = Const.BOM
}
@@ -18,9 +19,9 @@ internal class BufferedLineReader(
fun readLineWithTerminator(): String? {
val sb = StringBuilder()
do {
val c = br.read()

if (c == -1) {
val c = try{
br.readCodePointValue()
} catch(_: EOFException){
if (sb.isEmptyLine()) {
return null
} else {
@@ -35,23 +36,21 @@ internal class BufferedLineReader(
}

if (ch == '\r') {
br.mark(1)
val c2 = br.read()
if (c2 == -1) {
val s2 = br.peek()

val c2 = try {
s2.readCodePointValue()
} catch(_: EOFException){
break
} else if (c2.toChar() == '\n') {
sb.append('\n')
} else {
br.reset()
}

if (c2.toChar() == '\n') {
sb.append('\n')
br.readCodePointValue()
}
break
}
} while (true)
return sb.toString()
}

fun close() {
br.close()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.github.doyaaaaaken.kotlincsv.dsl
package com.jsoizo.kotlincsv.dsl

import com.github.doyaaaaaken.kotlincsv.client.CsvReader
import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext
import com.jsoizo.kotlincsv.client.CsvReader
import com.jsoizo.kotlincsv.client.CsvReaderImpl
import com.jsoizo.kotlincsv.dsl.context.CsvReaderContext

/**
* DSL Method which provides `CsvReader`
@@ -31,5 +32,5 @@ import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext
*/
fun csvReader(init: CsvReaderContext.() -> Unit = {}): CsvReader {
val context = CsvReaderContext().apply(init)
return CsvReader(context)
return CsvReaderImpl(context)
}
13 changes: 13 additions & 0 deletions src/commonMain/kotlin/com/jsoizo/kotlincsv/dsl/CsvReaderScope.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.jsoizo.kotlincsv.dsl

interface CsvReaderScope {
/**
* read all csv rows as Sequence
*/
fun readAllAsSequence(fieldsNum: Int? = null): Sequence<List<String>>

/**
* read all csv rows as Sequence with header information
*/
fun readAllWithHeaderAsSequence(): Sequence<Map<String, String>>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.github.doyaaaaaken.kotlincsv.dsl
package com.jsoizo.kotlincsv.dsl

import com.github.doyaaaaaken.kotlincsv.client.CsvWriter
import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvWriterContext
import com.jsoizo.kotlincsv.client.CsvWriter
import com.jsoizo.kotlincsv.client.CsvWriterImpl
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext

/**
* DSL Method which provides `CsvWriter`
@@ -30,5 +31,5 @@ import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvWriterContext
*/
fun csvWriter(init: CsvWriterContext.() -> Unit = {}): CsvWriter {
val context = CsvWriterContext().apply(init)
return CsvWriter(context)
return CsvWriterImpl(context)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.dsl

interface ICsvFileWriter {
interface CsvWriterScope {
fun writeRow(row: List<Any?>)

fun writeRow(vararg entry: Any?)
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.github.doyaaaaaken.kotlincsv.dsl.context
package com.jsoizo.kotlincsv.dsl.context

import com.github.doyaaaaaken.kotlincsv.util.Const
import com.github.doyaaaaaken.kotlincsv.util.CsvDslMarker
import com.github.doyaaaaaken.kotlincsv.util.logger.Logger
import com.github.doyaaaaaken.kotlincsv.util.logger.LoggerNop
import com.jsoizo.kotlincsv.util.Const
import com.jsoizo.kotlincsv.util.CsvDslMarker
import com.jsoizo.kotlincsv.util.logger.Logger
import com.jsoizo.kotlincsv.util.logger.LoggerNop

/**
* Interface for CSV Reader settings
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.doyaaaaaken.kotlincsv.dsl.context
package com.jsoizo.kotlincsv.dsl.context

import com.github.doyaaaaaken.kotlincsv.util.CsvDslMarker
import com.jsoizo.kotlincsv.util.CsvDslMarker

/**
* DSL method for Quote settings on writing csv.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.github.doyaaaaaken.kotlincsv.dsl.context
package com.jsoizo.kotlincsv.dsl.context

import com.github.doyaaaaaken.kotlincsv.util.Const
import com.github.doyaaaaaken.kotlincsv.util.CsvDslMarker
import com.jsoizo.kotlincsv.util.CsvDslMarker

/**
* Interface for CSV Writer settings
@@ -10,17 +9,6 @@ import com.github.doyaaaaaken.kotlincsv.util.CsvDslMarker
*/
@CsvDslMarker
interface ICsvWriterContext {
/**
* Charset encoding
*
* The name must be supported by [java.nio.charset.Charset].
*
* ex.)
* "UTF-8"
* "SJIS"
*/
val charset: String

/**
* Character used as delimiter between each fields
*
@@ -76,7 +64,6 @@ interface ICsvWriterContext {
*/
@CsvDslMarker
class CsvWriterContext : ICsvWriterContext {
override var charset = Const.defaultCharset
override var delimiter: Char = ','
override var nullCode: String = ""
override var lineTerminator: String = "\r\n"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.parser
package com.jsoizo.kotlincsv.parser

/**
* Csv Parse logic while reading csv
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.doyaaaaaken.kotlincsv.parser
package com.jsoizo.kotlincsv.parser

import com.github.doyaaaaaken.kotlincsv.util.CSVParseFormatException
import com.github.doyaaaaaken.kotlincsv.util.Const
import com.jsoizo.kotlincsv.util.CSVParseFormatException
import com.jsoizo.kotlincsv.util.Const

/**
* @author doyaaaaaaken
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.util
package com.jsoizo.kotlincsv.util

/**
* General purpose Exception
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.util
package com.jsoizo.kotlincsv.util

/**
* Constant variables used in this project
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.util
package com.jsoizo.kotlincsv.util

/**
* @author doyaaaaaken
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.github.doyaaaaaken.kotlincsv.util.logger
package com.jsoizo.kotlincsv.util.logger

/**
* Logger interface for logging debug statements at runtime.
* Library consumers may provide implementations suiting their needs.
* @see [com.github.doyaaaaaken.kotlincsv.dsl.context.ICsvReaderContext.logger]
* @see [com.jsoizo.kotlincsv.dsl.context.ICsvReaderContext.logger]
*/
interface Logger {
fun info(message: String)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.doyaaaaaken.kotlincsv.util.logger
package com.jsoizo.kotlincsv.util.logger

/**
* Internal no-operation logger implementation, which does not log anything.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.context.CsvReaderContext
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import kotlinx.io.Buffer

class CsvReadWriteCompatibilityTest : StringSpec({
"CSVReader and CSVWriter are compatible" {
val data = listOf(
listOf("a", "bb", "ccc"),
listOf("d", "ee", "fff")
)
val buffer = Buffer()
SinkCsvWriterScope(CsvWriterContext(), buffer).writeRows(data)
val rctx = CsvReaderContext()
val actual = SourceCsvReaderScope(rctx, buffer, rctx.logger).readAllAsSequence().toList()
actual shouldBe data
}
})
104 changes: 104 additions & 0 deletions src/commonTest/kotlin/com/jsoizo/kotlincsv/client/CsvReaderImplTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.context.CsvReaderContext
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import kotlinx.io.Buffer

class CsvReaderImplTest : StringSpec({
"CsvReader class constructor" should {
"be created with CsvReaderContext argument" {
val context = CsvReaderContext().apply {
quoteChar = '\''
delimiter = '\t'
escapeChar = '"'
skipEmptyLine = true
}
val reader = CsvReaderImpl(context)
assertSoftly {
reader.quoteChar shouldBe '\''
reader.delimiter shouldBe '\t'
reader.escapeChar shouldBe '"'
reader.skipEmptyLine shouldBe true
}
}
}

"readAll method (with String argument)" should {
"read simple csv" {
val result = CsvReaderImpl().readAll(
"""a,b,c
|d,e,f
""".trimMargin()
)
result shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"read simple csv as receiver" {
val result = """a,b,c
|d,e,f
""".trimMargin().csvReadAll()
result shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"read simple csv as Source" {
Buffer().apply {
write("""a,b,c
|d,e,f
""".trimMargin().encodeToByteArray())
}.use {
CsvReaderImpl().readAll(it) shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
}
"read simple csv as Source receiver" {
Buffer().apply {
write("""a,b,c
|d,e,f
""".trimMargin().encodeToByteArray())
}.use {
it.csvReadAll() shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
}
"read csv with line separator" {
val result = CsvReaderImpl().readAll(
"""a,b,c,"x","y
| hoge"
|d,e,f,g,h
""".trimMargin()
)
val firstRow = listOf(
"a", "b", "c", "x", """y
| hoge""".trimMargin()
)
val secondRow = listOf("d", "e", "f", "g", "h")
result shouldBe listOf(firstRow, secondRow)
}
"get failed rowNum and colIndex when exception happened on parsing CSV" {
val reader = CsvReaderImpl()
val ex1 = shouldThrow<com.jsoizo.kotlincsv.util.CSVParseFormatException> {
reader.readAll("a,\"\"failed")
}
val ex2 = shouldThrow<com.jsoizo.kotlincsv.util.CSVParseFormatException> {
reader.readAll("a,b\nc,\"\"failed")
}
val ex3 = shouldThrow<com.jsoizo.kotlincsv.util.CSVParseFormatException> {
reader.readAll("a,\"b\nb\"\nc,\"\"failed")
}

assertSoftly {
ex1.rowNum shouldBe 1
ex1.colIndex shouldBe 4
ex1.char shouldBe 'f'

ex2.rowNum shouldBe 2
ex2.colIndex shouldBe 4
ex2.char shouldBe 'f'

ex3.rowNum shouldBe 3
ex3.colIndex shouldBe 4
ex3.char shouldBe 'f'
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.CsvWriterScope
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.io.Buffer
import kotlinx.io.readString

class SinkCsvWriterScopeTest : StringSpec({
fun writeToBuffer(write: CsvWriterScope.() -> Unit) : Buffer {
val ctx = CsvWriterContext()
val buffer = Buffer()
SinkCsvWriterScope(ctx, buffer).apply { write() }.close()
return buffer
}

"writeRow method should write any primitive types" {
val row = listOf("String", 'C', 1, 2L, 3.45, true, null)
val expected = "String,C,1,2,3.45,true,\r\n"

writeToBuffer { writeRow(row) }.readString() shouldBe expected
}
"writeRow method should write kotlinx.datetime.LocalDate and kotlinx.datetime.LocalDateTime types" {
val row = listOf(
LocalDate(2019, 8, 19),
LocalDateTime(2020, 9, 20, 14, 32, 21)
)
val expected = "2019-08-19,2020-09-20T14:32:21\r\n"
writeToBuffer { writeRow(row) }.readString() shouldBe expected
}
"writeRow method should write row from variable arguments" {
val date1 = LocalDate(2019, 8, 19)
val date2 = LocalDateTime(2020, 9, 20, 14, 32, 21)

val expected = "a,b,c\r\n" +
"d,e,f\r\n" +
"1,2,3\r\n" +
"2019-08-19,2020-09-20T14:32:21\r\n"
writeToBuffer {
writeRow("a", "b", "c")
writeRow("d", "e", "f")
writeRow(1, 2, 3)
writeRow(date1, date2)
}.readString() shouldBe expected
}
"writeAll method should write Sequence data" {
val rows = listOf(listOf("a", "b", "c"), listOf("d", "e", "f")).asSequence()
val expected = "a,b,c\r\nd,e,f\r\n"
writeToBuffer {
writeRows(rows)
}.readString() shouldBe expected
}
"writeAll method should write escaped field when a field contains quoteChar in it" {
val rows = listOf(listOf("a", "\"b", "c"), listOf("d", "e", "f\""))
val expected = "a,\"\"\"b\",c\r\nd,e,\"f\"\"\"\r\n"
writeToBuffer{
writeRows(rows)
}.readString() shouldBe expected
}
"writeAll method should write escaped field when a field contains delimiter in it" {
val rows = listOf(listOf("a", ",b", "c"), listOf("d", "e", "f,"))
val expected = "a,\",b\",c\r\nd,e,\"f,\"\r\n"
writeToBuffer{
writeRows(rows)
}.readString() shouldBe expected
}
"writeAll method should write quoted field when a field contains cr or lf in it" {
val rows = listOf(listOf("a", "\nb", "c"), listOf("d", "e", "f\r\n"))
val expected = "a,\"\nb\",c\r\nd,e,\"f\r\n\"\r\n"
writeToBuffer{
writeRows(rows)
}.readString() shouldBe expected
}
"writeAll method should write no line terminator when row is empty for rows from list" {
val rows = listOf(listOf("a", "b", "c"), listOf(), listOf("d", "e", "f"))
val expected = "a,b,c\r\nd,e,f\r\n"
writeToBuffer{
writeRows(rows)
}.readString() shouldBe expected
}
"writeAll method should write no line terminator when row is empty for rows from sequence" {
val rows = listOf(listOf("a", "b", "c"), listOf(), listOf("d", "e", "f")).asSequence()
val expected = "a,b,c\r\nd,e,f\r\n"
writeToBuffer{
writeRows(rows)
}.readString() shouldBe expected
}

"flush method should flush stream" {
val row = listOf("a", "b")
val ctx = CsvWriterContext()
val buffer = Buffer()
SinkCsvWriterScope(ctx, buffer).apply {
writeRow(row)
flush()
buffer.readString() shouldBe "a,b\r\n"
}
}
})
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import kotlinx.io.Buffer

class BufferedLineReaderTest : StringSpec({
class SourceLineReaderTest : StringSpec({
"regard \\n as line terminator" {
val str = "a,b,c\nd,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\n"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -18,8 +19,8 @@ class BufferedLineReaderTest : StringSpec({

"regard \\r\\n as line terminator" {
val str = "a,b,c\r\nd,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\r\n"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -29,8 +30,8 @@ class BufferedLineReaderTest : StringSpec({

"regard \\r as line terminator" {
val str = "a,b,c\rd,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\r"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -40,8 +41,8 @@ class BufferedLineReaderTest : StringSpec({

"regard \\u2028 as line terminator" {
val str = "a,b,c\u2028d,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\u2028"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -51,8 +52,8 @@ class BufferedLineReaderTest : StringSpec({

"regard \\u2029 as line terminator" {
val str = "a,b,c\u2029d,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\u2029"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -62,8 +63,8 @@ class BufferedLineReaderTest : StringSpec({

"regard \\u0085 as line terminator" {
val str = "a,b,c\u0085d,e,f"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\u0085"
blr.readLineWithTerminator() shouldBe "d,e,f"
@@ -73,8 +74,8 @@ class BufferedLineReaderTest : StringSpec({

"deal with \\r at the end of file" {
val str = "a,b,c\r"
val br = str.byteInputStream().bufferedReader()
val blr = BufferedLineReader(br)
val br = Buffer().apply{ write(str.encodeToByteArray()) }
val blr = SourceLineReader(br)
assertSoftly {
blr.readLineWithTerminator() shouldBe "a,b,c\r"
blr.readLineWithTerminator() shouldBe null

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
package com.github.doyaaaaaken.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext
import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import com.github.doyaaaaaken.kotlincsv.util.CSVFieldNumDifferentException
import com.github.doyaaaaaken.kotlincsv.util.CSVParseFormatException
import com.github.doyaaaaaken.kotlincsv.util.Const
import com.github.doyaaaaaken.kotlincsv.util.MalformedCSVException
package com.jsoizo.kotlincsv.client

import com.jsoizo.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.jsoizo.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import com.jsoizo.kotlincsv.dsl.csvReader
import com.jsoizo.kotlincsv.util.CSVFieldNumDifferentException
import com.jsoizo.kotlincsv.util.CSVParseFormatException
import com.jsoizo.kotlincsv.util.MalformedCSVException
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import java.io.File

class CsvReaderTest : WordSpec({
"CsvReader class constructor" should {
"be created with no argument" {
val reader = CsvReader()
reader.charset shouldBe Const.defaultCharset
}
"be created with CsvReaderContext argument" {
val context = CsvReaderContext().apply {
charset = Charsets.ISO_8859_1.name()
quoteChar = '\''
delimiter = '\t'
escapeChar = '"'
skipEmptyLine = true
}
val reader = CsvReader(context)
assertSoftly {
reader.charset shouldBe Charsets.ISO_8859_1.name()
reader.quoteChar shouldBe '\''
reader.delimiter shouldBe '\t'
reader.escapeChar shouldBe '"'
reader.skipEmptyLine shouldBe true
}
}
}

import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readString

/**
* This class is not in commonTest because reading Path(s) from files is not supported on all
* platforms.
*/
class CsvReaderTest : StringSpec({
"readAll method (with String argument)" should {
"read simple csv" {
val result = csvReader().readAll(
@@ -91,18 +72,13 @@ class CsvReaderTest : WordSpec({
}
}
}

"readAll method (with InputStream argument)" should {
"readAll method (with Path argument)" should {
"read simple csv" {
val file = readTestDataFile("simple.csv")
val result = csvReader().readAll(file.inputStream())
val result = csvReader().readAll(readTestDataFile("simple.csv"))
result shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
}

"readAll method (with File argument)" should {
"read simple csv" {
val result = csvReader().readAll(readTestDataFile("simple.csv"))
"read simple csv as receiver" {
val result = readTestDataFile("simple.csv").csvReadAll()
result shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"read tsv file" {
@@ -325,7 +301,29 @@ class CsvReaderTest : WordSpec({
val result = csvReader().readAllWithHeader(file)
result shouldBe expected
}

"read simple csv file as receiver" {
val file = readTestDataFile("with-header.csv")
val result = file.csvReadAllWithHeader()
result shouldBe expected
}
"read simple csv as Source" {
val src = SystemFileSystem.source(readTestDataFile("with-header.csv")).buffered()
src.use {
CsvReaderImpl().readAllWithHeader(it) shouldBe expected
}
}
"read simple csv as String receiver" {
val src = SystemFileSystem.source(readTestDataFile("with-header.csv")).buffered()
src.use {
it.readString().csvReadAllWithHeader() shouldBe expected
}
}
"read simple csv as Source receiver" {
val src = SystemFileSystem.source(readTestDataFile("with-header.csv")).buffered()
src.use {
it.csvReadAllWithHeader() shouldBe expected
}
}
"throw on duplicated headers" {
val file = readTestDataFile("with-duplicate-header.csv")
shouldThrow<MalformedCSVException> { csvReader().readAllWithHeader(file) }
@@ -356,12 +354,6 @@ class CsvReaderTest : WordSpec({
result shouldBe expected
}

"read from InputStream" {
val file = readTestDataFile("with-header.csv")
val result = csvReader().readAllWithHeader(file.inputStream())
result shouldBe expected
}

"read from String containing line break" {
val data = """h1,"h
|2",h3
@@ -382,53 +374,22 @@ class CsvReaderTest : WordSpec({
}
}

"open method (with fileName argument)" should {
val rows = csvReader().open("src/jvmTest/resources/testdata/csv/simple.csv") {
val row1 = readNext()
val row2 = readNext()
listOf(row1, row2)
"open method (with Path argument)" should {
val rows = csvReader().open(readTestDataFile("simple.csv")) {
readAllAsSequence().toList()
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}

"open method (with InputStream argument)" should {
val file = readTestDataFile("simple.csv")
val rows = csvReader().open(file.inputStream()) {
val row1 = readNext()
val row2 = readNext()
listOf(row1, row2)
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"execute as suspending function" should {
"open suspending method (with fileName argument)" {
val rows = csvReader().openAsync("src/jvmTest/resources/testdata/csv/simple.csv") {
val row1 = readNext()
val row2 = readNext()
listOf(row1, row2)
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"open suspending method (with file argument)" {
val file = readTestDataFile("simple.csv")
val rows = csvReader().openAsync(file) {
val row1 = readNext()
val row2 = readNext()
listOf(row1, row2)
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"open suspending method (with InputStream argument)" {
val fileStream = readTestDataFile("simple.csv").inputStream()
val rows = csvReader().openAsync(fileStream) {
val row1 = readNext()
val row2 = readNext()
listOf(row1, row2)
"open suspending method (with Path argument)" {
val rows = csvReader().openAsync(readTestDataFile("simple.csv")) {
readAllAsSequence().toList()
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
"validate test as flow" {
val fileStream = readTestDataFile("simple.csv").inputStream()
val fileStream = readTestDataFile("simple.csv")
val rows = mutableListOf<List<String>>()
csvReader().openAsync(fileStream) {
readAllAsSequence().asFlow().collect {
@@ -438,8 +399,28 @@ class CsvReaderTest : WordSpec({
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}
}

"csvReadAllAsSequence method (with Source receiver)" should {
val rows = SystemFileSystem.source(readTestDataFile("simple.csv")).buffered().use{
it.csvReadAllAsSequence().toList()
}
rows shouldBe listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
}

"csvReadAllAsSequenceWithHeader (with Source receiver)" should {
val expected = listOf(
mapOf("h1" to "a", "h2" to "b", "h3" to "c"),
mapOf("h1" to "d", "h2" to "e", "h3" to "f")
)
val result = SystemFileSystem.source(readTestDataFile("with-header.csv")).buffered().use{
it.csvReadAllWithHeaderAsSequence().toList()
}
result shouldBe expected
}
})

private fun readTestDataFile(fileName: String): File {
return File("src/jvmTest/resources/testdata/csv/$fileName")
private fun readTestDataFile(fileName: String): Path {
val path = Path("src", "jvmTest", "resources", "testdata", "csv", fileName)
println("Made path: $path")
return path
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
package com.github.doyaaaaaken.kotlincsv.client
package com.jsoizo.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvWriterContext
import com.github.doyaaaaaken.kotlincsv.dsl.context.WriteQuoteMode
import com.github.doyaaaaaken.kotlincsv.dsl.csvWriter
import com.github.doyaaaaaken.kotlincsv.util.Const
import com.jsoizo.kotlincsv.dsl.context.CsvWriterContext
import com.jsoizo.kotlincsv.dsl.context.WriteQuoteMode
import com.jsoizo.kotlincsv.dsl.csvWriter
import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.delay
import kotlinx.io.Buffer
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readString
import java.io.File
import java.nio.charset.Charset

import java.time.LocalDate
import java.time.LocalDateTime

class CsvWriterTest : WordSpec({

val testFileName = "test.csv"

afterTest { File(testFileName).delete() }
afterTest {
// afterTest getting called more than once and kotlinx-io throws exception if trying to
// delete non-existent file
Path(testFileName).also {
if(SystemFileSystem.exists(it)) {
SystemFileSystem.delete(it)
}
}
}

fun readTestFile(charset: Charset = Charsets.UTF_8): String {
return File(testFileName).readText(charset)
}

"CsvWriter class constructor" should {
"be created with no argument" {
val writer = CsvWriter()
writer.charset shouldBe Const.defaultCharset
}
"be created with CsvWriterContext argument" {
val context = CsvWriterContext().apply {
charset = Charsets.ISO_8859_1.name()
delimiter = '\t'
nullCode = "NULL"
lineTerminator = "\n"
@@ -39,9 +47,8 @@ class CsvWriterTest : WordSpec({
mode = WriteQuoteMode.ALL
}
}
val writer = CsvWriter(context)
val writer = CsvWriterImpl(context)
assertSoftly {
writer.charset shouldBe Charsets.ISO_8859_1.name()
writer.delimiter shouldBe '\t'
writer.nullCode shouldBe "NULL"
writer.lineTerminator shouldBe "\n"
@@ -59,26 +66,27 @@ class CsvWriterTest : WordSpec({
val expected = "a,b,\r\nd,2,1.0\r\n"

"write simple csv data into file with writing each rows" {
csvWriter().open(testFileName) {
val buffer = Buffer()
csvWriter().open(buffer) {
writeRow(row1)
writeRow(row2)
}
val actual = readTestFile()
val actual = buffer.readString()
actual shouldBe expected
}

"write simple csv data into file with writing all at one time" {
csvWriter().open(testFileName) { writeRows(listOf(row1, row2)) }
csvWriter().open(Path(testFileName)) { writeRows(listOf(row1, row2)) }
val actual = readTestFile()
actual shouldBe expected
}

"write simple csv data to the tail of existing file with append = true" {
val writer = csvWriter()
writer.open(File(testFileName), true) {
writer.open(Path(testFileName), true) {
writeRows(listOf(row1, row2))
}
writer.open(File(testFileName), true) {
writer.open(Path(testFileName), true) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -87,76 +95,61 @@ class CsvWriterTest : WordSpec({

"overwrite simple csv data with append = false" {
val writer = csvWriter()
writer.open(File(testFileName), false) {
writer.open(Path(testFileName), false) {
writeRows(listOf(row2, row2, row2))
}
writer.open(File(testFileName), false) {
writer.open(Path(testFileName), false) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
actual shouldBe expected
}
}

"writeAsString method" should {
val row1 = listOf("a", "b", null)
val row2 = listOf("d", "2", "1.0")
val expected = "a,b,\r\nd,2,1.0\r\n"

"write simple csv data to String" {
val actual = csvWriter().writeAsString {
writeRow(row1)
writeRow(row2)
}
actual shouldBe expected
}
}

"writeAll method without calling `open` method" should {
val rows = listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
val expected = "a,b,c\r\nd,e,f\r\n"

"write data with target file name" {
csvWriter().writeAll(rows, testFileName)
csvWriter().writeAll(rows, Path(testFileName))
val actual = readTestFile()
actual shouldBe expected
}

"write data with target file (java.io.File)" {
csvWriter().writeAll(rows, File(testFileName))
val actual = readTestFile()
"write data to Sink" {
val buffer = Buffer()
csvWriter().writeAll(rows, buffer)
val actual = buffer.readString()
actual shouldBe expected
}
}

"write data with target output stream (java.io.OutputStream)" {
csvWriter().writeAll(rows, File(testFileName).outputStream())
"writeAllAsync method without calling `open` method" should {
val rows = listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
val expected = "a,b,c\r\nd,e,f\r\n"

"write data with target file name" {
csvWriter().writeAllAsync(rows, Path(testFileName))
val actual = readTestFile()
actual shouldBe expected
}

"write data to String" {
val actual = csvWriter().writeAllAsString(rows)
"write data to Sink" {
val buffer = Buffer()
csvWriter().writeAllAsync(rows, buffer)
val actual = buffer.readString()
actual shouldBe expected
}
}

"Customized CsvWriter" should {
"write csv with SJIS charset" {
csvWriter {
charset = "SJIS"
}.open(File(testFileName)) {
writeRows(listOf(listOf("あ", "い")))
}
val actual = readTestFile(Charset.forName("SJIS"))
actual shouldBe "あ,い\r\n"
}
"write csv with '|' delimiter" {
val row1 = listOf("a", "b")
val row2 = listOf("c", "d")
val expected = "a|b\r\nc|d\r\n"
csvWriter {
delimiter = '|'
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -166,7 +159,7 @@ class CsvWriterTest : WordSpec({
val row = listOf(null, null)
csvWriter {
nullCode = "NULL"
}.open(testFileName) {
}.open(Path(testFileName)) {
writeRow(row)
}
val actual = readTestFile()
@@ -178,7 +171,7 @@ class CsvWriterTest : WordSpec({
val expected = "a,b\nc,d\n"
csvWriter {
lineTerminator = "\n"
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -192,7 +185,7 @@ class CsvWriterTest : WordSpec({
quote {
mode = WriteQuoteMode.ALL
}
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -206,7 +199,7 @@ class CsvWriterTest : WordSpec({
quote {
mode = WriteQuoteMode.NON_NUMERIC
}
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -220,7 +213,7 @@ class CsvWriterTest : WordSpec({
quote {
char = '\''
}
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -234,7 +227,7 @@ class CsvWriterTest : WordSpec({
mode = WriteQuoteMode.ALL
char = '_'
}
}.writeAll(rows, testFileName)
}.writeAll(rows, Path(testFileName))
val actual = readTestFile()
actual shouldBe expected
}
@@ -245,7 +238,7 @@ class CsvWriterTest : WordSpec({
csvWriter {
lineTerminator = "\n"
outputLastLineTerminator = false
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -258,7 +251,7 @@ class CsvWriterTest : WordSpec({
csvWriter {
lineTerminator = "\n"
outputLastLineTerminator = true
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -270,7 +263,7 @@ class CsvWriterTest : WordSpec({
val expected = "a,b\r\nc,d"
csvWriter {
outputLastLineTerminator = false
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -282,7 +275,7 @@ class CsvWriterTest : WordSpec({
val expected = "\uFEFFa,b\r\nc,d\r\n"
csvWriter {
prependBOM = true
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
@@ -298,7 +291,7 @@ class CsvWriterTest : WordSpec({
val expected = "a,b\r\nc,d\r\ne,f\r\ng,h\r\n1,2\r\n3,4"
csvWriter {
outputLastLineTerminator = false
}.open(File(testFileName)) {
}.open(Path(testFileName)) {
writeRow(row1)
writeRows(listOf(row2, row3))
writeRow(row4)
@@ -316,35 +309,85 @@ class CsvWriterTest : WordSpec({

"get raw writer from fileName string and can use it" {
@OptIn(KotlinCsvExperimental::class)
val writer = csvWriter().openAndGetRawWriter(testFileName)
val writer = CsvWriterImpl().openAndGetRawWriter(testFileName)
writer.writeRow(row1)
writer.writeRow(row2)
writer.close()

val actual = readTestFile()
actual shouldBe expected
}
}

"get raw writer from java.io.File and can use it" {
@OptIn(KotlinCsvExperimental::class)
val writer = csvWriter().openAndGetRawWriter(File(testFileName))
writer.writeRow(row1)
writer.writeRow(row2)
writer.close()

"suspend writeRow method" should {
"suspend write any primitive types to Path" {
val row = listOf("String", 'C', 1, 2L, 3.45, true, null)
val expected = "String,C,1,2,3.45,true,\r\n"
csvWriter().openAsync(Path(testFileName)) {
writeRow(row)
}
val actual = readTestFile()
actual shouldBe expected
}
"suspend write any primitive types to Sink" {
val row = listOf("String", 'C', 1, 2L, 3.45, true, null)
val expected = "String,C,1,2,3.45,true,\r\n"
val buffer = Buffer()
csvWriter().openAsync(buffer) {
writeRow(row)
}
val actual = buffer.readString()
actual shouldBe expected
}
"suspend write row from variable arguments" {
val date1 = LocalDate.of(2019, 8, 19)
val date2 = LocalDateTime.of(2020, 9, 20, 14, 32, 21)

"get raw writer from OutputStream and can use it" {
val ops = File(testFileName).outputStream()

@OptIn(KotlinCsvExperimental::class)
val writer = csvWriter().openAndGetRawWriter(ops)
writer.writeRow(row1)
writer.writeRow(row2)
writer.close()

val expected = "a,b,c\r\n" +
"d,e,f\r\n" +
"1,2,3\r\n" +
"2019-08-19,2020-09-20T14:32:21\r\n"
csvWriter().openAsync(Path(testFileName)) {
writeRow("a", "b", "c")
writeRow("d", "e", "f")
writeRow(1, 2, 3)
writeRow(date1, date2)
}
val actual = readTestFile()
actual shouldBe expected
}
"suspend write all Sequence data" {
val rows = listOf(listOf("a", "b", "c"), listOf("d", "e", "f")).asSequence()
val expected = "a,b,c\r\nd,e,f\r\n"
csvWriter().openAsync(Path(testFileName)) {
writeRows(rows)
}
val actual = readTestFile()
actual shouldBe expected
}
}
"suspend flush method" should {
"flush stream" {
val row = listOf("a", "b")
csvWriter().openAsync(Path(testFileName)) {
writeRow(row)
flush()
val actual = readTestFile()
actual shouldBe "a,b\r\n"
}
}
}
"validate suspend test as flow" should {
"execute line" {
val rows = listOf(listOf("a", "b", "c"), listOf("d", "e", "f")).asSequence()
val expected = "a,b,c\r\nd,e,f\r\n"
csvWriter().openAsync(Path(testFileName)) {
delay(100)
rows.forEach {
delay(100)
writeRow(it)
}
}
val actual = readTestFile()
actual shouldBe expected
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.doyaaaaaken.kotlincsv.dsl
package com.jsoizo.kotlincsv.dsl

import com.github.doyaaaaaken.kotlincsv.client.CsvReader
import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import com.jsoizo.kotlincsv.client.CsvReaderImpl
import com.jsoizo.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
import com.jsoizo.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
@@ -14,7 +14,7 @@ import io.kotest.matchers.types.shouldBeTypeOf
class CsvReaderDslTest : StringSpec({
"csvReader method should work as global method with no argument" {
val reader = csvReader()
reader.shouldBeTypeOf<CsvReader>()
reader.shouldBeTypeOf<CsvReaderImpl>()
}
"csvReader method should work as dsl" {
val reader = csvReader {
@@ -28,7 +28,6 @@ class CsvReaderDslTest : StringSpec({
excessFieldsRowBehaviour = ExcessFieldsRowBehaviour.IGNORE
}
assertSoftly {
reader.charset shouldBe Charsets.ISO_8859_1.name()
reader.quoteChar shouldBe '\''
reader.delimiter shouldBe '\t'
reader.skipEmptyLine shouldBe true
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.doyaaaaaken.kotlincsv.dsl
package com.jsoizo.kotlincsv.dsl

import com.github.doyaaaaaken.kotlincsv.client.CsvWriter
import com.github.doyaaaaaken.kotlincsv.dsl.context.WriteQuoteMode
import com.jsoizo.kotlincsv.client.CsvWriterImpl
import com.jsoizo.kotlincsv.dsl.context.WriteQuoteMode
import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
@@ -13,11 +13,10 @@ import io.kotest.matchers.types.shouldBeTypeOf
class CsvWriterDslTest : StringSpec({
"csvWriter method should work as global method with no argument" {
val writer = csvWriter()
writer.shouldBeTypeOf<CsvWriter>()
writer.shouldBeTypeOf<CsvWriterImpl>()
}
"csvWriter method should work as dsl" {
val writer = csvWriter {
charset = Charsets.ISO_8859_1.name()
delimiter = '\t'
nullCode = "NULL"
lineTerminator = "\n"
@@ -29,7 +28,6 @@ class CsvWriterDslTest : StringSpec({
}
}
assertSoftly {
writer.charset shouldBe Charsets.ISO_8859_1.name()
writer.delimiter shouldBe '\t'
writer.nullCode shouldBe "NULL"
writer.lineTerminator shouldBe "\n"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.doyaaaaaken.kotlincsv.parser
package com.jsoizo.kotlincsv.parser

import com.github.doyaaaaaken.kotlincsv.util.CSVParseFormatException
import com.jsoizo.kotlincsv.util.CSVParseFormatException
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec