-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite Docs Generator from Python to Scala (#1729)
- Loading branch information
1 parent
a32ceb7
commit f74d386
Showing
17 changed files
with
1,087 additions
and
572 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Docs Generator | ||
|
||
A service generating docs from contents of library, and saving them as `.html` | ||
files in a `docs` subdirectory. |
17 changes: 17 additions & 0 deletions
17
lib/scala/docs-generator/src/main/scala/org/enso/docs/generator/Constants.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.enso.docs.generator | ||
|
||
object Constants { | ||
val TEMPLATE_FILES_PATH = | ||
"./lib/scala/docs-generator/src/main/scala/org/enso/docs/generator/" | ||
val SOURCE_PATH = "./distribution/std-lib/Standard/src" | ||
val JS_TEMPLATE_NAME = "template.js" | ||
val CSS_TREE_FILE_NAME = "treeStyle.css" | ||
val OUTPUT_DIRECTORY = "docs-js" | ||
|
||
val HELP_OPTION = "help" | ||
val INPUT_PATH_OPTION = "input-path" | ||
val OUTPUT_DIR_OPTION = "output-dir" | ||
val DOCS_LIB_PATH_OPTION = "docs-lib-path" | ||
val JS_TEMPLATE_OPTION = "js-template" | ||
val CSS_TEMPLATE_OPTION = "css-template" | ||
} |
63 changes: 63 additions & 0 deletions
63
lib/scala/docs-generator/src/main/scala/org/enso/docs/generator/DocsGenerator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package org.enso.docs.generator | ||
|
||
import java.io.File | ||
import org.enso.syntax.text.{DocParser, Parser} | ||
import org.enso.syntax.text.docparser._ | ||
|
||
/** The Docs Generator class. Defines useful wrappers for Doc Parser. | ||
*/ | ||
object DocsGenerator { | ||
|
||
/** Generates HTML of docs from Enso program. | ||
*/ | ||
def run(program: String): String = { | ||
val parser = new Parser() | ||
val module = parser.run(program) | ||
val dropMeta = parser.dropMacroMeta(module) | ||
val doc = DocParserRunner.createDocs(dropMeta) | ||
val code = DocParserHTMLGenerator.generateHTMLForEveryDocumented(doc) | ||
code | ||
} | ||
|
||
/** Generates HTML from Documentation string. | ||
*/ | ||
def runOnPureDoc(comment: String): String = { | ||
val doc = DocParser.runMatched(comment) | ||
val html = DocParserHTMLGenerator.generateHTMLPureDoc(doc) | ||
html | ||
} | ||
|
||
/** Called if file doesn't contain docstrings, to let user know that they | ||
* won't find anything at this page, and that it is not a bug. | ||
*/ | ||
def mapIfEmpty(doc: String): String = { | ||
var tmp = doc | ||
if (doc.replace("<div>", "").replace("</div>", "").length == 0) { | ||
tmp = | ||
"\n\n*Enso Reference Viewer.*\n\nNo documentation available for chosen source file." | ||
tmp = runOnPureDoc(tmp).replace("style=\"font-size: 13px;\"", "") | ||
} | ||
tmp | ||
} | ||
|
||
/** Doc Parser may output file with many nested empty divs. | ||
* This simple function will remove all unnecessary HTML tags. | ||
*/ | ||
def removeUnnecessaryDivs(doc: String): String = { | ||
var tmp = doc | ||
while (tmp.contains("<div></div>")) | ||
tmp = tmp.replace("<div></div>", "") | ||
tmp | ||
} | ||
|
||
/** Traverses through root directory, outputs list of all accessible files. | ||
*/ | ||
def traverse(root: File): LazyList[File] = | ||
if (!root.exists) { LazyList.empty } | ||
else { | ||
LazyList.apply(root) ++ (root.listFiles match { | ||
case null => LazyList.empty | ||
case files => files.view.flatMap(traverse) | ||
}) | ||
} | ||
} |
220 changes: 220 additions & 0 deletions
220
lib/scala/docs-generator/src/main/scala/org/enso/docs/generator/Main.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
package org.enso.docs.generator | ||
|
||
import java.io._ | ||
import org.apache.commons.cli.{Option => CliOption, _} | ||
import scala.util.{Try, Using} | ||
import scala.io.Source | ||
import scalatags.Text.{all => HTML} | ||
import TreeOfCommonPrefixes._ | ||
import DocsGenerator._ | ||
import Constants._ | ||
import HTML._ | ||
|
||
/** The entry point for the documentation generator. | ||
* | ||
* The documentation generator is responsible for creating HTML documentation | ||
* for the Enso standard library. | ||
* It also generates JavaScript files containing react components for the | ||
* [[https://enso.org/docs/reference reference website]]. | ||
*/ | ||
object Main { | ||
|
||
/** Builds the [[Options]] object representing the CLI syntax. | ||
* | ||
* @return an [[Options]] object representing the CLI syntax | ||
*/ | ||
private def buildOptions = { | ||
val help = CliOption | ||
.builder("h") | ||
.longOpt(HELP_OPTION) | ||
.desc("Displays this message.") | ||
.build | ||
val input = CliOption.builder | ||
.longOpt(INPUT_PATH_OPTION) | ||
.numberOfArgs(1) | ||
.argName("path") | ||
.desc("Specifies working path.") | ||
.build | ||
val output = CliOption.builder | ||
.hasArg(true) | ||
.numberOfArgs(1) | ||
.argName("directory") | ||
.longOpt(OUTPUT_DIR_OPTION) | ||
.desc("Specifies name of output directory.") | ||
.build | ||
val docsPath = CliOption.builder | ||
.hasArg(true) | ||
.numberOfArgs(1) | ||
.argName("path") | ||
.longOpt(DOCS_LIB_PATH_OPTION) | ||
.desc("Specifies path of Docs Generator library.") | ||
.build | ||
val jsTemp = CliOption.builder | ||
.hasArg(true) | ||
.numberOfArgs(1) | ||
.argName("file") | ||
.longOpt(JS_TEMPLATE_OPTION) | ||
.desc("Specifies name of file containing JS template.") | ||
.build | ||
val cssTemp = CliOption.builder | ||
.hasArg(true) | ||
.numberOfArgs(1) | ||
.argName("file") | ||
.longOpt(CSS_TEMPLATE_OPTION) | ||
.desc("Specifies name of file containing CSS template.") | ||
.build | ||
|
||
val options = new Options | ||
options | ||
.addOption(help) | ||
.addOption(input) | ||
.addOption(output) | ||
.addOption(docsPath) | ||
.addOption(jsTemp) | ||
.addOption(cssTemp) | ||
|
||
options | ||
} | ||
|
||
/** Prints the help message to the standard output. | ||
* | ||
* @param options object representing the CLI syntax | ||
*/ | ||
private def printHelp(options: Options): Unit = | ||
new HelpFormatter().printHelp("Docs Generator", options) | ||
|
||
/** Terminates the process with a failure exit code. */ | ||
private def exitFail(): Nothing = sys.exit(1) | ||
|
||
/** Terminates the process with a success exit code. */ | ||
private def exitSuccess(): Nothing = sys.exit(0) | ||
|
||
/** Starting point. */ | ||
def main(args: Array[String]): Unit = { | ||
val options = buildOptions | ||
val parser = new DefaultParser | ||
val line = Try(parser.parse(options, args)).getOrElse { | ||
printHelp(options) | ||
exitFail() | ||
} | ||
if (line.hasOption(HELP_OPTION)) { | ||
printHelp(options) | ||
exitSuccess() | ||
} | ||
val path = | ||
Option(line.getOptionValue(INPUT_PATH_OPTION)).getOrElse(SOURCE_PATH) | ||
val outDir = | ||
Option(line.getOptionValue(OUTPUT_DIR_OPTION)).getOrElse(OUTPUT_DIRECTORY) | ||
val templateFilesPath = Option(line.getOptionValue(DOCS_LIB_PATH_OPTION)) | ||
.getOrElse(TEMPLATE_FILES_PATH) | ||
val jsTempFileName = Option(line.getOptionValue(JS_TEMPLATE_OPTION)) | ||
.getOrElse(JS_TEMPLATE_NAME) | ||
val cssFileName = Option(line.getOptionValue(CSS_TEMPLATE_OPTION)) | ||
.getOrElse(CSS_TREE_FILE_NAME) | ||
|
||
generateAllDocs( | ||
path, | ||
templateFilesPath, | ||
jsTempFileName, | ||
cssFileName, | ||
outDir | ||
) | ||
} | ||
|
||
/** Traverses through directory generating docs from every .enso file found. | ||
*/ | ||
def generateAllDocs( | ||
path: String, | ||
templateFilesPath: String, | ||
jsTempFileName: String, | ||
cssFileName: String, | ||
outDir: String | ||
): Unit = { | ||
val allFiles = traverse(new File(path)) | ||
.filter(f => f.isFile && f.getName.endsWith(".enso")) | ||
val allFileNames = allFiles.map( | ||
_.getPath | ||
.replace(path + "/", "") | ||
.replace(".enso", "") | ||
) | ||
val allPrograms = allFiles | ||
.map(f => Using(Source.fromFile(f, "UTF-8")) { _.mkString }) | ||
.toList | ||
val allDocs = allPrograms | ||
.map(s => run(s.getOrElse(""))) | ||
.map(mapIfEmpty) | ||
.map(removeUnnecessaryDivs) | ||
val treeNames = | ||
groupByPrefix(allFileNames.toList, '/').filter(_.elems.nonEmpty) | ||
val jsTemplate = new File(templateFilesPath + jsTempFileName) | ||
val templateCode = Using(Source.fromFile(jsTemplate, "UTF-8")) { | ||
_.mkString | ||
} | ||
val styleFile = new File(templateFilesPath + cssFileName) | ||
val styleCode = Using(Source.fromFile(styleFile, "UTF-8")) { _.mkString } | ||
val treeStyle = "<style jsx>{`" + styleCode.getOrElse("") + "`}</style>" | ||
val allDocJSFiles = allFiles.map { x => | ||
val name = x.getPath | ||
.replace(".enso", ".js") | ||
.replace("Standard/src", outDir) | ||
.replace("Main.js", "index.js") | ||
val ending = name.split(outDir + "/").tail.head | ||
name.replace(ending, ending.replace('/', '-')) | ||
} | ||
val dir = new File(allDocJSFiles.head.split(outDir).head + outDir + "/") | ||
dir.mkdirs() | ||
val zippedJS = allDocJSFiles.zip(allDocs) | ||
zippedJS.foreach(d => | ||
createDocJSFile(d._1, d._2, outDir, treeStyle, templateCode, treeNames) | ||
) | ||
} | ||
|
||
/** Takes a tuple of file path and documented HTML code, and generates JS doc | ||
* file with react components for Enso website. | ||
*/ | ||
private def createDocJSFile( | ||
path: String, | ||
htmlCode: String, | ||
outDir: String, | ||
treeStyle: String, | ||
templateCode: Try[String], | ||
treeNames: List[Node] | ||
): Unit = { | ||
val file = new File(path) | ||
file.createNewFile() | ||
val bw = new BufferedWriter(new FileWriter(file)) | ||
var treeCode = | ||
"<div>" + treeStyle + HTML | ||
.ul(treeNames.map(_.html())) | ||
.render | ||
.replace("class=", "className=") + "</div>" | ||
if (path.contains("/index.js")) { | ||
treeCode = treeCode.replace("a href=\"", "a href=\"reference/") | ||
} | ||
val partials = path | ||
.split(outDir + "/") | ||
.tail | ||
.head | ||
.replace(".js", "") | ||
.split("-") | ||
for (i <- 1 to partials.length) { | ||
val id = partials.take(i).mkString("-") | ||
val beg = "<input type=\"checkbox\" id=\"" + id + "\" " | ||
treeCode = treeCode.replace(beg, beg + "checked=\"True\"") | ||
} | ||
val docCopyBtnClass = "doc-copy-btn" | ||
bw.write( | ||
templateCode | ||
.getOrElse("") | ||
.replace( | ||
"{/*PAGE*/}", | ||
htmlCode | ||
.replace(docCopyBtnClass + " flex", docCopyBtnClass + " none") | ||
.replace("{", "{") | ||
.replace("}", "}") | ||
) | ||
.replace("{/*BREADCRUMBS*/}", treeCode) | ||
) | ||
bw.close() | ||
} | ||
} |
Oops, something went wrong.