To render a passage of extension documentation as HTML.


§1. Website renderer. We will make several HTML files, but only one at a time:

text_stream DOCF_struct;
text_stream *DOCF = NULL;

text_stream *DocumentationRenderer::open_subpage(pathname *P, text_stream *leaf) {
    if (P == NULL) return STDOUT;
    if (DOCF) internal_error("nested DC writes");
    filename *F = Filenames::in(P, leaf);
    SVEXPLAIN(2, "(writing documentation to file %f)\n", F);
    DOCF = &DOCF_struct;
    if (STREAM_OPEN_TO_FILE(DOCF, F, UTF8_ENC) == FALSE) {
        SVEXPLAIN(1, "(note: unable to write file %f)\n", F);
        DOCF = NULL;
        return NULL;  if we lack permissions, e.g., then write no documentation
    }
    return DOCF;
}

void DocumentationRenderer::close_subpage(void) {
    if (DOCF == NULL) internal_error("no DC page open");
    if (DOCF != STDOUT) STREAM_CLOSE(DOCF);
    DOCF = NULL;
}

§2. Our tree is turned into a tiny website, with a single index page for everything except the examples, and then up to 26 pages holding the content of examples A to Z.

void DocumentationRenderer::as_HTML(pathname *P, compiled_documentation *cd, text_stream *extras) {
    inbuild_nest *N = Supervisor::internal();
    if (N) {
        pathname *LP = Pathnames::down(Nests::get_location(N), I"PLs");
        Languages::set_default_directory(LP);
    }
    if (cd) {
        text_stream *OUT = DocumentationRenderer::open_subpage(P, I"index.html");
        if (OUT) {
            DocumentationRenderer::render_index_page(OUT, cd, extras);
            DocumentationRenderer::close_subpage();
        }
        for (int eg=1; eg<=cd->total_examples; eg++) {
            TEMPORARY_TEXT(leaf)
            WRITE_TO(leaf, "eg%d.html", eg);
            OUT = DocumentationRenderer::open_subpage(P, leaf);
            if (OUT) {
                DocumentationRenderer::render_example_page(OUT, cd, eg);
                DocumentationRenderer::close_subpage();
            }
            DISCARD_TEXT(leaf)
        }
        for (int ch=1; ch<=cd->total_headings[1]; ch++) {
            TEMPORARY_TEXT(leaf)
            WRITE_TO(leaf, "chapter%d.html", ch);
            OUT = DocumentationRenderer::open_subpage(P, leaf);
            if (OUT) {
                DocumentationRenderer::render_chapter_page(OUT, cd, ch);
                DocumentationRenderer::close_subpage();
            }
            DISCARD_TEXT(leaf)
        }
    }
}

§3.

void DocumentationRenderer::render_index_page(OUTPUT_STREAM, compiled_documentation *cd,
    text_stream *extras) {
    DocumentationRenderer::render_header(OUT, cd->title, NULL, cd->within_extension);
    if (cd->associated_extension) {
        DocumentationRenderer::render_extension_details(OUT, cd->associated_extension);
    }

    HTML_TAG("hr");
    if (cd->total_headings[1] > 0) {  there are chapters
        DocumentationRenderer::render_toc(OUT, cd);
        HTML_OPEN("em");
        InformFlavouredMarkdown::render_text(OUT, I"Click on Chapter, Section or Example numbers to read");
        HTML_CLOSE("em");
        markdown_item *md = cd->alt_tree->down;
        if ((md) && ((md->type != HEADING_MIT) || (Markdown::get_heading_level(md) > 1))) {
            HTML_TAG("hr");
            HTML_OPEN_WITH("p", "class=\"extensionsubheading\"");
            WRITE("introduction");
            HTML_CLOSE("p");
            for (markdown_item *md = cd->alt_tree->down; md; md = md->next) {
                if ((md->type == HEADING_MIT) && (Markdown::get_heading_level(md) == 1))
                    break;
                Markdown::render_extended(OUT, md, InformFlavouredMarkdown::variation());
            }
        }
    } else {  there are only sections and examples, or not even that
        HTML_OPEN_WITH("p", "class=\"extensionsubheading\"");
        WRITE("documentation");
        HTML_CLOSE("p");
        if (cd->empty) {
            HTML_OPEN("p");
            InformFlavouredMarkdown::render_text(OUT, I"None is provided.");
            HTML_CLOSE("p");
        } else {
            HTML_OPEN_WITH("div", "class=\"markdowncontent\"");
            Markdown::render_extended(OUT, cd->alt_tree,
                InformFlavouredMarkdown::variation());
            HTML_CLOSE("div");
        }
    }
    WRITE("%S", extras);

    Enter the small print3.1;
    WRITE("These documentation pages are first generated when an extension is "
        "installed, and refreshed each time the project successfully translates.");
    Exit the small print3.2;
    DocumentationRenderer::render_footer(OUT);
}

