Skip to content

Commit

Permalink
Add a fluent API for handling most common lens/prism systems.
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg committed Dec 19, 2023
1 parent 75bdb68 commit 9f217c1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
*/
package com.diffplug.selfie

/** Given a full [Snapshot], a lens returns either null or a single [SnapshotValue]. */
@OptIn(ExperimentalStdlibApi::class)
interface SnapshotLens : AutoCloseable {
val defaultLensName: String
fun transform(testClass: String, key: String, snapshot: Snapshot): SnapshotValue?
override fun close() {}
}

/**
* A prism transforms a single [Snapshot] into a new [Snapshot], transforming / creating / removing
* [SnapshotValue]s along the way.
*/
@OptIn(ExperimentalStdlibApi::class)
interface SnapshotPrism : AutoCloseable {
fun transform(className: String, key: String, snapshot: Snapshot): Snapshot
Expand All @@ -30,3 +35,68 @@ interface SnapshotPrism : AutoCloseable {
fun interface SnapshotPredicate {
fun test(testClass: String, key: String, snapshot: Snapshot): Boolean
}

/** A prism with a fluent API for creating [LensHoldingPrism]s gated by predicates. */
open class CompoundPrism : SnapshotPrism {
private val prisms = mutableListOf<SnapshotPrism>()
fun add(prism: SnapshotPrism): CompoundPrism {
prisms.add(prism)
return this
}
fun ifClassKeySnapshot(predicate: SnapshotPredicate): LensHoldingPrism {
val prismWhere = LensHoldingPrism(predicate)
add(prismWhere)
return prismWhere
}
fun ifSnapshot(predicate: (Snapshot) -> Boolean) = ifClassKeySnapshot { _, _, snapshot ->
predicate(snapshot)
}
fun forEverySnapshot(): LensHoldingPrism = ifSnapshot { true }
fun ifString(predicate: (String) -> Boolean) = ifSnapshot {
!it.value.isBinary && predicate(it.value.valueString())
}
fun ifStringIsProbablyHtml(): LensHoldingPrism {
val regex = Regex("<\\/?[a-z][\\s\\S]*>")
return ifString { regex.find(it) != null }
}
override fun transform(className: String, key: String, snapshot: Snapshot): Snapshot {
var current = snapshot
prisms.forEach { current = it.transform(className, key, current) }
return current
}
override fun close() = prisms.forEach(SnapshotPrism::close)
}

/** A prism which applies lenses to a snapshot. */
open class LensHoldingPrism(val predicate: SnapshotPredicate) : SnapshotPrism {
private val lenses = mutableListOf<SnapshotPrism>()
private fun addLensOrReplaceRoot(name: String?, lens: SnapshotLens): LensHoldingPrism {
lenses.add(
object : SnapshotPrism {
override fun transform(testClass: String, key: String, snapshot: Snapshot): Snapshot {
val lensValue = lens.transform(testClass, key, snapshot)
return if (lensValue == null) snapshot
else {
if (name == null) snapshot.withNewRoot(lensValue) else snapshot.lens(name, lensValue)
}
}
override fun close() = lens.close()
})
return this
}
fun addLens(name: String, lens: SnapshotLens): LensHoldingPrism = addLensOrReplaceRoot(name, lens)
fun addLens(lens: SnapshotLens): LensHoldingPrism =
addLensOrReplaceRoot(lens.defaultLensName, lens)
fun replaceRootWith(lens: SnapshotLens): LensHoldingPrism = addLensOrReplaceRoot(null, lens)
override fun transform(testClass: String, key: String, snapshot: Snapshot): Snapshot {
if (!predicate.test(testClass, key, snapshot)) {
return snapshot
}
var current = snapshot
lenses.forEach { current = it.transform(testClass, key, snapshot) }
return current
}
override fun close() {
lenses.forEach(SnapshotPrism::close)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
*/
package com.diffplug.selfie.junit5

import com.diffplug.selfie.Snapshot
import com.diffplug.selfie.CompoundPrism
import com.diffplug.selfie.SnapshotPrism
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

interface SelfieSettingsAPI {
fun openPrismTrain(layout: SnapshotFileLayout): SnapshotPrism
/** Returns a prism train which will be used to transform snapshots. */
fun createPrismTrain(layout: SnapshotFileLayout): SnapshotPrism

/**
* Defaults to `__snapshot__`, null means that snapshots are stored at the same folder location as
Expand Down Expand Up @@ -58,7 +59,7 @@ interface SelfieSettingsAPI {
val clazz = Class.forName("com.diffplug.selfie.SelfieSettings")
return clazz.getDeclaredConstructor().newInstance() as SelfieSettingsAPI
} catch (e: ClassNotFoundException) {
return SelfieSettingsNoOp
return SelfieSettingsNoOp()
} catch (e: InstantiationException) {
throw AssertionError(
"Unable to instantiate com.diffplug.SelfieSettings, is it abstract?", e)
Expand All @@ -67,10 +68,15 @@ interface SelfieSettingsAPI {
}
}

private object SelfieSettingsNoOp : SelfieSettingsAPI {
override fun openPrismTrain(layout: SnapshotFileLayout): SnapshotPrism =
object : SnapshotPrism {
override fun transform(testClass: String, key: String, snapshot: Snapshot): Snapshot =
snapshot
}
private class SelfieSettingsNoOp : StandardSelfieSettings() {
override fun setupPrismTrain(prismTrain: CompoundPrism) {}
}

abstract class StandardSelfieSettings : SelfieSettingsAPI {
protected abstract fun setupPrismTrain(prismTrain: CompoundPrism)
override fun createPrismTrain(layout: SnapshotFileLayout): SnapshotPrism {
val prismTrain = CompoundPrism()
setupPrismTrain(prismTrain)
return prismTrain
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ internal class ClassProgress(val parent: Progress, val className: String) {
internal class Progress {
val settings = SelfieSettingsAPI.initialize()
val layout = SnapshotFileLayout.initialize(settings)
val prismTrain = settings.openPrismTrain(layout)
val prismTrain = settings.createPrismTrain(layout)

private var progressPerClass = ArrayMap.empty<String, ClassProgress>()
private fun forClass(className: String) = synchronized(this) { progressPerClass[className]!! }
Expand Down

0 comments on commit 9f217c1

Please sign in to comment.