Adjust Doc Generator to comply with new Tailwind-css-based stylesheet (#1646)

This commit is contained in:
Maciej Mikołajek 2021-04-22 22:39:01 +02:00 committed by GitHub
parent d9e1a47460
commit c1edaef725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 334 additions and 183 deletions

View File

@ -30,10 +30,14 @@ final case class Doc(
) extends Doc.Symbol {
val repr: Repr.Builder = R + tags + synopsis + body
val htmlWoTags: Doc.HTML = Seq(
HTML.div(htmlCls())(synopsis.html)(body.html)
HTML.div(synopsis.html)(body.html)
)
val htmlWoTagsMain: Doc.HTML = synopsis match {
case Some(s) => Seq(HTML.div(s.htmlBig)(body.html))
case None => Seq(HTML.div(body.html))
}
val html: Doc.HTML = Seq(
HTML.div(htmlCls())(tags.html)(synopsis.html)(body.html)
HTML.div(tags.html)(synopsis.html)(body.html)
)
}
@ -179,13 +183,12 @@ object Doc {
val htmlIdBtn = HTML.`id` := uniqueIDBtn
val firstIndent = elems.head.indent
val elemsHTML = elems.toList.map(elem => elem.htmlOffset(firstIndent))
val btnStyle = HTML.`style` := "display: flex"
val copyClass = HTML.`class` := "copyCodeBtn"
val copyBtn = HTML.button(htmlIdBtn)(copyClass)(btnStyle)("Copy")
val htmlStyle = HTML.`style` := "display: block"
val copyClass = HTML.`class` := "doc-copy-btn flex"
val codeClass = HTML.`class` := "doc-code-container"
val copyBtn = HTML.button(htmlIdBtn)(copyClass)("Copy")
Seq(
HTML.div(
HTML.div(htmlCls())(htmlStyle)(htmlIdCode)(elemsHTML),
HTML.div(codeClass)(htmlIdCode)(HTML.pre(elemsHTML)),
copyBtn
)
)
@ -354,7 +357,11 @@ object Doc {
*/
final case class Header(elems: List[Elem]) extends Elem {
val repr: Repr.Builder = R + elems.map(_.repr)
val html: HTML = Seq(HTML.div(htmlCls())(elems.map(_.html)))
val html: HTML = Seq(
HTML.div(HTML.`class` := "summary")(
elems.map(_.html)
)
)
}
object Header {
def apply(elem: Elem): Header = Header(elem :: Nil)
@ -380,7 +387,7 @@ object Doc {
val repr: Repr.Builder = R + firstIndentRepr + elemsRepr
override def htmlCls(): generic.AttrPair[Builder, String] = {
HTML.`class` := typ.toString
HTML.`class` := typ.toString.toLowerCase
}
override def indent: Int =
@ -432,6 +439,12 @@ object Doc {
}
val repr: Repr.Builder = R + indent + elemsRepr
override def htmlCls(): generic.AttrPair[Builder, String] = {
HTML.`class` := ""
}
override val html: HTML = Seq(HTML.p(elems.map(_.html)))
}
object Raw {
@ -457,7 +470,18 @@ object Doc {
val newLn: Elem = Elem.Newline
val repr: Repr.Builder = R + elems.head + elems.tail.map(R + newLn + _)
val html: HTML = {
Seq(HTML.div(htmlCls())(elems.toList.map(_.html)))
Seq(
HTML.div(HTML.`class` := "")(
elems.toList.map(_.html)
)
)
}
val htmlBig: HTML = {
Seq(
HTML.div(HTML.`class` := "summary")(
elems.toList.map(_.html)
)
)
}
}
object Synopsis {
@ -480,7 +504,7 @@ object Doc {
R + newLn + _
)
val html: HTML = Seq(
HTML.div(htmlCls())(elems.toList.map(_.html))
HTML.div(elems.toList.map(_.html))
)
}
@ -502,7 +526,11 @@ object Doc {
val newLn: Elem = Elem.Newline
val repr: Repr.Builder =
R + elems.head + elems.tail.map(R + newLn + _) + newLn
val html: HTML = Seq(HTML.div(htmlCls())(elems.toList.map(_.html)))
val html: HTML = Seq(
HTML.div(HTML.`class` := "tags")(
elems.toList.map(_.html)
)
)
}
object Tags {
def apply(elem: Tag): Tags = Tags(List1(elem))
@ -516,15 +544,35 @@ object Doc {
*/
final case class Tag(indent: Int, typ: Tag.Type, details: Option[String])
extends Elem {
val name: String = typ.toString.toUpperCase
val name: String = typ.toString.toUpperCase
val cName: String = typ.toString.toLowerCase
val repr: Repr.Builder = typ match {
case Tag.Unrecognized => R + indent + details
case _ => R + indent + name + details
}
val html: HTML = typ match {
case Tag.Unrecognized =>
Seq(HTML.div(HTML.`class` := name)(details.html))
case _ => Seq(HTML.div(HTML.`class` := name)(name)(details.html))
val html: HTML = {
typ match {
case Tag.Unrecognized =>
Seq(
HTML.p(HTML.`class` := "tag")(
HTML.span(HTML.`class` := cName)(details.html)
)
)
case Tag.Type.TextOnly =>
Seq(
HTML.p(HTML.`class` := "tag")(
HTML.span(HTML.`class` := cName)("TEXT ONLY")(
details.html
)
)
)
case _ =>
Seq(
HTML.p(HTML.`class` := "tag")(
HTML.span(HTML.`class` := cName)(name)(details.html)
)
)
}
}
}
object Tag {
@ -554,10 +602,7 @@ object Doc {
}
implicit final class ExtForTagDetails(val self: Option[String]) {
val html: HTML = {
val htmlCls = HTML.`class` := this.getClass.toString.split('$').last
Seq(self.map(HTML.div(htmlCls)(_)))
}
val html: HTML = Seq(self.map(HTML.span(HTML.`class` := "details")(_)))
}
}
}

View File

@ -522,18 +522,13 @@ case class DocParserDef() extends Parser[Doc] {
typ: Elem.List.Type,
content: String
): Unit = {
if (list.inListFlag) {
list.addContent(Elem.List.Indent.Invalid(indent, typ, content))
} else {
if (!list.inListFlag && typ == Elem.List.Ordered) {
onPushingNewLine()
if (typ == Elem.List.Ordered) {
formatter.onPushing(Elem.Formatter.Bold)
result.current = Some(content)
result.push()
} else {
result.current = Some(" " * indent + typ.marker + content)
result.push()
}
formatter.onPushing(Elem.Formatter.Bold)
result.current = Some(content)
result.push()
} else {
list.addContent(Elem.List.Indent.Invalid(indent, typ, content))
}
}

View File

@ -54,7 +54,16 @@ object DocParser {
/** Doc Parser running methods, as described above, in class [[DocParser]]
*/
def runMatched(input: String): Doc = new DocParser().runMatched(input)
def runMatched(input: String): Doc = {
val lines = input.split("\n")
var indentSecondLine = 0
if (lines.tail.nonEmpty) {
val s = lines.tail.head
indentSecondLine = s.indexOf(s.trim())
}
val in = " " * indentSecondLine + lines.mkString("\n")
new DocParser().runMatched(in)
}
def run(input: String): Result[Doc] = new DocParser().run(input)
}
@ -289,13 +298,29 @@ object DocParserHTMLGenerator {
*/
def generateHTMLForEveryDocumented(ast: AST): String = {
var allDocs = new String
val extMethodsHeader = HTML.h2(
HTML.div(HTML.`class` := "ml-20 flex")(
HTML.raw(
"<MethodsIcon className=\"-ml-16 -mb-3 mr-4 self-center h-12 p-2 text-content-title-on-dark bg-accent-important fill-current rounded-xl\" />"
),
HTML.p("Extension Methods")
)
)
var extensionMethods = new String
ast.map { elem =>
elem match {
case AST.Documented.any(documented) =>
val file = onHTMLRendering(documented)
allDocs += file.code.toString() + generateHTMLForEveryDocumented(
documented
) + HTML.hr + HTML.br
documented.ast match {
case AST.App.Infix.any(_) =>
extensionMethods += HTML
.div(HTML.`class` := "ml-20 mb-20")(file.code)
.toString()
case _ =>
allDocs += HTML
.div(HTML.`class` := "mb-20")(file.code)
.toString()
}
case AST.Def.any(tp) =>
tp.body match {
case Some(body) => allDocs += generateHTMLForEveryDocumented(body)
@ -306,7 +331,10 @@ object DocParserHTMLGenerator {
}
elem
}
allDocs
if (extensionMethods.length > 0) {
extensionMethods = extMethodsHeader.render + extensionMethods
}
"<div>" + allDocs + extensionMethods + "</div>"
}
/** Function to generate HTML File from pure doc comment w/o connection to AST
@ -314,9 +342,16 @@ object DocParserHTMLGenerator {
* @param doc - Doc from Doc Parser
* @return - HTML Code from Doc
*/
def generateHTMLPureDoc(doc: Doc): String = {
HTML.html(createHTMLHead(""), HTML.body(doc.html)).toString()
}
def generateHTMLPureDoc(doc: Doc): String =
HTML
.html(
HTML.body(
HTML.div(HTML.`class` := "doc")(HTML.style := "font-size: 13px;")(
doc.html
)
)
)
.toString()
//////////////////////////////////////////////////////////////////////////////
//// HTML Rendering of Documentation /////////////////////////////////////////
@ -329,7 +364,7 @@ object DocParserHTMLGenerator {
* @return - HTML code with file name
*/
def onHTMLRendering(documented: AST.Documented): htmlFile = {
val htmlCode = renderHTML(documented.ast, documented.doc)
val htmlCode = DocumentedToHtml(documented.ast, documented.doc)
val astLines = documented.ast.show().split("\n")
val fileName =
astLines.head
@ -341,19 +376,6 @@ object DocParserHTMLGenerator {
}
case class htmlFile(code: TypedTag[String], name: String)
/** Function invoked by [[onHTMLRendering]] to render HTML File
*
* @param ast - AST from Parser
* @param doc - Doc from Doc Parser
* @return - HTML Code from Doc and contents of [[AST.Def]] or
* [[AST.App.Infix]], with optional title made from AST
*/
def renderHTML(ast: AST, doc: Doc): TypedTag[String] = {
val title = ast.show().split("\n").head.split("=").head
val documentation = DocumentedToHtml(ast, doc)
HTML.html(createHTMLHead(title), HTML.body(documentation))
}
/** This function is used to get HTML content of Doc and try to render AST,
* by finding if it also contains Documented to retrieve Doc and it's AST,
* or simply call show() method on other element of AST.
@ -363,49 +385,23 @@ object DocParserHTMLGenerator {
* @return - HTML Code from Doc and contents of [[AST.Def]] or
* [[AST.App.Infix]]
*/
def DocumentedToHtml(
ast: AST,
doc: Doc,
isInOtherDoc: Boolean = false
): TypedTag[String] = {
val docClass = HTML.`class` := "Documentation"
val astHeadCls = HTML.`class` := "ASTHead"
val astHTML = createHTMLFromAST(ast)
var astName = HTML.div(astHeadCls)(astHTML.header)
val strikeoutStyle = HTML.`style` := "text-decoration-line:line-through;"
if (doc.tags.html.mkString.contains("DEPRECATED")) {
astName = HTML.div(astHeadCls)(strikeoutStyle)(astHTML.header)
}
def DocumentedToHtml(ast: AST, doc: Doc): TypedTag[String] = {
val astHTML = createHTMLFromAST(ast, doc.tags)
val astName = HTML.div(astHTML.header)
astHTML.body match {
case Some(b) =>
val astBodyCls = HTML.`class` := "ASTData"
val astBody = Seq(HTML.div(astBodyCls)(b))
case Some(body) =>
// Case when producing main page
HTML.div(docClass)(
doc.tags.html,
HTML.div(HTML.`class` := "main ml-20")(
astName,
doc.htmlWoTags,
astBody
doc.htmlWoTagsMain,
body
)
case None =>
// Case when listing constructors/methods | Name | Synopsis | Tags |
var content = HTML.div(doc.htmlWoTags)
if (doc.tags.html.mkString.contains("DEPRECATED")) {
content = HTML.div(strikeoutStyle)(doc.htmlWoTags)
}
if (isInOtherDoc) {
HTML.div(docClass)(
astName,
content,
doc.tags.html
)
} else {
HTML.div(docClass)(
doc.tags.html,
astName,
content
)
}
// Case when listing atoms or methods
HTML.div(
astName,
doc.htmlWoTags
)
}
}
@ -433,20 +429,29 @@ object DocParserHTMLGenerator {
* @param ast - AST
* @return - HTML Code
*/
def createHTMLFromAST(ast: AST): astHtmlRepr = {
def createHTMLFromAST(
ast: AST,
tags: Option[Doc.Tags] = None
): astHtmlRepr = {
ast match {
case AST.Def.any(d) =>
d.body match {
case Some(body) =>
body match {
case AST.Block.any(b) => createDefWithBody(d.name, d.args, b)
case AST.Block.any(b) =>
createDefWithBody(d.name, d.args, b, tags)
case _ =>
astHtmlRepr(createDefWithoutBody(d.name, d.args))
astHtmlRepr(createAtomHtmlRepr(d.name, d.args, tags))
}
case None => astHtmlRepr(createDefWithoutBody(d.name, d.args))
case None => astHtmlRepr(createAtomHtmlRepr(d.name, d.args, tags))
}
case AST.App.Infix.any(i) => astHtmlRepr(createInfixHtmlRepr(i))
case _ => astHtmlRepr()
case AST.App.Infix.any(i) =>
if (i.larg.show().split(" ").nonEmpty) {
astHtmlRepr(createInfixHtmlRepr(i, tags))
} else {
astHtmlRepr()
}
case _ => astHtmlRepr()
}
}
@ -462,59 +467,94 @@ object DocParserHTMLGenerator {
def createDefWithBody(
name: AST.Cons,
args: List[AST],
body: AST.Block
body: AST.Block,
tags: Option[Doc.Tags]
): astHtmlRepr = {
val firstLine = Line(Option(body.firstLine.elem), body.firstLine.off)
val constructorsHeader = HTML.h2(`class` := "constr")("Constructors")
val methodsHeader = HTML.h2(`class` := "constr")("Methods")
val allLines = firstLine :: body.lines
val generatedCode = renderHTMLOnLine(allLines)
val typesList =
generatedCode.filter(_.toString().contains("class=\"DefTitle\""))
val infixList =
generatedCode.filter(_.toString().contains("class=\"Infix\""))
val head = createDefTitle(name, args)
val clsBody = HTML.`class` := "DefBody"
val lines =
HTML.div(clsBody)(constructorsHeader, typesList, methodsHeader, infixList)
val cls = HTML.`class` := "Def"
astHtmlRepr(HTML.div(cls)(head), HTML.div(cls)(lines))
}
val firstLine = Line(Option(body.firstLine.elem), body.firstLine.off)
val atomsHeader = HTML.h2(
HTML.div(HTML.`class` := "flex")(
HTML.raw(
"<AtomsIcon className=\"-ml-16 -mb-3 mr-4 self-center h-12 p-2 text-content-title-on-dark bg-accent-important fill-current rounded-xl\" />"
),
HTML.p("Atoms")
)
)
/** Helper function for [[createHTMLFromAST]] to generate appropriate code
* from [[AST.Def]] when it doesn't contain anything in it's body
*
* @param name - Def Name
* @param args - Def Arguments
* @return - HTML code generated from Def
*/
def createDefWithoutBody(
name: AST.Cons,
args: List[AST]
): TypedTag[String] = {
val cls = HTML.`class` := "DefNoBody"
HTML.div(cls)(createDefTitle(name, args))
}
val extMethodsHeader = HTML.h2(
HTML.div(HTML.`class` := "flex")(
HTML.raw(
"<MethodsIcon className=\"-ml-16 -mb-3 mr-4 self-center h-12 p-2 text-content-title-on-dark bg-accent-important fill-current rounded-xl\" />"
),
HTML.p("Methods")
)
)
/** Helper function for [[createDefWithBody]] or [[createDefWithoutBody]]
* to generate [[AST.Def]] title form it's name and args
*
* @param name - Def Name
* @param args - Def Arguments
* @return - Def title in HTML
*/
def createDefTitle(name: AST.Cons, args: List[AST]): TypedTag[String] = {
val clsTitle = HTML.`class` := "DefTitle"
val clsArgs = HTML.`class` := "DefArgs"
val nameStr = name.show()
val argsStr = args.map(_.show())
var argsStrUrl = argsStr.mkString("_")
if (argsStr.nonEmpty) {
argsStrUrl = "_" + argsStrUrl
val allLines = firstLine :: body.lines
val generatedCode = renderHTMLOnLine(allLines)
val atoms = generatedCode.filter(
_.toString().contains("class=\"atom flex\"")
)
val methods = generatedCode.filter(
_.toString().contains("class=\"method flex\"")
)
val head = createDocTitle(name, args, tags)
var methodsLines = HTML.div()
var atomsLines = HTML.div()
if (methods.nonEmpty) {
methodsLines =
HTML.div(HTML.`class` := "methods")(extMethodsHeader, methods)
}
val pageHref = HTML.`href` := nameStr + argsStrUrl + ".html"
val innerDiv = HTML.div(clsTitle)(nameStr, HTML.div(clsArgs)(argsStr))
HTML.a(pageHref)(innerDiv)
if (atoms.nonEmpty) {
atomsLines = HTML.div(HTML.`class` := "atoms")(atomsHeader, atoms)
}
val lines = HTML.div(atomsLines, methodsLines)
astHtmlRepr(head, lines)
}
def createDocTitle(
name: AST.Cons,
args: List[AST],
tags: Option[Doc.Tags]
): TypedTag[String] = {
val nameStr = name.show()
val argsStr = args.map(_.show())
val tagsHtml = tags.getOrElse(Doc.Elem.Text("")).html
HTML.h1(
HTML.p(
HTML.span(HTML.`class` := "name")(nameStr),
" ",
HTML.span(HTML.`class` := "parameter")(argsStr)
),
tagsHtml
)
}
/** Function to generate Atoms from it's name and tags.
*
* @param name - Atom Name
* @param args - Atom Arguments
* @param tags - Atom Tags
* @return - HTMl doc of atom.
*/
def createAtomHtmlRepr(
name: AST.Cons,
args: List[AST],
tags: Option[Doc.Tags]
): TypedTag[String] = {
val nameStr = name.show()
val argsStr = args.map(_.show() + " ")
val tagsHtml = tags.getOrElse(Doc.Elem.Text("")).html
HTML.div(HTML.`class` := "atom flex")(
HTML.p(
HTML.span(HTML.`class` := "name")(nameStr),
" ",
HTML.span(HTML.`class` := "parameter")(argsStr)
),
tagsHtml
)
}
/** Helper function for [[createHTMLFromAST]] to generate appropriate HTML
@ -523,13 +563,22 @@ object DocParserHTMLGenerator {
* @param infix - AST Infix
* @return - HTML code generated from Infix
*/
def createInfixHtmlRepr(infix: AST.App.Infix): TypedTag[String] = {
val cls = HTML.`class` := "Infix"
val pageHref = HTML.`href` := infix.larg
.show()
.replaceAll(" ", "_") + ".html"
val innerDiv = HTML.div(cls)(infix.larg.show())
HTML.a(pageHref)(innerDiv)
def createInfixHtmlRepr(
infix: AST.App.Infix,
tags: Option[Doc.Tags]
): TypedTag[String] = {
val nameStr = infix.larg.show().split(" ").head
val argsStr = infix.larg.show().split(" ").tail.mkString(" ")
val tagsHtml = tags.getOrElse(Doc.Elem.Text("")).html
HTML.div(HTML.`class` := "method flex")(
HTML.p(
HTML.span(HTML.`class` := "name")(nameStr),
" ",
HTML.span(HTML.`class` := "argument")(argsStr)
),
tagsHtml
)
}
/** Helper function for [[createDefWithBody]] to traverse through body's lines
@ -542,18 +591,9 @@ object DocParserHTMLGenerator {
def renderHTMLOnLine(lines: List[AST.Block.OptLine]): List[TypedTag[String]] =
lines match {
case Line(Some(AST.Documented.any(doc)), _) :: rest =>
val cls = HTML.`class` := "DefDoc"
val docHtml = DocumentedToHtml(doc.ast, doc.doc, true)
HTML.div(cls)(docHtml) :: renderHTMLOnLine(rest)
case x :: rest =>
x match {
case Line(Some(d), _) =>
val cls = HTML.`class` := "DefNoDoc"
val astHtml = createHTMLFromAST(d)
val div = HTML.div(cls)(astHtml.header, astHtml.body)
div :: renderHTMLOnLine(rest)
case _ => renderHTMLOnLine(rest)
}
val docHtml = DocumentedToHtml(doc.ast, doc.doc)
HTML.div(docHtml) :: renderHTMLOnLine(rest)
case _ :: rest => renderHTMLOnLine(rest)
case other =>
other match {
case Nil => List()

View File

@ -506,16 +506,12 @@ object Main extends scala.App {
| ## The pow function calculates power of integers.
| pow x y = x ** y
|""".stripMargin
val inC =
"""
|## DEPRECATED
| REMOVED - replaced by Foo Bar
| ADDED
| MODIFIED
| UPCOMING
| ALAMAKOTA a kot ma Ale
| Optional values.
|
val inCdeprecated =
"""## ADDED in 2.0
| MODIFIED in 2.1
| UNSTABLE
| Optional values.
|
| Type `Option` represents an optional value: every `Option` is either `Some`
| and contains a value, or `None`, and does not. Option types are very common
| in Enso code, as they have a number of uses:
@ -527,20 +523,94 @@ object Main extends scala.App {
| - Optional function arguments.
| `Option`s are commonly paired with pattern matching to query the presence of
| a value and take action, always accounting for the None case.
|
|type Option a
| ## The `Some` type indicates a presence of a value.
|
| ## ADVANCED
| The `Some` type indicates a presence of a value.
| type Some a
|
| ## The `None` type indicates a lack of a value.
|
| It is a very common type and is used by such types as `Maybe` or `List`.
| Also, `None` is the return value of functions which do not return an
| explicit value.
| ## MODIFIED
| The `None` type indicates a lack of a value.
|
| It is a very common type and is used by such types as `Maybe` or `List`.
| Also, `None` is the return value of functions which do not return an
| explicit value.
| type None
|
| ## DEPRECATED
| PRIVATE
| UNSTABLE
| TEXTONLY
| The `Nothing` is previous `None`.
| type Nothing
|
| ## The pow function calculates power of integers.
|
| ! Important
| This function, if used wildly, will break space-time continuum.
| pow x y = x ** y
|
|## TEXTONLY
| PRIVATE
| This is a testing framework for `Option`.
|
| ? Info
| It doesn't do too much in current state.
|type Option_Testing
| type Foo
| type Bar
|""".stripMargin
val inC =
"""from Standard.Base import all
|
|## A type representing computations that may fail.
|type Maybe
|
| ## No contained value.
| Nothing
|
| ## A value.
|
| Arguments:
| - value: The contained value in the maybe.
| type Some value
|
| ## Applies the provided function to the contained value if it exists,
| otherwise returning the provided default value.
|
| Arguments:
| - default: The value to return if `this` is Nothing. This value is lazy
| and hence will not execute any provided computation unless it is used.
| - function: The function to execute on the value inside the `Some`, if it
| is a just.
|
| > Example
| Apply a function over a Some value to get 4.
| (Some 2).maybe 0 *2
| maybe : Any -> (Any -> Any) -> Any
| maybe ~default function = case this of
| Nothing -> default
| Some val -> function val
|
| ## Check if the maybe value is `Some`.
|
| > Example
| Check if `Nothing` is `Some`.
| Nothing.is_some
| is_some : Boolean
| is_some = case this of
| Nothing -> False
| Some _ -> True
|
| ## Check if the maybe value is `Nothing`.
|
| > Example
| Check if `Nothing` is `Nothing`.
| Nothing.is_nothing
| is_nothing : Boolean
| is_nothing = this.is_some.not
|
|""".stripMargin
println("--- PARSING ---")
@ -608,6 +678,7 @@ object Main extends scala.App {
| Here is a small test of Example Section
| Import Foo
| def Bar a
| Foo x y
|""".stripMargin
val doc2 = DocParser.runMatched(inpOnlyDoc)
val htmlCode2 = DocParserHTMLGenerator.generateHTMLPureDoc(doc2)