Skip to content

Commit

Permalink
Make variable priting configurable.
Browse files Browse the repository at this point in the history
This commit makes it possible to customize pretty-printing of captured variables.
It's possible to tweak pretty-printing based on
- runtime type of binder
- string representation of static type of binder
- position of binder in either enclosing statment or section

This change required improving the handling of blank lines so that users
that customize pretty-printing don't fiddle with brittle `\n`
manipulation.
  • Loading branch information
olafurpg committed Sep 17, 2018
1 parent c419558 commit 4de0dc4
Show file tree
Hide file tree
Showing 14 changed files with 317 additions and 87 deletions.
2 changes: 1 addition & 1 deletion docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ object Main {
// generate out/readme.md from working directory
val exitCode = mdoc.Main.process(settings)
// (optional) exit the main function with exit code 0 (success) or 1 (error)
sys.exit(exitCode)
if (exitCode != 0) sys.exit(exitCode)
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion mdoc-docs/src/main/scala/mdoc/docs/Docs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ object Docs {
)
)
)
sys.exit(exitCode)
if (exitCode != 0) sys.exit(exitCode)
}
}
2 changes: 1 addition & 1 deletion mdoc/src/main/scala/mdoc/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Main {

def main(args: Array[String]): Unit = {
val code = process(args, System.out, PathIO.workingDirectory.toNIO)
sys.exit(code)
if (code != 0) sys.exit(code)
}

def process(args: Array[String], out: PrintStream, cwd: Path): Int = {
Expand Down
3 changes: 3 additions & 0 deletions mdoc/src/main/scala/mdoc/MainSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ final class MainSettings private (
def withHeaderIdGenerator(headerIdGenerator: String => String): MainSettings = {
copy(settings.copy(headerIdGenerator = headerIdGenerator))
}
def withVariablePrinter(variablePrinter: Variable => String): MainSettings = {
copy(settings.copy(variablePrinter = variablePrinter))
}

private[this] implicit def cwd: AbsolutePath = settings.cwd
private[this] def copy(
Expand Down
61 changes: 61 additions & 0 deletions mdoc/src/main/scala/mdoc/Variable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package mdoc

import mdoc.internal.markdown.ReplVariablePrinter

/**
* A captured variable in a code fence.
*
* Example, the code fence below has two statements:
*
* - The first statement has a single variable with name `x`
* - The second statement has a two variables with names `y` and `z`
*
* {{{
* ```scala mdoc
* val x = 1
* val (y, z) = (2, 3)
* ```
* }}}
*
* @param name the variable name, for example `x`
* @param staticType the pretty-printed static type of this variable, for example `List[Int]`
* @param runtimeValue the runtime value of this variable.
* @param indexOfVariableInStatement the index of this variable in the enclosing statement.
* For example, in `val (a, b) = ???` the variable `a` has index
* 0 and variable `b` has index `1`.
* @param totalVariablesInStatement The total number of variables in this statements. For example,
* in `val a = N` the total number is 1 and for `val (a, b) = ...` the
* total number is 2.
* @param indexOfStatementInCodeFence the index of the enclosing statement within the enclosing code fence.
* For example, in
* {{{
* ```scala
* val x = 1
* val y = 2
* ```
* }}}
* The variable `y` has index 1 and variable `x` has index 0.
* @param totalStatementsInCodeFence the total number of statement in the enclosing code fence.
* For example, the total number is 2 for the code fence below.
* {{{
* ```scala
* val x = 1
* val y = 2
* ```
* }}}
*
*/
final class Variable private[mdoc] (
val name: String,
val staticType: String,
val runtimeValue: Any,
val indexOfVariableInStatement: Int,
val totalVariablesInStatement: Int,
val indexOfStatementInCodeFence: Int,
val totalStatementsInCodeFence: Int
) {
def isUnit: Boolean = staticType.endsWith("Unit")
override def toString: String = {
ReplVariablePrinter(this)
}
}
16 changes: 12 additions & 4 deletions mdoc/src/main/scala/mdoc/internal/cli/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.InvalidPathException
import java.nio.file.PathMatcher

import metaconfig.Conf
import metaconfig.ConfDecoder
import metaconfig.ConfEncoder
Expand All @@ -18,15 +19,15 @@ import metaconfig.annotation._
import metaconfig.generic
import metaconfig.generic.Surface
import org.typelevel.paiges.Doc

import scala.annotation.StaticAnnotation
import scala.meta.internal.io.PathIO
import scala.meta.io.AbsolutePath
import scala.meta.io.RelativePath
import mdoc.StringModifier
import mdoc.Variable
import mdoc.Reporter
import mdoc.internal.BuildInfo
import mdoc.internal.markdown.GitHubIdGenerator
import mdoc.internal.markdown.MarkdownCompiler
import mdoc.internal.markdown.{ReplVariablePrinter, GitHubIdGenerator, MarkdownCompiler}

class Section(val name: String) extends StaticAnnotation

Expand Down Expand Up @@ -102,7 +103,10 @@ case class Settings(
inputStream: InputStream = System.in,
@Hidden()
@Description("The generator for header IDs, defaults to GitHub ID generator")
headerIdGenerator: String => String = GitHubIdGenerator
headerIdGenerator: String => String = GitHubIdGenerator,
@Hidden()
@Description("The pretty printer for variables")
variablePrinter: Variable => String = ReplVariablePrinter
) {
def isFileWatching: Boolean = watch && !check

Expand Down Expand Up @@ -208,6 +212,8 @@ object Settings extends MetaconfigScalametaImplicits {
ConfDecoder.stringConfDecoder.map(_ => System.in)
implicit val headerIdGeneratorDecoder: ConfDecoder[String => String] =
ConfDecoder.stringConfDecoder.flatMap(_ => ConfError.message("unsupported").notOk)
implicit val variablePrinterDecoder: ConfDecoder[Variable => String] =
ConfDecoder.stringConfDecoder.flatMap(_ => ConfError.message("unsupported").notOk)

implicit val pathEncoder: ConfEncoder[AbsolutePath] =
ConfEncoder.StringEncoder.contramap { path =>
Expand All @@ -222,5 +228,7 @@ object Settings extends MetaconfigScalametaImplicits {
ConfEncoder.StringEncoder.contramap(_ => "<input stream>")
implicit val headerIdGeneratorEncoder: ConfEncoder[String => String] =
ConfEncoder.StringEncoder.contramap(_ => "<String => String>")
implicit val variablePrinterEncoder: ConfEncoder[Variable => String] =
ConfEncoder.StringEncoder.contramap(_ => "<Variable => String>")

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor {
mod match {
case Modifier.Silent =>
case Modifier.Default | Modifier.Fail =>
val str = Renderer.renderEvaluatedSection(rendered, section, ctx.reporter)
val str = Renderer.renderEvaluatedSection(
rendered,
section,
ctx.reporter,
ctx.settings.variablePrinter
)
val content: BasedSequence = CharSubSequence.of(str)
block.setContent(List(content).asJava)
case Modifier.Passthrough =>
Expand Down
130 changes: 65 additions & 65 deletions mdoc/src/main/scala/mdoc/internal/markdown/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@ package mdoc.internal.markdown

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.charset.StandardCharsets
import scala.meta._
import scala.meta.inputs.Position
import mdoc.Reporter
import mdoc.Variable
import mdoc.document.CompileResult
import mdoc.document.CrashResult
import mdoc.document.CrashResult.Crashed
import mdoc.internal.document.MdocExceptions
import mdoc.internal.pos.PositionSyntax._
import mdoc.internal.pos.TokenEditDistance
import scala.meta._
import scala.meta.inputs.Position

object Renderer {

def render(
sections: List[Input],
compiler: MarkdownCompiler,
reporter: Reporter,
filename: String
filename: String,
printer: Variable => String
): String = {
val inputs =
sections.map(s => SectionInput(s, dialects.Sbt1(s).parse[Source].get, Modifier.Default))
Expand All @@ -30,7 +29,7 @@ object Renderer {
MarkdownCompiler.buildDocument(compiler, reporter, inputs, instrumented, filename)
doc.sections
.map(s => s"""```scala
|${Renderer.renderEvaluatedSection(doc, s, reporter)}
|${Renderer.renderEvaluatedSection(doc, s, reporter, printer)}
|```""".stripMargin)
.mkString("\n")
}
Expand Down Expand Up @@ -66,93 +65,94 @@ object Renderer {
}

def appendMultiline(sb: PrintStream, string: String): Unit = {
val isTrailing = string.endsWith("\n")
appendMultiline(sb, string, string.length)
}
def appendMultiline(sb: PrintStream, string: String, N: Int): Unit = {
var i = 0
val N = if (isTrailing) string.length - 1 else string.length
while (i < N) {
string.charAt(i) match {
case '\n' =>
sb.append("\n// ")
case ch => sb.append(ch)
case ch =>
sb.append(ch)
}
i += 1
}
}

def appendFreshMultiline(sb: PrintStream, string: String): Unit = {
val N = string.length - (if (string.endsWith("\n")) 1 else 0)
sb.append("// ")
appendMultiline(sb, string)
appendMultiline(sb, string, N)
}

def renderEvaluatedSection(
doc: EvaluatedDocument,
section: EvaluatedSection,
reporter: Reporter
reporter: Reporter,
printer: Variable => String
): String = {
val baos = new ByteArrayOutputStream()
val sb = new PrintStream(baos)
var first = true
section.section.statements.zip(section.source.stats).foreach {
case (statement, tree) =>
if (first) {
first = false
} else {
sb.append("\n")
}
val stats = section.source.stats.lift
val totalStats = section.source.stats.length
section.section.statements.zip(section.source.stats).zipWithIndex.foreach {
case ((statement, tree), statementIndex) =>
val pos = tree.pos
val leadingBlankLines = stats(statementIndex - 1) match {
case None =>
0
case Some(previousStatement) =>
tree.pos.startLine - previousStatement.pos.endLine
}
sb.append("\n" * leadingBlankLines)
val endOfLinePosition =
Position.Range(pos.input, pos.startLine, pos.startColumn, pos.endLine, Int.MaxValue)
sb.append(endOfLinePosition.text)
if (statement.out.nonEmpty) {
sb.append("\n")
appendFreshMultiline(sb, statement.out)
}
sb.append("\n")

statement.binders.foreach { binder =>
section.mod match {
case Modifier.Fail =>
binder.value match {
case CompileResult.TypecheckedOK(_, tpe, tpos) =>
reporter.error(
tpos.toMeta(section),
s"Expected compile error but statement type-checked successfully"
)
appendMultiline(sb, tpe)
case CompileResult.ParseError(msg, tpos) =>
appendFreshMultiline(sb, tpos.formatMessage(section, msg))
case CompileResult.TypeError(msg, tpos) =>
val mpos = tpos.toMeta(section)
appendFreshMultiline(sb, tpos.formatMessage(section, msg))
case _ =>
val obtained = pprint.PPrinter.BlackWhite.apply(binder).toString()
throw new IllegalArgumentException(
s"Expected Macros.CompileResult." +
s"Obtained $obtained"
)
}
case Modifier.Default | Modifier.Passthrough =>
val lines = pprint.PPrinter.BlackWhite.tokenize(binder.value)
if (binder.name.startsWith("res") && binder.tpe.render == "Unit") {
() // do nothing
} else {
sb.append("// ")
.append(binder.name)
.append(": ")
.append(binder.tpe.render)
.append(" = ")
lines.foreach { lineStr =>
val line = lineStr.plainText
appendMultiline(sb, line)
val N = statement.binders.length
statement.binders.zipWithIndex.foreach {
case (binder, i) =>
section.mod match {
case Modifier.Fail =>
sb.append('\n')
binder.value match {
case CompileResult.TypecheckedOK(_, tpe, tpos) =>
reporter.error(
tpos.toMeta(section),
s"Expected compile error but statement type-checked successfully"
)
appendMultiline(sb, tpe)
case CompileResult.ParseError(msg, tpos) =>
appendFreshMultiline(sb, tpos.formatMessage(section, msg))
case CompileResult.TypeError(msg, tpos) =>
appendFreshMultiline(sb, tpos.formatMessage(section, msg))
case _ =>
val obtained = pprint.PPrinter.BlackWhite.apply(binder).toString()
throw new IllegalArgumentException(
s"Expected Macros.CompileResult." +
s"Obtained $obtained"
)
}
sb.append("\n")
}

case Modifier.Crash =>
throw new IllegalArgumentException(Modifier.Crash.toString)
case c @ (Modifier.Str(_, _) | Modifier.Silent) =>
throw new IllegalArgumentException(c.toString)
}
case Modifier.Default | Modifier.Passthrough =>
val variable = new mdoc.Variable(
binder.name,
binder.tpe.render,
binder.value,
i,
N,
statementIndex,
totalStats
)
sb.append(printer(variable))
case Modifier.Crash =>
throw new IllegalArgumentException(Modifier.Crash.toString)
case c @ (Modifier.Str(_, _) | Modifier.Silent) =>
throw new IllegalArgumentException(c.toString)
}
}
}
baos.toString.trim
Expand Down
Loading

0 comments on commit 4de0dc4

Please sign in to comment.