Packages

Create .dot diagrams from your ecto schemas. Export them to `png`, `svg` or `pdf`.

Current section

Files

Jump to
ecto_dot lib ecto_dot.ex
Raw

lib/ecto_dot.ex

defmodule EctoDot do
@moduledoc """
EctoDot provides an easy way to generate .dot, .png, .svg and .pdf diagrams directly from your ecto schema modules.
"""
alias EctoDot.Schema
alias EctoDot.Association
alias EctoDot.Diagram
@doc """
Creates the .dot representation including attributes and associations between the received schema module/s.
Note: only associations between received modules will be shown.
"""
def diagram(mods, opts \\ [])
def diagram(mod, opts) when is_atom(mod) do
diagram([mod], opts)
end
def diagram(mods, opts) when is_list(mods) do
name = Keyword.get(opts, :name, "Diagram")
schemas =
mods
|> Enum.map(&Schema.from_ecto/1)
associations =
mods
|> Enum.flat_map(&Association.from_ecto/1)
|> Enum.filter(fn assoc ->
Enum.member?(mods, assoc.to)
end)
%Diagram{name: name, schemas: schemas, associations: associations}
end
@doc """
Creates the .dot representation including attributes and associations accesible from the received schema module/s.
"""
def expanded_diagram(mods, opts \\ [])
def expanded_diagram(mod, opts) when is_atom(mod) do
expanded_diagram([mod], opts)
end
def expanded_diagram(mods, opts) when is_list(mods) do
reachable_modules =
expand_modules(MapSet.new(), MapSet.new(mods))
|> MapSet.to_list()
diagram(reachable_modules, opts)
end
defp expand_modules(reachable, to_expand) do
if Enum.empty?(to_expand) do
reachable
else
will_expand = Enum.at(to_expand, 0)
new_reachable = MapSet.put(reachable, will_expand)
rest_to_expand = MapSet.delete(to_expand, will_expand)
new_to_expand =
Association.from_ecto(will_expand)
|> Enum.map(fn assoc -> assoc.to end)
|> Enum.into(MapSet.new())
|> MapSet.union(rest_to_expand)
|> MapSet.difference(new_reachable)
expand_modules(new_reachable, new_to_expand)
end
end
@doc """
Exports a diagram. Params:
* A diagram returned from `diagram/2` or `expanded_diagram/2`
* The path where the file/s will be generated, including the filename (no extension)
* A list with the formats you want to be exported: [:dot, :png, :svg, :pdf]
"""
def export(%Diagram{} = diag, path, formats \\ [:dot]) do
dot = Diagram.to_dot(diag)
output_file = fn format -> Path.rootname(path) <> ".#{format}" end
dot_file = output_file.(:dot)
# need to generate anyway
File.write(dot_file, dot)
try do
formats
|> Enum.each(fn
format when format in [:png, :svg, :pdf] ->
file = output_file.(format)
IO.puts("Generating #{file}")
System.cmd("dot", ["-T#{format}", dot_file, "-o#{file}"])
:dot ->
IO.puts("Generating #{dot_file}")
end)
rescue
_ ->
IO.puts(
"It seems dot is not installed or in PATH. Please install it if you want to generate png, svg or pdf files with it."
)
after
if :dot not in formats do
File.rm(dot_file)
end
end
:ok
end
end