lib/sycamore/tree.rb in sycamore-0.1.0 vs lib/sycamore/tree.rb in sycamore-0.2.0

- old
+ new

@@ -16,14 +16,16 @@ ######################################################################## # @group CQS reflection ######################################################################## # the names of all command methods, which add elements to a Tree - ADDITIVE_COMMAND_METHODS = %i[add << replace add_node_with_empty_child] << :[]= + ADDITIVE_COMMAND_METHODS = %i[add << replace add_node_with_empty_child + clear_child_of_node] << :[]= # the names of all command methods, which delete elements from a Tree - DESTRUCTIVE_COMMAND_METHODS = %i[delete >> clear compact replace] << :[]= + DESTRUCTIVE_COMMAND_METHODS = %i[delete >> clear compact replace + clear_child_of_node] << :[]= # the names of all additive command methods, which only add elements from a Tree PURE_ADDITIVE_COMMAND_METHODS = ADDITIVE_COMMAND_METHODS - DESTRUCTIVE_COMMAND_METHODS # the names of all destructive command methods, which only delete elements from a Tree @@ -42,11 +44,11 @@ eql? matches? === ==] # the names of all methods, which side-effect-freeze return only a value QUERY_METHODS = PREDICATE_METHODS + %i[new_child dup hash to_native_object to_h to_s inspect - node nodes keys child_of child_at dig fetch + node node! nodes keys child_of child_at dig fetch search size total_size tsize height each each_path paths each_node each_key each_pair] << :[] %i[COMMAND_METHODS QUERY_METHODS PREDICATE_METHODS ADDITIVE_COMMAND_METHODS DESTRUCTIVE_COMMAND_METHODS @@ -221,16 +223,10 @@ self end ## - # Adds a node with an empty child to this tree. - # - # @return +self+ as a proper command method - # - # @raise [InvalidNode] - # # @api private # def add_node_with_empty_child(node) raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable @@ -239,10 +235,21 @@ end self end + ## + # @api private + # + def clear_child_of_node(node) + raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable + + @data[node] = Nothing + + self + end + private def add_child(node, children) return add_node(node) if Nothing.like?(children) add_node_with_empty_child(node) @data[node] << children @@ -287,11 +294,10 @@ # tree.delete "d" => {foo: :bar} # tree.to_h # => {"d" => {foo: :baz}} # tree.delete "d" => {foo: :baz} # tree.to_h # => {} # - # @todo differentiate a greedy and a non-greedy variant # @todo support Paths # def delete(nodes_or_tree) case when Tree.like?(nodes_or_tree) then delete_tree(nodes_or_tree) @@ -372,10 +378,13 @@ # reference a deeper node with a path of nodes. # # Note that even if you assign a {Sycamore::Tree} directly the given tree # will not become part of this tree by reference. # + # An exception is the assignment of the {Nothing} tree: it will delete the + # child tree at the given path entirely. + # # @overload []=(*path, node) # Replaces the contents of the child at the given path with a single node. # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object # @param node [Object] # @@ -404,16 +413,27 @@ # tree.to_h # => {:foo => :baz, 1 => {2 => {3 => 4}}} # tree[1] = tree[:foo] # tree.to_h # => {:foo => :baz, 1 => :baz} # tree[:foo] << :bar # tree.to_h # => {:foo => [:baz, :bar], 1 => :baz} + # tree[:foo] = Sycamore::Nothing + # tree.to_h # => {:foo => nil, 1 => :baz} # def []=(*args) path, nodes_or_tree = args[0..-2], args[-1] raise ArgumentError, 'wrong number of arguments (given 1, expected 2)' if path.empty? - child_at(*path).replace(nodes_or_tree) + if nodes_or_tree.equal? Sycamore::Nothing + if path.size == 1 + clear_child_of_node(path.first) + else + path, node = path[0..-2], path[-1] + child_at(*path).clear_child_of_node(node) + end + else + child_at(*path).replace(nodes_or_tree) + end end ## # Deletes all nodes and their children. # @@ -444,11 +464,11 @@ # tree.to_h # => {foo: :bar} # def compact @data.each do |node, child| case when child.nothing? then next - when child.empty? then @data[node] = Nothing + when child.empty? then clear_child_of_node(node) else child.compact end end self @@ -487,18 +507,41 @@ # tree = Tree[foo: 1, bar: [2,3]] # tree[:foo].node # => 1 # tree[:baz].node # => nil # tree[:bar].node # => raise Sycamore::NonUniqueNodeSet, "multiple nodes present: [2, 3]" # + # @see Tree#node! + # def node nodes = self.nodes raise NonUniqueNodeSet, "multiple nodes present: #{nodes}" if nodes.size > 1 nodes.first end ## + # The only node of this tree or an exception, if none or more {#nodes nodes} present. + # + # @return [Object] the single present node + # + # @raise [EmptyNodeSet] if no nodes present + # @raise [NonUniqueNodeSet] if more than one node present + # + # @example + # tree = Tree[foo: 1, bar: [2,3]] + # tree[:foo].node! # => 1 + # tree[:baz].node! # => raise Sycamore::EmptyNodeSet, "no node present" + # tree[:bar].node! # => raise Sycamore::NonUniqueNodeSet, "multiple nodes present: [2, 3]" + # + # @see Tree#node + # + def node! + raise EmptyNodeSet, 'no node present' if empty? + node + end + + ## # The child tree of a node. # # When a child to the given node is not a present, an {Absence} object # representing the missing tree is returned. # @@ -781,9 +824,35 @@ when elements.is_a?(Enumerable) elements.all? { |element| include_node? element } else include_node? elements end + end + + ## + # Searches the tree for one or multiple nodes or a complete tree. + # + # @param nodes_or_tree [Object, Array, Tree, Hash] + # @return [Array<Path>] + # + # @example + # tree = Tree[ "a" => [:foo, 100], "b" => { foo: [:bar, :baz] } ] + # tree.search :bar # => [Sycamore::Path["b", :foo]] + # tree.search :foo # => [Sycamore::Path["a"], Sycamore::Path["b"]] + # tree.search [:bar, :baz] # => [Sycamore::Path["b", :foo]] + # tree.search foo: :bar # => [Sycamore::Path["b"]] + # tree.search 42 # => [] + # + def search(nodes_or_tree) + _search(nodes_or_tree) + end + + protected def _search(query, current_path: Path::ROOT, results: []) + results << current_path if include?(query) + each do |node, child| + child._search(query, current_path: current_path/node, results: results) + end + results end ## # The number of {#nodes nodes} in this tree. #