Add the following to your build.gradle
.
repositories {
maven { url "https://dl.bintray.com/jannis/kotlin-pretty" }
}
dependencies {
implementation "kotlin-pretty:kotlin-pretty:0.6.0"
}
In this section we will go over creating a very basic rich document for a datatype and how to render it. This will not be the prettiest or best layout possible, just a simple example to get started.
Suppose we have an error-report datatype:
data class ErrReport(
// what happened
val errorMsg: String,
// in which file it happened
val file: String,
// at what line, or which lines did it happen
val lineSpan: Pair<Int, Int>
)
Now we want to render this to a responsive document which adapts to the maximum width that we give it. In order to do so we will define a method doc
.
import pretty.*
import pretty.symbols.colon
fun ErrReport.doc(): Doc<Nothing> = "Error in file".text() spaced
file.text() spaced
"at".text() spaced
lineSpan.prettyLineSpan() + colon() + hardLine() +
errorMsg.reflow()
fun Pair<Int, Int>.prettyLineSpan(): Doc<Nothing> =
if (first == second) "line $first".text()
else if (first > second) "line $second to line $first".text()
else "line $first to line $second".text()
There is a lot going on here, especially with all those infix functions, so let us go over it one by one:
The type signature ErrReport.doc()
hints that we return Doc<Nothing>
, this means the resulting document is not annotated. The type parameter refers to annotations, you can learn more about that here. For now that is not important as we will not be using annotations yet.
Next up is file.text()
: This calls the extension function String.text(): Doc<Nothing>
which converts a string to an un-annotated document.
This method has an invariant that it will never be used on a string with a newline. If it is, it will break the layout algorithm and will not produce the best result. Use
String.doc()
instead which properly handles newlines, but comes at a performance cost.
The resulting document is then combined with one of the most basic combinators of kotlin-pretty called spaced
. As the name suggests it takes two documents and inserts one space between them.
Because rendering a pair of integers needs some additional logic we define another extension function here to help deal with the special cases where the numbers two are in wrong order or are equal.
The next interesting functions are colon
and hardline
. colon
refers to the string :
and hardline
to a newline which will never be flattened. TODO add link to learn more about flattening here.
Lastly we have the function String.reflow()
which replaces each space with a softLine
which the layout algorithm can either keep as a space or replace with a newline.
This completes our function which turns an error report into a rich, structured document.
Now that we have a way to get a rich document, we need a way to actually render it and for the sake of simplicity we are simply going to render it to a string.
The first step is to turn our document Doc
into a simplified SimpleDoc
which represents a stream of tokens that encode the final structure. This is done using the built-in method Doc<A>.layoutPretty(pw: PageWidth): SimpleDoc<A>
. The argument PageWidth
is a simple datatype that encodes the width constraints for rendering:
sealed class PageWidth {
// no width constraints
object Unbounded : PageWidth()
data class Available(
// The max width which the algorithm will try and not exceed
val maxWidth: Int,
// A fraction which will be applied to the current column width to tweak the max-width setting.
// The layout algorithm will then try to not exceed columns of size maxWidth * ribbonFrac
val ribbonFrac: Float
): PageWidth()
}
Now that we have a SimpleDoc
layout all that is left to do is rendering it. To render any SimpleDoc<A>
as a string, kotlin-pretty defines an extension method SimpleDoc<A>.renderString(): String
.
val report = ErrReport("Failed to do some very important task", "someFile.txt", 4 to 10)
report
.doc()
.layoutPretty(PageWidth.Available(80, 0.5F))
.renderString()
// Error in file someFile.txt at line 4 to line 10:
// Failed to do some very important task
And with a much smaller width:
report.doc()
.layoutPretty(PageWidth.Available(20, 0.5F))
.renderString()
// Error in file someFile.txt at line 4 to line 10:
// Failed to
// do some
// very
// important
// task
With the larger max width the input stays on two lines and nicely fits the required max-width. However it fails to fit the ribbon-width (80 * 0.5
in that case), but because there are no alternatives with more newlines the algorithm cannot choose a better layout. With the second much more constrained render this becomes even more obvious. The first line does not fit in 20 characters, but because there is no better layout it has to render it like that.
To make a better layout we should have presented the layout algorithm with more options by using something like softline
which can be either a space or a newline. You can learn more about those other options in the other sections of the docs.
TODO add sections here: