AtuinStand.Node (atuin_stand v0.2.0)

View Source

A node in an AtuinStand.Tree.

You can access the node's ID via the id property, and the tree it belongs to via the tree property.

Since Node structs only hold a reference to their containing tree, nodes might be invalidated if the tree is manipulated such that the node is removed. In this case, the Node functions will return {:error, :not_found}.

For a more detailed overview of the API, see AtuinStand.

Raising API

Every function that can return an error tuple has a raising version that raises an error instead of returning an error tuple, and returns the result instead of an ok tuple if successful.

Each error tuple maps to a specific exception:

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> AtuinStand.Node.create_child(root, "node1")
{:ok, %AtuinStand.Node{id: "node1", tree: tree}}
iex> AtuinStand.Node.create_child!(root, "node2")
%AtuinStand.Node{id: "node2", tree: tree}
iex> AtuinStand.Node.create_child!(root, "node1")
** (AtuinStand.Error.DuplicateNode) Node with id "node1" already exists

Summary

Functions

Returns a list of all ancestors of the given node, starting at the node's parent and ending at the root node (inclusive).

A raising version of ancestors/1.

Returns a list of all children of the given node.

A raising version of children/1.

Creates a new child node with the given ID.

Deletes the node from the tree.

Returns the depth of the given node.

A raising version of depth/1.

Returns a list of all descendants of the given node.

Returns the user-defined data associated with the node.

A raising version of get_data/1.

Moves the node after the given node.

A raising version of move_after/2.

Moves the node before the given node.

Moves the node to a new parent node.

Returns the parent of the given node.

A raising version of parent/1.

Moves the node to a new position amongst its siblings.

A raising version of reposition/2.

Sets the user-defined data associated with the node. Returns the node.

A raising version of set_data/2.

Returns a list of all siblings (other nodes with the same parent) of the given node.

A raising version of siblings/1.

Types

t()

@type t() :: %AtuinStand.Node{id: atom() | String.t(), tree: AtuinStand.Tree.t()}

Functions

ancestors(node)

@spec ancestors(node :: t()) :: {:ok, [t()]} | {:error, atom()}

