Here is an overview of the weave program. This file turns a literate source file into one or more html files.
Now we parse the codeblocks across all chapters in the program. We have four arrays:
string[string] defLocations;
string[][string] redefLocations;
string[][string] addLocations;
string[][string] useLocations;
foreach (chapter; p.chapters) {
foreach (s; chapter.sections) {
foreach (block; s.blocks) {
if (block.isCodeblock) {
if (block.modifiers.canFind(Modifier.noWeave)) {
defLocations[block.name] = "noWeave";
continue;
}
{Check if it's a root block, 4}
if (block.modifiers.canFind(Modifier.additive)) {
if (block.name !in addLocations || !addLocations[block.name].canFind(to!string(s.num)))
addLocations[block.name] ~= chapter.num() ~ ":" ~ to!string(s.num);
} else if (block.modifiers.canFind(Modifier.redef)) {
if (block.name !in redefLocations || !redefLocations[block.name].canFind(to!string(s.num)))
redefLocations[block.name] ~= chapter.num() ~ ":" ~ to!string(s.num);
} else {
defLocations[block.name] = chapter.num() ~ ":" ~ to!string(s.num);
}
foreach (lineObj; block.lines) {
string line = strip(lineObj.text);
if (line.startsWith("@{") && line.endsWith("}")) {
useLocations[line[2..$ - 1]] ~= chapter.num() ~ ":" ~ to!string(s.num);
}
}
}
}
}
}
Used in section 1
Here we simply loop through all the chapters in the program and get the html for them.
If noOutput
is false, we generate html files in the outDir
.
foreach (c; p.chapters) { string output = weaveChapter(c, p, defLocations, redefLocations, addLocations, useLocations); if (!noOutput) { string dir = outDir; if (isBook) { dir = outDir ~ "/_book"; if (!dir.exists()) { mkdir(dir); } } File f = File(dir ~ "/" ~ stripExtension(baseName(c.file)) ~ ".html", "w"); f.write(output); f.close(); } }
Used in section 1
If the program being compiled is a book, we should also write a table of contents file.
string dir = outDir ~ "/_book"; File f = File(dir ~ "/" ~ p.title ~ "_contents.html", "w"); f.writeln( q"DELIMITER <!DOCTYPE html> <html> <head> <style> ul#contents {list-style-type: none;}a{color:#337ABF;}body{min-width:200px;max-width:850px;margin:0 auto;padding:30px;}.chapter-nav{font-size: 10pt;}.codeblock_name,code,pre.prettyprint{font-family:Monaco,"Lucida Console",monospace}body{font-size:14pt}.codeblock_name,.math,.seealso,code{font-size:10pt}.codeblock{page-break-inside:avoid;padding-bottom:15px}.math{text-indent:0}pre.prettyprint{font-size:10pt;padding:10px;border-radius:10px;border:none;white-space:pre-wrap}.codeblock_name{margin-top:1.25em;display:block}a:link{text-decoration:none}a:link:not(.lit):hover{text-decoration:underline}a:link:active{color:red}h4{padding-right:1.25em}h4.noheading{margin-bottom:0}h1{text-align:center}code{padding:2px}pre{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.two-col{list-style-type:none}.two-col li:before{content:'-';padding:5px;margin-right:5px;color:orange;background-color:#fff;display:inline-block}@media print{body{font-size:10pt}pre.prettyprint{font-size:8pt}.seealso{font-size:9pt}.codeblock_name,.math,code{font-size:8pt}.math{text-indent:0}} </style> </head> <body> <div class="container"> DELIMITER" ); f.writeln("<h1>" ~ p.title ~ "</h1>"); f.writeln(filterMarkdown(p.text)); f.writeln("<ul id=\"contents\">"); foreach (c; p.chapters) { f.writeln("<li>" ~ c.num() ~ ". <a href=\"" ~ stripExtension(baseName(c.file)) ~ ".html\">" ~ c.title ~ "</a></li>"); } f.writeln(" </ul> </div> </body> "); f.close();
Used in section 1
We check if the block is a root code block. We check this using a regex that basically checks if it the name has an extension. Additionally, users can put the block name in quotes to force it to be a root block.
If the block name is in quotes, we have to make sure to remove those once we're done.
This function weaves a single chapter.
string weaveChapter(Chapter c, Program p, string[string] defLocations, string[][string] redefLocations, string[][string] addLocations, string[][string] useLocations) { {prettify} {css} {katex} string output = ""; {Write the head of the HTML, 6} {Write the body, 7} if (use_katex) { {Write the katex source, 16} } if (isBook) { output ~= "<br>"; int index = cast(int) p.chapters.countUntil(c); if (index - 1 >= 0) { Chapter lastChapter = p.chapters[p.chapters.countUntil(c)-1]; output ~= "<a style=\"float:left;\" class=\"chapter-nav\" href=\"" ~ stripExtension(baseName(lastChapter.file)) ~ ".html\">Previous Chapter</a>"; } if (index + 1 < p.chapters.length) { Chapter nextChapter = p.chapters[p.chapters.countUntil(c)+1]; output ~= "<a style=\"float:right;\" class=\"chapter-nav\" href=\"" ~ stripExtension(baseName(nextChapter.file)) ~ ".html\">Next Chapter</a>"; } } output ~= "</body>\n"; return output; }
Used in section 1
This writes out the start of the document. Mainly the scripts (prettify.js) and the css (prettiy css, default css, and colorscheme css). It also adds the title of the document.
string prettifyExtension; foreach (cmd; p.commands) { if (cmd.name == "@overwrite_css") { defaultCSS = readall(File(cmd.args)); } else if (cmd.name == "@add_css") { defaultCSS ~= readall(File(cmd.args)); } else if (cmd.name == "@colorscheme") { colorschemeCSS = readall(File(cmd.args)); } if (cmd.name == "@code_type") { if (cmd.args.length > 1) { string ext = cmd.args.split()[1][1..$]; if (ext in extensions) { prettifyExtension = "<script>\n" ~ extensions[ext] ~ "</script>\n"; } } } } foreach (cmd; c.commands) { if (cmd.name == "@overwrite_css") { defaultCSS = readall(File(cmd.args)); } else if (cmd.name == "@add_css") { defaultCSS ~= readall(File(cmd.args)); } else if (cmd.name == "@colorscheme") { colorschemeCSS = readall(File(cmd.args)); } if (cmd.name == "@code_type") { if (cmd.args.length > 1) { string ext = cmd.args.split()[1][1..$]; if (ext in extensions) { prettifyExtension = "<script>\n" ~ extensions[ext] ~ "</script>\n"; } } } } string css = colorschemeCSS ~ defaultCSS; string scripts = "<script>\n" ~ prettify ~ "</script>\n"; scripts ~= prettifyExtension; bool use_katex = false; output ~= "<!DOCTYPE html>\n" "<html>\n" "<head>\n" "<meta charset=\"utf-8\">\n" "<title>" ~ c.title ~ "</title>\n" ~ scripts ~ "<style>\n" ~ css ~ "</style>\n" "</head>\n";
Used in section 5
Now we write the body -- this is the meat of the weaver. First we write
a couple things at the beginning: making sure the prettyprint
function is
called when the page loads, and writing out the title as an h1
.
Then we loop through each section in the chapter. At the beginning of each section,
we write the title, and an empty a
link so that the section title can be linked to.
We also have to determine if the section title should be a noheading
class. If the
section title is empty, then the class should be noheading
which means that the prose
will be moved up a bit towards it -- otherwise it looks like there is too much empty space
between the title and the prose.
output ~= "<body onload=\"prettyPrint()\">\n" "<section>\n" "<h1>" ~ c.title ~ "</h1>\n"; foreach (s; c.sections) { string noheading = s.title == "" ? " class=\"noheading\"" : ""; output ~= "<a name=\"" ~ c.num() ~ ":" ~ to!string(s.num) ~ "\"><div class=\"section\"><h4" ~ noheading ~ ">" ~ to!string(s.num) ~ ". " ~ s.title ~ "</h4></a>\n"; foreach (block; s.blocks) { if (!block.modifiers.canFind(Modifier.noWeave)) { if (!block.isCodeblock) { {Weave a prose block, 8} } else { {Weave a code block, 9} } } } output ~= "</div>\n"; }
Used in section 5
Weaving a prose block is not very complicated. First we get the html by sending it
through the markdown compiler by using the filterMarkdown
function. We disable the
use of underscores for italics because that interferes with the katex use of underscores
for math.
Then use a giant regex to determine if any katex is being used. Really what that regex does is determine if there is something in dollar signs or double dollar signs which aren't escaped by backslashes. Most of the regex is negative look behind to make sure the dollar sign isn't escaped.
string html = filterMarkdown(block.text(), MarkdownFlags.disableUnderscoreEmphasis); if (html.matchAll(regex(r"(?<!\\)[\$](?<!\\)[\$](.*?)(?<!\\)[\$](?<!\\)[\$]")) || html.matchAll(regex(r"(?<!\\)[\$](.*?)(?<!\\)[\$]"))) { use_katex = true; }
Used in section 7
Here we use the same regex to actually perform the substitution. Double dollars mean a block math which means we have to use a div. For inline math (single dollars) we use a span. After that substitution we replace all backslash dollars to real dollar signs.
Finally we add this html to the output and add a newline for good measure.
html = html.replaceAll(regex(r"(?<!\\)[\$](?<!\\)[\$](.*?)(?<!\\)[\$](?<!\\)[\$]"), "<div class=\"math\">$1</div>"); html = html.replaceAll(regex(r"(?<!\\)[\$](.*?)(?<!\\)[\$]"), "<span class=\"math\">$1</span>"); html = html.replaceAll(regex(r"\\\$"), "$$"); output ~= html ~ "\n";
Used in section 7
Here we create the title for the codeblock. For the title, we have to link
to the definition (which is usually the current block, but sometimes not
because of +=
). We also need to make the title bold (<strong>
) if it
is a root code block.
{Find the definition location, 10} {Make the title bold if necessary, 10} output ~= "<span class=\"codeblock_name\">{" ~ name ~ " <a href=\"" ~ htmlFile ~ "#" ~ def ~ "\">" ~ defLocation ~ "</a>}" ~ extra ~ "</span>\n";
Used in section 9
To find the definition location we use the handy defLocation
array that we made
earlier. The reason we have both the variables def
and defLocation
is because
the definition location might be in another chapter, in which case it should be
displayed as chapterNum:sectionNum
but if it's in the current file, the chapterNum
can be removed. def
gives us the real definition location, and defLocation
is the
one that will be used -- it strips out the chapterNum
if necessary.
string chapterNum; string def; string defLocation; string htmlFile = ""; if (block.name !in defLocations) { error(c.file, block.startLine, "{" ~ block.name ~ "} is never defined"); } else { def = defLocations[block.name]; defLocation = def; auto index = def.indexOf(":"); string chapter = def[0..index]; htmlFile = getChapterHtmlFile(p.chapters, chapter); if (chapter == c.num()) { defLocation = def[index + 1..$]; } }
We also add the +=
or :=
if necessary. This needs to be the extra
because
it goes outside the {}
and is not really part of the name anymore.
string extra = ""; if (block.modifiers.canFind(Modifier.additive)) { extra = " +="; } else if (block.modifiers.canFind(Modifier.redef)) { extra = " :="; }
We simple put the title in in a strong tag if it is a root codeblock to make it bold.
string name; if (block.isRootBlock) { name = "<strong>" ~ block.name ~ "</strong>"; } else { name = block.name; }
At the beginning, we open the pre tag. If a codetype is defined, we tell the prettyprinter to use that, otherwise, the pretty printer will try to figure out how to syntax highlight on its own -- and it's pretty good at that.
if (block.codeType.split().length > 1) {
output ~= "<pre class=\"prettyprint lang-" ~ block.codeType.split()[1][1..$] ~ "\">\n";
} else {
output ~= "<pre class=\"prettyprint\">\n";
}
foreach (lineObj; block.lines) {
{Write the line, 11}
}
output ~= "</pre>\n";
Used in section 9
Now we loop through each line. The only complicated thing here is if the line is a codeblock use. Then we have to link to the correct definition location.
Also we escape all ampersands and greater than and less than signs before writing them.
string line = lineObj.text;
string strippedLine = strip(line);
if (strippedLine.startsWith("@{") && strippedLine.endsWith("}")) {
{Link a used codeblock, 11}
} else {
output ~= line.replace("&", "&").replace(">", ">").replace("<", "<") ~ "\n";
}
For linking the used codeblock, it's pretty much the same deal as before. We
reuse the def
and defLocation
variables. We also write the final html as
a span with the nocode
class, that way it won't be syntax highlighted by the
pretty printer.
def = ""; defLocation = ""; if (strip(strippedLine[2..$ - 1]) !in defLocations) { error(c.file, lineObj.lineNum, "{" ~ strip(strippedLine[2..$ - 1]) ~ "} is never defined"); } else if (defLocations[strip(strippedLine[2..$ - 1])] != "noWeave") { def = defLocations[strippedLine[2..$ - 1]]; defLocation = def; auto index = def.indexOf(":"); string chapter = def[0..index]; htmlFile = getChapterHtmlFile(p.chapters, chapter); if (chapter == c.num()) { defLocation = def[index + 1..$]; } def = ", <a href=\"" ~ htmlFile ~ "#" ~ def ~ "\">" ~ defLocation ~ "</a>"; } output ~= "<span class=\"nocode pln\">" ~ leadingWS(line) ~ "{" ~ strippedLine[2..$ - 1] ~ def ~ "}</span>\n";
Writing the links is pretty similar to figuring out where a codeblock
was defined because we have access to the sectionLocations
array (which is
addLocations
, useLocations
, or redefLocations
). Then we just
have a few if statements to figure out the grammar -- where to put the and
and whether to have plurals and whatnot.
string linkLocations(string text, string[][string] sectionLocations, Program p, Chapter c, Section s, parser.Block block) { if (block.name in sectionLocations) { string[] locations = dup(sectionLocations[block.name]); if (locations.canFind(c.num() ~ ":" ~ to!string(s.num))) { locations = remove(locations, locations.countUntil(c.num() ~ ":" ~ to!string(s.num))); } if (locations.length > 0) { string seealso = "<p class=\"seealso\">" ~ text; if (locations.length > 1) { seealso ~= "s "; } else { seealso ~= " "; } foreach (i; 0 .. locations.length) { string loc = locations[i]; string locName = loc; auto index = loc.indexOf(":"); string chapter = loc[0..index]; string htmlFile = getChapterHtmlFile(p.chapters, chapter); if (chapter == c.num()) { locName = loc[index + 1..$]; } loc = "<a href=\"" ~ htmlFile ~ "#" ~ loc ~ "\">" ~ locName ~ "</a>"; if (i == 0) { seealso ~= loc; } else if (i == locations.length - 1) { seealso ~= " and " ~ loc; } else { seealso ~= ", " ~ loc; } } seealso ~= "</p>"; return seealso; } } return ""; }
Used in section 1
Writing the 'added to' links is pretty similar to figuring out where a codeblock
was defined because we have access to the addLocations
array. Then we just
have a few if statements to figure out the grammar -- where to put the and
and whether to have plurals and whatnot.
This is pretty much the same as the 'added to' links except we use the
useLocations
array.
This is the source code for katex which should only be used if math is used in the literate file. We include a script which uses the cdn first because that will use better fonts, however it needs the user to be connected to the internet. In the case that the user is offline, we include the entire source for katex, but it will use worse fonts (still better than nothing though).
output ~= "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.css\">\n" "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js\"></script>\n"; output ~= katex;
Used in section 5
Then we loop over all the math divs and spans and render the katex.
output ~= q"DELIMITER <script> var mathDivs = document.getElementsByClassName("math") for (var i = 0; i < mathDivs.length; i++) { var el = mathDivs[i]; var texTxt = el.textContent; try { var displayMode = false; if (el.tagName == 'DIV') { displayMode = true; } katex.render(texTxt, el, {displayMode: displayMode}); } catch(err) { el.innerHTML = "<span class='err'>"+err+"</span>"; } } </script> DELIMITER";
Used in section 5