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

Trivial Graph Format (TGF) serialization support.

Provides functions to serialize and deserialize graphs in TGF format,
a very 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`.

# `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: `Kernel.to_string/1`
- Edge formatter: `Kernel.to_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.

**Time Complexity:** O(1)

## 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}`

## Returns

TGF options tuple for use with `serialize_with/2`

## 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`.

**Time Complexity:** O(V + E)

## 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.

## 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.

**Time Complexity:** O(V + E)

## Parameters

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

## Returns

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

## 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.

**Time Complexity:** O(V + E) + file I/O

## 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

## 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.

# `serialize`

Serializes a graph to TGF format using default label conversion.

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

**Time Complexity:** O(V + E)

## 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.

**Time Complexity:** O(V + E) where V is nodes and E is edges

## Parameters

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

## Returns

TGF format string

## 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.

**Time Complexity:** O(V + E) + file I/O

## Parameters

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

## Returns

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

## 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.

**Time Complexity:** O(V + E) + file I/O

## 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

## 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*
