lib/mix/mermaid.ex

defmodule Mix.Mermaid do
  @moduledoc """
  Mermaid Diagram helper functions.
  """

  @doc """
  Generate the option string for a mermaid config file if it exists.
  """
  def config do
    if File.exists?("mermaidConfig.json") do
      "--configFile #{Path.expand("mermaidConfig.json")}"
    end
  end

  # sobelow_skip ["Traversal"]
  def generate_diagram(source, suffix, "plain", markdown, message) do
    file = Mix.Mermaid.file(source, suffix, "mmd")

    File.write!(file, markdown)

    Mix.shell().info(message <> " (#{file})")
  end

  # sobelow_skip ["Traversal"]
  def generate_diagram(source, suffix, "md", markdown, message) do
    file = Mix.Mermaid.file(source, suffix, "md")

    File.write!(file, """
    ```mermaid
    #{markdown}
    ```
    """)

    Mix.shell().info(message <> " (#{file})")
  end

  def generate_diagram(source, suffix, format, markdown, message)
      when format in ["svg", "pdf", "png"] do
    file = Mix.Mermaid.file(source, suffix, format)

    Mix.Mermaid.create_diagram(file, markdown)

    Mix.shell().info(message <> " (#{file})")
  end

  def generate_diagram(_, _, format, _, _) do
    Mix.shell().error("""
    Invalid format `#{format}`.

    Valid options are `plain`, `md`, `svg`, `pdf` or `png`.
    """)
  end

  @doc """
  Generate a diagram filename next to the source file.
  """
  def file(source, suffix, extension) do
    filename =
      source
      |> Path.basename()
      |> Path.rootname()
      |> Kernel.<>("-" <> suffix <> "." <> extension)

    source
    |> Path.dirname()
    |> Path.join(filename)
  end

  @doc """
  Generate a Mermaid diagram using the CLI.

  For more info see https://github.com/mermaid-js/mermaid-cli
  """
  def create_diagram(file, markdown) do
    "sh"
    |> System.cmd([
      "-c",
      """
      cat <<EOF | mmdc --output #{file} --pdfFit #{config()}
      #{markdown}
      EOF
      """
    ])
    |> case do
      {_, 0} ->
        :ok

      {text, exit_status} ->
        raise "Creating Mermaid diagram #{file} exited with status: #{exit_status}\n#{text}"
    end
  end
end