lib/graphvix/record.ex

defmodule Graphvix.Record do
  @moduledoc """
  Models a graph vertex that has a shape of `record`.

  A record's label can be a single string, a single row or column, or a nested
  alternation of rows and columns.

  Once a record is created by `Graphvix.Record.new/2` it can be added to a graph using
  `Graphvix.Graph.add_record/2`.

  See `new/2` for more complete usage examples.

  ## Example

      iex> import Record, only: [column: 1]
      iex> graph = Graph.new()
      iex> record = Record.new(["a", "B", column(["c", "D"])], color: "blue")
      iex> {graph, _rid} = Graph.add_record(graph, record)
      iex> Graph.to_dot(graph)
      ~s(digraph G {\\n\\n  v0 [label="a | B | { c | D }",shape="record",color="blue"]\\n\\n})

  """

  defstruct body: nil,
            properties: []

  alias __MODULE__
  alias Graphvix.RecordSubset

  @type body :: String.t() | [any()] | RecordSubset.t()
  @type t :: %__MODULE__{body: Record.t(), properties: keyword()}

  @doc """
  Returns a new `Graphvix.Record` struct that can be added to a graph as a vertex.

  ## Examples

  A record's can be a simple text label

      iex> record = Record.new("just a plain text record")
      iex> Record.to_label(record)
      "just a plain text record"

  or it can be a single row or column of strings

      iex> import Record, only: [row: 1]
      iex> record = Record.new(row(["a", "b", "c"]))
      iex> Record.to_label(record)
      "a | b | c"

      iex> import Record, only: [column: 1]
      iex> record = Record.new(column(["a", "b", "c"]))
      iex> Record.to_label(record)
      "{ a | b | c }"

  or it can be a series of nested rows and columns

      iex> import Record, only: [row: 1, column: 1]
      iex> record = Record.new(
      ...>   row([
      ...>     "a",
      ...>     column([
      ...>       "b", "c", "d"
      ...>     ]),
      ...>     column([
      ...>       "e",
      ...>       "f",
      ...>       row([
      ...>         "g", "h", "i"
      ...>       ])
      ...>     ])
      ...>   ])
      ...> )
      iex> Record.to_label(record)
      "a | { b | c | d } | { e | f | { g | h | i } }"

  passing a plain list defaults to a row

      iex> record = Record.new(["a", "b", "c"])
      iex> Record.to_label(record)
      "a | b | c"

  Each cell can contain a plain string, or a string with a port attached,
  allowing edges to be drawn directly to and from that cell, rather than the
  vertex. Ports are created by passing a tuple of the form `{port_name, label}`

      iex> record = Record.new(["a", {"port_b", "b"}])
      iex> Record.to_label(record)
      "a | <port_b> b"

  A second, optional argument can be passed specifying other formatting and
  styling properties for the vertex.

      iex> record = Record.new(["a", {"port_b", "b"}, "c"], color: "blue")
      iex> graph = Graph.new()
      iex> {graph, _record_id} = Graph.add_record(graph, record)
      iex> Graph.to_dot(graph)
      ~s(digraph G {\\n\\n  v0 [label="a | <port_b> b | c",shape="record",color="blue"]\\n\\n})


  """
  def new(body, properties \\ [])

  def new(string, properties) when is_bitstring(string) do
    %__MODULE__{body: string, properties: properties}
  end

  def new(list, properties) when is_list(list) do
    %__MODULE__{body: Graphvix.RecordSubset.new(list), properties: properties}
  end

  def new(row_or_column = %Graphvix.RecordSubset{}, properties) do
    %__MODULE__{body: row_or_column, properties: properties}
  end

  @doc """
  A helper method that takes a list of cells and returns them as a row inside a
  `Graphvix.Record` struct.

  The list can consist of a mix of string labels or tuples of cell labels +
  port names.

  This function provides little functionality on its own. See the documentation
  for `Graphvix.Record.new/2` for usage examples in context.
  """
  def row(cells) do
    %RecordSubset{cells: cells, is_column: false}
  end

  @doc """
  A helper method that takes a list of cells and returns them as a column inside a
  `Graphvix.Record` struct.

  The list can consist of a mix of string labels or tuples of cell labels +
  port names.

  This function provides little functionality on its own. See the documentation
  for `Graphvix.Record.new/2` for usage examples in context.
  """
  def column(cells) do
    %RecordSubset{cells: cells, is_column: true}
  end

  @doc false
  def to_label(record)

  def to_label(%{body: string}) when is_bitstring(string) do
    string
  end

  def to_label(%{body: subset = %RecordSubset{}}) do
    RecordSubset.to_label(subset, true)
  end
end