Module: Plushie::Tree

Defined in:
lib/plushie/tree.rb

Overview

Utilities for working with UI trees.

Provides normalization, search, and diffing for Node trees. The diff algorithm produces patch operations per the wire protocol spec (replace_node, update_props, insert_child, remove_child).

See Also:

  • "Patch"

Class Method Summary collapse

Class Method Details

.diff(old_tree, new_tree) ⇒ Array<Hash>

Diff two normalized trees, producing an array of patch operations.

Each op is a Hash with string keys matching the wire protocol: { "op" => "replace_node", "path" => [...], "node" => {...} } { "op" => "update_props", "path" => [...], "props" => {...} } { "op" => "insert_child", "path" => [...], "index" => n, "node" => {...} } { "op" => "remove_child", "path" => [...], "index" => n }



107
108
109
110
111
112
113
114
# File 'lib/plushie/tree.rb', line 107

def self.diff(old_tree, new_tree)
  return [] if old_tree.nil? && new_tree.nil?
  return [{"op" => "replace_node", "path" => [], "node" => node_to_wire(new_tree)}] if old_tree.nil?
  return [{"op" => "remove_child", "path" => [], "index" => 0}] if new_tree.nil?
  return [{"op" => "replace_node", "path" => [], "node" => node_to_wire(new_tree)}] if old_tree.id != new_tree.id

  diff_node(old_tree, new_tree, [])
end

.exists?(tree, id) ⇒ Boolean

Check if a node with the given ID exists.



39
40
41
# File 'lib/plushie/tree.rb', line 39

def self.exists?(tree, id)
  !find(tree, id).nil?
end

.find(tree, id) ⇒ Node?

Find a node by ID (depth-first).



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/plushie/tree.rb', line 21

def self.find(tree, id)
  return nil if tree.nil?
  trees = tree.is_a?(Array) ? tree : [tree]

  trees.each do |node|
    return node if node.id == id
    found = find(node.children, id)
    return found if found
  end

  nil
end

.find_all(tree) {|Node| ... } ⇒ Array<Node>

Find all nodes matching a predicate (depth-first).

Yields:

  • (Node)

    predicate block



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/plushie/tree.rb', line 64

def self.find_all(tree, &predicate)
  result = []
  trees = tree.is_a?(Array) ? tree : [tree]

  trees.each do |node|
    result << node if predicate.call(node)
    result.concat(find_all(node.children, &predicate))
  end

  result
end

.ids(tree) ⇒ Array<String>

Return all node IDs in depth-first order.



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/plushie/tree.rb', line 47

def self.ids(tree)
  result = []
  trees = tree.is_a?(Array) ? tree : [tree]

  trees.each do |node|
    result << node.id
    result.concat(ids(node.children))
  end

  result
end

.node_to_wire(node) ⇒ Hash

Convert a Node to a plain wire-ready Hash (recursive).



120
121
122
123
124
125
126
127
# File 'lib/plushie/tree.rb', line 120

def self.node_to_wire(node)
  {
    "id" => node.id,
    "type" => node.type,
    "props" => encode_props(node.props),
    "children" => node.children.map { |c| node_to_wire(c) }
  }
end

.normalize(tree) ⇒ Array<Node>

Normalize a tree for wire transport. Converts symbol prop values to strings via Encode, resolves scoped IDs, and validates tree structure.



86
87
88
89
90
# File 'lib/plushie/tree.rb', line 86

def self.normalize(tree)
  return [Node.new(id: "root", type: "container")] if tree.nil?
  trees = tree.is_a?(Array) ? tree : [tree]
  trees.compact.map { |node| normalize_node(node, "") }
end