From 05877a7d0cceb7c88dd10f72ec0ab1a5b73b9547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Miko=C5=82ajek?= Date: Mon, 25 Nov 2019 10:36:31 +0100 Subject: [PATCH] Documentation Parser: Extended HTML Files Generator (#237) * Update CSS * Add some code - WIP * recursive Documented HTML generation * WIP - Better documentation, better code, better test * documentation wip * code complexity changes * update docs * update docs * move saving out of class * move all html generator code to separate object * Fix documentation * Change example text * 80 chars/ln * Merge remote-tracking branch 'origin/master' into wip/mm/doc-parser-html-output # Conflicts: # Syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala * Merge remote-tracking branch 'origin/master' into wip/mm/doc-parser-html-output # Conflicts: # Syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala * just add example * add sass * minor fix * cleanup * rm unused func --- .../scala/org/enso/syntax/text/ast/Doc.scala | 36 +- .../org/enso/syntax/text/DocParser.scala | 328 ++++++++++++---- .../enso/syntax/text/DocParserStyle/style.css | 333 ---------------- .../syntax/text/DocParserStyle/style.sass | 359 ++++++++++++++++++ .../scala/org/enso/syntax/text/Parser.scala | 100 +++-- 5 files changed, 703 insertions(+), 453 deletions(-) delete mode 100644 common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.css create mode 100644 common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.sass diff --git a/common/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/Doc.scala b/common/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/Doc.scala index a18b0a1d9b1..ca1d90630cd 100644 --- a/common/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/Doc.scala +++ b/common/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/Doc.scala @@ -35,7 +35,7 @@ final case class Doc( } object Doc { - def apply(): Doc = Doc(None, None, None) + def apply(): Doc = Doc(None, None, None) def apply(tags: Tags): Doc = Doc(Some(tags), None, None) def apply(synopsis: Synopsis): Doc = Doc(None, Some(synopsis), None) @@ -61,18 +61,8 @@ object Doc { * extending tokens and getting HTML file out of Doc Parser */ sealed trait Symbol extends Repr.Provider { - def show() = repr.build() + def show(): String = repr.build() def html: HTML - def renderHTML(cssLink: String): HTMLTag = { - val metaEquiv = HTML.httpEquiv := "Content-Type" - val metaCont = HTML.content := "text/html" - val metaChar = HTML.charset := "UTF-8" - val meta = HTML.meta(metaEquiv)(metaCont)(metaChar) - val cssRel = HTML.rel := "stylesheet" - val cssHref = HTML.href := cssLink - val css = HTML.link(cssRel)(cssHref) - HTML.html(HTML.head(meta, css), HTML.body(html)) - } def htmlCls(): generic.AttrPair[Builder, String] = HTML.`class` := getClass.toString.split('$').last.split('.').last @@ -155,7 +145,7 @@ object Doc { } object Unclosed { - def apply(typ: Type): Unclosed = Unclosed(typ, Nil) + def apply(typ: Type): Unclosed = Unclosed(typ, Nil) def apply(typ: Type, elem: Elem): Unclosed = Unclosed(typ, elem :: Nil) def apply(typ: Type, elems: Elem*): Unclosed = Unclosed(typ, elems.toList) @@ -340,7 +330,7 @@ object Doc { */ sealed trait Section extends Symbol { def indent: Int - def elems: List[Elem] + def elems: List[Elem] def reprOfNormalText(elem: Elem, prevElem: Elem): Repr.Builder = { prevElem match { @@ -363,7 +353,7 @@ object Doc { val html: HTML = Seq(HTML.div(htmlCls())(elems.map(_.html))) } object Header { - def apply(elem: Elem): Header = Header(elem :: Nil) + def apply(elem: Elem): Header = Header(elem :: Nil) def apply(elems: Elem*): Header = Header(elems.toList) } @@ -441,13 +431,13 @@ object Doc { } object Raw { - def apply(indent: Int): Raw = Raw(indent, Nil) - def apply(indent: Int, elem: Elem): Raw = Raw(indent, elem :: Nil) + def apply(indent: Int): Raw = Raw(indent, Nil) + def apply(indent: Int, elem: Elem): Raw = Raw(indent, elem :: Nil) def apply(indent: Int, elems: Elem*): Raw = Raw(indent, elems.toList) - val defaultIndent = 0 - def apply(): Raw = Raw(defaultIndent, Nil) - def apply(elem: Elem): Raw = Raw(defaultIndent, elem :: Nil) - def apply(elems: Elem*): Raw = Raw(defaultIndent, elems.toList) + val defaultIndent = 0 + def apply(): Raw = Raw(defaultIndent, Nil) + def apply(elem: Elem): Raw = Raw(defaultIndent, elem :: Nil) + def apply(elems: Elem*): Raw = Raw(defaultIndent, elems.toList) } } @@ -511,7 +501,7 @@ object Doc { val html: HTML = Seq(HTML.div(htmlCls())(elems.toList.map(_.html))) } object Tags { - def apply(elem: Tag): Tags = Tags(List1(elem)) + def apply(elem: Tag): Tags = Tags(List1(elem)) def apply(elems: Tag*): Tags = Tags(List1(elems.head, elems.tail.toList)) /** Tag - one single tag for Tags @@ -535,7 +525,7 @@ object Doc { } } object Tag { - val defaultIndent = 0 + val defaultIndent = 0 def apply(typ: Type): Tag = Tag(defaultIndent, typ, None) def apply(typ: Type, details: String): Tag = Tag(defaultIndent, typ, Some(details)) diff --git a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParser.scala b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParser.scala index 2e397ebf023..867e6cf7703 100644 --- a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParser.scala +++ b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParser.scala @@ -1,5 +1,6 @@ package org.enso.syntax.text +import java.io.{File, PrintWriter} import org.enso.flexer import org.enso.flexer.Reader import org.enso.syntax.text.ast.Doc @@ -20,6 +21,7 @@ import org.enso.syntax.text.AST.Block.{LineOf => Line} * * It is used to create structured documentation from the blocks of commented * text created by the main Enso parser. + * * It has been built on the same foundation as Parser, so in order not to * duplicate information, please refer to Parser documentation. */ @@ -47,65 +49,6 @@ class DocParser { * @return - unmatched result possibly containing Doc */ def run(input: String): Result[Doc] = engine.run(new Reader(input)) - - ////////////////////////////////////////////////////////////////////////////// - //// HTML Rendering of Documentation ///////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - - // TODO remove this functionality from parser - /** - * Used to create HTML files from Doc with or without title after Doc Parser - * Runner finished it's job - * - * @param documented - documented made by Doc Parser Runner from AST and Doc - */ - def onHTMLRendering(documented: AST.Documented): Unit = { - val path = "syntax/specialization/target/" - val cssFileName = "style.css" - val htmlCode = renderHTML(documented.ast, documented.doc, cssFileName) - val astLines = documented.ast.show().split("\n") - val fileName = astLines.head.replaceAll("/", "") - htmlCode - } - - /** - * Function invoked by [[onHTMLRendering]] to render HTML File - * - * @param ast - ast from Doc Parser Runner - * @param doc - Doc from Doc Parser - * @param cssLink - string containing CSS file name - * @return - HTML Code from Doc with optional title from AST - */ - def renderHTML( - ast: AST, - doc: Doc, - cssLink: String = "style.css" - ): TypedTag[String] = { - val title = ast.show().split("\n").head - val astHtml = Seq(HTML.div(HTML.`class` := "ASTData")(ast.show())) - val docClass = HTML.`class` := "Documentation" - val documentation = Seq(HTML.div(docClass)(doc.html, astHtml)) - HTML.html(createHTMLHead(title, cssLink), HTML.body(documentation)) - } - - /** - * Function invoked by [[renderHTML]] to create HTML.Head part of file - * - * @param title - HTML page title - * @param cssLink - string containing CSS file name - * @return - HTML Head Code - */ - def createHTMLHead(title: String, cssLink: String): TypedTag[String] = { - val metaEquiv = HTML.httpEquiv := "Content-Type" - val metaCont = HTML.content := "text/html" - val metaChar = HTML.charset := "UTF-8" - val meta = HTML.meta(metaEquiv)(metaCont)(metaChar) - val cssRel = HTML.rel := "stylesheet" - val cssHref = HTML.href := cssLink - val css = HTML.link(cssRel)(cssHref) - val fileTitle = scalatags.Text.tags2.title(title) - HTML.head(meta, css)(fileTitle) - } } object DocParser { @@ -117,7 +60,6 @@ object DocParser { */ def runMatched(input: String): Doc = new DocParser().runMatched(input) def run(input: String): Result[Doc] = new DocParser().run(input) - } //////////////////////////////////////////////////////////////////////////////// @@ -130,8 +72,9 @@ object DocParser { * Essentially it binds together Enso Parser with Doc Parser. * When Parser finishes its job it invokes runner with AST created by it after * resolving macros. Then Runner does it's job - running Doc Parser on every - * [[AST.Comment]], combined with connecting [[Doc]] with AST in [[AST.Documented]] - * node, which gets AST from [[AST.Def]] and [[AST.App.Infix]] + * [[AST.Comment]], combined with connecting [[Doc]] with AST in + * [[AST.Documented]] node, which gets AST from [[AST.Def]] and + * [[AST.App.Infix]] */ object DocParserRunner { ////////////////////////////////////////////////////////////////////////////// @@ -165,8 +108,8 @@ object DocParserRunner { } /** - * Helper functions for [[createDocs]] - * to traverse through Module and Def body + * This is a helper function for [[createDocs]] to traverse through + * [[AST.Module]] and create Docs from comments with appropriate [[AST]] */ def createDocsFromModule(m: AST.Module): AST.Module = { val emptyLine = List1(AST.Block.OptLine()) @@ -175,6 +118,11 @@ object DocParserRunner { AST.Module(transformedLines) } + /** + * This is a helper function for [[createDocs]] to traverse through + * [[AST.Def]] and create Docs from comments inside [[AST.Def]] with + * appropriate [[AST]] + */ def createDocsFromDefBody( name: AST.Cons, args: List[AST], @@ -301,6 +249,7 @@ object DocParserRunner { * method * * @param comment - comment found in AST + * @param emptyLines - Empty lines in between Doc and AST * @param off - line offset * @param ast - AST to go with comment into Documented * @return - [[AST.Documented]] @@ -315,24 +264,263 @@ object DocParserRunner { val documented = Some(AST.Documented(doc, emptyLines, ast)) Line(documented, off) } +} - ////////////////////////////////////////////////////////////////////////////// - //// Generating HTML for created Doc's /////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//// Doc Parser HTML Generator ///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +/** + * This is Doc Parser HTML Generator. + * + * Essentially it enables Doc Parser to create pretty HTML files from + * documented code. + * + * When Doc Parser finishes its job user can invoke DocParserHTMLGenerator by + * simply passing the output of Doc Parser onto function called + * [[DocParserHTMLGenerator.generateHTMLForEveryDocumented]], and it will + * automatically traverse through AST prepared by Doc Parser and generate + * HTML files in all appropriate places. + */ +object DocParserHTMLGenerator { /** * This method is used for generation of HTML files from parsed and * reformatted [[AST.Documented]] * * @param ast - parsed AST.Module and reformatted using Doc Parser + * @param path - path to save file + * @param cssFileName - name of file containing stylesheets for the HTML code */ - def generateHTMLForEveryDocumented(ast: AST): Unit = { + def generateHTMLForEveryDocumented( + ast: AST, + path: String, + cssFileName: String + ): Unit = { ast.map { elem => elem match { - case AST.Documented.any(d) => new DocParser().onHTMLRendering(d) - case _ => generateHTMLForEveryDocumented(elem) + case AST.Documented.any(d) => + val file = onHTMLRendering(d, cssFileName) + saveHTMLToFile(path, file._2, file._1) + case _ => generateHTMLForEveryDocumented(elem, path, cssFileName) } elem } } + + /** + * Saves HTML code to file + * + * @param path - path to file + * @param name - file name + * @param code - HTML code generated with Doc Parser + */ + def saveHTMLToFile( + path: String, + name: String, + code: TypedTag[String] + ): Unit = { + val writer = new PrintWriter(new File(path + name + ".html")) + writer.write(code.toString) + writer.close() + } + + ////////////////////////////////////////////////////////////////////////////// + //// HTML Rendering of Documentation ///////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + /** + * Used to create HTML files from Doc with or without title after Doc Parser + * Runner finished it's job + * + * @param documented - documented made by Doc Parser Runner from AST and Doc + * @param cssFileName - name of file containing stylesheets for the HTML code + * @return - tuple containing HTML code with file name + */ + def onHTMLRendering( + documented: AST.Documented, + cssFileName: String + ): (TypedTag[String], String) = { + val htmlCode = renderHTML(documented.ast, documented.doc, cssFileName) + val astLines = documented.ast.show().split("\n") + val fileName = astLines.head.replaceAll("/", "") + (htmlCode, fileName) + } + + /** + * Function invoked by [[onHTMLRendering]] to render HTML File + * + * @param ast - AST from Parser + * @param doc - Doc from Doc Parser + * @param cssLink - string containing CSS file name + * @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, + cssLink: String = "style.css" + ): TypedTag[String] = { + val title = ast.show().split("\n").head + val documentation = DocumentedToHtml(ast, doc) + HTML.html(createHTMLHead(title, cssLink), 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. + * + * @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]] + */ + def DocumentedToHtml( + ast: AST, + doc: Doc + ): TypedTag[String] = { + val astCls = HTML.`class` := "ASTData" + val astHtml = Seq(HTML.div(astCls)(createHTMLFromAST(ast))) + val docClass = HTML.`class` := "Documentation" + HTML.div(docClass)(doc.html, astHtml) + } + + /** + * Function invoked by [[DocumentedToHtml]] to create HTML from AST in + * [[AST.Documented]] + * + * @param ast - AST + * @return - HTML Code + */ + def createHTMLFromAST(ast: AST): TypedTag[String] = { + 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 _ => createDefWithoutBody(d.name, d.args) + } + case None => createDefWithoutBody(d.name, d.args) + } + case AST.App.Infix.any(i) => createInfixHtmlRepr(i) + case _ => HTML.div() + } + } + + /** + * Helper function for [[createHTMLFromAST]] to generate appropriate code + * from [[AST.Def]] with traversing through body and creating HTML code + * on elements in it + * + * @param name - Def Name + * @param args - Def Arguments + * @param body - Def body + * @return - HTML code generated from Def + */ + def createDefWithBody( + name: AST.Cons, + args: List[AST], + body: AST.Block + ): TypedTag[String] = { + val firstLine = Line(Option(body.firstLine.elem), body.firstLine.off) + val allLines = firstLine :: body.lines + val generatedCode = renderHTMLOnLine(allLines) + val head = createDefTitle(name, args) + val clsBody = HTML.`class` := "DefBody" + val lines = HTML.div(clsBody)(generatedCode) + val cls = HTML.`class` := "Def" + HTML.div(cls)(head, lines) + } + + /** + * 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)) + } + + /** + * 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" + HTML.div(clsTitle)(name.show(), HTML.div(clsArgs)(args.map(_.show()))) + } + + /** + * Helper function for [[createHTMLFromAST]] to generate appropriate HTML + * code from [[AST.App.Infix]] + * + * @param infix - AST Infix + * @return - HTML code generated from Infix + */ + def createInfixHtmlRepr(infix: AST.App.Infix): TypedTag[String] = { + val cls = HTML.`class` := "Infix" + HTML.div(cls)(infix.larg.show()) + } + + /** + * Helper function for [[createDefWithBody]] to traverse through body's lines + * and try to generate HTML code from [[AST.Documented]] parts of it. It also + * tries to find nested [[AST.Def]] and [[AST.App.Infix]] inside of body + * + * @param lines - lines inside of Def body + * @return - HTML code generated from contents of lines + */ + 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) + HTML.div(cls)(docHtml) :: renderHTMLOnLine(rest) + case x :: rest => + x match { + case Line(Some(d), _) => + val cls = HTML.`class` := "DefNoDoc" + val astHtml = createHTMLFromAST(d) + HTML.div(cls)(astHtml) :: renderHTMLOnLine(rest) + case _ => renderHTMLOnLine(rest) + } + case other => + other match { + case Nil => List() + case _ :: rest => renderHTMLOnLine(rest) + } + } + + /** + * Function invoked by [[DocumentedToHtml]] to create HTML.Head part of file + * + * @param title - HTML page title + * @param cssLink - string containing CSS file name + * @return - HTML Head Code + */ + def createHTMLHead(title: String, cssLink: String): TypedTag[String] = { + val metaEquiv = HTML.httpEquiv := "Content-Type" + val metaCont = HTML.content := "text/html" + val metaChar = HTML.charset := "UTF-8" + val meta = HTML.meta(metaEquiv)(metaCont)(metaChar) + val cssRel = HTML.rel := "stylesheet" + val cssHref = HTML.href := cssLink + val css = HTML.link(cssRel)(cssHref) + val fileTitle = scalatags.Text.tags2.title(title) + HTML.head(meta, css)(fileTitle) + } } diff --git a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.css b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.css deleted file mode 100644 index 5593a974633..00000000000 --- a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.css +++ /dev/null @@ -1,333 +0,0 @@ -/*////////////////////////////////////////////////////////////////////////////// -//// UNDER CONSTRUCTION //// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*/////////// -//// DOM //// -///////////*/ - -body { - -webkit-font-smoothing: antialiased; - font-style: normal; - word-wrap: break-word; - font-size: 17px; - line-height: 1.52947; - font-weight: 400; - letter-spacing: -0.021em; - font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica", - "Arial", sans-serif; - background-color: white; - color: #333333; - font-style: normal; -} - -p { - display: block; - margin-block-start: 1em; - margin-block-end: 1em; - margin-inline-start: 0px; - margin-inline-end: 0px; -} - -a:hover { - color: #35a5ff !important; - text-decoration: none; -} - -a { - color: #0070c9; - background-color: transparent; - text-decoration: none; - display: inline-block; - transition: all 0.3s ease; -} - -img { - display: block; -} - -code { - color: #0070c9; - background-color: transparent; - font-size: inherit; - font-family: monospace; - line-height: inherit; - display: inline-block; - white-space: pre-wrap; -} - -button { - display: inline-block; - padding: 8px 30px; - margin: 10px 0; - outline: none; - background-color: #777; - border: none; - color: #fafafa; - border-radius: 5px; - box-shadow: 5px 0 #555; - font-size: 13px; - vertical-align: top; - transition: all 0.3s ease; -} - -button:hover { - background-color: #333; -} - -b { - font-weight: 600; -} - -h1 { - font-size: 34px; - line-height: 1.08824; - font-weight: 500; - letter-spacing: 0.01em; -} - -h2 { - font-size: 28px; - line-height: 1.1073; - font-weight: 500; - letter-spacing: 0.012em; -} - -.Body h2 { - margin: 0; - margin-top: 0.65rem; -} - -/*/////////////////// -//// Invalid AST //// -///////////////////*/ - -.creator .Unclosed, -.creator .invalidIndent, -.creator .invalidLink{ - display: inline; - color: orangered; -} - -.Unclosed, -.invalidIndent, -.invalidLink { - display: inline; -} - - -/*////////////// -//// Header //// -//////////////*/ - -.Header { - font-size: 19px; - font-weight: 500; -} - -.Important .Header, -.Info .Header, -.Example .Header { - margin-bottom: 0.7em; - font-weight: 600; - letter-spacing: -0.021em; - line-height: 17px; - font-synthesis: none; - font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica", - "Arial", sans-serif; -} - -/*//////////// -//// Tags //// -////////////*/ - -.Doc .Tags { - margin-left: auto; - margin-right: auto; - margin-bottom: 20px; - padding: 15px 0; - text-align: center; - background-color: #fafafa; -} - -.Doc .ExtForTagDetails { - margin: 0 3px; - color: #999999; -} - -.Doc .Tags .DEPRECATED, -.Doc .Tags .MODIFIED, -.Doc .Tags .ADDED, -.Doc .Tags .UPCOMING, -.Doc .Tags .REMOVED, -.Doc .Tags .UNRECOGNIZED { - line-height: 1.5; - font-weight: 400; - border-radius: 3px; - font-size: 12px; - letter-spacing: -0.021em; - border: 1px solid; - display: inline-flex; - padding: 3px 15px; - margin: 2px; - white-space: nowrap; - background: transparent; -} - -.Doc .Tags .DEPRECATED { - border-color: #c35400; - color: #c35400; -} - -.Doc .Tags .MODIFIED { - border-color: #8A82CF; - color: #8A82CF; -} - -.Doc .Tags .ADDED { - border-color: #79A129; - color: #79A129; -} - -.Doc .Tags .UPCOMING, -.Doc .Tags .REMOVED, -.Doc .Tags .UNRECOGNIZED { - border-color: #888888; - color: #666666; -} - -.creator .Doc .Tags .UNRECOGNIZED { - border: 2px solid; - color: orangered; -} -/*//////////////// -//// Sections //// -////////////////*/ - -.Raw, -.Important, -.Info, -.CodeBlock, -.Example { - margin-top: 0; - margin-left: auto; - margin-right: auto; - position: relative; -} - -.Body .Raw { - padding: 0.75em 0; - margin-bottom: 0.6rem; - font-size: 17px; - line-height: 1.52947; - font-weight: 400; - letter-spacing: -0.021em; - font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica", - "Arial", sans-serif; - background-color: white; - color: #333333; - font-style: normal; -} - -.Synopsis .Raw { - margin-bottom: 2rem; -} - -.Important, -.Info, -.CodeBlock, -.Example { - font-size: 17; - padding-top: 0.94118rem; - padding-bottom: 0.94118rem; - padding-left: 18px; - padding-right: 10px; - border: 1px solid #8a82cf; - border-left: 6px solid #8a82cf; - border-radius: 6px; - margin: 0.7em 0; -} - -.Important { - border-right: 0px; - border-top: 0px; - border-bottom: 0px; - border-color: #fee450; - background-color: #fbf8e8; -} - -.Info { - border-color: 69c5e8; -} - -.Example { - border-color: #8a82cf; -} - -.CodeBlock { - border-color: #7cd58b; - margin: 10px 20px; - display: none; -} - -/*///////////////////////// -//// Synopsis & Detail //// -/////////////////////////*/ - -.Synopsis, -.Body { - margin: 0 auto; - padding: 5px; - margin-bottom: 20px; - text-align: left; -} - -.Synopsis { - font-size: 20px; - line-height: 1.5; - font-weight: 300; - letter-spacing: 0.017em; - border-bottom: 1px solid #d6d6d6 -} - -.Doc { - margin: 0; - width: 100%; - background-color: #ffffff; -} - -@media (min-width: 300px) { - .Synopsis, - .Body { - width: 380px - } -} - -@media (min-width: 500px) { - .Synopsis, - .Body { - width: 440px - } -} - -@media (min-width: 600px) { - .Synopsis, - .Body { - width: 490px - } -} - -@media (min-width: 900px) { - .Synopsis, - .Body { - width: 670px - } -} - -@media (min-width: 1300px) { - .Synopsis, - .Body { - width: 780px - } -} diff --git a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.sass b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.sass new file mode 100644 index 00000000000..cfbb873be12 --- /dev/null +++ b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/DocParserStyle/style.sass @@ -0,0 +1,359 @@ +/*/////////// +//// DOM //// +///////////*/ + +body + -webkit-font-smoothing: antialiased + font-style: normal + word-wrap: break-word + font-size: 17px + line-height: 1.52947 + font-weight: 400 + letter-spacing: -0.021em + font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica","Arial", sans-serif + background-color: white + color: #333333 + font-style: normal + +p + display: block + margin-block-start: 1em + margin-block-end: 1em + margin-inline-start: 0 + margin-inline-end: 0 + +a:hover + color: #35a5ff !important + text-decoration: none + +a + color: #0070c9 + background-color: transparent + text-decoration: none + display: inline-block + transition: all 0.3s ease + +img + display: block + +code + color: #0070c9 + background-color: transparent + font-size: inherit + font-family: monospace + line-height: inherit + display: inline-block + white-space: pre-wrap + +button + display: inline-block + padding: 8px 30px + margin: 10px 0 + outline: none + background-color: #777 + border: none + color: #fafafa + border-radius: 5px + box-shadow: 5px 0 #555 + font-size: 13px + vertical-align: top + transition: all 0.3s ease + +button:hover + background-color: #333 + +b + font-weight: 600 + +h1 + font-size: 34px + line-height: 1.08824 + font-weight: 500 + letter-spacing: 0.01em + + +h2 + font-size: 28px + line-height: 1.1073 + font-weight: 500 + letter-spacing: 0.012em + + +.Body h2 + margin: 0 + margin-top: 0.65rem + + +/*/////////////////// +//// Invalid AST //// +///////////////////*/ + +// Creator - a special mode for displaying parsing errors in output +.creator + .Unclosed, + .invalidIndent, + .invalidLink + display: inline + color: orangered + + .Tags + .UNRECOGNIZED + border: 2px solid + color: orangered + +.Unclosed, +.invalidIndent, +.invalidLink + display: inline + +/*////////////// +//// Header //// +//////////////*/ + +.Header + font-size: 19px + font-weight: 500 + + +.Important .Header, +.Info .Header, +.Example .Header + margin-bottom: 0.7em + font-weight: 600 + letter-spacing: -0.021em + line-height: 17px + font-synthesis: none + font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica","Arial", sans-serif + + +/*//////////// +//// Tags //// +////////////*/ + +.Tags + margin-left: auto + margin-right: auto + margin-bottom: 20px + padding: 15px 10px + text-align: center + background-color: #fafafa + border-radius: 8px + + .DEPRECATED, + .MODIFIED, + .ADDED, + .UPCOMING, + .REMOVED, + .UNRECOGNIZED + line-height: 1.5 + font-weight: 400 + border-radius: 3px + font-size: 12px + letter-spacing: -0.021em + border: 1px solid + display: inline-flex + padding: 3px 15px + margin: 2px + white-space: nowrap + background: transparent + + .DEPRECATED + border-color: #c35400 + color: #c35400 + .MODIFIED + border-color: #8A82CF + color: #8A82CF + .ADDED + border-color: #79A129 + color: #79A129 + .UPCOMING, + .REMOVED, + .UNRECOGNIZED + border-color: #888888 + color: #666666 + +.ExtForTagDetails + margin: 0 3px + color: #999999 + +/*//////////////// +//// Sections //// +////////////////*/ + +.Raw, +.Important, +.Info, +.CodeBlock, +.Example + margin-top: 0 + margin-left: auto + margin-right: auto + position: relative + + +.Body .Raw + padding: 0.75em 0 + margin-bottom: 0.6rem + font-size: 17px + line-height: 1.52947 + font-weight: 400 + letter-spacing: -0.021em + font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica","Arial", sans-serif + background-color: white + color: #333333 + font-style: normal + + +.Synopsis .Raw + margin-bottom: 2rem + + +.Important, +.Info, +.CodeBlock, +.Example + font-size: 17px + padding-top: 0.94118rem + padding-bottom: 0.94118rem + padding-left: 18px + padding-right: 10px + border: 1px solid #8a82cf + border-left: 6px solid #8a82cf + border-radius: 6px + margin: 0.7em 0 + + +.Important + border-right: 0 + border-top: 0 + border-bottom: 0 + border-color: #fee450 + background-color: #fbf8e8 + + +.Info + border-color: #69c5e8 + + +.Example + border-color: #8a82cf + + +.CodeBlock + border-color: #7cd58b + margin: 10px 20px + display: none + + +/*/////////////////////////////////// +//// HTML generated - Def, Infix //// +///////////////////////////////////*/ + +.Def + padding: 10px 20px + + .Synopsis, + .Body, + .Tags, + .ASTData + width: 90% !important + + +.DefTitle + display: inline-flex + font-size: x-large + font-weight: 600 + margin-bottom: 20px + + +.DefArgs + margin-left: 5px + font-weight: 400 + color: #0070c9 + + +.Infix + font-size: large + font-weight: 500 + margin-bottom: 20px + + +/*///////////////////////// +//// Synopsis & Detail //// +/////////////////////////*/ + +.Synopsis, +.Body + margin: 0 auto + padding: 5px + margin-bottom: 20px + text-align: left + + +.Synopsis + font-size: 20px + line-height: 1.5 + font-weight: 300 + letter-spacing: 0.017em + border-bottom: 1px solid #d6d6d6 + + +.Documentation + .ASTData + margin: 20px auto + text-align: left + line-height: 1.05 + letter-spacing: 0.008em + background-color: #fafafa + border-radius: 6px + + .Documented + margin: 0 + width: 100% + background-color: #ffffff + + +@media (min-width: 300px) + .Synopsis, + .Body, + .Tags, + .Documentation .ASTData + width: 380px + + + +@media (min-width: 500px) + .Synopsis, + .Body, + .Tags, + .Documentation .ASTData + width: 440px + + + +@media (min-width: 600px) + .Synopsis, + .Body, + .Tags, + .Documentation .ASTData + width: 490px + + + +@media (min-width: 900px) + .Synopsis, + .Body, + .Tags, + .Documentation .ASTData + width: 670px + + + +@media (min-width: 1300px) + .Synopsis, + .Body, + .Tags, + .Documentation .ASTData + width: 780px + + diff --git a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala index 6f6ef7daddd..d56ad4e67a1 100644 --- a/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala +++ b/common/scala/syntax/specialization/src/main/scala/org/enso/syntax/text/Parser.scala @@ -261,30 +261,72 @@ object Main extends App { val in2 = "(a) b = c]" val inp2 = "a (b (c)) x" - val inp = """## This function adds `x` to `y` - |add x y = x + y - |mul x y = x * y - | - |## This function divides `x` by `y` - |div x y = x / y - | - |## Just a comment - | - |## Doc for infix with empty lines between - | - |sub x y = x - y - | - |## Foo bar baz - | bax - |def Maybe a - | ## test attached to Just - | def Just val:a - | def Nothing - |""".stripMargin + val inp = + """ + |## + | DEPRECATED + | REMOVED - replaced by Foo Bar + | ADDED + | MODIFIED + | UPCOMING + | ALAMAKOTA a kot ma Ale + | This is a test of Enso Documentation Parser. This is a short synopsis. + | + | Here you can write the body of documentation. On top you can see tags + | added to this piece of code. You can customise your text with _Italic_ + | ~Strikethrough~ or *Bold*. ~_*Combined*_~ is funny + | + | + | There are 3 kinds of sections + | - Important + | - Info + | - Example + | * You can use example to add multiline code to your documentation + | + | ! Important + | Here is a small test of Important Section + | + | ? Info + | Here is a small test of Info Section + | + | > Example + | Here is a small test of Example Section + | Import Foo + | def Bar a + |type Maybe a + | ## test attached to Just + | type Just val:a + | type Nothing + |""".stripMargin + val inC = + """ + |## 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: + | - Initial values. + | - Return values for functions that are not defined over their entire input range (partial functions). + | - Return value for otherwise reporting simple errors, where `None` is returned on error. + | - Optional struct fields. + | - 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 `None` 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. + | type None + |""".stripMargin println("--- PARSING ---") - val mod = parser.run(new Reader(inp)) + val mod = parser.run(new Reader(inC)) println(Debug.pretty(mod.toString)) @@ -297,18 +339,22 @@ object Main extends App { } println("------") - println(mod.show() == inp) + println(mod.show() == inC) println("------") println(mod.show()) println("------") /** Invoking the Enso Documentation Parser */ println("===== DOCUMENTATION =====") - val isGeneratingHTML = false - val droppedMeta = parser.dropMacroMeta(mod) - val documentation = DocParserRunner.createDocs(droppedMeta) - val documentationHTML = - DocParserRunner.generateHTMLForEveryDocumented(documentation) + val droppedMeta = parser.dropMacroMeta(mod) + val documentation = DocParserRunner.createDocs(droppedMeta) + val htmlPath = "target/" + val cssFileName = "style.css" + DocParserHTMLGenerator.generateHTMLForEveryDocumented( + documentation, + htmlPath, + cssFileName + ) println(Debug.pretty(documentation.toString)) println("------") println(documentation.show())