Current section
Files
Jump to
Current section
Files
lib/dotx.ex
defmodule Dotx do
@type id :: binary | %Dotx.HTML{html: binary}
@type nodeid :: [binary]
@type graph :: graph(edge) | graph(flatedge)
@type graph(edgetype) :: %Dotx.Graph{
strict: boolean,
type: :graph | :digraph,
id: nil | id,
attrs: %{optional(id) => id},
nodes_attrs: %{optional(id) => id},
edges_attrs: %{optional(id) => id},
graphs_attrs: %{optional(id) => id},
children: [dotnode | edgetype | subgraph(edgetype)]
}
@type dotnode :: %Dotx.Node{id: nodeid, attrs: %{optional(id) => id}}
@type edge :: %Dotx.Edge{
attrs: %{optional(id) => id}, bidir: boolean,
from: dotnode | subgraph(edge), to: dotnode | subgraph(edge) | edge
}
@type flatedge :: %Dotx.Edge{
attrs: %{optional(id) => id}, bidir: boolean,
from: dotnode, to: dotnode
}
@type subgraph(edgetype) :: %Dotx.SubGraph{
id: nil | id,
attrs: %{optional(id) => id},
nodes_attrs: %{optional(id) => id},
edges_attrs: %{optional(id) => id},
graphs_attrs: %{optional(id) => id},
children: [dotnode | edgetype | subgraph(edgetype)]
}
@moduledoc """
This library is a DOT parser and generator.
Main functions are `encode/1` and `decode/1` (usable also via `to_string`
and the `String.Chars` protocol).
Some additional helper function are useful to inspect a graph and use them :
- `flatten/1` create unitary edge for every DOT shortand (inline edge
`a->b->c` or graph edge `{a b}->c`)
- `spread_attributes/1` spread default attributes from graph/subgraphs to
all children handling inheritance of attributes.
"""
@spec encode(graph) :: binary
def encode(graph) do to_string(graph) end
@spec decode(binary) :: {:ok,graph(edge)} | {:error,msg :: binary}
def decode(bin) do Dotx.Graph.parse(bin) end
@spec decode!(binary) :: graph(edge)
def decode!(bin) do
case decode(bin) do
{:ok,graph}->graph
{:error,msg}->
raise ArgumentError, "cannot parse DOT : #{msg}"
end
end
@spec flatten(graph(edge)) :: graph(flatedge)
def flatten(%Dotx.Graph{children: children}=graph) do
%{graph|children: Enum.flat_map(children,&flatten(&1))}
end
def flatten(%Dotx.SubGraph{children: children}=graph) do
[%{graph|children: Enum.flat_map(children,&flatten(&1))}]
end
def flatten(%Dotx.Edge{}=e) do Dotx.Edge.flatten(e) end
def flatten(other) do [other] end
@spec spread_attributes(graph) :: graph
def spread_attributes(graph) do
%{graph|children: Enum.map(graph.children,&spread_attributes(&1,graph))}
end
def spread_attributes(%Dotx.SubGraph{children: children}=e,graph) do
graph = %{graph|nodes_attrs: Map.merge(graph.nodes_attrs,e.nodes_attrs),
edges_attrs: Map.merge(graph.edges_attrs,e.edges_attrs),
graphs_attrs: Map.merge(graph.graphs_attrs,e.graphs_attrs)}
%{e|attrs: Map.merge(graph.graphs_attrs,e.attrs),
children: Enum.map(children,&spread_attributes(&1,graph))}
end
def spread_attributes(%Dotx.Edge{attrs: attrs,from: from, to: to}=e,graph) do
%{e|attrs: Map.merge(graph.edges_attrs,attrs),
from: spread_attributes(from,graph), to: spread_attributes(to,graph)}
end
def spread_attributes(%Dotx.Node{attrs: attrs}=e,graph) do
%{e|attrs: Map.merge(graph.nodes_attrs,attrs)}
end
@spec identify(graph(edge)) :: graph(edge)
def identify(graph) do {g,_} = identify(graph,0); g end
def identify(%{id: id, children: children}=graph,i) do
{graph,i} = case id do nil-> {%{graph| id: id || "x#{i}"},i+1}; _-> {graph,i} end
{backchildren,i} = Enum.reduce(children,{[],i},fn e, {acc,i}->
{e,i} = identify(e,i)
{[e|acc],i}
end)
{%{graph|children: Enum.reverse(backchildren)},i}
end
def identify(%Dotx.Edge{}=e,i) do
{from,i} = identify(e.from,i)
{to,i} = identify(e.to ,i)
{%{e|from: from, to: to},i}
end
def identify(o,i) do {o,i} end
# TODO nodes flatten with a "graph" attribute with deepest subgraph owning node
# @spec nodes(graph(flatedge)) :: [dotnode]
# TODO edges flatten with a "graph" attribute containing owning subgraph id
# @spec edges(graph(flatedge)) :: [flatedge]
end