# frozen-string-literal: true # Copyright (C) 2020 Thomas Baron # # This file is part of term_utils. # # term_utils is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # term_utils is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with term_utils. If not, see . module TermUtils # Represents a general-purpose tree node that holds a key-value pair. class PropertyTreeNode # @return [PropertyTreeNode] attr_accessor :parent_node # @return [Array] attr_accessor :child_nodes # @return [Object] attr_accessor :key # @return [Object] attr_accessor :value # Creates a new PropertyTreeNode. # @param opts [Hash] # option opts [Object] :key # option opts [Object] :value def initialize(opts = {}, &block) @parent_node = nil @child_nodes = nil @key = opts.fetch(:key, nil) @value = opts.fetch(:value, nil) block.call(self) if block end # For dup method. def initialize_dup(other) @parent_node = nil if other.child_nodes @child_nodes = [] other.child_nodes.each do |n| new_node = n.dup new_node.parent_node = self @child_nodes << new_node end end super end # Tests whether this one is the head of the tree (i.e. has no parent). # @return [Boolean] def head? @parent_node == nil end # Tests whether this one is a leaf of the tree (i.e. has no child). # @return [Boolean] def leaf? @child_nodes == nil end # Returns the child node identified by a given key. # @param key [Object] # @return [PropertyTreeNode, nil] def child_node(key) if @child_nodes @child_nodes.find { |n| n.key == key } end end # Creates a new node and adds it as a child. # @param opts [Hash] # @option opts [Object] :key # @option opts [Object] :value # @return [PropertyTreeNode] def define_node(opts = {}, &block) new_node = TermUtils::PropertyTreeNode.new(opts) new_node.parent_node = self @child_nodes = [] unless @child_nodes @child_nodes << new_node block.call(new_node) if block new_node end # Builds the path of keys. # @return [Array] def path p = @parent_node ? @parent_node.path : [] p << @key if @key p end # Iterates over every node. # @param opts [Hash] # @option opts [Array] :path # @option opts [Boolean] :leaf_only # @return [nil] def each_node(opts = {}, &block) rpath = nil if opts.key? :path rpath = opts[:path].dup end dive = true if @key hide = false if rpath if rpath.shift == @key unless rpath.empty? hide = true end else dive = false hide = true end end unless hide || (opts[:leaf_only] && @child_nodes) if opts.key? :block opts[:block].call(self) elsif block block.call(self) end end end # if @key if dive && @child_nodes ropts = opts.dup if rpath if rpath.empty? ropts.delete :path else ropts[:path] = rpath end end if block ropts[:block] = block end @child_nodes.each do |n| n.each_node(ropts) end end nil end # Collects nodes. # @param opts [Hash] # @option opts [Array] :path # @option opts [Boolean] :leaf_only # @return [Array] def collect_nodes(opts = {}) nodes = [] each_node(opts) do |n| nodes << n end nodes end # Collects node paths. # @param opts [Hash] # @option opts [Array] :path # @option opts [Boolean] :leaf_only # @return [Array>] def collect_paths(opts = {}) paths = [] each_node(opts) do |n| paths << n.path end paths end # Collect node values. # @param opts [Hash] # @option opts [Array] :path # @option opts [Boolean] :leaf_only # @return [Array] def collect_values(opts = {}) vals = [] each_node(opts) do |n| vals << n.value if n.value end vals end # Finds the node identified by a given path of keys. # @param path [Array] # @return [PropertyTreeNode] def find_node(path) catch :found do each_node(path: path) do |n| throw :found, n end nil end end # Tests whether the node identified by a given path of keys exists. # @param path [Array] # @return [Boolean] def node_exists?(path) find_node(path) != nil end # Evaluates the total number of nodes in the tree represented by this one. # @param path [Array] # @return [Integer] def eval_child_count(path) node = find_node(path) if node node.child_nodes ? node.child_nodes.length : 0 end end # Finds the node identified by a given path of keys and returns its value. # @param path [Array] # @return [Object] def find_node_value(path) node = find_node(path) if node node.value end end end end