# `Yog.IO.TGF`
[🔗](https://github.com/code-shoily/yog_ex/blob/v1.0.0/lib/yog/io/tgf.ex#L1)

Trivial Graph Format (TGF) serialization support.

Provides functions to serialize and deserialize graphs in TGF format,
a simple text-based format suitable for quick graph exchange and debugging.

## Format Overview

TGF consists of three parts:
1. **Node section**: Each line is `node_id node_label`
2. **Separator**: A single `#` character on its own line
3. **Edge section**: Each line is `source_id target_id [edge_label]`

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Alice")
    ...> |> Yog.add_node(2, "Bob")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: "follows")
    iex>
    iex> tgf_string = Yog.IO.TGF.serialize(graph)
    iex> String.contains?(tgf_string, "1 Alice")
    true
    iex> String.contains?(tgf_string, "1 2")
    true

## Parsing Behavior

When parsing TGF files, the following behaviors apply:
- **Auto-node creation**: If an edge references a node ID that was not declared
  in the node section, a node is automatically created with the ID as its label.
- **Empty labels**: Nodes without labels default to using their ID as the label.
- **Malformed lines**: Lines that cannot be parsed are skipped and collected
  as warnings in the `TgfResult`.

## Complexity

- `serialize/1` and `serialize_with/2`: $\mathcal{O}(V \log V + E)$ time complexity due to node ID sorting, and $\mathcal{O}(V + E)$ space complexity.
- `parse/2` and `parse_with/4`: $\mathcal{O}(V + E)$ time and space complexity.

# `default_options`

Returns default TGF serialization options.

Default behavior:
- Node labels: Convert data to string using `to_string/1`
- Edge labels: No labels (returns `:none`)
- Node formatter: `Yog.Utils.safe_string/1`
- Edge formatter: `Yog.Utils.safe_string/1`

## Example

    iex> {:tgf_options, _node_fn, _edge_fn, _node_fmt, _edge_fmt} = Yog.IO.TGF.default_options()
    iex> :ok
    :ok

# `options_with`

Creates TGF options with custom node and edge label functions.

## Parameters

- `node_label` - Function to convert node data to string label `(node_data) -> string`
- `edge_label` - Function to convert edge data to optional label `(edge_data) -> :none | {:some, string}`
- `opts` - Keyword list options:
  - `:node_formatter` - Function converting node ID to string (default: `&Yog.Utils.safe_string/1`)
  - `:edge_formatter` - Function converting edge label to string (default: `&Yog.Utils.safe_string/1`)

## Returns

TGF options tuple for use with `serialize_with/2`

## Errors

- Raises `ArgumentError` if `node_label` or `edge_label` are not arity-1 functions.
- Raises `ArgumentError` if `opts` is not a keyword list or contains unknown options.

## Example

    iex> options = Yog.IO.TGF.options_with(
    ...>   fn data -> "Node: " <> to_string(data) end,
    ...>   fn weight -> {:some, "W:" <> to_string(weight)} end
    ...> )
    iex> {:tgf_options, _, _, _, _} = options
    iex> :ok
    :ok

# `parse`

Parses a TGF string into a graph with String labels.

Node and edge labels are stored as strings. For custom data structures,
use `parse_with/4`.

## Parameters

- `input` - TGF format string
- `gtype` - `:directed` or `:undirected`

## Returns

- `{:ok, {:tgf_result, graph, warnings}}` on success
- `{:error, reason}` on parsing failure

The warnings list contains any malformed lines that were skipped.

## Errors

- Raises `ArgumentError` if `gtype` is not `:directed` or `:undirected`.
- Raises `ArgumentError` if `input` is not a binary.

## Example

    iex> tgf_string = """
    ...> 1 Alice
    ...> 2 Bob
    ...> #
    ...> 1 2 follows
    ...> """
    iex> {:ok, {:tgf_result, graph, []}} = Yog.IO.TGF.parse(tgf_string, :directed)
    iex> Yog.Model.node_count(graph)
    2

# `parse_with`

Parses a TGF string into a graph with custom parsers.

This function allows you to transform TGF labels into custom Elixir data
structures as the graph is built.

## Parameters

- `input` - TGF format string
- `graph_type` - `:directed` or `:undirected`
- `node_parser` - Function transforming node label to node data `(label) -> node_data` or `(id, label) -> node_data`
- `edge_parser` - Function transforming edge label to edge data `(string | nil) -> edge_data`

