tealr_doc_gen 0.3.0-alpha2

A cli tool to create online documentation for apis made with tealr
<%

function split_used_by(users)
    local as_params = {}
    local as_returns = {}
    local as_fields = {}
    for key, user in ipairs(users) do
        if #user.as_params > 0 then
            table.insert(as_params, {
                type_name=user.type_name,
                as_params=user.as_params
            })
        end
        if #user.as_return > 0 then
            table.insert(as_returns, {
                type_name=user.type_name,
                as_returns=user.as_return
            })
        end
        if #user.as_fields > 0 then
            table.insert(as_fields, {
                type_name=user.type_name,
                as_fields=user.as_fields
            })
        end
    end
    return as_params,as_returns,as_fields
end

function render_used_by(users)
    local as_params,as_returns,as_fields = split_used_by(users)
    local function render(to_render, name, field_name)
        if #to_render > 0 then
%>
            <h3 class="is-size-5"><%=name%><h3>
            <ul>
<%
                for _k, used_in_type in ipairs(to_render) do
                    for _k, used_by in ipairs(used_in_type[field_name]) do
%> 
                        <li>
                            <a href="<%=create_link(used_in_type.type_name) .. "#"..used_by%>"><%=used_in_type.type_name.."."..used_by%></a>
                        </li>
<% 
                    end
                end
%>
            </ul>
<%
        end
    end
%>
    <div class="panel max-width">
        <input type="checkbox" id="type-uses" class="is-hidden hideable-state"/>
        <div class="panel-block is-hidden hideable-content">
<%
            render(as_params,"As Parameter:","as_params")
            render(as_returns,"Returned by:","as_returns")
            render(as_fields,"As field:","as_fields")
%>
        </div>
    </div>
<%
end

function render(str)
%><%=str-%><%
end

function is_string_starting_with(str,start)
   return string.sub(str,1,string.len(start))==start
end

function get_string_from(str,from)
    return string.sub(str,from,string.len(str))
end

local function headingToSize(heading) 
    return "is-" .. get_string_from(heading, 1)
end

function render_markdown(markdown)
    local code_to_compile = nil
    local skip_rust_example = false

    local transformer = function(toTransform)
        if toTransform:IsStart() then
            local start = toTransform:GetStartOrNil()
            if start:IsCodeBlock() then
                local block = start:GetCodeBlockOrNil()
                local fence = block:GetFencedOrNil()
                if fence == "teal_lua" then
                    code_to_compile = "local " .. library_name .. " = require(\""..definition_file_folder.."." .. library_name .."\")"
                    return
                elseif is_string_starting_with(fence, "rs") or is_string_starting_with(fence,"rust") then
                    skip_rust_example = true
                    return
                end
            elseif start:IsLink() then
                local link_type,to,title = start:GetLinkOrNil()
                if is_string_starting_with(to,"#") then
                    to = create_link(get_string_from(to,1))
                elseif not is_string_starting_with(to,"http") then
                    to = create_link(to)
                end
                return markdown_event_creator.NewStartFrom(start.NewLinkFrom(link_type,to,title))
            elseif start:IsHeading() then
                local heading_level, fragment, classes = start:GetHeadingOrNil()
                table.insert(classes,"subtitle")
                table.insert(classes,headingToSize(heading_level))
                return markdown_event_creator.NewStartFrom(
                    markdown_tag_creator.NewHeadingFrom(heading_level,fragment,classes)
                )
                
            end
        elseif code_to_compile ~= nil and toTransform:IsText() then
            local text = toTransform:GetTextOrNil()
            code_to_compile = code_to_compile .. '\n'.. text
            return
        elseif skip_rust_example and toTransform:IsText() then
            return
        elseif skip_rust_example and toTransform:IsEnd() then
            local blockEnd = toTransform:GetEndOrNil()
            if blockEnd:IsCodeBlock() then
                local codeBlock = blockEnd:GetCodeBlockOrNil()
                local fence = codeBlock:GetFencedOrNil() --or ""
                if is_string_starting_with(fence, "rs") or is_string_starting_with(fence,"rust")  then
                    skip_rust_example = false
                    return
                end
            end
        elseif code_to_compile ~= nil and toTransform:IsEnd() then
            local blockEnd = toTransform:GetEndOrNil()
            if blockEnd:IsCodeBlock() then
                local codeBlock = blockEnd:GetCodeBlockOrNil()
                if codeBlock:IsFenced() and codeBlock:GetFencedOrNil() == "teal_lua" then
                    local tl = require("tl")
                    local env = tl.init_env(false,false,true)
                    local output,result = tl.gen(code_to_compile,env)
                    if #result.syntax_errors > 0 then
                        print("Syntax errors found at code:")
                        print(code_to_compile)
                        print("Errors:")
                        for k,v in ipairs(result.syntax_errors) do
                            print("Syntax error found at:" .. v.msg .. ' x=' .. tostring(v.x) .. ' y=' ..tostring(v.y))
                        end
                    end
                    if #result.type_errors > 0 then
                        print("Type errors found at code:")
                        print(code_to_compile)
                        print("Errors:")
                        for k,v in ipairs(result.type_errors) do
                            print("Type error found at:" .. v.msg .. ' x=' .. tostring(v.x) .. ' y=' ..tostring(v.y))
                        end
                    end
                    local teal_code = code_to_compile
                    code_to_compile = nil
                    return {
                        markdown_event_creator.NewHtmlFrom(
                            [[
                                <div class="tabs">
                                    <ul>
                                        <li class="select-teal"><a>Teal</a></li>
                                        <li class="select-lua"><a>Lua</a></li>
                                    </ul>
                                </div>
                            ]]
                        ),
                        markdown_event_creator.NewHtmlFrom("<div class=\"code-block-teal\">"),
                        markdown_event_creator.NewStartFrom(
                            markdown_tag_creator.NewCodeBlockFrom(
                                markdown_codeblock_kind_creator.NewFencedFrom("lua")
                            )
                        ),
                        markdown_event_creator.NewTextFrom(teal_code),
                        markdown_event_creator.NewEndFrom(
                            markdown_tag_creator.NewCodeBlockFrom(
                                markdown_codeblock_kind_creator.NewFencedFrom("lua")
                            )
                        ),
                        markdown_event_creator.NewHtmlFrom("</div>"),
                        markdown_event_creator.NewHtmlFrom("<div class=\"code-block-lua\">"),
                        markdown_event_creator.NewStartFrom(
                            markdown_tag_creator.NewCodeBlockFrom(
                                markdown_codeblock_kind_creator.NewFencedFrom("lua")
                            )
                        ),
                        markdown_event_creator.NewTextFrom(output),
                        markdown_event_creator.NewEndFrom(
                            markdown_tag_creator.NewCodeBlockFrom(
                                markdown_codeblock_kind_creator.NewFencedFrom("lua")
                            )
                        ),
                        markdown_event_creator.NewHtmlFrom("</div>")
                    }
                end
            end
        end
        return toTransform
    end
