yog_io/tgf
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:
- Node section: Each line is
node_id node_label - Separator: A single
#character on its own line - Edge section: Each line is
source_id target_id [edge_label]
Example
import yog/model.{Directed}
import yog_io/tgf
// Create a simple graph
let graph =
model.new(Directed)
|> model.add_node(1, "Alice")
|> model.add_node(2, "Bob")
let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: "follows")
// Serialize to TGF
let tgf_string = tgf.serialize(graph)
// Write to file
let assert Ok(Nil) = tgf.write("graph.tgf", graph)
Output Format
1 Alice
2 Bob
#
1 2 follows
Characteristics
- Human-readable: Simple text format, easy to understand
- Compact: Minimal syntax overhead
- No metadata: Does not preserve graph type (directed/undirected)
- Line-oriented: One element per line
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. This provides a lenient parsing mode that accepts minimal TGF files.
-
Empty labels: Nodes without labels default to using their ID as the label string. Edges without labels receive an empty string
""which is passed to the edge parser. -
Whitespace handling: Multiple consecutive spaces in labels are collapsed to single spaces. Leading and trailing whitespace on lines is trimmed.
-
Malformed lines: Lines that cannot be parsed are skipped and collected as warnings in the
TgfResult, rather than causing the entire parse to fail.
References
Types
Errors that can occur during TGF parsing.
pub type TgfError {
EmptyInput
InvalidNodeLine(line: Int, content: String)
InvalidEdgeLine(line: Int, content: String)
InvalidNodeId(line: Int, value: String)
InvalidEdgeEndpoint(line: Int, value: String)
DuplicateNodeId(line: Int, id: Int)
ReadError(path: String, error: String)
WriteError(path: String, error: String)
}
Constructors
-
EmptyInputEmpty input string
-
InvalidNodeLine(line: Int, content: String)Invalid node line format
-
InvalidEdgeLine(line: Int, content: String)Invalid edge line format
-
InvalidNodeId(line: Int, value: String)Invalid node ID (not an integer)
-
InvalidEdgeEndpoint(line: Int, value: String)Invalid edge endpoint (not an integer)
-
DuplicateNodeId(line: Int, id: Int)Duplicate node ID encountered
-
ReadError(path: String, error: String)File read error
-
WriteError(path: String, error: String)File write error
Options for TGF serialization.
pub type TgfOptions(n, e) {
TgfOptions(
node_label: fn(n) -> String,
edge_label: fn(e) -> option.Option(String),
)
}
Constructors
-
TgfOptions( node_label: fn(n) -> String, edge_label: fn(e) -> option.Option(String), )Arguments
- node_label
-
Function to convert node data to a label string
- edge_label
-
Function to convert edge data to an optional label string Returns None for no label (just source and target)
Result type for TGF parsing.
pub type TgfResult(n, e) {
TgfResult(
graph: model.Graph(n, e),
warnings: List(#(Int, String)),
)
}
Constructors
-
TgfResult( graph: model.Graph(n, e), warnings: List(#(Int, String)), )Arguments
- warnings
-
Lines that couldn’t be parsed (with line numbers)
Values
pub fn default_options() -> TgfOptions(String, String)
Default TGF serialization options.
Default configuration:
- Node labels: Uses the node data’s string representation
- Edge labels: None (edges are just
source target)
pub fn options_with(
node_label node_label: fn(n) -> String,
edge_label edge_label: fn(e) -> option.Option(String),
) -> TgfOptions(n, e)
Creates TGF options with custom node and edge label functions.
Example
let options = tgf.options_with(
node_label: fn(person) { person.name },
edge_label: fn(weight) { Some(int.to_string(weight)) },
)
pub fn parse(
input: String,
gtype: model.GraphType,
) -> Result(TgfResult(String, String), TgfError)
Parses a TGF string into a graph with String labels.
Convenience function for the common case where both node and edge data are just Strings.
Example
let tgf_string = "1 Alice\n2 Bob\n#\n1 2 follows"
case tgf.parse(tgf_string, Directed) {
Ok(result) -> {
// result.graph is Graph(String, String)
process_graph(result.graph)
}
Error(e) -> handle_error(e)
}
pub fn parse_with(
input: String,
graph_type gtype: model.GraphType,
node_parser node_parser: fn(Int, String) -> n,
edge_parser edge_parser: fn(String) -> e,
) -> Result(TgfResult(n, e), TgfError)
Parses a TGF string into a graph with custom parsers.
The graph type (directed/undirected) must be specified since TGF doesn’t encode this information.
Example
let tgf_string = "1 Alice\n2 Bob\n#\n1 2 follows"
let result = tgf.parse_with(
tgf_string,
graph_type: Directed,
node_parser: fn(id, label) { label },
edge_parser: fn(label) { label },
)
case result {
Ok(tgf.TgfResult(graph, warnings)) -> {
// Use the graph
process_graph(graph)
}
Error(e) -> handle_error(e)
}
pub fn read(
path: String,
gtype: model.GraphType,
) -> Result(TgfResult(String, String), TgfError)
Reads a graph from a TGF file.
Convenience function that reads node and edge data as strings.
Example
let result = tgf.read("graph.tgf", Directed)
case result {
Ok(tgf.TgfResult(graph, warnings)) -> {
// Use the graph
process_graph(graph)
}
Error(e) -> handle_error(e)
}
pub fn read_with(
path: String,
graph_type gtype: model.GraphType,
node_parser node_parser: fn(Int, String) -> n,
edge_parser edge_parser: fn(String) -> e,
) -> Result(TgfResult(n, e), TgfError)
Reads a graph from a TGF file with custom parsers.
Example
let result = tgf.read_with(
"graph.tgf",
graph_type: Directed,
node_parser: fn(id, label) { Person(id, label) },
edge_parser: fn(label) { String.to_int(label) },
)
pub fn serialize(graph: model.Graph(String, String)) -> String
Serializes a graph to TGF format.
Convenience function for graphs with String node and edge data.
Example
let tgf_string = tgf.serialize(graph)
pub fn serialize_with(
options: TgfOptions(n, e),
graph: model.Graph(n, e),
) -> String
Serializes a graph to TGF format with custom label functions.
This function allows you to control how node and edge data are converted to TGF labels.
Time Complexity: O(V + E)
Example
import yog/model.{Directed}
import yog_io/tgf
type Person {
Person(name: String, age: Int)
}
let graph =
model.new(Directed)
|> model.add_node(1, Person("Alice", 30))
|> model.add_node(2, Person("Bob", 25))
|> model.add_edge(from: 1, to: 2, with: "follows")
let options = tgf.options_with(
node_label: fn(p) { p.name },
edge_label: fn(label) { Some(label) },
)
let tgf_string = tgf.serialize_with(options, graph)
pub fn to_string(graph: model.Graph(String, String)) -> String
Converts a graph to a TGF string.
Alias for serialize for consistency with other modules.
pub fn write(
path: String,
graph: model.Graph(String, String),
) -> Result(Nil, simplifile.FileError)
Writes a graph to a TGF file.
Example
let assert Ok(Nil) = tgf.write("graph.tgf", graph)
pub fn write_with(
path: String,
options: TgfOptions(n, e),
graph: model.Graph(n, e),
) -> Result(Nil, simplifile.FileError)
Writes a graph to a TGF file with custom label functions.
Example
let options = tgf.options_with(
node_label: fn(p) { p.name },
edge_label: fn(w) { Some(int.to_string(w)) },
)
let assert Ok(Nil) = tgf.write_with("graph.tgf", options, graph)