Scala implementation of the NSV (Newline-Separated Values) format.
Maven Central publishing is planned. For now, the package is available via GitHub Packages, which requires a GitHub account and a personal access token with read:packages scope.
resolvers += "GitHub nsv-format" at "https://maven.pkg.github.com/nsv-format/nsv-scala"
credentials += Credentials(
"GitHub Package Registry", "maven.pkg.github.com", "_", sys.env("GITHUB_TOKEN")
)
libraryDependencies += "org.nsv-format" %% "nsv-scala" % "0.3.0"import org.nsvformat.Nsv
// Decode from string
val data = Nsv.decode("a\nb\nc\n\nd\ne\nf\n\n")
// Seq(Seq("a", "b", "c"), Seq("d", "e", "f"))
// Encode to string
val encoded = Nsv.encode(Seq(Seq("a", "b"), Seq("c", "d")))
// "a\nb\n\nc\nd\n\n"// Escape individual cells
Nsv.escape("hello\nworld") // "hello\\nworld"
Nsv.escape("") // "\\"
// Unescape
Nsv.unescape("hello\\nworld") // "hello\nworld"
Nsv.unescape("\\") // ""import org.nsvformat.Reader
import java.io.File
// From file
val reader = Reader.fromFile(new File("data.nsv"))
for (row <- reader) {
println(row) // Each row is Seq[String]
}
// From any java.io.Reader
val reader = new Reader(someJavaReader)Resumable semantics: Reader preserves partial state across EOF, enabling tailing/streaming:
val reader = new Reader(socketInputStream)
while (true) {
if (reader.hasNext) {
val row = reader.next()
processRow(row)
} else {
// No complete row available, wait for more data
Thread.sleep(100)
}
}import org.nsvformat.Writer
val writer = Writer.fromFile(new File("output.nsv"))
writer.writeRow(Seq("a", "b", "c"))
writer.writeRow(Seq("d", "e", "f"))
// Or write multiple rows at once
writer.writeRows(data)import org.nsvformat.Util
// Flatten with terminators
val flat = Util.spill(Seq(Seq("a", "b"), Seq("c")), "")
// Seq("a", "b", "", "c", "")
// Recover structure
val structured = Util.unspill(flat, "")
// Seq(Seq("a", "b"), Seq("c"))
// Generic over types
Util.spill(Seq(Seq(1, 2), Seq(3)), -1)
// Seq(1, 2, -1, 3, -1)decode(s: String): Seq[Seq[String]]- Parse NSV string to seqseqencode(data: Seq[Seq[String]]): String- Serialize seqseq to NSV stringescape(s: String): String- Escape cell content (handles\,\n, empty string)unescape(s: String): String- Unescape cell content
Iterator for row-by-row reading with resumable semantics:
new Reader(reader: java.io.Reader)- Construct from any ReaderReader.fromFile(file: java.io.File)- Convenience factory (buffered)Reader.fromPath(path: java.nio.file.Path)- Convenience factory (buffered)hasNext: Boolean- Check if complete row available (non-terminal)next(): Seq[String]- Get next row
new Writer(writer: java.io.Writer)- Construct from any WriterWriter.fromFile(file: java.io.File)- Convenience factory (buffered)Writer.fromPath(path: java.nio.file.Path)- Convenience factory (buffered)writeRow(row: Seq[String]): Unit- Write single rowwriteRows(rows: Seq[Seq[String]]): Unit- Write multiple rows
Generic structural operations:
spill[T](seqseq: Seq[Seq[T]], marker: T): Seq[T]- Flatten dimension with terminatorsunspill[T](seq: Seq[T], marker: T): Seq[Seq[T]]- Recover dimension by picking up terminators
sbt test63 tests covering:
- Escape/unescape operations
- Encode/decode invertibility
- Spill/unspill operations
- Reader/Writer functionality
- Resumable reading semantics
- Edge cases (empty rows, escape sequences, etc.)