%>
    <div class="container">
        <%- parse_markdown(markdown, transformer) %>
    </div>
<%
end

function renderTealTypeOf(type_parts)
    for k,v in ipairs(type_parts) do 
        local name = v:GetSymbolOrNil()
        if name then
            render(name)
        else
            name = v:GetTypeOrNil()
            if name.type_kind == "Builtin" or name.type_kind == "Generic" then
                render(name.name)
            else
%>
                <a href="<%=create_link(name.name)-%>"><%= name.name -%></a>
<%
            end
        end
    end
end



function render_record_part(header_name, record, parts)
    function render_part(part_name, record)
        for x, member in pairs(dedupe_by(record[part_name],function(x)return x.name end)) do
%>
            <div class="card block">
                <div id="<%= member.name -%>" class="card-heading">
                    <code class="card-header-title">
                        <p><%= member.name -%>: <%renderTealTypeOf(member.signature or member.teal_type) %> </p>
                    </code>
                </div>
                <% 
                    if record.documentation[member.name] then 
                %>
                        <div class="card-content content">
                            <% render_markdown(record.documentation[member.name]) %>
                        </div>
                <%
                    end
                %>
            </div>
<%
        end
    end

%>
    <div class="panel max-width">
        <div class="panel-heading">
            <p class="subtitle"><%=header_name-%>:</p>
        </div>
        <div class="panel-block">
            <div class="container">
<%
                for _,name in ipairs(parts) do
                    render_part(name,record)
                end
%>
            </div>
        </div>
    </div>
<%
end

function render_definition_files()
    for k,v in pairs(definition_config) do 
%>
        <a class="navbar-item" href="definitions/<%=  library_name .. v.extension %>"><%=k%></a>
<% 
    end
end


local page_name
local type_name
local type_members
local used_by
local t = page:GetTypeOrNil()
local index = page:GetIndexPageOrNil()
local custom_page = page:GetCustomPageOrNil()
if t then
    type_name = t.type_name
    type_members = t.type_members
    used_by = t.used_by
elseif index then
    type_name = index.type_name
    type_members = index.type_members
elseif custom_page then
    page_name = custom_page.name
end
page_name = page_name or type_name
%>

