Quillon.Path (Quillon v0.1.0)

Copy Markdown View Source

Tree traversal and manipulation functions for Quillon documents.

A path is a list of integers representing indices into children arrays:

# Path [] = root node
# Path [0] = first child
# Path [0, 1] = second child of first child

Examples

doc = Quillon.document([
  Quillon.paragraph("Hello"),
  Quillon.heading(1, "Title")
])

# Get node at path
{:ok, para} = Quillon.Path.get(doc, [0])

# Update node at path
{:ok, updated} = Quillon.Path.update(doc, [0], fn para ->
  put_elem(para, 1, Map.put(elem(para, 1), :custom, true))
end)

# Delete node at path
{:ok, smaller} = Quillon.Path.delete(doc, [1])

Summary

Types

A Quillon document node tuple

A path is a list of non-negative integers representing child indices

Functions

Delete the node at the given path.

Find the path to a node with the given id.

Get the node at the given path.

Get a node by its id.

Insert a node at the given path.

Move a node from one path to another.

Reorder children at the given path according to an id list.

Update the node at the given path using a function.

Update a node by its id using a function.

Types

doc_node()

@type doc_node() :: {atom(), map(), list()}

A Quillon document node tuple

path()

@type path() :: [non_neg_integer()]

A path is a list of non-negative integers representing child indices

Functions

delete(arg1, arg2)

@spec delete(doc_node(), path()) :: {:ok, doc_node()} | {:error, :invalid_path}

Delete the node at the given path.

Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the path doesn't exist.

Note: Cannot delete the root node (empty path).

Examples

iex> doc = {:document, %{}, [{:paragraph, %{}, []}, {:heading, %{level: 1}, []}]}
iex> {:ok, updated} = Quillon.Path.delete(doc, [0])
iex> length(elem(updated, 2))
1

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.delete(doc, [])
{:error, :invalid_path}

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.delete(doc, [5])
{:error, :invalid_path}

find_path(arg, id)

@spec find_path(doc_node(), any()) :: {:ok, path()} | {:error, :not_found}

Find the path to a node with the given id.

Searches depth-first for a node whose attrs contain the specified id. Returns {:ok, path} if found, {:error, :not_found} if no node has that id.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.find_path(doc, "p1")
{:ok, [0]}

iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.find_path(doc, "p2")
{:ok, [1]}

iex> doc = {:document, %{id: "doc"}, [{:paragraph, %{id: "p1"}, []}]}
iex> Quillon.Path.find_path(doc, "doc")
{:ok, []}

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.find_path(doc, "missing")
{:error, :not_found}

get(node, arg2)

@spec get(doc_node(), path()) :: {:ok, doc_node()} | {:error, :invalid_path}

Get the node at the given path.

Returns {:ok, node} if found, {:error, :invalid_path} if the path doesn't exist.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [])
{:ok, {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}}

iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [0])
{:ok, {:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}}

iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [0, 0])
{:ok, {:text, %{text: "Hi", marks: []}, []}}

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.get(doc, [5])
{:error, :invalid_path}

get_by_id(root, id)

@spec get_by_id(doc_node(), any()) :: {:ok, doc_node()} | {:error, :not_found}

Get a node by its id.

Returns {:ok, node} if found, {:error, :not_found} if no node has that id.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.get_by_id(doc, "p1")
{:ok, {:paragraph, %{id: "p1"}, []}}

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.get_by_id(doc, "missing")
{:error, :not_found}

insert(arg1, arg2, new_node)

@spec insert(doc_node(), path(), doc_node()) ::
  {:ok, doc_node()} | {:error, :invalid_path}

Insert a node at the given path.

The path specifies where to insert: the last element is the index position in the parent's children list. The node is inserted at that index, shifting existing nodes to the right.

Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the parent path doesn't exist.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> new_para = {:paragraph, %{id: "new"}, []}
iex> {:ok, updated} = Quillon.Path.insert(doc, [1], new_para)
iex> length(elem(updated, 2))
2

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> new_para = {:paragraph, %{}, []}
iex> {:ok, updated} = Quillon.Path.insert(doc, [0], new_para)
iex> {:ok, first} = Quillon.Path.get(updated, [0])
iex> elem(first, 1)
%{}

move(root, from_path, to_path)

@spec move(doc_node(), path(), path()) :: {:ok, doc_node()} | {:error, :invalid_path}

Move a node from one path to another.

The node at from_path is removed and inserted at to_path. The to_path is calculated after the removal.

Returns {:ok, updated_root} if successful, or an error tuple.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}, {:paragraph, %{id: "p3"}, []}]}
iex> {:ok, updated} = Quillon.Path.move(doc, [0], [2])
iex> {:ok, first} = Quillon.Path.get(updated, [0])
iex> Map.get(elem(first, 1), :id)
"p2"

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.move(doc, [5], [0])
{:error, :invalid_path}

reorder(root, path, id_list)

@spec reorder(doc_node(), path(), [any()]) ::
  {:ok, doc_node()} | {:error, :invalid_path}

Reorder children at the given path according to an id list.

The children of the node at path are reordered to match the order of ids in id_list. Children whose ids are not in the list are appended at the end in their original order.

Returns {:ok, updated_root} if successful, or an error tuple.

Examples

iex> para1 = {:paragraph, %{id: "p1"}, []}
iex> para2 = {:paragraph, %{id: "p2"}, []}
iex> para3 = {:paragraph, %{id: "p3"}, []}
iex> doc = {:document, %{}, [para1, para2, para3]}
iex> {:ok, updated} = Quillon.Path.reorder(doc, [], ["p3", "p1", "p2"])
iex> Enum.map(elem(updated, 2), fn {_, attrs, _} -> attrs.id end)
["p3", "p1", "p2"]

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.reorder(doc, [5], ["a"])
{:error, :invalid_path}

update(node, arg2, fun)

@spec update(doc_node(), path(), (doc_node() -> doc_node())) ::
  {:ok, doc_node()} | {:error, :invalid_path}

Update the node at the given path using a function.

Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the path doesn't exist.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> {:ok, updated} = Quillon.Path.update(doc, [0], fn {type, attrs, children} -> {type, Map.put(attrs, :custom, true), children} end)
iex> {:ok, para} = Quillon.Path.get(updated, [0])
iex> elem(para, 1)
%{custom: true}

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.update(doc, [5], fn n -> n end)
{:error, :invalid_path}

update_by_id(root, id, fun)

@spec update_by_id(doc_node(), any(), (doc_node() -> doc_node())) ::
  {:ok, doc_node()} | {:error, :not_found}

Update a node by its id using a function.

Returns {:ok, updated_root} if successful, {:error, :not_found} if no node has that id.

Examples

iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}]}
iex> {:ok, updated} = Quillon.Path.update_by_id(doc, "p1", fn {type, attrs, children} -> {type, Map.put(attrs, :custom, true), children} end)
iex> {:ok, para} = Quillon.Path.get_by_id(updated, "p1")
iex> Map.get(elem(para, 1), :custom)
true

iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.update_by_id(doc, "missing", fn n -> n end)
{:error, :not_found}