§3.1. Enter the small print3.1 =

    HTML_TAG("hr")
    HTML_OPEN("p")
    HTML_OPEN("em");

§3.2. Exit the small print3.2 =

    HTML_CLOSE("em");
    HTML_CLOSE("p");

§4.

void DocumentationRenderer::render_example_page(OUTPUT_STREAM, compiled_documentation *cd, int eg) {
    TEMPORARY_TEXT(title)
    WRITE_TO(title, "Example %c", 'A' + eg - 1);
    DocumentationRenderer::render_header(OUT, cd->title, title, cd->within_extension);
    DISCARD_TEXT(title)
    DocumentationRenderer::render_example(OUT, cd, eg);
    DocumentationRenderer::render_footer(OUT);
}

void DocumentationRenderer::render_chapter_page(OUTPUT_STREAM, compiled_documentation *cd, int ch) {
    TEMPORARY_TEXT(title)
    WRITE_TO(title, "Chapter %d", ch);
    DocumentationRenderer::render_header(OUT, cd->title, title, cd->within_extension);
    DISCARD_TEXT(title)
    DocumentationRenderer::render_chapter(OUT, cd, ch);
    Enter the small print3.1;
    WRITE("This is Chapter %d of %d", ch, cd->total_headings[1]);
    if (ch > 1) {
        WRITE(" &bull; ");
        HTML_OPEN_WITH("a", "href=\"chapter%d.html\"", ch-1);
        WRITE("Chapter %d", ch-1);
        HTML_CLOSE("a");
    }
    if (ch < cd->total_headings[1]) {
        WRITE(" &bull; ");
        HTML_OPEN_WITH("a", "href=\"chapter%d.html\"", ch+1);
        WRITE("Chapter %d", ch+1);
        HTML_CLOSE("a");
    }
    Exit the small print3.2;
    DocumentationRenderer::render_footer(OUT);
}

§5. Each of these pages is equipped with the same Javascript and CSS.

void DocumentationRenderer::render_header(OUTPUT_STREAM, text_stream *title, text_stream *ptitle,
    inform_extension *within) {
    InformPages::header(OUT, title, JAVASCRIPT_FOR_ONE_EXTENSION_IRES, NULL);
    ExtensionWebsite::add_home_breadcrumb(NULL);
    if (within) {
        ExtensionWebsite::add_breadcrumb(within->as_copy->edition->work->title,
            I"../index.html");
    }
    ExtensionWebsite::add_breadcrumb(title, I"index.html");
    if (Str::len(ptitle) > 0) ExtensionWebsite::add_breadcrumb(ptitle, NULL);
    ExtensionWebsite::titling_and_navigation(OUT,
        I"Documentation provided by the extension author");
}

void DocumentationRenderer::render_footer(OUTPUT_STREAM) {
    InformPages::footer(OUT);
}

§6. This function is the only one which assumes our documentation comes from an extension.

void DocumentationRenderer::render_extension_details(OUTPUT_STREAM, inform_extension *E) {
    inbuild_edition *edition = E->as_copy->edition;
    inbuild_work *work = edition->work;

    HTML_OPEN_WITH("p", "class=\"extensionsubheading\"");
    WRITE("about this extension");
    HTML_CLOSE("p");

    HTML_OPEN("p");
    WRITE("This is ");
    Works::write_to_HTML_file(OUT, work, TRUE);
    semantic_version_number V = E->as_copy->edition->version;
    if (VersionNumbers::is_null(V)) WRITE(", which gives no version number");
    else WRITE(", version %v", &V);
    WRITE(".");
    HTML_CLOSE("p");

    if (Str::len(E->rubric_as_lexed) > 0) {
        HTML_OPEN("p");
        InformFlavouredMarkdown::render_text(OUT, E->rubric_as_lexed);
        HTML_CLOSE("p");
    }

    if (Str::len(E->extra_credit_as_lexed) > 0) {
        HTML_OPEN("p");
        HTML_OPEN("em");
        InformFlavouredMarkdown::render_text(OUT, E->extra_credit_as_lexed);
        HTML_CLOSE("em");
        HTML_CLOSE("p");
    }
    compatibility_specification *C = E->as_copy->edition->compatibility;
    if (Str::len(C->parsed_from) > 0) {
        HTML_OPEN_WITH("p", "class=\"extensionsubheading\"");
        WRITE("compatibility");
        HTML_CLOSE("p");
        HTML_OPEN("p");
        InformFlavouredMarkdown::render_text(OUT, C->parsed_from);
        HTML_CLOSE("p");
    }
}

§7. Now for the Table of Contents, which shows chapters, sections and examples in a hierarchical fashion.

void DocumentationRenderer::render_toc(OUTPUT_STREAM, compiled_documentation *cd) {
    HTML_OPEN("div");
    HTML_OPEN_WITH("p", "class=\"extensionsubheading\"");
    WRITE("contents");
    HTML_CLOSE("p");
    HTML_OPEN_WITH("ul", "class=\"extensioncontents\"");
    DocumentationRenderer::render_toc_r(OUT, cd->alt_tree, 0);
    HTML_CLOSE("ul");
    HTML_CLOSE("div");
}

