Skip to content

Instantly share code, notes, and snippets.

@myDisconnect
Forked from carymrobbins/pretty-print.scala
Last active April 10, 2022 17:21
Show Gist options
  • Save myDisconnect/1f7046b23e18b4b43dd3c5932d0db7dc to your computer and use it in GitHub Desktop.
Save myDisconnect/1f7046b23e18b4b43dd3c5932d0db7dc to your computer and use it in GitHub Desktop.
Pretty print Scala case classes and other data structures.
/*
* Original author's work: @see https://gist.github.com/carymrobbins/7b8ed52cd6ea186dbdf8
* - 2019-07-18 Added support for Option and Map types
* - 2019-11-25 Fixed Option types spacing
*/
object ClassUtils {
/**
* Pretty prints a Scala value similar to its source represention.
* Particularly useful for case classes.
*
* @param a - The value to pretty print.
* @param indentSize - Number of spaces for each indent.
* @param maxElementWidth - Largest element size before wrapping.
* @param depth - Initial depth to pretty print indents.
* @return
*/
def prettyPrint(a: Any, indentSize: Int = 2, maxElementWidth: Int = 30, depth: Int = 0): String = {
val indent = " " * depth * indentSize
val fieldIndent = indent + (" " * indentSize)
val nextDepth = prettyPrint(_: Any, indentSize, maxElementWidth, depth + 1)
a match {
case s: String =>
val replaceMap = Seq(
"\n" -> "\\n",
"\r" -> "\\r",
"\t" -> "\\t",
"\"" -> "\\\""
)
'"' + replaceMap.foldLeft(s) { case (acc, (c, r)) => acc.replace(c, r) } + '"'
case opt: Some[_] =>
val resultOneLine = s"Some(${nextDepth(opt.get)})"
if (resultOneLine.length <= maxElementWidth) return resultOneLine
s"Some(\n$fieldIndent${nextDepth(opt.get)}\n$indent)"
case xs: Seq[_] if xs.isEmpty =>
xs.toString()
case map: Map[_, _] if map.isEmpty =>
map.toString()
case xs: Map[_, _] =>
val result = xs.map { case (key, value) => s"\n$fieldIndent${nextDepth(key)} -> ${nextDepth(value)}" }.toString
"Map" + s"${result.substring(0, result.length - 1)}\n$indent)".substring(4)
// Make Strings look similar to their literal form.
// For an empty Seq just use its normal String representation.
case xs: Seq[_] =>
// If the Seq is not too long, pretty print on one line.
val resultOneLine = xs.map(nextDepth).toString()
if (resultOneLine.length <= maxElementWidth) return resultOneLine
// Otherwise, build it with newlines and proper field indents.
val result = xs.map(x => s"\n$fieldIndent${nextDepth(x)}").toString()
result.substring(0, result.length - 1) + "\n" + indent + ")"
// Product should cover case classes.
case p: Product =>
val prefix = p.productPrefix
// We'll use reflection to get the constructor arg names and values.
val cls = p.getClass
val fields = cls.getDeclaredFields.filterNot(_.isSynthetic).map(_.getName)
val values = p.productIterator.toSeq
// If we weren't able to match up fields/values, fall back to toString.
if (fields.length != values.length) return p.toString
fields.zip(values).toList match {
// If there are no fields, just use the normal String representation.
case Nil => p.toString
// If there is more than one field, build up the field names and values.
case kvps =>
val prettyFields = kvps.map { case (k, v) => s"$k = ${nextDepth(v)}" }
// If the result is not too long, pretty print on one line.
val resultOneLine = s"$prefix(${prettyFields.mkString(", ")})"
if (resultOneLine.length <= maxElementWidth) return resultOneLine
// Otherwise, build it with newlines and proper field indents.
s"$prefix(\n${kvps.map { case (k, v) => s"$fieldIndent$k = ${nextDepth(v)}" }.mkString(",\n")}\n$indent)"
}
// If we haven't specialized this type, just use its toString.
case _ => a.toString
}
}
}
@redsk
Copy link

redsk commented Feb 22, 2022

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment