Split documentation comment into sections (#3347)

This commit is contained in:
Dmitry Bushev 2022-03-21 10:14:25 +03:00 committed by GitHub
parent cc7333812d
commit 9d402bd599
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1688 additions and 228 deletions

View File

@ -610,8 +610,10 @@ lazy val `docs-generator` = (project in file("lib/scala/docs-generator"))
inConfig(Benchmark)(Defaults.testSettings),
Benchmark / unmanagedSourceDirectories +=
baseDirectory.value.getParentFile / "bench" / "scala",
libraryDependencies +=
libraryDependencies ++= Seq(
"com.storm-enroute" %% "scalameter" % scalameterVersion % "bench",
"org.scalatest" %% "scalatest" % scalatestVersion % Test
),
testFrameworks := List(
new TestFramework("org.scalatest.tools.Framework"),
new TestFramework("org.scalameter.ScalaMeterFramework")

View File

@ -1,9 +1,10 @@
package org.enso.docs.generator
import java.io.File
import org.enso.syntax.text.{DocParser, Parser}
import org.enso.syntax.text.docparser._
import java.io.File
/** Defines useful wrappers for Doc Parser.
*/
object DocParserWrapper {

View File

@ -0,0 +1,151 @@
package org.enso.docs.sections
import cats.kernel.Monoid
import cats.syntax.compose._
import org.enso.syntax.text.ast.Doc
/** Combine the documentation into a list of [[ParsedSection]]s. */
final class ParsedSectionsBuilder {
import ParsedSectionsBuilder._
/** Build the parsed sections from the provided documentation comment.
*
* @param doc the parsed documentation comment.
* @return the list of parsed sections.
*/
def build(doc: Doc): List[ParsedSection] = {
val tagSections = doc.tags.map(buildTags)
val synopsisSections = doc.synopsis.map(buildSynopsis)
val bodySections = doc.body.map(buildBody)
Monoid.combineAll(tagSections ++ synopsisSections ++ bodySections)
}
/** Process the tags section of the documentation comment.
*
* @param tags the tags section
* @return the list of parsed sections
*/
private def buildTags(tags: Doc.Tags): List[ParsedSection] =
tags.elems.toList.map { tag =>
Section.Tag(tag.name, tag.details.map(_.trim).map(Doc.Elem.Text))
}
/** Process the synopsis section of the documentation comment.
*
* @param synopsis the synopsis section
* @return the list of parsed sections
*/
private def buildSynopsis(synopsis: Doc.Synopsis): List[ParsedSection] =
(joinSections _ >>> buildSections)(synopsis.elems.toList)
/** Process the body section of the documentation comment.
*
* @param body the body section
* @return the list of parsed sections
*/
private def buildBody(body: Doc.Body): List[ParsedSection] =
(joinSections _ >>> buildSections)(body.elems.toList)
/** Process the list of [[Doc.Section]] documentation sections.
*
* @param sections the list of parsed documentation sections
* @return the list of parsed sections
*/
private def buildSections(
sections: List[Doc.Section]
): List[ParsedSection] =
sections.map {
case Doc.Section.Raw(_, elems) =>
elems match {
case Doc.Elem.Text(text) :: t =>
val (key, value) = text.span(_ != const.COLON)
if (value.nonEmpty) {
val line = value.drop(1).stripPrefix(const.SPACE)
val body = if (line.isEmpty) t else Doc.Elem.Text(line) :: t
Section.Keyed(key, body)
} else {
Section.Paragraph(elems)
}
case _ =>
Section.Paragraph(elems)
}
case Doc.Section.Marked(_, _, typ, elems) =>
elems match {
case head :: tail =>
val header = head match {
case header: Doc.Section.Header =>
Some(header.repr.build())
case _ =>
None
}
Section.Marked(buildMark(typ), header, tail)
case Nil =>
Section.Marked(buildMark(typ), None, elems)
}
}
/** Create the [[Section.Mark]] from the [[Doc.Section.Marked.Type]]
* section type.
*
* @param typ the type of documentation section
* @return the corresponding section mark
*/
private def buildMark(typ: Doc.Section.Marked.Type): Section.Mark =
typ match {
case Doc.Section.Marked.Important => Section.Mark.Important
case Doc.Section.Marked.Info => Section.Mark.Info
case Doc.Section.Marked.Example => Section.Mark.Example
}
/** Preprocess the list of documentation sections and join the paragraphs of
* the same offset with the marked section.
*
* ==Example==
* In the parsed [[Doc.Section]], the "Some paragraph" is a separate section,
* while having the same indentation. This pass joins them into a single
* section.
*
* {{{
* ? Info
* Some info.
*
* Some paragraph.
* }}}
*
* @param sections the list of documentation sections
* @return preprocessed list of documentation sections with the
* paragraphs joined into the corresponding marked sections.
*/
private def joinSections(sections: List[Doc.Section]): List[Doc.Section] = {
val init: Option[Doc.Section.Marked] = None
val stack: List[Doc.Section] = Nil
val (result, acc) = sections.foldLeft((stack, init)) {
case ((stack, acc), section) =>
(section, acc) match {
case (marked: Doc.Section.Marked, _) =>
(acc.toList ::: stack, Some(marked))
case (raw: Doc.Section.Raw, None) =>
(raw :: stack, acc)
case (raw: Doc.Section.Raw, Some(marked)) =>
if (raw.indent == marked.indent) {
val newElems = marked.elems ::: Doc.Elem.Newline :: raw.elems
(stack, Some(marked.copy(elems = newElems)))
} else {
(raw :: marked :: stack, None)
}
}
}
(acc.toList ::: result).reverse
}
}
object ParsedSectionsBuilder {
object const {
final val COLON = ':'
final val SPACE = " "
}
}

View File

@ -0,0 +1,93 @@
package org.enso.docs.sections
/** The base trait for the section. */
sealed trait Section[A]
object Section {
/** The documentation tag.
*
* {{{
* name text
* }}}
*
* ==Example==
*
* {{{
* UNSTABLE
* DEPRECATED
* ALIAS Length
* }}}
*
* @param name the tag name
* @param text the tag text
*/
case class Tag[A](name: String, text: Option[A]) extends Section[A]
/** The paragraph of the text.
*
* ==Example==
*
* {{{
* Arbitrary text in the documentation comment.
*
* This is another paragraph.
* }}}
*
* @param body the elements that make up this paragraph
*/
case class Paragraph[A](body: List[A]) extends Section[A]
/** The section that starts with the key followed by the colon and the body.
*
* {{{
* key: body
* }}}
*
* ==Example==
*
* {{{
* Arguments:
* - one: the first
* - two: the second
* }}}
*
* {{{
* Icon: table-from-rows
* }}}
*
* @param key the section key
* @param body the elements the make up the body of the section
*/
case class Keyed[A](key: String, body: List[A]) extends Section[A]
/** The section that starts with the mark followed by the header and the body.
*
* {{{
* mark header
* body
* }}}
*
* ==Example==
*
* {{{
* > Example
* This is how it's done.
* foo = bar baz
* }}}
*
* {{{
* ! Notice
* This is important.
* }}}
*/
case class Marked[A](mark: Mark, header: Option[String], body: List[A])
extends Section[A]
/** The base trait for the section marks. */
sealed trait Mark
object Mark {
case object Important extends Mark
case object Info extends Mark
case object Example extends Mark
}
}

View File

@ -0,0 +1,9 @@
package org.enso.docs
import org.enso.syntax.text.ast.Doc
package object sections {
type ParsedSection = Section[Doc.Elem]
type RenderedSection = Section[String]
}

View File

@ -0,0 +1,480 @@
package org.enso.docs.sections
import org.enso.syntax.text.DocParser
import org.enso.syntax.text.ast.Doc
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class ParsedSectionsBuilderTest extends AnyWordSpec with Matchers {
import ParsedSectionsBuilderTest._
"DocSectionsGenerator" should {
"generate single tag" in {
val comment =
""" UNSTABLE
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Tag("UNSTABLE", None)
)
parseSections(comment) shouldEqual expected
}
"generate multiple tags" in {
val comment =
""" UNSTABLE
| DEPRECATED
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Tag("UNSTABLE", None),
Section.Tag("DEPRECATED", None)
)
parseSections(comment) shouldEqual expected
}
"generate tag with description" in {
val comment =
""" ALIAS Check Matches
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Tag("ALIAS", Some(Doc.Elem.Text("Check Matches")))
)
parseSections(comment) shouldEqual expected
}
"generate description single line" in {
val comment =
""" hello world
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(List(Doc.Elem.Text("hello world")))
)
parseSections(comment) shouldEqual expected
}
"generate description multiline" in {
val comment =
""" hello world
| second line
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(
Doc.Elem.Text("hello world"),
Doc.Elem.Newline,
Doc.Elem.Text("second line")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate description multiple paragraphs" in {
val comment =
""" Hello world
| second line
|
| Second paragraph
| multiline
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(
Doc.Elem.Text("Hello world"),
Doc.Elem.Newline,
Doc.Elem.Text("second line"),
Doc.Elem.Newline
)
),
Section.Paragraph(
List(
Doc.Elem.Text("Second paragraph"),
Doc.Elem.Newline,
Doc.Elem.Text("multiline")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate keyed arguments" in {
val comment =
""" Description
|
| Arguments:
| - one: The first
| - two: The second
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Keyed(
"Arguments",
List(
Doc.Elem.Newline,
Doc.Elem.List(
1,
Doc.Elem.List.Unordered,
Doc.Elem.Text("one: The first"),
Doc.Elem.Text("two: The second")
)
)
)
)
parseSections(comment) shouldEqual expected
}
"generate keyed icon" in {
val comment =
""" Description
|
| Icon: my-icon
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Keyed(
"Icon",
List(Doc.Elem.Text("my-icon"))
)
)
parseSections(comment) shouldEqual expected
}
"generate keyed aliases" in {
val comment =
""" Description
|
| Aliases: foo, bar baz, redshift®
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Keyed(
"Aliases",
List(Doc.Elem.Text("foo, bar baz, redshift"), Doc.Elem.Text("®"))
)
)
parseSections(comment) shouldEqual expected
}
"generate marked example" in {
val comment =
""" Description
|
| > Example
| Simple program
| main = 42
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Example,
Some("Example"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Simple program"),
Doc.Elem.Newline,
Doc.Elem.CodeBlock(Doc.Elem.CodeBlock.Line(7, "main = 42"))
)
)
)
parseSections(comment) shouldEqual expected
}
"generate marked multiple examples" in {
val comment =
""" Description
|
| > Example
| Simple program
| Multiline
| main = 42
|
| > Example
| Another example
|
| import Foo.Bar
|
| main =
| 42
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Example,
Some("Example"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Simple program"),
Doc.Elem.Newline,
Doc.Elem.Text("Multiline"),
Doc.Elem.Newline,
Doc.Elem.CodeBlock(Doc.Elem.CodeBlock.Line(7, "main = 42")),
Doc.Elem.Newline
)
),
Section.Marked(
Section.Mark.Example,
Some("Example"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Another example"),
Doc.Elem.Newline,
Doc.Elem.CodeBlock(
Doc.Elem.CodeBlock.Line(7, "import Foo.Bar"),
Doc.Elem.CodeBlock.Line(7, "main ="),
Doc.Elem.CodeBlock.Line(11, "42")
)
)
)
)
parseSections(comment) shouldEqual expected
}
"generate marked important" in {
val comment =
""" Description
|
| ! This is important
| Beware of nulls.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Important,
Some("This is important"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Beware of nulls.")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate marked info" in {
val comment =
""" Description
|
| ? Out of curiosity
| FYI.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Info,
Some("Out of curiosity"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("FYI.")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate marked info multiple sections" in {
val comment =
""" Description
|
| ? Out of curiosity
| FYI.
|
| Another section.
|
| And another.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Info,
Some("Out of curiosity"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("FYI."),
Doc.Elem.Newline,
Doc.Elem.Newline,
Doc.Elem.Text("Another section."),
Doc.Elem.Newline,
Doc.Elem.Newline,
Doc.Elem.Text("And another.")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate marked info and paragraph sections" in {
val comment =
""" Description
|
| ? Out of curiosity
| FYI.
|
| Another section.
|
| This is paragraph.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Info,
Some("Out of curiosity"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("FYI."),
Doc.Elem.Newline,
Doc.Elem.Newline,
Doc.Elem.Text("Another section."),
Doc.Elem.Newline
)
),
Section.Paragraph(
List(Doc.Elem.Text("This is paragraph."))
)
)
parseSections(comment) shouldEqual expected
}
"generate marked info and important sections" in {
val comment =
""" Description
|
| ? Out of curiosity
| FYI.
|
| ! Warning
| Pretty important.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Paragraph(
List(Doc.Elem.Text("Description"), Doc.Elem.Newline)
),
Section.Marked(
Section.Mark.Info,
Some("Out of curiosity"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("FYI."),
Doc.Elem.Newline
)
),
Section.Marked(
Section.Mark.Important,
Some("Warning"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Pretty important.")
)
)
)
parseSections(comment) shouldEqual expected
}
"generate multiple sections" in {
val comment =
""" DEPRECATED
|
| Some paragraph
| Second line
|
| Arguments:
| - one: The first
| - two: The second
|
| ! This is important
| Boo.
|
| ? Out of curiosity
| FYI.
|""".stripMargin.linesIterator.mkString("\n")
val expected = List(
Section.Tag("DEPRECATED", None),
Section.Paragraph(List(Doc.Elem.Newline)),
Section.Paragraph(
List(
Doc.Elem.Text("Some paragraph"),
Doc.Elem.Newline,
Doc.Elem.Text("Second line"),
Doc.Elem.Newline
)
),
Section.Keyed(
"Arguments",
List(
Doc.Elem.Newline,
Doc.Elem.List(
1,
Doc.Elem.List.Unordered,
Doc.Elem.Text("one: The first"),
Doc.Elem.Text("two: The second")
),
Doc.Elem.Newline
)
),
Section.Marked(
Section.Mark.Important,
Some("This is important"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("Boo."),
Doc.Elem.Newline
)
),
Section.Marked(
Section.Mark.Info,
Some("Out of curiosity"),
List(
Doc.Elem.Newline,
Doc.Elem.Text("FYI.")
)
)
)
parseSections(comment) shouldEqual expected
}
}
}
object ParsedSectionsBuilderTest {
val parsedSectionsBuilder = new ParsedSectionsBuilder
def parseSections(comment: String): List[ParsedSection] = {
val doc = DocParser.runMatched(comment)
parsedSectionsBuilder.build(doc)
}
}

View File

@ -284,57 +284,179 @@ object Doc {
//// List - Ordered & Unordered, Invalid Indent ////////////////////////////
////////////////////////////////////////////////////////////////////////////
/** An element of the list.
*
* The list can contain following elements:
* - [[ListItem]] a plain list element
* - [[List]] a sublist
* - [[MisalignedItem]] a list item that is not aligned correctly with the
* previous list item
*/
sealed trait ListElem extends Elem {
/** Append elements to this list item.
*
* @param xs elements to append
*/
def append(xs: scala.List[Elem]): ListElem
}
/** A list item that can hold a complex element structure.
*
* @param elems the elements that make up this list item
*/
final case class ListItem(elems: scala.List[Elem]) extends ListElem {
override val repr: Repr.Builder = R + elems
override def html: HTML = elems.map(_.html)
/** @inheritdoc */
override def append(xs: scala.List[Elem]): ListItem =
copy(elems = elems :++ xs)
}
object ListItem {
/** Create a list item from the provided elemenets
*
* @param elems the elements that make up the list item
* @return the list item
*/
def apply(elems: Elem*): ListItem =
ListItem(elems.toList)
}
/** The list item that is not aligned correctly with the previous list item.
*
* @param indent the indentation of this list item
* @param typ the list type
* @param elems the elements that make up this list item
*/
final case class MisalignedItem(
indent: Int,
typ: List.Type,
elems: scala.List[Elem]
) extends ListElem {
override val repr: Repr.Builder =
R + indent + typ.marker + List.ElemIndent + elems
override def html: HTML =
elems.map(_.html)
/** @inheritdoc */
override def append(xs: scala.List[Elem]): MisalignedItem =
copy(elems = elems :++ xs)
}
object MisalignedItem {
/** Create a misaligned item from the provided elements.
*
* @param indent the indentation of this list item
* @param typ the list type
* @param elems the elements that make up the list item
* @return the new misaligned item
*/
def apply(indent: Int, typ: List.Type, elems: Elem*): MisalignedItem =
new MisalignedItem(indent, typ, elems.toList)
}
/** List - block used to hold ordered and unordered lists
*
* @param indent - specifies indentation of list
* @param typ - type of list
* @param elems - elements which make up list
*
* Indent.Invalid - holds list element with invalid indent
*
* @param indent specifies indentation of list
* @param typ the list type
* @param elems the elements that make up this list
*/
final case class List(indent: Int, typ: List.Type, elems: List1[Elem])
extends Elem {
val repr: Repr.Builder = R + indent + typ.marker + elems.head + elems.tail
.map {
case elem @ (_: Elem.Invalid) => R + Newline + elem
case elem @ (_: List) => R + Newline + elem
case elem =>
R + Newline + indent + typ.marker + elem
final case class List(indent: Int, typ: List.Type, elems: List1[ListElem])
extends ListElem {
val repr: Repr.Builder = {
val listElems = elems.reverse
R + indent + typ.marker + List.ElemIndent + listElems.head +
listElems.tail.map {
case elem: List =>
R + Newline + elem
case elem: ListItem =>
R + Newline + indent + typ.marker + List.ElemIndent + elem
case elem: MisalignedItem =>
R + Newline + elem
}
}
val html: HTML = {
val elemsHTML = elems.toList.map {
case elem @ (_: List) => elem.html
case elem => Seq(HTML.li(elem.html))
val elemsHTML = elems.reverse.toList.map {
case elem: List => elem.html
case elem: ListElem => Seq(HTML.li(elem.html))
}
Seq(typ.HTMLMarker(elemsHTML))
}
/** @inheritdoc */
override def append(xs: scala.List[Elem]): List = {
val newElems = List1(elems.head.append(xs), elems.tail)
this.copy(elems = newElems)
}
/** Add a new list item.
*
* @param item the list item to add
* @return the new list with this item added
*/
def addItem(item: ListElem): List = {
val newElems = elems.prepend(item)
this.copy(elems = newElems)
}
/** Add an empty list item.
*
* @return the new list with an empty list item added
*/
def addItem(): List =
this.copy(elems = elems.prepend(ListItem(Nil)))
}
object List {
def apply(indent: Int, listType: Type, elem: Elem): List =
List(indent, listType, List1(elem))
def apply(indent: Int, listType: Type, elems: Elem*): List =
List(indent, listType, List1(elems.head, elems.tail.toList))
val ElemIndent: Int = 1
/** Create an empty list.
*
* @param indent the list indentation
* @param typ the list type
* @return the new list
*/
def empty(indent: Int, typ: Type): List =
new List(indent, typ, List1(ListItem(Nil)))
/** Create a new list.
*
* @param indent the list indentation
* @param typ the list type
* @param elem the first elements of this list
* @param elems the rest of the list elements
* @return the new list
*/
def apply(indent: Int, typ: Type, elem: Elem, elems: Elem*): List = {
val listItems = (elem :: elems.toList).reverse.map {
case list: List => list
case elem: ListItem => elem
case elem: MisalignedItem => elem
case elem => ListItem(elem)
}
new List(
indent,
typ,
List1.fromListOption(listItems).get
)
}
/** The list type. */
abstract class Type(val marker: Char, val HTMLMarker: HTMLTag)
final case object Unordered extends Type('-', HTML.ul)
final case object Ordered extends Type('*', HTML.ol)
object Indent {
final case class Invalid(indent: Int, typ: Type, elem: Elem)
extends Elem.Invalid {
val repr: Repr.Builder = R + indent + typ.marker + elem
val html: HTML = {
val className = this.productPrefix
val htmlCls = HTML.`class` := className + getObjectName
Seq(HTML.div(htmlCls)(elem.html))
}
}
}
def getObjectName: String =
getClass.toString.split('$').last
}
}
@ -352,7 +474,7 @@ object Doc {
*/
sealed trait Section extends Symbol {
def indent: Int
var elems: List[Elem]
def elems: List[Elem]
def reprOfNormalText(elem: Elem, prevElem: Elem): Repr.Builder = {
prevElem match {
@ -387,7 +509,7 @@ object Doc {
indentBeforeMarker: Int,
indentAfterMarker: Int,
typ: Marked.Type,
var elems: List[Elem]
elems: List[Elem]
) extends Section {
val marker: String = typ.marker.toString
val firstIndentRepr: Repr.Builder =
@ -443,7 +565,7 @@ object Doc {
case object Example extends Type('>')
}
final case class Raw(indent: Int, var elems: List[Elem]) extends Section {
final case class Raw(indent: Int, elems: List[Elem]) extends Section {
val dummyElem = Elem.Text("")
val newLn: Elem = Elem.Newline
val elemsRepr: List[Repr.Builder] = elems.zip(dummyElem :: elems).map {

View File

@ -56,7 +56,8 @@ case class DocParserDef() extends Parser[Doc] {
val lowerChar: Pattern = range('a', 'z')
val upperChar: Pattern = range('A', 'Z')
val digit: Pattern = range('0', '9')
val whitespace: Pattern = ' '.many1
val space: Pattern = ' '
val whitespace: Pattern = space.many1
val newline: Char = '\n'
val char: Pattern = lowerChar | upperChar
@ -205,18 +206,6 @@ case class DocParserDef() extends Parser[Doc] {
}
case Some(_) | None => result.push()
}
if (result.stack.tail.head.isInstanceOf[Elem.List]) {
val code = result.current.get.asInstanceOf[Elem.CodeBlock]
result.pop()
result.pop()
val list = result.current.get.asInstanceOf[Elem.List]
val last = list.elems.toList.last.repr + newline + code.elems.repr
val newElems =
list.elems.reverse.tail.reverse :+ Elem.stringToText(last.build())
val nElems = List1(newElems).get
val newList = Elem.List(list.indent, list.typ, nElems)
result.current = Some(newList)
}
result.push()
}
@ -226,7 +215,7 @@ case class DocParserDef() extends Parser[Doc] {
}
val notNewLine: Pattern = not(newline).many1
val CODE: State = state.define("Code")
lazy val CODE: State = state.define("Code")
ROOT || code.inlinePattern || code.onPushingInline(currentMatch)
CODE || newline || { state.end(); state.begin(NEWLINE) }
@ -453,24 +442,31 @@ case class DocParserDef() extends Parser[Doc] {
var stack: List[Int] = Nil
def current: Int =
stack match {
case Nil => 0
case ::(head, _) => head
case h :: _ => h
case Nil => 0
}
def onIndent(): Unit =
logger.trace {
val diff = currentMatch.length - current
if (diff < 0 && list.inListFlag) {
list.appendInnerToOuter()
stack = stack.tail
} else if (
currentMatch.length > section.currentIndentRaw && result.stack.nonEmpty
) {
tryToFindCodeInStack()
stack +:= currentMatch.length
state.begin(CODE)
if (list.isInList) {
if (diff > list.minIndent) {
tryToFindCodeInStack()
stack +:= currentMatch.length
state.begin(CODE)
} else {
text.push(currentMatch)
}
} else {
section.currentIndentRaw = currentMatch.length
if (
currentMatch.length > section.currentIndentRaw && result.stack.nonEmpty
) {
tryToFindCodeInStack()
stack +:= currentMatch.length
state.begin(CODE)
} else {
section.currentIndentRaw = currentMatch.length
}
}
}
@ -487,51 +483,36 @@ case class DocParserDef() extends Parser[Doc] {
result.push()
}
def onIndentForListCreation(
indent: Int,
typ: Elem.List.Type,
content: String
): Unit =
def onIndentForListCreation(indent: Int, typ: Elem.List.Type): Unit =
logger.trace {
val diff = indent - current
if (diff > 0) {
/* NOTE
* Used to push new line before pushing first list
*/
if (!list.inListFlag) onPushingNewLine()
val diff = indent - list.current
if (!list.isInList) {
onPushingNewLine()
stack +:= indent
list.inListFlag = true
list.addNew(indent, typ, content)
} else if (diff == 0 && list.inListFlag) {
list.addContent(content)
} else if (diff < 0 && list.inListFlag) {
if (stack.tail.head != indent) {
onInvalidIndent(indent, typ, content)
} else {
list.appendInnerToOuter()
list.addContent(content)
stack = stack.tail
}
list.startNewList(indent, typ)
} else if (diff == 0) {
list.endListItem()
list.startListItem()
} else if (diff > 0) {
stack +:= indent
list.endListItem()
list.startNewList(indent, typ)
} else {
onInvalidIndent(indent, typ, content)
if (indent > list.prev) {
list.endListItem()
list.startMisalignedListItem(indent, typ)
} else {
do {
list.endListItem()
list.endSublist()
} while (indent < list.current)
list.startListItem()
}
}
}
def onInvalidIndent(
indent: Int,
typ: Elem.List.Type,
content: String
): Unit = {
if (!list.inListFlag && typ == Elem.List.Ordered) {
onPushingNewLine()
formatter.onPushing(Elem.Formatter.Bold)
result.current = Some(content)
result.push()
} else {
list.addContent(Elem.List.Indent.Invalid(indent, typ, content))
}
}
def onPushingNewLine(): Unit =
logger.trace {
result.current = Some(Elem.Newline)
@ -540,9 +521,8 @@ case class DocParserDef() extends Parser[Doc] {
def onEmptyLine(): Unit =
logger.trace {
if (list.inListFlag) {
list.appendInnerToOuter()
list.inListFlag = false
if (list.isInList) {
list.endListItem(endList = true)
}
onPushingNewLine()
section.onEOS()
@ -569,7 +549,7 @@ case class DocParserDef() extends Parser[Doc] {
val EOFPattern: Pattern = indentPattern >> eof
}
val NEWLINE: State = state.define("Newline")
lazy val NEWLINE: State = state.define("Newline")
ROOT || newline || state.begin(NEWLINE)
NEWLINE || indent.EOFPattern || indent.onEOFPattern()
@ -584,79 +564,180 @@ case class DocParserDef() extends Parser[Doc] {
* there are 2 possible types of lists - ordered and unordered.
*/
final object list {
var inListFlag: Boolean = false
def addNew(indent: Int, listType: Elem.List.Type, content: Elem): Unit =
/** The minimum list indentation consisting of a list symbol and a space
* character.
*/
val minIndent: Int = 2
var stack: List[Int] = Nil
def current: Int =
stack match {
case h :: _ => h
case Nil => 0
}
def prev: Int =
stack match {
case _ :: p :: _ => p
case _ => 0
}
def isInList: Boolean = stack.nonEmpty
def startNewList(indent: Int, listType: Elem.List.Type): Unit =
logger.trace {
result.current = Some(Elem.List(indent, listType, content))
list.stack +:= indent
result.current = Some(Elem.List.empty(indent, listType))
result.push()
}
def addContent(content: Elem): Unit =
def startListItem(): Unit =
logger.trace {
result.pop()
result.current match {
case Some(list @ (_: Elem.List)) =>
var currentContent = list.elems
currentContent = currentContent.append(content)
result.current =
Some(Elem.List(list.indent, list.typ, currentContent))
case _ =>
case Some(l: Elem.List) =>
result.current = Some(l.addItem())
result.push()
case elem =>
throw new IllegalStateException(
s"Illegal startListItem state [current=$elem]"
)
}
result.push()
}
def appendInnerToOuter(): Unit =
def startMisalignedListItem(indent: Int, typ: Elem.List.Type): Unit =
logger.trace {
result.pop()
val innerList = result.current.orNull
result.stack.head match {
case outerList @ (_: Elem.List) =>
var outerContent = outerList.elems
innerList match {
case Elem.Newline =>
case _ => outerContent = outerContent.append(innerList)
}
result.pop()
result.current =
Some(Elem.List(outerList.indent, outerList.typ, outerContent))
case _ =>
}
result.push()
innerList match {
case Elem.Newline => indent.onPushingNewLine()
case _ =>
result.current match {
case Some(l: Elem.List) =>
val item = Elem.MisalignedItem(indent, typ, List())
result.current = Some(l.addItem(item))
result.push()
case elem =>
throw new IllegalStateException(
s"Illegal startMisalignedListItem state [current=$elem]"
)
}
}
def endListItem(endList: Boolean = false): Unit =
logger.trace {
section.checkForUnclosedFormattersOnEOS()
val elems = stackUnwind()
result.current match {
case Some(l: Elem.List) =>
if (endList) {
list.stack = list.stack.tail
}
result.current = Some(l.append(elems))
result.push()
case elem =>
throw new IllegalStateException(
s"Illegal endListItem state [current=$elem]"
)
}
}
def endSublist(): Unit =
logger.trace {
result.current match {
case None =>
result.pop()
result.current match {
case Some(sublist: Elem.List) =>
result.pop()
result.current match {
case Some(l: Elem.List) =>
list.stack = list.stack.tail
result.current = Some(l.addItem(sublist))
result.push()
case elem =>
throw new IllegalStateException(
s"Illegal endSublist stack [List,$elem,...]"
)
}
case elem =>
throw new IllegalStateException(
s"Illegal endSublist stack [$elem,...]"
)
}
case elem =>
throw new IllegalStateException(
s"Illegal endSublist state [current=$elem]"
)
}
}
def addLastItem(): Unit =
logger.trace {
val elems = stackUnwind()
result.current match {
case Some(l: Elem.List) =>
list.stack = list.stack.tail
if (elems.last == Elem.Newline) {
result.current = Some(l.append(elems.init))
result.push()
result.current = Some(Elem.Newline)
result.push()
} else {
result.current = Some(l.append(elems))
result.push()
}
while (stack.nonEmpty) {
list.endListItem()
list.endSublist()
}
case elem =>
throw new IllegalStateException(
s"Illegal addLastItem state [current=$elem]"
)
}
}
/** Get all elements from the stack that were added after the [[Elem.List]]
* node was pushed.
*/
def stackUnwind(): List[Elem] = {
@scala.annotation.tailrec
def go(elems: List[Elem]): List[Elem] = {
result.pop()
result.current match {
case Some(_: Elem.List) | None =>
elems
case Some(elem) =>
go(elem :: elems)
}
}
val init = result.current.toList
go(init)
}
def onOrdered(): Unit =
logger.trace {
state.end()
val matchedContent = currentMatch.split(orderedListTrigger)
val listIndent = matchedContent(0).length
val listElems = matchedContent(1)
indent.onIndentForListCreation(listIndent, Elem.List.Ordered, listElems)
val listIndent = currentMatch
.takeWhile(_ != orderedListTrigger)
.length
indent.onIndentForListCreation(listIndent, Elem.List.Ordered)
}
def onUnordered(): Unit =
logger.trace {
state.end()
val matchedContent = currentMatch.split(unorderedListTrigger)
val listIndent = matchedContent(0).length
val listElems = matchedContent(1)
indent.onIndentForListCreation(
listIndent,
Elem.List.Unordered,
listElems
)
val listIndent = currentMatch
.takeWhile(_ != unorderedListTrigger)
.length
indent.onIndentForListCreation(listIndent, Elem.List.Unordered)
}
val orderedListTrigger: Char = Elem.List.Ordered.marker
val unorderedListTrigger: Char = Elem.List.Unordered.marker
val orderedPattern: Pattern =
indent.indentPattern >> orderedListTrigger >> notNewLine
indent.indentPattern >> orderedListTrigger >> space
val unorderedPattern: Pattern =
indent.indentPattern >> unorderedListTrigger >> notNewLine
indent.indentPattern >> unorderedListTrigger >> space
}
NEWLINE || list.orderedPattern || list.onOrdered()
@ -742,6 +823,11 @@ case class DocParserDef() extends Parser[Doc] {
formatter.checkForUnclosed(Elem.Formatter.Strikeout)
}
def checkForUnclosedListsOnEOS(): Unit =
if (list.isInList) {
list.addLastItem()
}
def reverseStackOnEOS(): Unit =
logger.trace {
result.stack = result.stack.reverse
@ -794,6 +880,7 @@ case class DocParserDef() extends Parser[Doc] {
def onEOS(): Unit =
logger.trace {
checkForUnclosedFormattersOnEOS()
checkForUnclosedListsOnEOS()
reverseStackOnEOS()
header.create()
push()
@ -850,13 +937,17 @@ case class DocParserDef() extends Parser[Doc] {
def transformOverlyIndentedRawIntoCode(
baseIndent: Int
): Unit = {
var newStack = List[Section]()
var newStack = List[Section]()
var currentIndent = baseIndent
while (section.stack.nonEmpty) {
var current = section.pop().get
if (current.indent > baseIndent && current.isInstanceOf[Section.Raw]) {
if (
current.indent > currentIndent &&
current.isInstanceOf[Section.Raw]
) {
var stackOfCodeSections: List[Section] = List[Section]()
while (
section.stack.nonEmpty && current.indent > baseIndent && current
section.stack.nonEmpty && current.indent > currentIndent && current
.isInstanceOf[Section.Raw] && section.stack.head
.isInstanceOf[Section.Raw]
) {
@ -866,20 +957,30 @@ case class DocParserDef() extends Parser[Doc] {
}
}
stackOfCodeSections = stackOfCodeSections :+ current
val codeLines = stackOfCodeSections.flatMap(s => {
val inLines = s.repr.build().split("\n").map(_.trim)
inLines.map(Doc.Elem.CodeBlock.Line(s.indent, _))
})
val codeLines = stackOfCodeSections.flatMap {
_.repr
.build()
.split("\n")
.map { line =>
val (indent, text) = line.span(_ == ' ')
Doc.Elem.CodeBlock.Line(indent.length, text)
}
}
if (codeLines.nonEmpty) {
val l1CodeLines = List1(codeLines.head, codeLines.tail)
val codeBlock = Doc.Elem.CodeBlock(l1CodeLines)
val s = newStack.head
val sElems = newStack.head.elems :+ codeBlock
s.elems = sElems
val newElems = newStack.head.elems :+ codeBlock
val newSection = newStack.head match {
case marked: Doc.Section.Marked =>
marked.copy(elems = newElems)
case raw: Doc.Section.Raw =>
raw.copy(elems = newElems)
}
newStack = newStack.drop(1)
newStack +:= s
newStack +:= newSection
}
} else {
currentIndent = current.indent
newStack +:= current
}
}

View File

@ -43,7 +43,7 @@ class DocParserTests extends AnyFlatSpec with Matchers {
assertExpr(input, out)
}
def ?==(out: Doc): Unit = testBase in {
assertExpr(input, out, false)
assertExpr(input, out, assertShow = false)
}
}
@ -258,6 +258,151 @@ class DocParserTests extends AnyFlatSpec with Matchers {
.Marked(1, 4, Section.Marked.Important, Section.Header("Important"))
)
)
""" ! Important
| This is important.""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Marked(
1,
1,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
"This is important."
)
)
)
"""! Synopsis
| This _is_ important""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Marked(
0,
1,
Section.Marked.Important,
Section.Header("Synopsis"),
Doc.Elem.Newline,
"This ",
Formatter(Formatter.Italic, "is"),
" important"
)
)
)
"""Synopsis
|This _is1_ important""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Raw(
"Synopsis",
Doc.Elem.Newline,
"This ",
Formatter(Formatter.Italic, "is1"),
" important"
)
)
)
""" Synopsis
|
| ! Important
| This is important.""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Raw(1, "Synopsis", Doc.Elem.Newline)
),
Body(
Section.Marked(
1,
1,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
"This is important."
)
)
)
""" Synopsis
|
| ! Important
| This is important.
|
| And this""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Raw(1, "Synopsis", Doc.Elem.Newline)
),
Body(
Section.Marked(
1,
1,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
"This is important.",
Doc.Elem.Newline
),
Section.Raw(3, "And this")
)
)
""" Synopsis
|
| !Important
| This is a code
|
| And this is not""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Raw(1, "Synopsis", Doc.Elem.Newline)
),
Body(
Section.Marked(
1,
0,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
CodeBlock(CodeBlock.Line(4, "This is a code")),
Doc.Elem.Newline
),
Section.Raw(2, "And this is not")
)
)
"""Synopsis
|
|! Important
| This is important
|
| And this is a code""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?== Doc(
Synopsis(
Section.Raw("Synopsis", Doc.Elem.Newline)
),
Body(
Section.Marked(
0,
1,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
"This is important",
Doc.Elem.Newline,
CodeBlock(CodeBlock.Line(4, "And this is a code"))
)
)
)
"?Info" ?= Doc(
Synopsis(Section.Marked(Section.Marked.Info, Section.Header("Info")))
)
@ -293,6 +438,45 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Section.Marked(Section.Marked.Example, Section.Header("Example"))
)
)
"""Synopsis
|
| ! Important
| This is important
| And this is a code
|
|> Example
| This is example
| More code""".stripMargin.replaceAll(
System.lineSeparator(),
"\n"
) ?= Doc(
Synopsis(
Section.Raw("Synopsis", Doc.Elem.Newline)
),
Body(
Section.Marked(
1,
1,
Section.Marked.Important,
Section.Header("Important"),
Doc.Elem.Newline,
"This is important",
Doc.Elem.Newline,
CodeBlock(CodeBlock.Line(5, "And this is a code")),
Doc.Elem.Newline
),
Section.Marked(
0,
1,
Section.Marked.Example,
Section.Header("Example"),
Doc.Elem.Newline,
"This is example",
Doc.Elem.Newline,
CodeBlock(CodeBlock.Line(6, "More code"))
)
)
)
"""Foo *Foo* ~*Bar~ `foo bar baz bo`
|
|
@ -339,7 +523,7 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Section.Raw(
"ul:",
Newline,
List(2, List.Unordered, " Foo", " Bar")
List(2, List.Unordered, "Foo", "Bar")
)
)
)
@ -348,25 +532,25 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Section.Raw(
"ol:",
Newline,
List(2, List.Ordered, " Foo", " Bar")
List(2, List.Ordered, "Foo", "Bar")
)
)
)
"""List
| - First unordered item
| - Second unordered item
| - Third unordered item""".stripMargin
|- First
|- Second
|- Third""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
0,
List.Unordered,
" First unordered item",
" Second unordered item",
" Third unordered item"
"First",
"Second",
"Third"
)
)
)
@ -384,14 +568,234 @@ class DocParserTests extends AnyFlatSpec with Matchers {
List(
2,
List.Unordered,
" First unordered item",
" Second unordered item",
" Third unordered item"
"First unordered item",
"Second unordered item",
"Third unordered item"
),
Newline
)
)
)
"""List
|- _First_
|- *Second*""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
0,
List.Unordered,
Formatter(Formatter.Italic, "First"),
Formatter(Formatter.Bold, "Second")
)
)
)
)
"""List
|- _First_ list `item`
|- *Second* list ~item""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
0,
List.Unordered,
ListItem(
Formatter(Formatter.Italic, "First"),
" list ",
CodeBlock.Inline("item")
),
ListItem(
Formatter(Formatter.Bold, "Second"),
" list ",
Formatter.Unclosed(Formatter.Strikeout, "item")
)
)
)
)
)
"""List
|- _First_ list `item`
|- *Second* list ~item
|""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
0,
List.Unordered,
ListItem(
Formatter(Formatter.Italic, "First"),
" list ",
CodeBlock.Inline("item")
),
ListItem(
Formatter(Formatter.Bold, "Second"),
" list ",
Formatter.Unclosed(Formatter.Strikeout, "item", Newline)
)
)
)
)
)
""" List
| - unclosed_formatter
| - second""".stripMargin.stripMargin
.replaceAll(System.lineSeparator(), "\n") ?== Doc(
Synopsis(
Section.Raw(
1,
"List",
Newline,
List(
3,
List.Unordered,
ListItem(
"unclosed",
Formatter.Unclosed(Formatter.Italic, "formatter")
),
"second"
)
)
)
)
"""List
| - First
| - Second
| * First1
| * Second1
| - Third""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
List.Unordered,
"First",
"Second",
List(
4,
List.Ordered,
"First1",
"Second1"
),
"Third"
)
)
)
)
"""List
| - First
| - First1
| - First2
| - First3""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
List.Unordered,
"First",
List(
4,
List.Unordered,
"First1",
List(
6,
List.Unordered,
"First2",
List(
8,
List.Unordered,
"First3"
)
)
)
)
)
)
)
"""List
| - First
| - First1
| - First2
| - First3
|""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
List.Unordered,
"First",
List(
4,
List.Unordered,
"First1",
List(
6,
List.Unordered,
"First2",
List(
8,
List.Unordered,
ListItem("First3", Newline)
)
)
)
)
)
)
)
"""List
| - First
| - First1
| - First2
| - First3
| - Second""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
List.Unordered,
"First",
List(
4,
List.Unordered,
"First1",
List(
6,
List.Unordered,
"First2",
List(
8,
List.Unordered,
"First3"
)
)
),
"Second"
)
)
)
)
"""List
| - First unordered item
| - Second unordered item
@ -406,15 +810,15 @@ class DocParserTests extends AnyFlatSpec with Matchers {
List(
2,
List.Unordered,
" First unordered item",
" Second unordered item",
"First unordered item",
"Second unordered item",
List(
4,
List.Ordered,
" First ordered sub item",
" Second ordered sub item"
"First ordered sub item",
"Second ordered sub item"
),
" Third unordered item"
"Third unordered item"
)
)
)
@ -433,15 +837,15 @@ class DocParserTests extends AnyFlatSpec with Matchers {
List(
2,
List.Unordered,
" First unordered item",
" Second unordered item",
"First unordered item",
"Second unordered item",
List(
4,
List.Ordered,
" First ordered sub item",
" Second ordered sub item"
"First ordered sub item",
" Second ordered sub item"
),
" Third unordered item"
"Third unordered item"
)
)
)
@ -466,29 +870,29 @@ class DocParserTests extends AnyFlatSpec with Matchers {
List(
2,
List.Unordered,
" First unordered item",
" Second unordered item",
"First unordered item",
"Second unordered item",
List(
4,
List.Ordered,
" First ordered sub item",
" Second ordered sub item"
"First ordered sub item",
"Second ordered sub item"
),
" Third unordered item",
"Third unordered item",
List(
4,
List.Ordered,
" First ordered sub item",
" Second ordered sub item",
"First ordered sub item",
"Second ordered sub item",
List(
6,
List.Unordered,
" First unordered sub item",
" Second unordered sub item"
"First unordered sub item",
"Second unordered sub item"
),
" Third ordered sub item"
"Third ordered sub item"
),
" Fourth unordered item"
"Fourth unordered item"
)
)
)
@ -499,18 +903,12 @@ class DocParserTests extends AnyFlatSpec with Matchers {
//////////////////////////////////////////////////////////////////////////////
"""List
| - First unordered item
| - Second unordered item
| * First ordered sub item
| * Second ordered sub item
| - Third unordered item
| * First ordered sub item
| * Second ordered sub item
| - First unordered sub item
| - Second unordered sub item
| * Third ordered sub item
| * Wrong Indent Item
| - Fourth unordered item""".stripMargin
| - First
| * Aligned
| * Misaligned
| * Misaligned _styled_
| * Correct
| - Second""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
@ -519,30 +917,53 @@ class DocParserTests extends AnyFlatSpec with Matchers {
List(
2,
List.Unordered,
" First unordered item",
" Second unordered item",
"First",
List(
4,
5,
List.Ordered,
" First ordered sub item",
" Second ordered sub item"
"Aligned",
Elem.MisalignedItem(4, List.Ordered, "Misaligned"),
Elem.MisalignedItem(
3,
List.Ordered,
"Misaligned ",
Formatter(Formatter.Italic, "styled")
),
"Correct"
),
" Third unordered item",
"Second"
)
)
)
)
"""List
| - First
| - First1
| - Second1
| - First2
| - Second""".stripMargin
.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
"List",
Newline,
List(
2,
List.Unordered,
"First",
List(
4,
List.Ordered,
" First ordered sub item",
" Second ordered sub item",
List.Unordered,
"First1",
Elem.MisalignedItem(3, List.Unordered, "Second1"),
List(
6,
List.Unordered,
" First unordered sub item",
" Second unordered sub item"
),
" Third ordered sub item",
List.Indent.Invalid(3, List.Ordered, " Wrong Indent Item")
"First2"
)
),
" Fourth unordered item"
"Second"
)
)
)
@ -770,7 +1191,23 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Synopsis(
Section.Raw(
Newline,
List(1, List.Unordered, " bar\n baz"),
List(1, List.Unordered, ListItem("bar", Newline, " ", "baz")),
Newline
)
)
)
"""
| - bar
| baz
|""".stripMargin.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
Newline,
List(
1,
List.Unordered,
ListItem("bar", Newline, CodeBlock(CodeBlock.Line(5, "baz")))
),
Newline
)
)
@ -784,7 +1221,31 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Synopsis(
Section.Raw(
Newline,
List(1, List.Unordered, " bar\n baz", " bar\n baz"),
List(
1,
List.Unordered,
ListItem("bar", Newline, " ", "baz"),
ListItem("bar", Newline, " ", "baz")
),
Newline
)
)
)
"""
| - bar
| baz
| - bar
| baz
|""".stripMargin.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
Newline,
List(
1,
List.Unordered,
ListItem("bar", Newline, CodeBlock(CodeBlock.Line(5, "baz"))),
ListItem("bar", Newline, " ", "baz")
),
Newline
)
)
@ -800,8 +1261,41 @@ class DocParserTests extends AnyFlatSpec with Matchers {
1,
"This does foo:",
Newline,
List(1, List.Unordered, " bar\n baz"),
List(
1,
List.Unordered,
ListItem(
"bar",
Newline,
" ",
"baz",
Newline,
" ",
"Another raw text."
)
),
Newline
)
)
)
""" This does foo:
| - bar
| baz
|
| Another raw text.
|""".stripMargin.replaceAll(System.lineSeparator(), "\n") ?= Doc(
Synopsis(
Section.Raw(
1,
"This does foo:",
Newline,
List(1, List.Unordered, ListItem("bar", Newline, " ", "baz")),
Newline
)
),
Body(
Section.Raw(
1,
"Another raw text.",
Newline
)
@ -822,7 +1316,7 @@ class DocParserTests extends AnyFlatSpec with Matchers {
Newline,
"This does foo:",
Newline,
List(4, List.Unordered, " bar\n baz"),
List(4, List.Unordered, ListItem("bar", Newline, " ", "baz")),
Newline
)
)
@ -928,7 +1422,10 @@ class DocParserTests extends AnyFlatSpec with Matchers {
| import Standard.Base.System.File
| import Standard.Examples
|
| example_new = File.new Examples.csv_path
| example_new =
| path =
| Examples.csv_path
| File.new path
|""".stripMargin.replaceAll(System.lineSeparator(), "\n") ?== Doc(
Tags(Tags.Tag(0, Tags.Tag.Type.Alias, " New File")),
Synopsis(Section.Raw(0, Newline)),
@ -951,9 +1448,13 @@ class DocParserTests extends AnyFlatSpec with Matchers {
CodeBlock(
CodeBlock.Line(6, "import Standard.Base.System.File"),
CodeBlock.Line(6, "import Standard.Examples"),
CodeBlock.Line(6, "example_new = File.new Examples.csv_path")
CodeBlock.Line(6, "example_new ="),
CodeBlock.Line(10, "path ="),
CodeBlock.Line(14, "Examples.csv_path"),
CodeBlock.Line(10, "File.new path")
)
)
)
)
}