Skip to content

Commit

Permalink
Add more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Jan 31, 2024
1 parent d8fbf88 commit 64352b0
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 33 deletions.
98 changes: 78 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -127,17 +131,18 @@ println(pprint(p, defaultHeight = 10))

## <img src="https://github.com/deusaquilus/pprint-kotlin/assets/1369480/9026f8ca-479e-442d-966b-0c1f1f887986" width=50% height=50%>

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

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<String>)
>
> 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.
Expand Down Expand Up @@ -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<T>` parameter.
> You can extend it like this:
> ```kotlin
> class CustomPPrinter1<T>(val serializer: SerializationStrategy<T>, val config: PPrinterConfig) : PPrinter<T>(config) {
> ...
> class CustomPPrinter1<T>(override val serializer: SerializationStrategy<T>, override val config: PPrinterConfig) : PPrinter<T>(serializer, config) {
> // Overwrite `treeifyWithSerializer` instead of treeify
> override fun <R> treeifyWithSerializer(treeifyable: PPrinter.Treeifyable<R>, 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<T> 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 <reified T> myPPrint(value: T) = CustomPPrinter1(serializer<T>(), PPrinterConfig()).invoke(value)
> ```
For nested objects use Tree.Apply and recursively call the treeify method.
```kotlin
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.serializer
import kotlinx.serialization.serializerOrNull

open class PPrinter<T>(val serializer: SerializationStrategy<T>, override val config: PPrinterConfig = PPrinterConfig()): PPrinterBase<T>(config) {
open class PPrinter<T>(open val serializer: SerializationStrategy<T>, override open val config: PPrinterConfig = PPrinterConfig()): PPrinterBase<T>(config) {

companion object {
inline operator fun <reified T> invoke(config: PPrinterConfig = PPrinterConfig()) = PPrinter(serializer<T>(), config)
Expand Down
46 changes: 34 additions & 12 deletions pprint-kotlin-kmp/src/jvmTest/kotlin/io/exoquery/pprint/Example2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>)
Expand All @@ -21,14 +22,11 @@ data class IterStuff(val value: Iterator<Int>)
@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 {
Expand Down Expand Up @@ -62,7 +60,31 @@ fun usingSequence2() {
println(p)
}

object CustomPrinter {
class CustomPPrinter1<T>(override val serializer: SerializationStrategy<T>, override val config: PPrinterConfig) : PPrinter<T>(serializer, config) {
override fun <R> treeifyWithSerializer(treeifyable: PPrinter.Treeifyable<R>, 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 <reified T> myPPrint(value: T) = CustomPPrinter1(serializer<T>(), 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?)
Expand Down Expand Up @@ -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)
}

0 comments on commit 64352b0

Please sign in to comment.