Returns a list of all ancestors of the given node, starting at the node's parent and ending at the root node (inclusive).

Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(node1, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(node2, "node3")
iex> AtuinStand.Node.ancestors(node3)
{:ok, [node2, node1, root]}

ancestors!(node)

@spec ancestors!(node :: t()) :: [t()]

A raising version of ancestors/1.

children(node)

@spec children(node :: t()) :: {:ok, [t()]} | {:error, atom()}

Returns a list of all children of the given node.

Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(root, "node2")
iex> AtuinStand.Node.children(root)
{:ok, [node1, node2]}
iex> AtuinStand.Node.children(node1)
{:ok, []}

children!(node)

@spec children!(node :: t()) :: [t()]

A raising version of children/1.

create_child(parent_node, id)

@spec create_child(node :: t(), id :: String.t()) :: {:ok, t()} | {:error, atom()}

Creates a new child node with the given ID.

User-created nodes must have unique, string IDs. Returns {:error, :duplicate_id} if a node with the given ID already exists in the tree. Returns {:error, :not_found} if the parent node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> AtuinStand.Node.create_child(root, "node1")
{:ok, %AtuinStand.Node{id: "node1", tree: tree}}
iex> AtuinStand.Node.create_child(root, "node1")
{:error, :duplicate_id}

create_child!(parent_node, id)

@spec create_child!(node :: t(), id :: String.t()) :: t()

A raising version of create_child/2.

delete(node, strategy \\ :refuse)

@spec delete(node :: t(), strategy :: :refuse | :cascade | :reattach) ::
  {:ok, t()} | {:error, atom()}

Deletes the node from the tree.

Returns {:error, :invalid_operation} if the node is the root node. Returns {:error, :not_found} if the node is not found in the tree.

Provide a strategy to specify what to do with the node's children:

  • :refuse - return {:error, :has_children} if the node being deleted has children
  • :cascade - recursively delete the node and all of its children
  • :reattach - move the node's children to the node's parent before deleting it

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(node1, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(node2, "node3")
iex> AtuinStand.Node.delete(node1, :refuse)
{:error, :has_children}
iex> AtuinStand.Node.delete(node1, :reattach)
iex> AtuinStand.Node.descendants(root)
{:ok, [node2, node3]}
iex> AtuinStand.Node.delete(node2, :cascade)
iex> AtuinStand.Node.descendants(root)
{:ok, []}

delete!(node, strategy \\ :refuse)

@spec delete!(node :: t(), strategy :: :refuse | :cascade | :reattach) :: t()

A raising version of delete/2.

depth(node)

@spec depth(node :: t()) :: {:ok, non_neg_integer()} | {:error, atom()}

Returns the depth of the given node.

For any node, the depth is the number of edges on the path to the root node. The root node has a depth of 0, and every other node has a depth of 1 + its parent's depth.

Equivalent to length(AtuinStand.Node.ancestors(node)).

Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> AtuinStand.Node.depth(root)
{:ok, 0}
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> AtuinStand.Node.depth(node1)
{:ok, 1}
iex> {:ok, node2} = AtuinStand.Node.create_child(node1, "node2")
iex> AtuinStand.Node.depth(node2)
{:ok, 2}

depth!(node)

@spec depth!(node :: t()) :: non_neg_integer()

A raising version of depth/1.

descendants(node, order \\ :dfs)

@spec descendants(node :: t(), order :: :dfs | :bfs) ::
  {:ok, [t()]} | {:error, atom()}

Returns a list of all descendants of the given node.

Provide :dfs or :bfs as an optional argument to return the results in depth-first or breadth-first order, respectively. Defaults to :dfs.

Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(node1, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(node2, "node3")
iex> {:ok, node4} = AtuinStand.Node.create_child(root, "node4")
iex> AtuinStand.Node.descendants(node1, :dfs)
{:ok, [node2, node3]}
iex> AtuinStand.Node.descendants(root, :bfs)
{:ok, [node1, node4, node2, node3]}

descendants!(node, order \\ :dfs)

@spec descendants!(node :: t(), order :: :dfs | :bfs) :: [t()]

A raising version of descendants/2.

get_data(node)

@spec get_data(node :: t()) :: {:ok, map()} | {:error, atom()}

Returns the user-defined data associated with the node.

If the node is not found, returns {:error, :not_found}.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> AtuinStand.Node.set_data(node1, %{"name" => "Node 1"})
iex> AtuinStand.Node.get_data(node1)
{:ok, %{"name" => "Node 1"}}

get_data!(node)

@spec get_data!(node :: t()) :: map()

A raising version of get_data/1.

move_after(node, other)

@spec move_after(node :: t(), other :: t()) :: {:ok, t()} | {:error, atom()}

Moves the node after the given node.

Returns {:error, :invalid_operation} if the node is the root node or if the move would create a cycle in the tree. Returns {:error, :not_found} if either node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(root, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(root, "node3")
iex> AtuinStand.Node.move_after(node1, node3)
iex> AtuinStand.Node.children(root)
{:ok, [node2, node3, node1]}

move_after!(node, other)

@spec move_after!(node :: t(), other :: t()) :: t()

A raising version of move_after/2.

move_before(node, other)

@spec move_before(node :: t(), other :: t()) :: {:ok, t()} | {:error, atom()}

Moves the node before the given node.

Returns {:error, :invalid_operation} if the node is the root node or if the move would create a cycle in the tree. Returns {:error, :not_found} if either node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(root, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(root, "node3")
iex> AtuinStand.Node.move_before(node3, node1)
iex> AtuinStand.Node.children(root)
{:ok, [node3, node1, node2]}

move_before!(node, other)

@spec move_before!(node :: t(), other :: t()) :: t()

A raising version of move_before/2.

move_to(node, new_parent, index \\ nil)

@spec move_to(node :: t(), new_parent :: t(), index :: non_neg_integer() | nil) ::
  {:ok, t()} | {:error, atom()}

Moves the node to a new parent node.

Returns {:error, :invalid_operation} if the node is the root node or if the move would create a cycle in the tree. Returns {:error, :not_found} if the either node is not found in the tree.

Provide an optional index to specify the position of the node in the new parent's child list. The node will be inserted at the end if no index is provided.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(node1, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(node2, "node3")
iex> AtuinStand.Node.move_to(node1, node3)
{:error, :invalid_operation}
iex> AtuinStand.Node.move_to(node2, root)
iex> AtuinStand.Node.children(root)
{:ok, [node1, node2]}
iex> AtuinStand.Node.move_to(node3, root, 1)
iex> AtuinStand.Node.children(root)
{:ok, [node1, node3, node2]}
iex> AtuinStand.Node.move_to(node2, root, 1)
iex> AtuinStand.Node.children(root)
{:ok, [node1, node2, node3]}

move_to!(node, new_parent, index \\ nil)

@spec move_to!(node :: t(), new_parent :: t(), index :: non_neg_integer() | nil) ::
  t()

A raising version of move_to/3.

parent(node)

@spec parent(node :: t()) :: {:ok, t()} | {:error, atom()}

Returns the parent of the given node.

Returns {:error, :not_found} if the node is not found in the tree. Returns {:error, :invalid_operation} if the node is the root node.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> AtuinStand.Node.parent(root)
{:error, :invalid_operation}
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> AtuinStand.Node.parent(node1)
{:ok, %AtuinStand.Node{id: :root, tree: tree}}
iex> fake_node = %AtuinStand.Node{id: "fake", tree: tree}
iex> AtuinStand.Node.parent(fake_node)
{:error, :not_found}

parent!(node)

@spec parent!(node :: t()) :: t()

A raising version of parent/1.

reposition(node, index)

@spec reposition(node :: t(), index :: non_neg_integer()) ::
  {:ok, t()} | {:error, atom()}

Moves the node to a new position amongst its siblings.

Returns {:error, :invalid_operation} if the node is the root node. Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(root, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(root, "node3")
iex> AtuinStand.Node.reposition(node2, 0)
iex> AtuinStand.Node.children(root)
{:ok, [node2, node1, node3]}
iex> AtuinStand.Node.reposition(node2, 2)
iex> AtuinStand.Node.children(root)
{:ok, [node1, node3, node2]}

reposition!(node, index)

@spec reposition!(node :: t(), index :: non_neg_integer()) :: t()

A raising version of reposition/2.

set_data(node, data)

@spec set_data(node :: t(), data :: map()) :: {:ok, t()} | {:error, atom()}

Sets the user-defined data associated with the node. Returns the node.

The data must be a map, otherwise returns {:error, :invalid_data}. When the tree is serialized to JSON, the data is serialized as well, so any atom keys will be converted to strings.

If the node is not found, returns {:error, :not_found}.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node1} = AtuinStand.Tree.node(tree, "node1")
iex> AtuinStand.Node.set_data(node1, %{"name" => "Node 1"})
iex> AtuinStand.Node.get_data(node1)
{:ok, %{"name" => "Node 1"}}

set_data!(node, data)

@spec set_data!(node :: t(), data :: map()) :: t()

A raising version of set_data/2.

siblings(node)

@spec siblings(node :: t()) :: {:ok, [t()]} | {:error, atom()}

Returns a list of all siblings (other nodes with the same parent) of the given node.

Returns {:error, :not_found} if the node is not found in the tree.

Examples

iex> tree = AtuinStand.Tree.new()
iex> root = AtuinStand.Tree.root(tree)
iex> {:ok, node1} = AtuinStand.Node.create_child(root, "node1")
iex> {:ok, node2} = AtuinStand.Node.create_child(root, "node2")
iex> {:ok, node3} = AtuinStand.Node.create_child(root, "node3")
iex> AtuinStand.Node.siblings(node1)
{:ok, [node2, node3]}

siblings!(node)

@spec siblings!(node :: t()) :: [t()]

A raising version of siblings/1.