Skip to content

Commit

Permalink
WIP: implement useUnixNewLines
Browse files Browse the repository at this point in the history
- redo jvm line reader to keep incremental reads and figure it out useUnixNewLines
- still got some failures, but want to get feedback about the line reader
  • Loading branch information
jknack committed Sep 9, 2023
1 parent 05a3112 commit 266c1cc
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ class SnapshotFile {
fun parse(valueReader: SnapshotValueReader): SnapshotFile {
try {
val result = SnapshotFile()
result.unixNewlines =
TODO("""add internal method to SnapshotValueReader to query if newline is \n or \r\n""")
result.unixNewlines = valueReader.unixNewlines
val reader = SnapshotReader(valueReader)
// only if the first value starts with 📷
if (reader.peekKey()?.startsWith(HEADER_PREFIX) == true) {
Expand Down Expand Up @@ -220,6 +219,7 @@ class SnapshotReader(val valueReader: SnapshotValueReader) {
/** Provides the ability to parse a snapshot file incrementally. */
class SnapshotValueReader(val lineReader: LineReader) {
var line: String? = null
val unixNewlines = lineReader.unixNewlines()

/** The key of the next value, does not increment anything about the reader's state. */
fun peekKey(): String? {
Expand Down Expand Up @@ -324,6 +324,7 @@ class SnapshotValueReader(val lineReader: LineReader) {
expect class LineReader {
fun getLineNumber(): Int
fun readLine(): String?
fun unixNewlines(): Boolean

companion object {
fun forString(content: String): LineReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ actual class LineReader {
}
actual fun getLineNumber(): Int = TODO()
actual fun readLine(): String? = TODO()
actual fun unixNewlines(): Boolean = TODO()
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,74 @@
*/
package com.diffplug.selfie

import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.LineNumberReader
import java.io.Reader
import java.io.StringReader
import java.nio.CharBuffer
import java.nio.charset.StandardCharsets

actual class LineReader(reader: Reader) : LineNumberReader(reader) {
actual class LineReader(reader: Reader) {
private val reader = LineTerminatorAware(LineTerminatorReader(reader))

actual companion object {
actual fun forString(content: String) = LineReader(StringReader(content))
actual fun forBinary(content: ByteArray) =
LineReader(InputStreamReader(content.inputStream(), StandardCharsets.UTF_8))
}
actual fun getLineNumber(): Int = reader.lineNumber
actual fun readLine(): String? = reader.readLine()
actual fun unixNewlines(): Boolean = reader.lineTerminator.unixNewlines()
}

/**
* Keep track of carriage return char to figure it out if we need unix new line or not. The first
* line is kept in memory until we require the next line.
*/
private open class LineTerminatorAware(val lineTerminator: LineTerminatorReader) :
LineNumberReader(lineTerminator) {
/** First line is initialized as soon as possible. */
private var firstLine: String? = super.readLine()
override fun readLine(): String? {
if (this.firstLine != null) {
val result = this.firstLine
this.firstLine = null
return result
}
return super.readLine()
}
}

/**
* Override all read operations to find the carriage return. We want to keep lazy/incremental reads.
*/
private class LineTerminatorReader(reader: Reader) : BufferedReader(reader) {
private val CR: Int = '\r'.code
private var unixNewlines = System.lineSeparator().equals("\n")
override fun read(cbuf: CharArray): Int {
val result = super.read(cbuf)
unixNewlines = cbuf.indexOf(CR.toChar()) == -1
return result
}
override fun read(target: CharBuffer): Int {
val result = super.read(target)
unixNewlines = target.indexOf(CR.toChar()) == -1
return result
}
override fun read(cbuf: CharArray, off: Int, len: Int): Int {
val result = super.read(cbuf, off, len)
unixNewlines = cbuf.indexOf(CR.toChar()) == -1
return result
}
override fun read(): Int {
val ch = super.read()
if (ch == CR) {
unixNewlines = false
}
return ch
}
fun unixNewlines(): Boolean {
return unixNewlines
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.selfie

import io.kotest.matchers.shouldBe
import kotlin.test.Test

class LineReaderTest {

@Test
fun shouldFindUnixSeparatorFromBinary() {
val reader = LineReader.forBinary("This is a new line\n".encodeToByteArray())
reader.unixNewlines() shouldBe true
reader.readLine() shouldBe "This is a new line"
}

@Test
fun shouldFindWindowsSeparatorFromBinary() {
val reader = LineReader.forBinary("This is a new line\r\n".encodeToByteArray())
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "This is a new line"
}

@Test
fun shouldFindUnixSeparatorFromString() {
val reader = LineReader.forString("This is a new line\n")
reader.unixNewlines() shouldBe true
reader.readLine() shouldBe "This is a new line"
}

@Test
fun shouldFindWindowsSeparatorFromString() {
val reader = LineReader.forString("This is a new line\r\n")
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "This is a new line"
}

@Test
fun shouldGetOSLineSeparatorWhenThereIsNone() {
val reader = LineReader.forBinary("This is a new line".encodeToByteArray())
reader.unixNewlines() shouldBe System.lineSeparator().equals("\n")
reader.readLine() shouldBe "This is a new line"
}

@Test
fun shouldReadNextLineWithoutProblem() {
val reader = LineReader.forBinary("First\r\nSecond\r\n".encodeToByteArray())
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "First"
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "Second"
reader.unixNewlines() shouldBe false
}

@Test
fun shouldUseFirstLineSeparatorAndIgnoreNext() {
val reader = LineReader.forBinary("First\r\nAnother separator\n".encodeToByteArray())
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "First"
reader.unixNewlines() shouldBe false
reader.readLine() shouldBe "Another separator"
reader.unixNewlines() shouldBe false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,16 @@ internal class SnapshotFileLayout(
val snapshotRootFolder = rootFolder(properties.getProperty("output-dir"))
// it's pretty easy to preserve the line endings of existing snapshot files, but it's
// a bit harder to create a fresh snapshot file with the correct line endings.
val unixNewlines: Boolean =
TODO("find the first file in the snapshot folder and check if it has unix newlines")
return SnapshotFileLayout(snapshotRootFolder, snapshotFolderName, unixNewlines)
val candidate =
snapshotRootFolder
.resolve(snapshotFolderName!!)
.toFile()
.walkTopDown()
.maxDepth(1)
.filter { it.isFile }
.firstOrNull()
val cr = candidate?.readText()?.contains('\r') ?: System.lineSeparator().equals("\r\n")
return SnapshotFileLayout(snapshotRootFolder, snapshotFolderName, !cr)
}
private fun snapshotFolderName(snapshotDir: String?): String? {
if (snapshotDir == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ open class Harness(subproject: String) {
buildLauncher.run()
return null
} catch (e: BuildException) {
e.printStackTrace()
return parseBuildException(task)
}
}
Expand Down

0 comments on commit 266c1cc

Please sign in to comment.