|
| 1 | +defmodule Reactor.Mermaid do |
| 2 | + @moduledoc """ |
| 3 | + Converts Reactors and their related entities into a Mermaid diagram. |
| 4 | + """ |
| 5 | + @options Spark.Options.new!( |
| 6 | + expand?: [ |
| 7 | + type: :boolean, |
| 8 | + required: false, |
| 9 | + default: false, |
| 10 | + doc: "Whether or not to expand composed Reactors" |
| 11 | + ], |
| 12 | + describe?: [ |
| 13 | + type: :boolean, |
| 14 | + required: false, |
| 15 | + default: false, |
| 16 | + doc: "Whether or not to include descriptions, if available" |
| 17 | + ], |
| 18 | + direction: [ |
| 19 | + type: {:in, [:top_to_bottom, :bottom_to_top, :right_to_left, :left_to_right]}, |
| 20 | + required: false, |
| 21 | + default: :top_to_bottom, |
| 22 | + doc: "The direction to render the flowchart" |
| 23 | + ], |
| 24 | + indent: [ |
| 25 | + type: :non_neg_integer, |
| 26 | + required: false, |
| 27 | + default: 0, |
| 28 | + doc: "How much to indent the resulting mermaid" |
| 29 | + ] |
| 30 | + ) |
| 31 | + |
| 32 | + @type options :: unquote(Spark.Options.option_typespec(@options)) |
| 33 | + |
| 34 | + import __MODULE__.Utils |
| 35 | + |
| 36 | + @doc """ |
| 37 | + Convert the Reactor into Mermaid. |
| 38 | +
|
| 39 | + ## Options |
| 40 | +
|
| 41 | + #{Spark.Options.docs(@options)} |
| 42 | + """ |
| 43 | + @spec to_mermaid(module | Reactor.t(), options) :: {:ok, iodata()} | {:error, any} |
| 44 | + def to_mermaid(reactor, options \\ []) |
| 45 | + |
| 46 | + def to_mermaid(reactor, options) do |
| 47 | + with {:ok, options} <- Spark.Options.validate(options, @options) do |
| 48 | + do_to_mermaid(reactor, options) |
| 49 | + end |
| 50 | + end |
| 51 | + |
| 52 | + @doc """ |
| 53 | + Convert the Reactor into Mermaid |
| 54 | +
|
| 55 | + Raising version of `to_mermaid/2` |
| 56 | + """ |
| 57 | + @spec to_mermaid!(module | Reactor.t(), options) :: iodata | no_return() |
| 58 | + def to_mermaid!(reactor, options \\ []) do |
| 59 | + case to_mermaid(reactor, options) do |
| 60 | + {:ok, iodata} -> iodata |
| 61 | + {:error, reason} when is_exception(reason) -> raise reason |
| 62 | + {:error, reason} -> raise RuntimeError, reason |
| 63 | + end |
| 64 | + end |
| 65 | + |
| 66 | + defp do_to_mermaid(reactor, options) when is_atom(reactor) do |
| 67 | + if Code.ensure_loaded?(reactor) && function_exported?(reactor, :spark_is, 0) && |
| 68 | + reactor.spark_is() == Reactor do |
| 69 | + do_to_mermaid(reactor.reactor(), options) |
| 70 | + else |
| 71 | + {:error, ArgumentError.exception(message: "`reactor` argument is not a Reactor")} |
| 72 | + end |
| 73 | + end |
| 74 | + |
| 75 | + defp do_to_mermaid(reactor, options) when is_struct(reactor, Reactor) do |
| 76 | + with {:ok, reactor} <- Reactor.Planner.plan(reactor) do |
| 77 | + mermaid = __MODULE__.Render.to_mermaid(reactor, indent(options)) |
| 78 | + {:ok, indentify([["flowchart #{direction(options[:direction])}\n"] | mermaid], options)} |
| 79 | + end |
| 80 | + end |
| 81 | + |
| 82 | + defp direction(:top_to_bottom), do: "TD" |
| 83 | + defp direction(:bottom_to_top), do: "BT" |
| 84 | + defp direction(:left_to_right), do: "LR" |
| 85 | + defp direction(:right_to_left), do: "RL" |
| 86 | +end |
0 commit comments