defmodule DG.Sigil do @moduledoc """ Sigils that parse `mermaid.js` format flowchart into `DG` iex> import DG.Sigil ...> dg = ~G"\"" ...> graph LR ...> a[some label] ...> b[other label] ...> 1-->2 ...> 3[three] -- three to four --> 4[four] ...> a --> b ...> "\"" iex> DG.vertex(dg, "a") {"a", "some label"} iex> Enum.sort(DG.vertices(dg)) ["1", "2", "3", "4", "a", "b"] iex> length(DG.edges(dg)) 3 """ use AbnfParsec, abnf: """ wsp = %x20 / %x09 lwsp = *(wsp / LF / CRLF) direction = "TB" / "TD" / "LR" type = "flowchart" / "graph" label = 1*(ALPHA / DIGIT / %x20) square-brace-open = "[" square-brace-close = "]" vertex-id = 1*(ALPHA / DIGIT) vertex = vertex-id [square-brace-open label square-brace-close] edge = vertex *wsp ("-->" / "--" label "-->") *wsp vertex vertex-or-edge = [lwsp] (edge / vertex) graph = lwsp type 1*wsp direction lwsp *vertex-or-edge """, parse: :graph, unbox: ["vertex-or-edge", "vertex-id"], ignore: ["wsp", "lwsp", "square-brace-open", "square-brace-close"], unwrap: ["type", "direction", "label"], transform: %{ "vertex-id" => {:reduce, {List, :to_string, []}}, "label" => [{:reduce, {List, :to_string, []}}, {:map, {String, :trim, []}}] } defp unwrap_vertex({:vertex, [v]}) do {:vertex, v} end defp unwrap_vertex({:vertex, [v, label: label]}) do {:vertex, v, label} end defp extract_vertex({:vertex, [v | _]}), do: v defp extract_vertex({:vertex, v, _label}), do: v defp extract_vertex({:vertex, v}), do: v defp extract_edge({:edge, v1, v2, _label}), do: {v1, v2} defp extract_edge({:edge, v1, v2}), do: {v1, v2} @doc false def prepare_gen(string) do {:ok, [graph: [{:type, _type}, {:direction, direction} | content]], _, _, _, _} = parse(string) vertices = content |> Enum.flat_map(fn {:vertex, _} = v -> [unwrap_vertex(v)] {:edge, [v1, "-->", v2]} -> [unwrap_vertex(v1), unwrap_vertex(v2)] {:edge, [v1, "--", _label, "-->", v2]} -> [unwrap_vertex(v1), unwrap_vertex(v2)] end) |> Enum.uniq_by(&extract_vertex/1) edges = content |> Enum.filter(fn {:edge, _} -> true _ -> false end) |> Enum.map(fn {:edge, [v1, "-->", v2]} -> {:edge, extract_vertex(v1), extract_vertex(v2)} {:edge, [v1, "--", {:label, label}, "-->", v2]} -> {:edge, extract_vertex(v1), extract_vertex(v2), label} end) |> Enum.uniq_by(&extract_edge/1) {direction, vertices, edges} end defp gen(string) do {direction, vertices, edges} = prepare_gen(string) quote do DG.new( unquote(Macro.escape(vertices)), unquote(Macro.escape(edges)), direction: unquote(direction) ) end end defmacro sigil_g({:<<>>, _, [string]}, _opts), do: gen(string) defmacro sigil_g({:<<>>, _, _pieces} = string, _opts) do quote do import unquote(__MODULE__), only: [prepare_gen: 1] {direction, vertices, edges} = prepare_gen(unquote(string)) DG.new(vertices, edges, direction: direction) end end defmacro sigil_G({:<<>>, _, [string]}, _opts), do: gen(string) end