## Returns

- `{:ok, {:tgf_result, graph, warnings}}` on success
- `{:error, reason}` on parsing failure

## Errors

- Raises `ArgumentError` if `graph_type` is not `:directed` or `:undirected`.
- Raises `ArgumentError` if `input` is not a binary.
- Raises `ArgumentError` if `node_parser` or `edge_parser` are invalid functions.

## Example

    tgf = "1 Alice\n2 Bob\n#\n1 2 5\n"

    node_parser = fn label -> String.upcase(label) end
    edge_parser = fn label ->
      case label do
        nil -> 1
        val -> String.to_integer(val)
      end
    end

    {:ok, {:tgf_result, graph, _warnings}} =
      Yog.IO.TGF.parse_with(tgf, :directed, node_parser, edge_parser)

# `read`

Reads a graph from a TGF file using String labels.

## Parameters

- `path` - File path to read from
- `gtype` - `:directed` or `:undirected`

## Returns

- `{:ok, {:tgf_result, graph, warnings}}` on success
- `{:error, reason}` on file read or parse failure

## Errors

- Raises `ArgumentError` if `path` is not a binary string.

## Example

    {:ok, {:tgf_result, graph, warnings}} =
      Yog.IO.TGF.read("network.tgf", :directed)

    if warnings != [] do
      IO.puts("Warning: Some lines were malformed")
    end

# `read_with`

Reads a graph from a TGF file with custom parsers.

## Errors

- Raises `ArgumentError` if `path` is not a binary string.

# `serialize`

Serializes a graph to TGF format using default label conversion.

Node data is converted to strings, edge labels are omitted.

## Errors

- Raises `ArgumentError` if `graph` is not a valid Yog graph struct.

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Alice")
    ...> |> Yog.add_node(2, "Bob")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: "follows")
    iex> tgf = Yog.IO.TGF.serialize(graph)
    iex> String.contains?(tgf, "1 Alice") and String.contains?(tgf, "1 2")
    true

# `serialize_with`

Serializes a graph to TGF format with custom label functions.

Allows full control over how node and edge data are converted to TGF labels.

## Parameters

- `options` - TGF options tuple (see `options_with/2`)
- `graph` - The `Yog.Graph` or `Yog.DAG` to serialize

## Returns

TGF format string

## Errors

- Raises `ArgumentError` if `graph` is not a valid Yog graph struct.
- Raises `ArgumentError` if `options` is not a valid options tuple.

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, %{name: "Alice"})
    ...> |> Yog.add_node(2, %{name: "Bob"})
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 10)
    iex> options = Yog.IO.TGF.options_with(
    ...>   fn data -> data.name end,
    ...>   fn weight -> {:some, Integer.to_string(weight)} end
    ...> )
    iex> tgf_string = Yog.IO.TGF.serialize_with(options, graph)
    iex> String.contains?(tgf_string, "1 Alice") and String.contains?(tgf_string, "1 2 10")
    true

# `write`

Writes a graph to a TGF file using default label conversion.

## Parameters

- `path` - File path to write to
- `graph` - The graph to serialize

## Returns

- `:ok` on success
- `{:error, reason}` on file write failure

## Errors

- Raises `ArgumentError` if `path` is not a binary.
- Raises `ArgumentError` if `graph` is not a valid Yog graph struct.

## Example

    graph = Yog.directed()
    |> Yog.add_node(1, "Alice")
    |> Yog.add_node(2, "Bob")
    |> Yog.add_edge_ensure(from: 1, to: 2, with: "follows")

    Yog.IO.TGF.write("network.tgf", graph)
    # => :ok

# `write_with`

Writes a graph to a TGF file with custom label functions.

## Parameters

- `path` - File path to write to
- `options` - TGF options tuple (see `options_with/2`)
- `graph` - The graph to serialize

## Returns

- `:ok` on success
- `{:error, reason}` on file write failure

## Errors

- Raises `ArgumentError` if `path` is not a binary.
- Raises `ArgumentError` if `options` or `graph` are invalid.

## Example

    graph = Yog.directed() |> Yog.add_node(1, %{name: "Alice"})
    options = Yog.IO.TGF.options_with(fn d -> d.name end, fn _ -> :none end)

    Yog.IO.TGF.write_with("network.tgf", options, graph)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