<!DOCTYPE html>
<html>

<head id="head">
    <link id="bulma-theme" rel="stylesheet" href="https://unpkg.com/bulmaswatch/default/bulmaswatch.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <link id="codeHighlight" rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/a11y-light.min.css"
        integrity="sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
    <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/a11y-dark.min.css" -->
    <!-- integrity="sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==" -->
    <!-- crossorigin="anonymous" referrerpolicy="no-referrer" /> -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js"
        integrity="sha512-IaaKO80nPNs5j+VLxd42eK/7sYuXQmr+fyywCNA0e+C6gtQnuCXNtORe9xR4LqGPz5U9VpH+ff41wKs/ZmC3iA=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <title><%= page_name-%></title>
    <style>
        html,
        body,
        #app {
            height: 100%;
        }

        #app {
            min-height: 100%;
        }

        .footer {
            margin-top: -12px;
        }
        .hideable-state:checked+.hideable-content {
            display: block !important;
        }

        .sticky-sidebar {
            height: calc(100vh - 3.25rem);
            position: sticky;
            top: 0;
            overflow: auto;
        }
        .max-width {
            max-width:100%;
        }
        .hidden-themes-selector {
            display:none;
        }
    </style>
</head>

<body>
    <div id="app">
        <nav class="navbar has-shadow">
            <div class="navbar-brand">
                <div class="navbar-item is-hidden-desktop">
                    <label for="menu-toggle" role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                    </label>
                    
                </div>
            </div>
            <div class="navbar-menu" id="navbar-id">
                <div class="navbar-start">
                    <div class="navbar-item has-dropdown is-hoverable">
                        <a class="navbar-link hidden-themes-selector">
                            Themes
                        </a>
                        <div class="navbar-dropdown is-boxed">
                            <div class="columns">
                                <div class="collumn theme-select-column1"></div>
                                <div class="collumn theme-select-column2"></div>
                            </div>
                        </div>
                    </div>
                    <div class="navbar-item has-dropdown is-hoverable">
                        <a class="navbar-link">
                            Definition files
                        </a>
                        <div class="navbar-dropdown is-boxed">
                        <%
                            render_definition_files()
                        %>
                        </div>
                    </div>
                </div>
            </div>
        </nav>
        <section class="main-content columns is-fullheight">
            <input type="checkbox" id="menu-toggle" class="is-hidden hideable-state"/>
            <aside id="sidebar" class="column is-2 is-narrow-mobile is-fullheight section is-hidden-touch sticky-sidebar hideable-content">
                <div class="is-hidden-desktop">
                    <div class="dropdown is-hoverable">
                        <div class="dropdown-trigger">
                            <label for="theme-toggle" class="button hidden-themes-selector" aria-haspopup="true" aria-controls="dropdown-menu4">
                                <span>Themes</span>
                                <span class="icon is-small">
                                    <i class="fa fa-angle-down" aria-hidden="true"></i>
                                </span>
                            </label>
                        </div>
                        <input type="checkbox" id="theme-toggle" class="is-hidden hideable-state"/>
                        <div class="dropdown-menu hideable-content" id="dropdown-menu4" role="menu">
                            <div class="dropdown-content">
                                <div class="columns">
                                    <div class="collumn theme-select-column1"></div>
                                    <div class="collumn theme-select-column2"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="dropdown is-hoverable">
                        <div class="dropdown-trigger">
                            <label for="definition-files-toggle" class="button" aria-haspopup="true" aria-controls="dropdown-menu5">
                                <span>Def files</span>
                                <span class="icon is-small">
                                    <i class="fa fa-angle-down" aria-hidden="true"></i>
                                </span>
                            </label>
                        </div>
                        <input type="checkbox" id="definition-files-toggle" class="is-hidden hideable-state"/>
                        <div class="dropdown-menu hideable-content" id="dropdown-menu5" role="menu">
                            <div class="dropdown-content">
                                <% render_definition_files() %>
                            </div>
                        </div>
                    </div>
                </div>
                <p class="menu-label is-hidden-touch">Navigation</p>
                <ul class="menu-list">
                    <% for i, side_bar_type in pairs(side_bar_types) do %>
                        <li>
                            <a href="<%= side_bar_type.link_to -%>" class="{is_active}">
                                <span class="icon"><i class="fa fa-file"></i></span>
                                <%= side_bar_type.name -%>
                            </a>
                            <ul>
                                <% for z, type_member in pairs(side_bar_type.members) do %>
                                    <li>
                                        <a href="<%= side_bar_type.link_to -%>#<%= type_member.name -%>">
                                            <span class="icon is-small"><i class="fa fa-link"></i></span>
                                            <%= type_member.name -%>
                                        </a>
                                    </li>
                                <% end %>
                            </ul>
                        </li>
                    <% end %>
                </ul>
            </aside>
            <div class="container column is-10">
                <div class="section">
                    <div class="panel">
                        <div class="panel-heading">
                            <h1 class="title">
                                <%= page_name -%>
                                <%
                                    if page:IsType() then
                                %>
                                        <label style="float:right" for="type-uses" class="button">Uses</label>
                                <%
                                    end
                                %>
                            </h1>
                        </div>
                        <div class="panel-block">
                            <div class="container max-width">
                                <%
                                    local custom_page = page:GetCustomPageOrNil()
                                    if custom_page then
                                        render_markdown(custom_page.markdown_content)
                                    else
                                        if page:GetType() then
                                            render_used_by(used_by)
                                        end
                                        local record = type_members:GetRecordOrNil() 
                                        if record then
                                %>
                                            <div class="panel max-width">
                                                <div class="panel-heading">
                                                    <p class="subtitle">Type doc:</p>
                                                </div>
                                                <div class="panel-block">
                                                    <% render_markdown(record.type_doc or "") %>
                                                </div>
                                            </div>
                                <%
                                            render_record_part("Fields",record,{"fields","static_fields"})
                                            render_record_part("Methods",record, {
                                                "methods",
                                                "mut_methods","functions",
                                                "mut_functions",
                                                "meta_method",
                                                "meta_method_mut",
                                                "meta_function",
                                                "meta_function_mut"
                                            })
                                        end
                                        local enum = type_members:GetEnumOrNil()
                                        if enum then
                                %>
                                            <div class="panel max-width">
                                                <div class="panel-heading">
                                                    <p class="subtitle">Type doc:</p>
                                                </div>
                                                <div class="panel-block">
                                                    <% render_markdown(enum.type_doc or "") %>
                                                </div>
                                            </div>
                                            <div class="panel max-width">
                                                <div class="panel-heading">
                                                    <p class="subtitle">Variants</p>
                                                </div>
                                                <div class="panel-block">
                                                    <div class="container max-width">
                                                        <% for _, variant in ipairs(enum.variants) do %>
                                                            <div class="card block">
                                                                <div id="<%= variant %>" class="card-heading">
                                                                    <code class="card-header-title"><p>"<%= variant %>" </p></code>
                                                                </div>
                                                            </div>
                                                        <% end %>
                                                    </div>
                                                </div>
                                            </div>
                                <%
                                        end
                                        if index then
                                %>
                                            <div class="panel max-width">
                                                <div class="panel-heading">
                                                    <p class="subtitle">Globals:</p>
                                                </div>
                                                <div class="panel-block">
                                                    <div class="container max-width">
                                                        <% for _, global_instance in ipairs(globals) do %>
                                                            <div class="card block">
                                                                <div id="<%= global_instance.name %>" class="card-heading">
                                                                    <code class="card-header-title">
                                                                        <h3>global <%= global_instance.name %> : <% renderTealTypeOf(global_instance.teal_type) %> </h3>
                                                                    </code>
                                                                </div>
                                                        <%
                                                                    if global_instance.doc then
                                                        %>
                                                                        <div class="card-content content">
                                                        <%     
                                                                            render_markdown(global_instance.doc)
                                                        %>
                                                                        </div>
                                                        <%
                                                                    end
                                                        %>
                                                            </div>
                                                        <% end %>
                                                    </div>
                                                </div>
                                            </div>
                                <%
                                        end
                                            if index then
                                %>
                                            <div class="panel max-width">
                                                <div class="panel-heading">
                                                    <p class="subtitle">Types:</p>
                                                </div>
                                                <div class="panel-block">
                                                    <div class="container max-width">
                                                        <% 
                                                            for _, teal_type in ipairs(all_types) do 
                                                                local record = teal_type:GetRecordOrNil()
                                                                if record and not record.should_be_inlined then
                                                        %>
                                                                    <div class="card block">
                                                                        <div id="<%= record.type_name %>" class="card-heading">
                                                                            <code class="card-header-title">
                                                                                <p><% renderTealTypeOf(record.type_name) %> </p>
                                                                            </code>
                                                                        </div>
                                                                        <div class="card-content content">
                                                                            <% render_markdown(record.type_doc) %>
                                                                        </div>
                                                                    </div>
                                                        <% 
                                                                end
                                                            end
                                                        %>
                                                    </div>
                                                </div>
                                            </div>
                                <%
                                        end
                                    end
                                %>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>
    <script>
        document.querySelectorAll(".hidden-themes-selector").forEach(x=>x.classList.remove("hidden-themes-selector"))
        const bulmaTheme = document.getElementById("bulma-theme")
        const isDarkTheme = {
            "Cerulean": false,
            "Cosmo": false,
            "Cyborg": true,
            "Darkly": true,
            "Default": false,
            "Flatly": false,
            "Journal": false,
            "Litera": false,
            "Lumen": false,
            "Lux": false,
            "Materia": false,
            "Minty": false,
            "Nuclear": true,
            "Pulse": false,
            "Sandstone": false,
            "Simplex": false,
            "Slate": true,
            "Solar": true,
            "Spacelab": false,
            "Superhero": true,
            "United": false,
            "Yeti": false
        }
        const lightHighlight = ` <link id="codeHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/a11y-light.min.css"
        integrity="sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />`
        const darkHighlight = `<link id="codeHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/a11y-dark.min.css"
integrity="sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg=="
crossorigin="anonymous" referrerpolicy="no-referrer" />`
        const setTheme = (str, name) => {
            bulmaTheme.href = str;
            document.getElementById("codeHighlight").remove()
            const head = document.getElementById("head")
            if (isDarkTheme[name]) {
                head.insertAdjacentHTML("beforeend", darkHighlight)
            } else {
                head.insertAdjacentHTML("beforeend", lightHighlight)
            }
        }
        const v = localStorage.getItem("editor_last_selected");
        try {
            if (v) {
                const res = JSON.parse(v);
                if (res && res.name && res.url) {
                    setTheme(res.url, res.name)
                }
            }
        } catch (e) {
            console.error("Error while setting saved theme. Going back to default", e)
            localStorage.removeItem("editor_last_selected")
        }

        fetch("https://jenil.github.io/bulmaswatch/api/themes.json")
            .then(x => x.json())
            .then(x => {

                const max = x.themes.length / 2
                const col1 = document.querySelectorAll(".theme-select-column1")
                const col2 = document.querySelectorAll(".theme-select-column2")
                x.themes.forEach((z, k) => {
                    const a = document.createElement("a")
                    a.onclick = () => {
                        setTheme(z.css, z.name)
                        localStorage.setItem(
                            "editor_last_selected",
                            JSON.stringify({ url: z.css, name: z.name })
                        );
                    }
                    a.text = z.name;
                    const div = document.createElement("div")
                    div.classList.add("navbar-item")
                    div.appendChild(a)
                    if (k < max) {
                        col1.forEach(x=>x.appendChild(div.cloneNode(true)))
                    } else {
                        col2.forEach(x=>x.appendChild(div.cloneNode(true)))
                    }
                })
            })
        const getOpposite = (language) => language == "teal" ? "lua" : "teal"
        const preferredLanguage = localStorage.getItem("last_selected_language") || "teal";
        const disableLanguage = getOpposite(preferredLanguage)
        const elementsToDisable = document.getElementsByClassName("code-block-" + disableLanguage)
        for (var i = 0; i < elementsToDisable.length; i++) {
            elementsToDisable[i].style.display = "none"
        }

        const createSwitchLanguage = (newLang) =>
            () => {
                const oppositeLanguage = getOpposite(newLang)
                const toShow = document.getElementsByClassName("code-block-" + newLang)

                for (var i = 0; i < toShow.length; i++) {
                    toShow[i].style.display = ""; // or
                }
                const toHide = document.getElementsByClassName("code-block-" + oppositeLanguage)
                for (var i = 0; i < toHide.length; i++) {
                    toHide[i].style.display = "none";
                }
                const removeFromActive = document.getElementsByClassName("select-" + oppositeLanguage)
                for (var i = 0; i < removeFromActive.length; i++) {
                    removeFromActive[i].classList.remove("is-active")
                }
                const addToActive = document.getElementsByClassName("select-" + newLang)
                for (var i = 0; i < addToActive.length; i++) {
                    addToActive[i].classList.add("is-active")
                }
                localStorage.setItem("last_selected_language", newLang)

            }

        const elementsToSetActive = document.getElementsByClassName("select-" + preferredLanguage)
        for (var i = 0; i < elementsToSetActive.length; i++) {
            elementsToSetActive[i].classList.add("is-active")
            elementsToSetActive[i].onclick = createSwitchLanguage(preferredLanguage)
        }
        const otherLangSelectors = document.getElementsByClassName("select-" + disableLanguage)
        for (var i = 0; i < otherLangSelectors.length; i++) {
            otherLangSelectors[i].onclick = createSwitchLanguage(disableLanguage)
        }

    </script>
    <script>hljs.highlightAll();</script>
</body>

</html>