void DocumentationRenderer::link_to(OUTPUT_STREAM, markdown_item *md) {
    if (md->type != HEADING_MIT) internal_error("not a heading");
    text_stream *ch = RETRIEVE_POINTER_text_stream(md->user_state);
    HTML_OPEN_WITH("a", "style=\"text-decoration: none\" href=%S", ch);
}

void DocumentationRenderer::render_toc_r(OUTPUT_STREAM, markdown_item *md, int L) {
    if (md->type == HEADING_MIT) {
        if (L > 0) {
            HTML_OPEN_WITH("li", "class=\"exco%d\"", L);
            HTML::begin_span(OUT, I"indexblack");
            HTML_OPEN("b");
            DocumentationRenderer::link_to(OUT, md);
            Markdown::render_extended(OUT, md->down,
                InformFlavouredMarkdown::variation());
            HTML_CLOSE("a");
            HTML_CLOSE("b");
            HTML::end_span(OUT);
            HTML_CLOSE("li");
        }
    }
    if (md->type == INFORM_EXAMPLE_HEADING_MIT) {
        HTML_OPEN_WITH("li", "class=\"exco%d\"", L);
        IFM_example *E = RETRIEVE_POINTER_IFM_example(md->user_state);
        TEMPORARY_TEXT(link)
        WRITE_TO(link, "style=\"text-decoration: none\" href=\"eg%d.html#eg%d\"",
            E->number, E->number);
        HTML::begin_span(OUT, I"indexblack");
        HTML_OPEN_WITH("a", "%S", link);
        WRITE("Example %c &mdash; ", E->letter);
        InformFlavouredMarkdown::render_text(OUT, E->name);
        HTML_CLOSE("a");
        HTML::end_span(OUT);
        DISCARD_TEXT(link)
        HTML_CLOSE("li");
    }
    for (markdown_item *ch = md->down; ch; ch = ch->next)
        DocumentationRenderer::render_toc_r(OUT, ch, L+1);
}

void DocumentationRenderer::render_example(OUTPUT_STREAM, compiled_documentation *cd, int eg) {
    HTML_OPEN("div");
    markdown_item *alt_EN = InformFlavouredMarkdown::find_example(cd->alt_tree, eg);
    if (alt_EN == NULL) {
        WRITE("Example %d is missing", eg);
    } else {
        IFM_example *E = RETRIEVE_POINTER_IFM_example(alt_EN->user_state);
        TEMPORARY_TEXT(link)
        if (alt_EN->down) {
            WRITE_TO(link, "style=\"text-decoration: none\" href=\"eg%d.html\"", E->number);
            InformFlavouredMarkdown::render_example_heading(OUT, E, link);
        }
        DISCARD_TEXT(link)
        markdown_item *passage_node = alt_EN->down;
        while (passage_node) {
            Markdown::render_extended(OUT, passage_node,
                InformFlavouredMarkdown::variation());
            passage_node = passage_node->next;
        }
        HTML_CLOSE("div");
        Enter the small print3.1;
		WRITE("This example is drawn from ");
        tree_node *H = EN->parent;
        if (H->type == heading_TNT) {
            cdoc_heading *E = RETRIEVE_POINTER_cdoc_heading(H->content);
            if (E->level == 1) {
                DocumentationRenderer::link_to(OUT, E);
                WRITE("Chapter %S", E->count);
                HTML_CLOSE("a");
            } else if (E->level == 2) {
                DocumentationRenderer::link_to(OUT, E);
                WRITE("Section %S", E->count);
                HTML_CLOSE("a");
            } else {
                HTML_OPEN_WITH("a", "href=\"index.html\"");
                WRITE("the introduction");
                HTML_CLOSE("a");
            }
        }
*/
        Exit the small print3.2;
    }
}

void DocumentationRenderer::render_chapter(OUTPUT_STREAM, compiled_documentation *cd, int ch) {
    HTML_OPEN("div");
    int found = FALSE;
    TEMPORARY_TEXT(wanted)
    WRITE_TO(wanted, "Chapter %d:", ch);
    markdown_item *md = cd->alt_tree->down;
    int rendering = FALSE;
    while (md) {
        if ((md->type == HEADING_MIT) && (Markdown::get_heading_level(md) == 1)) {
            text_stream *S = md->stashed;
            if (Str::begins_with(S, wanted)) { rendering = TRUE; found = TRUE; }
            else rendering = FALSE;
        }
        if (rendering) Markdown::render_extended(OUT, md, InformFlavouredMarkdown::variation());
        md = md->next;
    }
    if (found == FALSE) WRITE("Chapter %d is missing", ch);
    HTML_CLOSE("div");
}