{"id":23954120,"url":"https://github.com/ExoQuery/pprint-kotlin","last_synced_at":"2025-09-12T13:31:12.866Z","repository":{"id":214683358,"uuid":"736139106","full_name":"ExoQuery/pprint-kotlin","owner":"ExoQuery","description":"Li Haoyi's excellent Pretty Printing library ported to Kotlin!","archived":false,"fork":false,"pushed_at":"2024-12-31T07:09:09.000Z","size":228,"stargazers_count":175,"open_issues_count":5,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-24T22:06:40.671Z","etag":null,"topics":["kotlin","ported","pretty-print","scala"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ExoQuery.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-27T05:05:17.000Z","updated_at":"2025-05-05T21:11:01.000Z","dependencies_parsed_at":"2025-02-19T18:15:34.896Z","dependency_job_id":"c7443ff0-2f60-466c-921a-8b2d21df2507","html_url":"https://github.com/ExoQuery/pprint-kotlin","commit_stats":null,"previous_names":["deusaquilus/pprint-kotlin","exoquery/pprint-kotlin"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ExoQuery/pprint-kotlin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ExoQuery%2Fpprint-kotlin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ExoQuery%2Fpprint-kotlin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ExoQuery%2Fpprint-kotlin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ExoQuery%2Fpprint-kotlin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ExoQuery","download_url":"https://codeload.github.com/ExoQuery/pprint-kotlin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ExoQuery%2Fpprint-kotlin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274821176,"owners_count":25356226,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","ported","pretty-print","scala"],"created_at":"2025-01-06T15:00:29.059Z","updated_at":"2025-09-12T13:31:12.854Z","avatar_url":"https://github.com/ExoQuery.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"# PPrint for Kotlin\n\nThis is a port of Li Haoyi's excellent Scala pretty-printing library into Kotlin [PPrint](https://github.com/com-lihaoyi/PPrint).\n(As well as Li Haoyi's excellent Ansi-Formatting library Fansi!)\n\n## Usage\n\nPPrint for Kotlin is available in both JVM and Kotlin Multiplatform flavors. The JVM flavor uses `kotlin-reflect`, the KMP flavor uses `kotlinx-serialization`.\n\nAdd the following to your build.gradle.kts:\n\n```kotlin\nkotlin {\n  jvmToolchain(11) // if this version is any less the library will not load\n}\n\nimplementation(\"io.exoquery:pprint-kotlin:2.0.2\")\n\n// For Kotlin Multiplatform add serialization to your plugins:\n// plugins {\n//   kotlin(\"plugin.serialization\") version \"1.9.22\"\n// }\n// Then add the following to your dependencies\n// implementation(\"io.exoquery:pprint-kotlin-kmp:2.0.2\")\n// implementation(\"org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.2\")\n```\n\nThen use the library like this: \n```kotlin\nimport io.exoquery.pprint\n// For kotlin multiplatform use: import io.exoquery.kmp.pprint \n\ndata class Name(val first: String, val last: String)\ndata class Person(val name: Name, val age: Int)\nval p = Person(Name(\"Joe\", \"Bloggs\"), 42)\nprintln(pprint(p))\n```\n\nIt will print the following beautiful output:\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/ce866664-7959-46fb-a8c8-9a636a315281\" width=50% height=50%\u003e\n\nPPrint-Kotlin supports most of the same features and options as the Scala version.\nI will document them here over time however for now please refer to the Scala documentation\n* For PPrint here - https://github.com/com-lihaoyi/PPrint\n* For Fansi here - https://github.com/com-lihaoyi/fansi\n\n## Nested Data and Complex Collections\n\nPPrint excels at printing nested data structures and complex collections. For example:\n\n#### Lists embedded in objects:\n```kotlin\ndata class Address(val street: String, val zip: Int)\ndata class Customer(val name: Name, val addresses: List\u003cAddress\u003e)\n\nval p = Customer(Name(\"Joe\", \"Bloggs\"), listOf(Address(\"foo\", 123), Address(\"bar\", 456), Address(\"baz\", 789)))\nprintln(pprint(p))\n```\n\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/3c7b7e18-d246-451c-ae3d-bfcc102ccefc\" width=50% height=50%\u003e\n\n\n#### Maps embedded in objects:\n```kotlin\ndata class Alias(val value: String)\ndata class ComplexCustomer(val name: Name, val addressAliases: Map\u003cAlias, Address\u003e)\n\nval p =\n  ComplexCustomer(\n    Name(\"Joe\", \"Bloggs\"),\n    mapOf(Alias(\"Primary\") to Address(\"foo\", 123), Alias(\"Secondary\") to Address(\"bar\", 456), Alias(\"Tertiary\") to Address(\"baz\", 789))\n  )\nprintln(pprint(p))\n```\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/813afad2-1cfa-4629-b2a8-253ac47254a4\" width=50% height=50%\u003e\n\n\n#### Lists embedded in maps embedded in objects:\n\n```kotlin\nval p =\n  VeryComplexCustomer(\n    Name(\"Joe\", \"Bloggs\"),\n    mapOf(\n      Alias(\"Primary\") to\n        listOf(Address(\"foo\", 123), Address(\"foo1\", 123), Address(\"foo2\", 123)),\n      Alias(\"Secondary\") to\n        listOf(Address(\"bar\", 456), Address(\"bar1\", 456), Address(\"bar2\", 456)),\n      Alias(\"Tertiary\") to\n        listOf(Address(\"baz\", 789), Address(\"baz1\", 789), Address(\"baz2\", 789))\n    )\n  )\nprintln(pprint(p))\n```\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/4f3aeb69-315f-4fd7-b831-c568c6daa26c\" width=50% height=50%\u003e\n\n## Removing Field Names\n\nBy default pprint will print the field names of data classes. You can remove these by using `showFieldNames = false`:\n\n```kotlin\nval p = Person(Name(\"Joe\", \"Bloggs\"), 42)\nprintln(pprint(p, showFieldNames = false))\n```\n\nFor larger ADTs this dramatically reduces the amount of output and often improves the readability.\n\n## User-controlled Width\n\nAnother nice feature of PPrint is that it can print data classes with a user-controlled width.\n\n```kotlin\nprintln(pprint(p, showFieldNames = false, defaultWidth = 30)) // Narrow\n```\n\u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/186047f4-dcfe-4331-9bd3-23f51549548a\" width=50% height=50%\u003e\n\n```kotlin\nprintln(pprint(p, showFieldNames = false, defaultWidth = 100)) // Wide\n```\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/c6539ed6-0584-4233-87d6-224b35e011b6\" width=70% height=70%\u003e\n\n## Infinite Sequences\n\nAnother very impressive ability of PPrint is that it can print infinite sequences, even if they are embedded\nother objects for example:\n```kotlin\ndata class SequenceHolder(val seq: Sequence\u003cString\u003e)\n\nvar i = 0\nval p = SequenceHolder(generateSequence { \"foo-${i++}\" })\nprintln(pprint(p, defaultHeight = 10))\n```\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/9026f8ca-479e-442d-966b-0c1f1f887986\" width=50% height=50%\u003e\n\n\u003e ### Infinite Sequences in Kotlin Multiplatform\n\u003e Note that in order to use Infinite sequences is Kotlin Multiplatform, you need to annotate\n\u003e the sequence-field using `@Serializable(with = PPrintSequenceSerializer::class)` for example:\n\u003e ```kotlin\n\u003e @Serializable\n\u003e data class SequenceHolder(@Serializable(with = PPrintSequenceSerializer::class) val seq: Sequence\u003cString\u003e)\n\u003e \n\u003e var i = 0\n\u003e val p = SequenceHolder(generateSequence { \"foo-${i++}\" })\n\u003e println(pprint(p, defaultHeight = 10))\n\u003e ```\n\u003e \n\u003e You should also be able to use the `@file:UseSerializers(PPrintSequenceSerializer::class)` to deliniate this for a entire file but this does not always work in practice.\n\u003e 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.\n\nPPrint is able to print this infinite sequence without stack-overflowing or running out of memory\nbecause it is highly lazy. It only evaluates the sequence as it is printing it,\nand the printing is always constrained by the height and width of the output. You can\ncontrol these with the `defaultHeight` and `defaultWidth` parameters to the `pprint` function.\n\n## Circular References\n\nSimilar to infinite sequences, PPrint will print circular references up to the specified defaultHeight after which the output will be truncated.\n```kotlin\ndata class Parent(var child: Child?)\ndata class Child(var parent: Parent?)\n\nval child = Child(parent = null)\nval parent = Parent(child = null)\nchild.parent = parent\nparent.child = child\nprintln(pprint(parent, defaultHeight = 10))\n```\n\n## \u003cimg src=\"https://github.com/deusaquilus/pprint-kotlin/assets/1369480/146c78eb-11e8-4cdb-a547-76ac9d79ce91\" width=50% height=50%\u003e\n\n\n## Black \u0026 White Printing\n\nThe output of the pprint function is not actually a java.lang.String, but a fansi.Str. This\nmeans you can control how it is printed. For example, to print it in black and white simple do:\n```kotlin\nimport io.exoquery.pprint\n\nval p = Person(Name(\"Joe\", \"Bloggs\"), 42)\n\n// Use Black \u0026 White Printing\nprintln(pprint(p).plainText)\n```\n\n## Extending PPrint\n\nIn order to extend pprint, subclass the PPrinter class and override the `treeify` function.\nFor example:\n```kotlin\nclass CustomPPrinter1(val config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree =\n    when (x) {\n      is java.time.LocalDate -\u003e Tree.Literal(x.format(DateTimeFormatter.ofPattern(\"MM/dd/YYYY\")))\n      else -\u003e super.treeify(x, escapeUnicode, showFieldNames)\n    }\n}\n\ndata class Person(val name: String, val born: LocalDate)\nval pp = CustomPPrinter1(PPrinterConfig())\nval joe = Person(\"Joe\", LocalDate.of(1981, 1, 1))\n\nprintln(pp.invoke(joe))\n//\u003e Person(name = \"Joe\", born = 01/01/1981)\n```\nThis printer can then be used as the basis of a custom `pprint`-like user defined function.\n\n\u003e ### Extending PPrint in Kotlin Multiplatform\n\u003e In Kotlin Multiplatform, the PPrinter is parametrized and takes an additional `SerializationStrategy\u003cT\u003e` parameter.\n\u003e You can extend it like this:\n\u003e ```kotlin\n\u003e class CustomPPrinter1\u003cT\u003e(override val serializer: SerializationStrategy\u003cT\u003e, override val config: PPrinterConfig) : PPrinter\u003cT\u003e(serializer, config) {\n\u003e   // Overwrite `treeifyValueOrNull` in order to handle leaf-types. Note that anything handled here will not be treated as a composite value.\n\u003e   override fun \u003cR\u003e treeifyValueOrNull(value: R, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree? =\n\u003e     when (value) {\n\u003e       is LocalDate -\u003e Tree.Literal(v.format(DateTimeFormatter.ofPattern(\"MM/dd/YYYY\")))\n\u003e       else -\u003e super.treeifyWith(treeifyable, escapeUnicode, showFieldNames)\n\u003e     }\n\u003e }\n\u003e \n\u003e // Define the class to serialize, it will not compile unless you add a @Contextual for the custom property\n\u003e @Serializeable data class Person(val name: String, @Contextual val born: LocalDate)\n\u003e val pp = CustomPPrinter1(Person.serializer(), PPrinterConfig())\n\u003e val joe = Person(\"Joe\", LocalDate.of(1981, 1, 1))\n\u003e println(pp.invoke(joe))\n\u003e ```\n\u003e You can write a custom pprint-function based on this class like this:\n\u003e ```kotlin\n\u003e inline fun \u003creified T\u003e myPPrint(value: T) = CustomPPrinter1(serializer\u003cT\u003e(), PPrinterConfig()).invoke(value)\n\u003e ```\n\nFor nested objects use Tree.Apply and recursively call the treeify method.\n```kotlin\n// A class that wouldn't normally print the right thing with pprint...\nclass MyJavaBean(val a: String, val b: Int) {\n  fun getValueA() = a\n  fun getValueB() = b\n}\n\n// Create the custom printer\nclass CustomPPrinter2(val config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, elementName: String?, esc: Boolean, names: Boolean): Tree =\n    when (x) {\n      // List through the properties of 'MyJavaBean' and recursively call treeify on them.\n      // (Note that Tree.Apply takes an iterator of properties so that the interface is lazy)\n      is MyJavaBean -\u003e \n        Tree.Apply(\"MyJavaBean\", listOf(x.getValueA() to \"A\", x.getValueB() to \"B\")\n          .map { (field, fieldName) -\u003e treeify(field, fieldName, esc, names) }.iterator()\n        )\n      else -\u003e super.treeify(x, esc, names)\n    }\n}\n\nval bean = MyJavaBean(\"abc\", 123)\nval pp = CustomPPrinter2(PPrinterConfig())\nprintln(pp.invoke(bean))\n//\u003e MyJavaBean(\"abc\", 123)\n```\n\nTo print field-names you use Tree.KeyValue:\n```kotlin\nclass CustomPPrinter3(val config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree {\n    // function to make recursive calls shorter\n    fun rec(x: Any?) = treeify(x, null, escapeUnicode, showFieldNames)\n    return when (x) {\n      // Recurse on the values, pass result into Tree.KeyValue.\n      is MyJavaBean -\u003e \n        Tree.Apply(\n          \"MyJavaBean\", \n          listOf(Tree.KeyValue(\"a\", rec(x.getValueA())), Tree.KeyValue(\"b\", rec(x.getValueB()))).iterator()\n        )\n      else -\u003e \n        super.treeify(x, esc, names)\n    }\n  }\n}\n\nval bean = MyJavaBean(\"abc\", 123)\nval pp = CustomPPrinter2(PPrinterConfig())\nprintln(pp.invoke(bean))\n//\u003e MyJavaBean(a = \"abc\", b = 123)\n```\n\nOften it is a good idea to honor the `showFieldNames` parameter only display key-values if it is enabled:\n```kotlin\nclass CustomPPrinter4(val config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree {\n    // function to make recursive calls shorter\n    fun rec(x: Any?) = treeify(x, escapeUnicode, showFieldNames)\n    fun field(fieldName: String, value: Any?) =\n      if (showFieldNames) Tree.KeyValue(fieldName, rec(value)) else rec(value) \n    return when (x) {\n      // Recurse on the values, pass result into Tree.KeyValue.\n      is MyJavaBean -\u003e \n        Tree.Apply(\"MyJavaBean\", listOf(field(\"a\", x.getValueA()), field(\"b\", x.getValueB())).iterator())\n      else -\u003e \n        super.treeify(x, escapeUnicode, showFieldNames)\n    }\n  }\n}\n\nval bean = MyJavaBean(\"abc\", 123)\nprintln(CustomPPrinter4(PPrinterConfig()).invoke(bean))\n//\u003e MyJavaBean(a = \"abc\", b = 123)\nprintln(CustomPPrinter4(PPrinterConfig(defaultShowFieldNames = false)).invoke(bean))\n//\u003e MyJavaBean(\"abc\", 123)\n```\n\n## PPrint with Kotlin Multiplatform (KMP)\n\nThe JVM-based PPrint relies on the `kotlin-reflect` library in order to recurse on the fields in a data class.\nFor PPrint-KMP, this is done by the `kotlinx-serialization` library. Therefore you need the kotlinx-serialization\nruntime as well as the compiler-plugin in order to use PPrint Multiplatform. The former should be pulled in\nautomatically when you import `pprint-kotlin-kmp`:\n```kotlin\nplugins {\n  kotlin(\"multiplatform\")\n  kotlin(\"plugin.serialization\") version \"1.9.22\"\n}\n\n...\n\nkotlin {\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(\"io.exoquery:pprint-kotlin-kmp:2.0.2\")\n        implementation(\"org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.2\")\n      }\n    }\n  }\n  ...\n}\n```\n\nSince Kotlin Multiplatform relies on the `@Serialization` (and related) annotations in order to deliniate a\nclass as serializable, you will need to use the `@Serializable` annotation on your data classes. For example:\n```kotlin\n@Serializable\ndata class Name(val first: String, val last: String)\n@Serializable\ndata class Person(val name: Name, val age: Int)\n\nval p = Person(Name(\"Joe\", \"Bloggs\"), 42)\npprint(p)\n//\u003e Person(name = Name(first = \"Joe\", last = \"Bloggs\"), age = 123)\n```\n\nIn some cases (i.e. custom fields) you will need to use the @Contextual annotation to deliniate a field as custom.\nSee the note about LocalDate in the [Extending PPrint in Kotlin Multiplatform](#extending-pprint-in-kotlin-multiplatform) section for more detail.\n\nWhen using sequences, you will need to annotate the \nsequence-field using `@Serializable(with = PPrintSequenceSerializer::class)`.\nSee the note in the [Infinite Sequences in Kotlin Multiplatform](#infinite-sequences-in-kotlin-multiplatform) section for more detail.\n\n## Using `elementName` metadata\n\nNote that the `elementName` parameter will contain the name of the field of the data class that is being printed.\nYou can use this to exclude particular fields. For example:\n```kotlin\nclass CustomPPrinter6(config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree =\n    when {\n      elementName == \"born\" -\u003e Tree.Literal(\"REDACTED\", elementName)\n      else -\u003e super.treeify(x, elementName, escapeUnicode, showFieldNames)\n    }\n}\n\ndata class Person(val name: String, val born: LocalDate)\nval pp = CustomPPrinter6(PPrinterConfig())\nval joe = Person(\"Joe\", LocalDate.of(1981, 1, 1))\n\nprintln(pp.invoke(joe))\n//\u003e Person(name = \"Joe\", born = REDACTED)\n```\n\nYou can also filter out fields on the parent-element level like this:\n```kotlin\ndata class PersonBorn(val name: String, val born: LocalDate)\n\nclass CustomPPrinter5(config: PPrinterConfig) : PPrinter(config) {\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree =\n    when {\n      x is PersonBorn -\u003e\n        when (val p = super.treeify(x, elementName, escapeUnicode, showFieldNames)) {\n          is Tree.Apply -\u003e p.copy(body = p.body.asSequence().toList().filter { it.elementName != \"born\" }.iterator())\n          else -\u003e error(\"Expected Tree.Apply\")\n        }\n      else -\u003e\n        super.treeify(x, elementName, escapeUnicode, showFieldNames)\n    }\n}\n\nval p = PersonBorn(\"Joe\", LocalDate.of(1981, 1, 1))\nprintln(CustomPPrinter5(PPrinterConfig()).invoke(p))\n//\u003e PersonBorn(name = \"Joe\")\n```\n\n## Using `elementName` metadata in Kotlin Multiplatform\n\nIf you want to filter out fields based on `elementName` in Kotlin Multiplatform inside of the PPrinter you need to override\nthe `treeifyComposite` method. \n\u003e Since `treeifyValueOrNull` will always be attempted, trying to do super.treeify (or super.treeifyComposite) inside of it will result in a stack-overflow. \n\nFor example:\n```kotlin\n@Serializable\ndata class PersonBorn(val name: String, val born: Long)\n\nclass CustomPPrinter6\u003cT\u003e(override val serializer: SerializationStrategy\u003cT\u003e, override val config: PPrinterConfig) : PPrinter\u003cT\u003e(serializer, config) {\n  override fun \u003cE\u003e treeifyComposite(elem: Treeifyable.Elem\u003cE\u003e, elementName: String?, showFieldNames: Boolean): Tree =\n    when(elem.value) {\n      is PersonBorn -\u003e\n        when (val p = super.treeifyComposite(elem, elementName, showFieldNames)) {\n          is Tree.Apply -\u003e p.copy(body = p.body.asSequence().toList().filter { it.elementName != \"born\" }.iterator())\n          else -\u003e error(\"Expected Tree.Apply\")\n        }\n      else -\u003e super.treeifyComposite(elem, elementName, showFieldNames)\n    }\n}\n\nval p = PersonBorn(\"Joe\", 1234567890)\nprintln(CustomPPrinter6\u003cPersonBorn\u003e(PersonBorn.serializer(), PPrinterConfig()).invoke(p))\n//\u003e PersonBorn(name = \"Joe\")\n```\n\n#### Sealed Hierarchies in Kotlin Multiplatform\n\nAccording to the `kotlinx-serialization` documentation, every member of a sealed hierarchy must be annotated with `@Serializable`.\nFor example, in the following hierarchy:\n```kotlin\n@Serializable\nsealed interface Colors {\n  @Serializable object Red : Colors\n  @Serializable object Green : Colors\n  @Serializable object Blue : Colors\n  @Serializable data class Custom(val value: String) : Colors\n}\n```\nEvery member is annotated with `@Serializable`.\n\nThis requirement extends to PPrint-Multiplatform as well since it relies on `kotlinx-serialization` \nto traverse the hierarchy.\n\n## Manual Printing in Kotlin Multiplatform\n\nIf you want to print values with a simple and well-defined structure in Kotlin Multiplatform, you can use the the PPrinterManual class.\nFor example:\n```kotlin\ndata class Person(val name: String, val age: Int)\n\nclass CustomPPrinter7 : PPrinterManual\u003cPerson\u003e() {\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree =\n    when (x) {\n      is Person -\u003e Tree.Apply(\"Person\", listOf(Tree.KeyValue(\"name\", Tree.Literal(x.name)), Tree.KeyValue(\"age\", Tree.Literal(x.age))).iterator())\n      else -\u003e super.treeify(x, elementName, escapeUnicode, showFieldNames) // will just use Tree.Literal(x.toString())\n    }\n}\n\nval p = Person(\"Joe\", 42)\nprintln(CustomPPrinter7().invoke(p))\n//\u003e Person(name = \"Joe\", age = 42)\n```\n\nFrequently, it will be useful to combine manual printers and automatic printers. For example:\n```kotlin\ndata class PersonFavorite(val name: String, val age: Int, val favoriteColor: Colors) /// See the Sealed Hierarchies section above\n\nclass ColorsPrinter(config: PPrinterConfig): PPrinter\u003cColors\u003e(Colors.serializer(), config)\n\nclass CustomPPrinter7(config: PPrinterConfig): PPrinterManual\u003cAny?\u003e(config) {\n  fun treeifyThis(x: Any?, elementName: String?) =\n    treeify(x, elementName, config.defaultEscapeUnicode, config.defaultShowFieldNames)\n\n  override fun treeify(x: Any?, elementName: String?, escapeUnicode: Boolean, showFieldNames: Boolean): Tree =\n    when (x) {\n      is PersonFavorite -\u003e\n        Tree.Apply(\n          \"PersonFavorite\",\n          iteratorOf(treeifyThis(x.name, \"name\"), treeifyThis(x.age, \"age\"), treeifyThis(x.favoriteColor, \"favoriteColor\")),\n          elementName\n        )\n      is Colors -\u003e ColorsPrinter(config).treeify(x, elementName, escapeUnicode, showFieldNames)\n      else -\u003e super.treeify(x, elementName, escapeUnicode, showFieldNames)\n    }\n}\n\nval joe = PersonFavorite(\"Joe\", 123, Colors.Custom(\"FF0000\"))\nval printer = CustomPPrinter7(PPrinterConfig())\nval p = printer(joe)\nprintln(p)\n//\u003e PersonFavorite(\"Joe\", 123, Custom(value = \"FF0000\"))\n```\n\n#### How do deal with Custom Fields in Kotlin Multiplatform\n\nIn general whenever you have a atom-property i.e. something not generic you can just mark the field as @Contextual\nso long as there is a specific case defined for it in `treeifyWith`. However if you are using a type such as\na collection that has a generic element requring its own serializer, you will need to use the\n`@Serializable(with = CustomSerializer::class)` syntax and define a `CustomSerializer` for the type.\nWhat is important to note is that `CustomSerializer` does not actually need a serialization implementation,\nyou it is just needed in order to be able to carry around the serializer for the generic type. For example,\nthe serializer for `Sequence` is defined as:\n```kotlin\nclass PPrintSequenceSerializer\u003cT\u003e(val element: KSerializer\u003cT\u003e) : KSerializer\u003cSequence\u003cT\u003e\u003e {\n  override val descriptor: SerialDescriptor = element.descriptor\n  override fun serialize(encoder: Encoder, value: Sequence\u003cT\u003e) = throw IllegalStateException(\"...\")\n  override fun deserialize(decoder: Decoder) = throw IllegalStateException(\"...\")\n}\n```\n(Note that a real user-defined serialzier for `Sequence` will work as well.)\n\n#### General Note on Generic ADTs and KMP\n\nDue to issues in kotlinx-serialization like [#1341](https://github.com/Kotlin/kotlinx.serialization/issues/1341) there are cases\nwhere kotlinx-serialization will not be able to serialize a generic ADT (GADT). This is inherently a problem for PPrint-KMP since it relies\non kotlinx-serialization to traverse the ADT. In general, if you are having trouble with a GADT, may need to define a custom serializer.\n\nFor example if you attempt to fully-type a partially-typed GADT element with a collection-type and then widen it\nto the GADT-root type you'll get some serious problems:\n```kotlin\n@Serializable\nsealed interface Root\u003cA, B\u003e\n@Serializable\ndata class Parent\u003cA, B\u003e(val child: Root\u003cA, B\u003e): Root\u003cA, B\u003e\n@Serializable\ndata class PartiallyTyped\u003cA\u003e(val value: A): Root\u003cA, String\u003e\n\nfun gadt() {\n  val value = Parent(PartiallyTyped(listOf(1,2,3)))\n  println(pprint(value))\n  // ========= Boom! =========\n  // Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'ArrayList' is not found in the polymorphic scope of 'Any'.\n}\n```\nI've made some comments on this issue [here](https://github.com/Kotlin/kotlinx.serialization/issues/1341#issuecomment-1920511403).\n\n# Fansi for Kotlin\nPPrint is powered by Fansi. It relies on this amazing library in order to be able to print out ansi-colored strings.\n\n\u003e NOTE. Most of this is taken from the original Fansi documentation [here](https://raw.githubusercontent.com/com-lihaoyi/fansi/master/readme.md)\n\nFansi is a Kotlin library (ported from Scala) that was designed make it easy to deal with fancy colored Ansi\nstrings within your command-line programs.\n\nWhile \"normal\" use of Ansi escapes with `java.lang.String`, you find yourself\nconcatenating colors:\n\n```scala\nval colored = Console.RED + \"Hello World Ansi!\" + Console.RESET\n```\n\nTo build your colored string. This works the first time, but is error prone\non larger strings: e.g. did you remember to put a `Console.RESET` where it's\nnecessary? Do you need to end with one to avoid leaking the color to the entire\nconsole after printing it?\n\nFurthermore, some operations are fundamentally difficult or error-prone with\nthis approach. For example,\n\n```scala\nval colored: String = Console.RED + \"Hello World Ansi!\" + Console.RESET\n\n// How to efficiently get the length of this string on-screen? We could try\n// using regexes to remove and Ansi codes, but that's slow and inefficient.\n// And it's easy to accidentally call `colored.length` and get a invalid length\nval length = ???\n\n// How to make the word `World` blue, while preserving the coloring of the\n// `Ansi!` text after? What if the string came from somewhere else and you\n// don't know what color that text was originally?\nval blueWorld = ???\n\n// What if I want to underline \"World\" instead of changing it's color, while\n// still preserving the original color?\nval underlinedWorld = ???\n\n// What if I want to apply underlines to \"World\" and the two characters on\n// either side, after I had already turned \"World\" blue?\nval underlinedBlue = ???\n```\n\nWhile simple to describe, these tasks are all error-prone and difficult to\ndo using normal `java.lang.String`s containing Ansi color codes. This is\nespecially so if, unlike the toy example above, `colored` is coming from some\nother part of your program and you're not sure what or how-many Ansi color\ncodes it already contains.\n\nWith Fansi, doing all these tasks is simple, error-proof and efficient:\n\n```scala\nval colored: fansi.Str = fansi.Color.Red(\"Hello World Ansi!\")\n// Or fansi.Str(\"Hello World Ansi!\").overlay(fansi.Color.Red)\n\nval length = colored.length // Fast and returns the non-colored length of string\n\nval blueWorld = colored.overlay(fansi.Color.Blue, 6, 11)\n\nval underlinedWorld = colored.overlay(fansi.Underlined.On, 6, 11)\n\nval underlinedBlue = blueWorld.overlay(fansi.Underlined.On, 4, 13)\n```\n\nAnd it just works:\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/d9b14cff-0527-41b7-96a7-25aa616f76aa)\n\nWhy Fansi?\n----------\n\nUnlike normal `java.lang.String`s with Ansi escapes embedded inside,\n`fansi.Str` allows you to perform a range of operations in an efficient\nmanner:\n\n- Extracting the non-Ansi `plainText` version of the string\n\n- Get the non-Ansi `length`\n\n- Concatenate colored Ansi strings without worrying about leaking\n  colors between them\n\n- Applying colors to certain portions of an existing `fansi.Str`,\n  and ensuring that the newly-applied colors get properly terminated\n  while existing colors are unchanged\n\n- Splitting colored Ansi strings at a `plainText` index\n\n- Rendering to colored `java.lang.String`s with Ansi escapes embedded,\n  which can be passed around or concatenated without worrying about\n  leaking colors.\n\nThese are tasks which are possible to do with normal `java.lang.String`,\nbut are tedious, error-prone and typically inefficient. Often, you can get\nby with adding copious amounts of `Console.RESET`s when working with colored\n`java.lang.String`s, but even that easily results in errors when you `RESET`\ntoo much and stomp over colors that already exist:\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/792d08b6-4594-477f-acfb-e095c921e5e9)\n\n\n`fansi.Str` allows you to perform these tasks safely and easily:\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/41a916dd-0605-4879-8ad2-b49c2516461c)\n\nFansi is also very efficient: `fansi.Str` uses just 3x as much memory as\n`java.lang.String` to hold all the additional formatting information.\n\n\u003e Note this was the case in Scala, I am not certain if the same is true in Kotlin.\n\nIts operations are probably about the same factor slower, as they are all\nimplemented using fast `arraycopy`s and while-loops similar to\n`java.lang.String`. That means that - unlike fiddling with Ansi-codes using\nregexes - you generally do not need to worry about performance when dealing with\n`fansi.Str`s. Just treat them as you would `java.lang.String`s: splitting them,\n`substring`ing them, and applying or removing colors or other styles at-will.\n\nUsing Fansi\n-----------\n\nThe main operations you need to know are:\n\n- Str(raw: CharSequence): fansi.String`, to construct colored\n  Ansi strings from a `java.lang.String`, with or without existing Ansi\n  color codes inside it.\n\n- `Str`, the primary data-type that you will use to pass-around\n  colored Ansi strings and manipulate them: concatenating, splitting,\n  applying or removing colors, etc.\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/46422458-8406-459c-bd44-578f1465a9ea)\n\n- `fansi.Attr`s are the individual modifications you can make to an\n  `fansi.Str`'s formatting. Examples are:\n    - `fansi.Bold.{On, Off}`\n    - `fansi.Reversed.{On, Off}`\n    - `fansi.Underlined.{On, Off}`\n    - `fansi.Color.*`\n    - `fansi.Back.*`\n    - `fansi.Attr.Reset`\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/a505dc23-186c-450f-8dd6-2af5616c0420)\n\n- `fansi.Attrs` represents a group of zero or more `fansi.Attr`s.\n  These that can be passed around together, combined via `++` or applied\n  to `fansi.Str`s all at once. Any individual `fansi.Attr` can be used\n  when `fansi.Attrs` is required, as can `fansi.Attrs.empty`.\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/b9c67518-0e85-4a27-9dca-14449920f983)\n\n- Using any of the `fansi.Attr` or `fansi.Attrs` mentioned above, e.g.\n  `fansi.Color.Red`, using `fansi.Color.Red(\"hello world ansi!\")` to create a\n  `fansi.Str` with that text and color, or\n  `fansi.Str(\"hello world ansi!\").overlay(fansi.Color.Blue, 6, 11)`\n\n- `.render` to convert a `fansi.Str` back into a `java.lang.String` with all\n  necessary Ansi color codes within it\n\nFansi also supports 8-bit 256-colors through `fansi.Color.Full` and\n`fansi.Back.Full`, as well as 24-bit 16-million-colors through\n`fansi.Color.True` and `fansi.Back.True`:\n\n![image](https://github.com/deusaquilus/pprint-kotlin/assets/1369480/f1c5c6b8-597c-448f-9df2-d19698d2ca16)\n\nNote that Fansi only performs the rendering of the colors to an ANSI-encoded\nstring. Final rendering will depend on whichever terminal you print the string\nto, whether it is able to display these sets of colors or not.\n\n_Thanks so much to Li Haoyi for building Fansi and PPrint!!_\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FExoQuery%2Fpprint-kotlin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FExoQuery%2Fpprint-kotlin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FExoQuery%2Fpprint-kotlin/lists"}