-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #198 from olafurpg/parse
Use custom parser for markdown processing.
- Loading branch information
Showing
23 changed files
with
449 additions
and
245 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
mdoc/src/main/scala/mdoc/internal/markdown/FenceInput.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package mdoc.internal.markdown | ||
|
||
import com.vladsch.flexmark.ast.FencedCodeBlock | ||
import scala.meta.inputs.Input | ||
import scala.meta.inputs.Position | ||
import mdoc.internal.cli.Context | ||
import mdoc.internal.markdown.Modifier.Str | ||
import mdoc.internal.markdown.Modifier.Default | ||
import mdoc.internal.markdown.Modifier.Post | ||
import mdoc.internal.markdown.Modifier.Pre | ||
|
||
case class PreFenceInput(block: CodeFence, input: Input, mod: Pre) | ||
case class StringFenceInput(block: CodeFence, input: Input, mod: Str) | ||
case class ScalaFenceInput(block: CodeFence, input: Input, mod: Modifier) | ||
|
||
class FenceInput(ctx: Context, baseInput: Input) { | ||
def getModifier(info: Text): Option[Modifier] = { | ||
val string = info.value.stripLineEnd | ||
if (!string.startsWith("scala mdoc")) None | ||
else { | ||
if (!string.contains(':')) Some(Modifier.Default()) | ||
else { | ||
val mode = string.stripPrefix("scala mdoc:") | ||
Modifier(mode) | ||
.orElse { | ||
val (name, info) = mode.split(":", 2) match { | ||
case Array(a) => (a, "") | ||
case Array(a, b) => (a, b) | ||
} | ||
ctx.settings.stringModifiers | ||
.collectFirst[Modifier] { | ||
case mod if mod.name == name => | ||
Str(mod, info) | ||
} | ||
.orElse { | ||
ctx.settings.postModifiers.collectFirst { | ||
case mod if mod.name == name => | ||
Post(mod, info) | ||
} | ||
} | ||
.orElse { | ||
ctx.settings.preModifiers.collectFirst { | ||
case mod if mod.name == name => | ||
Pre(mod, info) | ||
} | ||
} | ||
} | ||
.orElse { | ||
invalid(info, s"Invalid mode '$mode'") | ||
None | ||
} | ||
} | ||
} | ||
} | ||
|
||
private def invalid(info: Text, message: String): Unit = { | ||
val offset = "scala mdoc:".length | ||
val start = info.pos.start + offset | ||
val end = info.pos.end - 1 | ||
val pos = Position.Range(baseInput, start, end) | ||
ctx.reporter.error(pos, message) | ||
} | ||
private def invalidCombination(info: Text, mod1: String, mod2: String): Boolean = { | ||
invalid(info, s"invalid combination of modifiers '$mod1' and '$mod2'") | ||
false | ||
} | ||
|
||
private def isValid(info: Text, mod: Modifier): Boolean = { | ||
if (mod.isFail && mod.isCrash) { | ||
invalidCombination(info, "crash", "fail") | ||
} else if (mod.isSilent && mod.isInvisible) { | ||
invalidCombination(info, "silent", "invisible") | ||
} else if (mod.isCompileOnly) { | ||
val others = mod.mods - Mod.CompileOnly | ||
if (others.isEmpty) { | ||
true | ||
} else { | ||
val all = others.map(_.toString.toLowerCase).mkString(", ") | ||
invalid( | ||
info, | ||
s"""compile-only cannot be used in combination with $all""" | ||
) | ||
false | ||
} | ||
} else { | ||
true | ||
} | ||
} | ||
def unapply(block: CodeFence): Option[ScalaFenceInput] = { | ||
getModifier(block.info) match { | ||
case Some(mod) => | ||
if (isValid(block.info, mod)) { | ||
val input = Input.Slice(baseInput, block.body.pos.start, block.body.pos.end) | ||
Some(ScalaFenceInput(block, input, mod)) | ||
} else { | ||
None | ||
} | ||
case _ => None | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
mdoc/src/main/scala/mdoc/internal/markdown/MarkdownFile.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package mdoc.internal.markdown | ||
|
||
import scala.meta.inputs.Position | ||
import scala.meta.inputs.Input | ||
import mdoc.Reporter | ||
import scala.collection.mutable | ||
import scala.tools.nsc.doc.DocParser | ||
import scala.meta.io.RelativePath | ||
|
||
final case class MarkdownFile(input: Input, relativePath: RelativePath, parts: List[MarkdownPart]) { | ||
private val appends = mutable.ListBuffer.empty[String] | ||
def appendText(text: String): Unit = { | ||
appends += text | ||
} | ||
def renderToString: String = { | ||
val out = new StringBuilder() | ||
parts.foreach(_.renderToString(out)) | ||
appends.foreach(a => out.append(a).append("\n")) | ||
out.toString() | ||
} | ||
} | ||
object MarkdownFile { | ||
sealed abstract class State | ||
object State { | ||
case class CodeFence(start: Int, backticks: String, info: String) extends State | ||
case object Text extends State | ||
} | ||
class Parser(input: Input, reporter: Reporter) { | ||
private val text = input.text | ||
private def newPos(start: Int, end: Int): Position = { | ||
Position.Range(input, start, end) | ||
} | ||
private def newText(start: Int, end: Int): Text = { | ||
val adaptedEnd = math.max(start, end) | ||
val part = Text(text.substring(start, adaptedEnd)) | ||
part.pos = newPos(start, adaptedEnd) | ||
part | ||
} | ||
private def newCodeFence( | ||
state: State.CodeFence, | ||
backtickStart: Int, | ||
backtickEnd: Int | ||
): CodeFence = { | ||
val open = newText(state.start, state.start + state.backticks.length()) | ||
val info = newText(open.pos.end, open.pos.end + state.info.length()) | ||
val adaptedBacktickStart = math.max(0, backtickStart - 1) | ||
val body = newText(info.pos.end, adaptedBacktickStart) | ||
val close = newText(adaptedBacktickStart, backtickEnd) | ||
val part = CodeFence(open, info, body, close) | ||
part.pos = newPos(state.start, backtickEnd) | ||
part | ||
} | ||
def acceptParts(): List[MarkdownPart] = { | ||
var state: State = State.Text | ||
val parts = mutable.ListBuffer.empty[MarkdownPart] | ||
var curr = 0 | ||
text.linesWithSeparators.foreach { line => | ||
val end = curr + line.length() | ||
state match { | ||
case State.Text => | ||
if (line.startsWith("```")) { | ||
val backticks = line.takeWhile(_ == '`') | ||
val info = line.substring(backticks.length()) | ||
state = State.CodeFence(curr, backticks, info) | ||
} else { | ||
parts += newText(curr, end) | ||
} | ||
case s: State.CodeFence => | ||
if (line.startsWith(s.backticks) && | ||
line.forall(ch => ch == '`' || ch.isWhitespace)) { | ||
parts += newCodeFence(s, curr, end) | ||
state = State.Text | ||
} | ||
} | ||
curr += line.length() | ||
} | ||
state match { | ||
case s: State.CodeFence => | ||
parts += newCodeFence(s, text.length(), text.length()) | ||
case _ => | ||
} | ||
parts.toList | ||
} | ||
} | ||
def parse(input: Input, relativePath: RelativePath, reporter: Reporter): MarkdownFile = { | ||
val parser = new Parser(input, reporter) | ||
val parts = parser.acceptParts() | ||
MarkdownFile(input, relativePath, parts) | ||
} | ||
} | ||
|
||
sealed abstract class MarkdownPart { | ||
var pos: Position = Position.None | ||
final def renderToString(out: StringBuilder): Unit = this match { | ||
case Text(value) => | ||
out.append(value) | ||
case fence: CodeFence => | ||
fence.newPart match { | ||
case Some(newPart) => | ||
out.append(newPart) | ||
case None => | ||
fence.openBackticks.renderToString(out) | ||
fence.newInfo match { | ||
case None => | ||
fence.info.renderToString(out) | ||
case Some(newInfo) => | ||
out.append(newInfo) | ||
if (!newInfo.endsWith("\n")) { | ||
out.append("\n") | ||
} | ||
} | ||
fence.newBody match { | ||
case None => | ||
fence.body.renderToString(out) | ||
case Some(newBody) => | ||
out.append(newBody) | ||
} | ||
fence.closeBackticks.renderToString(out) | ||
} | ||
} | ||
} | ||
final case class Text(value: String) extends MarkdownPart | ||
final case class CodeFence(openBackticks: Text, info: Text, body: Text, closeBackticks: Text) | ||
extends MarkdownPart { | ||
var newPart = Option.empty[String] | ||
var newInfo = Option.empty[String] | ||
var newBody = Option.empty[String] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 0 additions & 16 deletions
16
mdoc/src/main/scala/mdoc/internal/markdown/MdocFormatterExtension.scala
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.