Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rabal/tree.rb
Views: 11766
#1# Rabal Tree,rb2# A basic Tree structure3#45class Tree67include Enumerable89# The name of this Tree10attr_accessor :name1112# The parent of this node. If this is nil then this Tree is a13# root.14attr_accessor :parent1516# The children of this node. If this Hash is empty, then this17# Tree is a leaf.18attr_accessor :children1920# An abstract data point that can be utilized by child classes21# for whatever they like. If this is non-nil and responds to22# method calls it will be searched as part of the23# 'method_missing' protocol.24attr_accessor :parameters2526#27# Create a new Tree with the given object.to_s as its +name+.28#2930def initialize(name)31@name = name.to_s32@parent = nil33@children = {}34@parameters = nil35end3637#38# Return true if this Tree has no children.39#40def is_leaf?41@children.empty?42end4344#45# Return true if this Tree has no parent.46#47def is_root?48@parent.nil?49end5051#52# Return the root node of the tree53#54def root55return self if is_root?56return @parent.root57end5859#60# Return the distance from the root61#62def depth63return 0 if is_root?64return (1 + @parent.depth)65end6667#68# Attach the given tree at the indicated path. The path given69# is always assumed to be from the root of the Tree being70# attached to.71#72# The path is given as a string separated by '/'. Each portion73# of the string is matched against the name of the particular74# tree.75#76# Given :77#78# a --- b --- c79# \80# d - e --- f81# \82# g - h83#84# * the path +a/b/c+ will match the path to Tree +c+85# * the path +d/e/g+ will _not_ match anything as the path must start at +a+ here86# * the path +a/d/e+ will _not_ match anytthin as +e+ is not a child of +d+87# * the path +a/d/e/g+ will match node +g+88#89# Leading and trailing '/' on the path are not necessary and removed.90#91def add_at_path(path,subtree)92parent_tree = tree_at_path(path)93parent_tree << subtree94return self95end9697#98# Return the Tree that resides at the given path99#100def tree_at_path(path_str)101path_str = path_str.chomp("/").reverse.chomp("/").reverse102path = path_str.split("/")103104# strip of the redundant first match if it is the same as105# the current node106find_subtree(path)107end108109#110# Add the given object to the Tree as a child of this node. If111# the given object is not a Tree then wrap it with a Tree.112#113def <<(subtree)114# this should not generally be the case, but wrap things115# up to be nice.116if not subtree.kind_of?(Tree) then117subtree = Tree.new(subtree)118end119120subtree.parent = self121122# FIXME: technically this should no longer be called 'post_add'123# but maybe 'add_hook'124subtree.post_add125126# Don't overwrite any existing children with the same name,127# just put the new subtree's children into the children of128# the already existing subtree.129130if children.has_key?(subtree.name) then131subtree.children.each_pair do |subtree_child_name,subtree_child_tree|132children[subtree.name] << subtree_child_tree133end134else135children[subtree.name] = subtree136end137138return self139end140141alias :add :<<142143#144# Allow for Enumerable to be included. This just wraps walk.145#146def each147self.walk(self,lambda { |tree| yield tree })148end149150#151# Count how many items are in the tree152#153def size154inject(0) { |count,n| count + 1 }155end156157#158# Walk the tree in a depth first manner, visiting the Tree159# first, then its children160#161def walk(tree,method)162method.call(tree)163tree.children.each_pair do |name,child|164walk(child,method)165end166#for depth first167#method.call(tree)168end169170#171# Allow for a method call to cascade up the tree looking for a172# Tree that responds to the call.173#174def method_missing(method_id,*params,&block)175if not parameters.nil? and parameters.respond_to?(method_id, true) then176return parameters.send(method_id, *params, &block)177elsif not is_root? then178@parent.send method_id, *params, &block179else180raise NoMethodError, "undefined method `#{method_id}' for #{name}:Tree"181end182end183184#185# allow for a hook so newly added Tree objects may do custom186# processing after being added to the tree, but before the tree187# is walked or processed188#189def post_add190end191#192# find the current path of the tree from root to here, return it193# as a '/' separates string.194#195def current_path196#197# WMAP: removed the asterixs and modified the first return198# to just return a '/'199#200return "/" if is_root?201return ([name,parent.current_path]).flatten.reverse.join("/")202end203204#205# Given the initial path to match as an Array find the Tree that206# matches that path.207#208def find_subtree(path)209if path.empty? then210return self211else212possible_child = path.shift213if children.has_key?(possible_child) then214children[possible_child].find_subtree(path)215else216raise PathNotFoundError, "`#{possible_child}' does not match anything at the current path in the Tree (#{current_path})"217end218end219end220221#222# Return true of false if the given subtree exists or not223#224def has_subtree?(path)225begin226find_subtree(path)227return true228rescue PathNotFoundError229return false230end231end232233#234# Allow for numerable to be included. This just wraps walk235#236def each237self.walk(self,lambda { |tree| yield tree })238end239240def each_datum241self.walk(self,lambda { |tree| yield tree.data})242end243end244245246