From 64352b0a05747b05420d25323053b4a61d79b81f Mon Sep 17 00:00:00 2001 From: Alexander Ioffe Date: Wed, 31 Jan 2024 12:02:33 -0500 Subject: [PATCH] Add more docs --- README.md | 98 +++++++++++++++---- .../kotlin/io/exoquery/kmp/pprint/PPrinter.kt | 2 +- .../kotlin/io/exoquery/pprint/Example2.kt | 46 ++++++--- 3 files changed, 113 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 3438fc2..bf69c32 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,12 @@ Add the following to your build.gradle.kts: ```kotlin implementation("io.exoquery:pprint-kotlin:1.1.0") -// For Kotlin Multiplatform use: -implementation("io.exoquery:pprint-kotlin-kmp:1.1.0") +// For Kotlin Multiplatform add serialization to your plugins: +// plugins { +// kotlin("plugin.serialization") version "1.9.22" +// } +// Then add the following to your dependencies +// implementation("io.exoquery:pprint-kotlin-kmp:1.1.0") ``` Then use the library like this: @@ -127,17 +131,18 @@ println(pprint(p, defaultHeight = 10)) ## -Note that in order to use Infinite sequences is Kotlin Multiplatform, you need to annotate -the sequence-field using `@Serializable(with = PPrintSequenceSerializer::class)` for example: -```kotlin -@Serializable -data class SequenceHolder(@Serializable(with = PPrintSequenceSerializer::class) val seq: Sequence) - -var i = 0 -val p = SequenceHolder(generateSequence { "foo-${i++}" }) -println(pprint(p, defaultHeight = 10)) -``` - +> ### Infinite Sequences in Kotlin Multiplatform +> Note that in order to use Infinite sequences is Kotlin Multiplatform, you need to annotate +> the sequence-field using `@Serializable(with = PPrintSequenceSerializer::class)` for example: +> ```kotlin +> @Serializable +> data class SequenceHolder(@Serializable(with = PPrintSequenceSerializer::class) val seq: Sequence) +> +> var i = 0 +> val p = SequenceHolder(generateSequence { "foo-${i++}" }) +> println(pprint(p, defaultHeight = 10)) +> ``` +> > You should also be able to use the `@file:UseSerializers(PPrintSequenceSerializer::class)` to deliniate this for a entire file. > See the kotlinx-serialization documentation for [Serializing 3rd Party Classes](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#serializing-3rd-party-classes) for more detail. @@ -198,22 +203,29 @@ println(pp.invoke(joe)) ``` This printer can then be used as the basis of a custom `pprint`-like user defined function. -> ### Note for Kotlin Multiplatform +> ### Extending PPrint in Kotlin Multiplatform > In Kotlin Multiplatform, the PPrinter is parametrized and takes an additional `SerializationStrategy` parameter. > You can extend it like this: > ```kotlin -> class CustomPPrinter1(val serializer: SerializationStrategy, val config: PPrinterConfig) : PPrinter(config) { -> ... +> class CustomPPrinter1(override val serializer: SerializationStrategy, override val config: PPrinterConfig) : PPrinter(serializer, config) { +> // Overwrite `treeifyWithSerializer` instead of treeify +> override fun treeifyWithSerializer(treeifyable: PPrinter.Treeifyable, escapeUnicode: Boolean, showFieldNames: Boolean): Tree = +> when (val v = treeifyable.value) { +> is LocalDate -> Tree.Literal(v.format(DateTimeFormatter.ofPattern("MM/dd/YYYY"))) +> else -> super.treeifyWithSerializer(treeifyable, escapeUnicode, showFieldNames) +> } > } > -> // You can then use this class like so: -> @Serializeable data class Person(val name: String, val born: LocalDate) +> // Define the class to serialize like this (it will not compile unless you add a @Contextual for the custom property) +> @Serializeable data class Person(val name: String, @Contextual val born: LocalDate) > val pp = CustomPPrinter1(Person.serializer(), PPrinterConfig()) > val joe = Person("Joe", LocalDate.of(1981, 1, 1)) > println(pp.invoke(joe)) > ``` -> TODO finish this example and write docs for how to write a custom pprint function that uses serializer instead of requring passing it in -> You can then use this () +> You can write a custom pprint-function based on this class like this: +> ```kotlin +> inline fun myPPrint(value: T) = CustomPPrinter1(serializer(), PPrinterConfig()).invoke(value) +> ``` For nested objects use Tree.Apply and recursively call the treeify method. ```kotlin @@ -290,6 +302,52 @@ println(CustomPPrinter4(PPrinterConfig(defaultShowFieldNames = false)).invoke(be //> MyJavaBean("abc", 123) ``` +## PPrint with Kotlin Multiplatform (KMP) + +The JVM-based PPrint relies on the `kotlin-reflect` library in order to recurse on the fields in a data class. +For PPrint-KMP, this is done by the `kotlinx-serialization` library. Therefore you need the kotlinx-serialization +runtime as well as the compiler-plugin in order to use PPrint Multiplatform. The former should be pulled in +automatically when you import `pprint-kotlin-kmp`: +```kotlin +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") version "1.9.22" +} + +... + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation("io.exoquery:pprint-kotlin-kmp:1.2.2") + // implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.2") + } + } + } + ... +} +``` + +Since Kotlin Multiplatform relies on the `@Serialization` (and related) annotations in order to deliniate a +class as serializable, you will need to use the `@Serializable` annotation on your data classes. For example: +```kotlin +@Serializable +data class Name(val first: String, val last: String) +@Serializable +data class Person(val name: Name, val age: Int) + +val p = Person(Name("Joe", "Bloggs"), 42) +pprint(p) +//> Person(name = Name(first = "Joe", last = "Bloggs"), age = 123) +``` + +In some cases (i.e. custom fields) you will need to use the @Contextual annotation to deliniate a field as custom. +See the note about LocalDate in the [Extending PPrint in Kotlin Multiplatform](#extending-pprint-in-kotlin-multiplatform) section for more detail. + +When using sequences, you will need to annotate the +sequence-field using `@Serializable(with = PPrintSequenceSerializer::class)`. +See the note in the [Infinite Sequences in Kotlin Multiplatform](#infinite-sequences-in-kotlin-multiplatform) section for more detail. # Fansi for Kotlin PPrint is powered by Fansi. It relies on this amazing library in order to be able to print out ansi-colored strings. diff --git a/pprint-kotlin-kmp/src/commonMain/kotlin/io/exoquery/kmp/pprint/PPrinter.kt b/pprint-kotlin-kmp/src/commonMain/kotlin/io/exoquery/kmp/pprint/PPrinter.kt index 6076be7..2e13a92 100644 --- a/pprint-kotlin-kmp/src/commonMain/kotlin/io/exoquery/kmp/pprint/PPrinter.kt +++ b/pprint-kotlin-kmp/src/commonMain/kotlin/io/exoquery/kmp/pprint/PPrinter.kt @@ -13,7 +13,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.serializer import kotlinx.serialization.serializerOrNull -open class PPrinter(val serializer: SerializationStrategy, override val config: PPrinterConfig = PPrinterConfig()): PPrinterBase(config) { +open class PPrinter(open val serializer: SerializationStrategy, override open val config: PPrinterConfig = PPrinterConfig()): PPrinterBase(config) { companion object { inline operator fun invoke(config: PPrinterConfig = PPrinterConfig()) = PPrinter(serializer(), config) diff --git a/pprint-kotlin-kmp/src/jvmTest/kotlin/io/exoquery/pprint/Example2.kt b/pprint-kotlin-kmp/src/jvmTest/kotlin/io/exoquery/pprint/Example2.kt index 79f32ad..4acf286 100644 --- a/pprint-kotlin-kmp/src/jvmTest/kotlin/io/exoquery/pprint/Example2.kt +++ b/pprint-kotlin-kmp/src/jvmTest/kotlin/io/exoquery/pprint/Example2.kt @@ -3,14 +3,15 @@ package io.exoquery.pprint import io.exoquery.kmp.pprint.PPrintSequenceSerializer import io.exoquery.kmp.pprint.PPrinter import io.exoquery.kmp.pprint -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import java.time.LocalDate +import java.time.format.DateTimeFormatter @Serializable -data class SerName(val first: String, val last: String) +data class Name(val first: String, val last: String) @Serializable -data class SerPerson(val name: SerName, val age: Int) +data class Person(val name: Name, val age: Int) @Serializable data class Stuff(val value: Map) @@ -21,14 +22,11 @@ data class IterStuff(val value: Iterator) @OptIn(ExperimentalSerializationApi::class) fun main() { // showMap() -// showPerson() + showPerson() // showIteratorInObject() // mutualRecurse() - - usingSequence0() - - // TODO test infinite sequence - // TODO test-self reference (see how the example was designed) +// usingSequence0() +// customPrinter() } object UsingSequence1 { @@ -62,7 +60,31 @@ fun usingSequence2() { println(p) } +object CustomPrinter { + class CustomPPrinter1(override val serializer: SerializationStrategy, override val config: PPrinterConfig) : PPrinter(serializer, config) { + override fun treeifyWithSerializer(treeifyable: PPrinter.Treeifyable, escapeUnicode: Boolean, showFieldNames: Boolean): Tree = + when (val v = treeifyable.value) { + is LocalDate -> Tree.Literal(v.format(DateTimeFormatter.ofPattern("MM/dd/YYYY"))) + else -> super.treeifyWithSerializer(treeifyable, escapeUnicode, showFieldNames) + } + } + + inline fun myPPrint(value: T) = CustomPPrinter1(serializer(), PPrinterConfig()).invoke(value) +} + +fun customPrinter() { + @Serializable + data class Person(val name: String, @Contextual val born: LocalDate) + val pp = CustomPrinter.CustomPPrinter1(Person.serializer(), PPrinterConfig()) + val joe = Person("Joe", LocalDate.of(1981, 1, 1)) + + println(pp.invoke(joe)) + + println(CustomPrinter.myPPrint(joe)) +} + +// TODO SHOULD MAKE THIS INTO A UNITE TEST (can use small width like 10) object MutualRecurse { @Serializable data class Foo(var bar: Bar?) @@ -95,8 +117,8 @@ fun showIteratorInObject() { } fun showPerson() { - val per = SerPerson(SerName("Joe", "Bloggs"), 123) - val printer = PPrinter(SerPerson.serializer()) + val per = Person(Name("Joe", "Bloggs"), 123) + val printer = PPrinter(Person.serializer()) val str = printer(per) println(str) }