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)
}