rust-lang / mdBook

Create book from markdown files. Like Gitbook but implemented in Rust
https://rust-lang.github.io/mdBook/
Mozilla Public License 2.0
17.24k stars 1.58k forks source link

Add Kroki diagram support #1993

Open sebastiansam55 opened 1 year ago

sebastiansam55 commented 1 year ago

Problem

Gitlab markdown allows you to embed diagrams that are then generated by kroki.

Would be very cool if this could be integrated to mdBook as well.

Kroki: https://kroki.io/

How it works in gitlab: You can just embed it in any markdown file and as long as it's enabled it will generate them (on the fly I believe, but caching them may make more sense for an mdBook implementation of the feature)

https://docs.gitlab.com/ee/administration/integration/kroki.html#create-diagrams

Proposed Solution

No response

Notes

No response

sebastiansam55 commented 1 year ago

In case anyone else is interested. Don't know enough rust to write a plugin but I was able to do this pretty easily with python, just running it before running mdBook in my CI/CD;

python3 ./krokipreproc.py ./src

import sys
import base64
import zlib
import re
import os

# https://stackoverflow.com/questions/46595101/how-can-i-create-named-edge-types-in-graphviz-dot-neato
# there are apparently some scenarios where ` would be used by graphviz, don't really even understand the syntax so not going to worry about it for now.

pat = re.compile(r"(```graphviz(([^`].|\n)*)```)", re.MULTILINE)
#pat = r"graph"

print("Looking for files in:", sys.argv[1])

def folder_loop(path):
    for filename in os.listdir(path):
        f = os.path.join(path, filename)
        if os.path.isfile(f):
            kroki_replace(f)
        elif os.path.isdir(f):
            folder_loop(f)

def kroki_replace(path):
    with open(path, 'r') as f:
        file_content = f.read()
        #print(file_content)
        matches = re.findall(pat, file_content)
        print(path, matches)

        for mat in matches:
            file_content = re.sub(mat[0], kroki_url(kroki_encode(mat[1])), file_content)

    with open(path, 'w') as f:
        f.write(file_content)

def kroki_url(encoded):
    return "![Kroki Graphviz](https://kroki.endpoint/graphviz/svg/"+encoded+")"

def kroki_encode(content):
    return base64.urlsafe_b64encode(zlib.compress(content.encode('utf-8'), 9)).decode('ascii')

folder_loop(sys.argv[1])
danieleades commented 1 year ago

this seems like something that belongs in a plugin.

for example, this one - https://github.com/JoelCourtney/mdbook-kroki-preprocessor

frafra commented 1 year ago

You can add a custom script to your header as well:

File theme/head.hbs:

<script type="module">
var server = "https://kroki.io";
var libraries = [
  "bytefield",
  "c4plantuml",
  "d2",
  "ditaa",
  "erd",
  "graphviz",
  "dot",
  "nomnoml",
  "pikchr",
  "lantuml",
  "structurizr",
  "svgbob",
  "umlet",
  "vega",
  "vegalite",
  "wavedrom",
];
import pako from "https://cdn.jsdelivr.net/npm/pako@2/+esm";
import { Base64 } from "https://cdn.jsdelivr.net/npm/js-base64@3/base64.mjs";
function getLanguage(classList) {
  for (let i = 0; i < classList.length; i++) {
    if (classList[i].startsWith("language-")) {
      return classList[i].replace("language-", "");
    }
  }
}
document.addEventListener("DOMContentLoaded", function () {
  var selector = libraries.map((i) => ".language-" + i).join(", ");
  var nodes = document.querySelectorAll(selector);
  for (let i = 0; i < nodes.length; i++) {
    var data = new TextEncoder("utf-8").encode(nodes[i].innerText);
    var buffer = pako.deflate(data, { level: 9 });
    var result = Base64.fromUint8Array(buffer, true);
    var language = getLanguage(nodes[i].classList);
    var graph = document.createElement("object");
    graph.width = "100%";
    graph.data = `${server}/${language}/svg/${result}`;
    nodes[i].parentElement.replaceWith(graph);
  }
});
</script>

References: