All Downloads are FREE. Search and download functionalities are using the official Maven repository.

slate4j.MavenDocs Maven / Gradle / Ivy

package slate4j;

import com.google.gson.Gson;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.DataNode;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.NodeVisitor;
import slate4j.model.SlateFile;
import slate4j.model.SlateHeading;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_RESOURCES;
import static slate4j.IO.*;
import static slate4j.model.SlateFile.toSlateFile;

@Mojo( defaultPhase = GENERATE_RESOURCES, name = "compile" )
public final class MavenDocs extends AbstractMojo {

    @Parameter(defaultValue = "src/main/docs/index.html.md")
    public File indexFile;
    @Parameter
    public File logoFile;
    @Parameter(defaultValue = "target/classes/docs/index.html")
    public File outputFile;

    @Override
    public void execute() throws MojoFailureException {
        try {
            final String html = compileSlateDocument(indexFile, logoFile);
            outputFile.getParentFile().mkdirs();
            Files.writeString(outputFile.toPath(), html, CREATE, TRUNCATE_EXISTING);
        } catch (Exception e) {
            throw new MojoFailureException(e.getMessage(), e);
        }
    }

    public static String compileSlateDocument(final File indexFile, final File logoFile) throws IOException {
        final Gson gson = new Gson();
        final SlateFile slateFile = toSlateFile(indexFile);

        final Document wrapper = Jsoup.parse(resourceToString("/webbin/wrapper.html"));
        wrapper.selectFirst("title").text(slateFile.header.title);
        wrapper.selectFirst("body").attr("data-languages", gson.toJson(slateFile.header.languages));
        for (final var langSelector : wrapper.select("div.lang-selector")) {
            for (final var lang : slateFile.header.languages) {
                langSelector.appendChild(
                    wrapper.createElement("a")
                        .attr("href", "#")
                        .attr("data-language-name", lang)
                        .text(lang));
            }
        }

        final String js = resourceToString("/webbin/custom.min.js");
        final String css = resourceToString("/webbin/custom.min.css");
        wrapper.selectFirst("head")
            .appendChild( wrapper.createElement("script").appendChild(new DataNode(js)) )
            .appendChild( wrapper.createElement("style").appendChild(new DataNode(css)) );

        final String logo = existsFile(logoFile)
                ? encodeBase64(readAsBytes(logoFile))
                : resourceToString("/static/img/logo.base64.txt");
        wrapper.selectFirst("img[class=logo]").attr("src", "data:image/png;base64,"+logo);

        final Element body = Jsoup.parseBodyFragment(markdownToHtml(slateFile.content)).body();
        insertTableOfContents(wrapper, body);

        final Element element = wrapper.selectFirst("div.content");
        for (final var child : body.children()) {
            element.appendChild(child);
        }

        for (final var codeTags : wrapper.select("code")) {
            final String language = toLanguageClass(codeTags.attr("class"));
            if (language == null) continue;

            codeTags.parent().addClass("highlight").addClass(language).addClass("tab-"+language);
        }

        for (final var notice : wrapper.select("aside[class=notice]")) {
            notice.prependChild( wrapper.createElement("li").addClass("fas").addClass("fa-info-circle") );
        }
        for (final var success : wrapper.select("aside[class=success]")) {
            success.prependChild( wrapper.createElement("li").addClass("fas").addClass("fa-check-circle") );
        }
        for (final var warning : wrapper.select("aside[class=warning]")) {
            warning.prependChild( wrapper.createElement("li").addClass("fas").addClass("fa-exclamation-circle") );
        }

        return wrapper.html();
    }

    private static String toLanguageClass(final String classAttr) {
        for (final String clas : classAttr.split(" ")) {
            if (clas.startsWith("language-"))
                return clas.substring(9);
        }
        return null;
    }

    private static void insertTableOfContents(final Document wrapper, final Element body) {
        final List toc = discoverH1AndH2(body);

        final Element tocElement = wrapper.selectFirst("#toc");
        for (final SlateHeading heading : toc) {
            final Element topLi = wrapper.createElement("li").appendChild(
                wrapper.createElement("a")
                    .attr("href", "#" + heading.id)
                    .attr("class", "toc-h1 toc-link")
                    .attr("data-title", heading.name)
                    .text(heading.name));
            if (!heading.subHeadings.isEmpty()) {
                final Element ul = wrapper.createElement("ul").attr("class", "toc-list-h2");
                for (final SlateHeading subHeading : heading.subHeadings) {
                    ul.appendChild(
                        wrapper.createElement("li").appendChild(
                            wrapper.createElement("a")
                                .attr("href", "#" + subHeading.id)
                                .attr("class", "toc-h2 toc-link")
                                .attr("data-title", subHeading.name)
                                .text(subHeading.name)));
                }
                topLi.appendChild(ul);
            }
            tocElement.appendChild(topLi);
        }
    }

    private static List discoverH1AndH2(Element body) {
        final List toc = new ArrayList<>();

        body.traverse(new NodeVisitor() {
            private int index = 0;
            public void head(Node node, int depth) {
                if (node instanceof Element) {
                    final Element element = (Element) node;
                    final String name = element.tagName();
                    if (name.equals("h1") || name.equals("h2")) {
                        final var sh = new SlateHeading(toLevel(name), toUniqueId(index++, element), element.text(), new ArrayList<>());
                        element.attr("id", sh.id);
                        if (sh.level == 1) toc.add(sh);
                        else toc.get(toc.size()-1).subHeadings.add(sh);
                    }
                }
            }

            private int toLevel(final String name) {
                return Integer.parseInt(name.substring(1, 2));
            }

            private String toUniqueId(final int index, final Element element) {
                return element.text().replaceAll(" ", "-").toLowerCase();
            }

            public void tail(Node node, int depth) {}
        });

        return toc;
    }

    public static String markdownToHtml(final String markdown) {
        final MutableDataSet options = new MutableDataSet();
        options.set(Parser.EXTENSIONS, List.of(TablesExtension.create()));
        final Parser parser = Parser.builder(options).build();
        final HtmlRenderer renderer = HtmlRenderer.builder(options).build();
        return renderer.render(parser.parse(markdown));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy