DEV Community

Erick πŸ™ƒ Navarro
Erick πŸ™ƒ Navarro

Posted on • Originally published at erick.navarro.io on

Add D2 support to Hugo

D2 is declarative language to generate diagrams, it's like mermaid on steroids, it has a cli so it's easy to use.

Hugo doesn't support it at the moment of writing this, there is an open issue where the conversation is being done.

So in the meantime official support is added we're going to make our own integration. It will have 2 parts:

A simple HTTP server in go

This server only uses standard library and do the follow:

  • receive d2 code in a POST payload.
  • call d2 cli and get its std output.
  • return this output as response body.
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os/exec"
)

func handleRenderRequest(w http.ResponseWriter, r *http.Request) {
    requestBody, err := io.ReadAll(r.Body)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    defer r.Body.Close()

    output, err := renderText(string(requestBody))

    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Fprintf(w, output)
}

func renderText(content string) (string, error) {
    // d2 -, will render the text received in stdin
    command := exec.Command("d2", "-")
    command.Stdin = bytes.NewBuffer([]byte(content))
    output, err := command.Output()
    if err != nil {
        return "", err
    }
    return string(output), nil
}

func main() {
    http.HandleFunc("POST /render", handleRenderRequest)
    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

We can also use d2 as a package but using it as a CLI is less code.

Now if we send a request the following request:

POST http://localhost:8080/render
shape: sequence_diagram
alice -> bob: What does it mean\nto be well-adjusted?
bob -> alice: The ability to play bridge or\ngolf as if they were games.
Enter fullscreen mode Exit fullscreen mode

Custom code block render hook

Hugo allow us to define custom code block render hooks, we're going to define a custom one using d2 hook so when we define the following code it will call our server and insert its resulting SVG.

D2 code here
Enter fullscreen mode Exit fullscreen mode

Now we need to create a new file in layouts/_default/_markup/render-codeblock-d2.html and put the following code:

{{- $renderHookName := "d2" }}

{{- $inner := trim .Inner "\n\r" }}
{{- $position := .Position }}

{{- $apiEndpoint := "http://localhost:8080/render" }}

{{- $opts := dict "method" "post" "body" $inner }}
{{- with resources.GetRemote $apiEndpoint $opts }}
  {{- with .Err }}
    {{- errorf "The %q code block render hook was unable to get the remote diagram. See %s. %s" $renderHookName $position . }}
  {{- else }}
      <div style="width: 600px">
    {{ .Content | safeHTML }}
      </div>
  {{- end }}
{{- else }}
  {{- errorf "The %q code block render hook was unable to get the remote diagram. See %s" $renderHookName $position }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode

This code uses resources.GetRemote to make a POST request to our server and then insert the response content as part of the document.

The render will only be done on build time so we don't need to have the render server always on

Conclusion

Hugo allows us to add custom features to our site and go allows us to accomplish task using on only standard library.

Enjoy! πŸŽ‰

Top comments (0)