lib/sgf/node.rb in SgfParser-2.0.0 vs lib/sgf/node.rb in SgfParser-3.0.0

- old
+ new

@@ -1,109 +1,165 @@ -module SGF +require 'observer' +# Your basic node. It holds information about itself, its parent, and its children. +class SGF::Node + include Enumerable, Observable - #Your basic node. It holds information about itself, its parent, and its children. - class Node + attr_accessor :children, :properties + attr_reader :parent, :depth - attr_accessor :parent, :children, :properties + # Creates a new node. You can pass in a hash. There are two special keys, parent and children. + # * parent: parent_node (nil by default) + # * children: [list, of, children] (empty array if nothing is passed) + # Anything else passed to the hash will become an SGF identity/property pair on the node. + def initialize children: [], parent: nil, **opts + # opts = { children: [], parent: nil } + # opts.merge! args + @depth = 0 + @children = [] + @properties = {} + @parent = nil + set_parent parent #opts.delete :parent + add_children children #opts.delete :children + add_properties opts + end - # Creates a new node. Arguments which can be passed in are: - # :parent => parent_node (nil by default) - # :children => [list, of, children] (empty array if nothing is passed) - # :properties => {hash_of => properties} (empty hash if nothing is passed) - def initialize args={} - @parent = args[:parent] - @children = [] - add_children args[:children] if args[:children] - @properties = Hash.new - add_properties args[:properties] if args[:properties] + # Set the given node as a parent and self as one of that node's children + def parent= parent + if @parent + @parent.children.delete self + @parent.delete_observer self end - #Takes an arbitrary number of child nodes, adds them to the list of children, and make this node their parent. - def add_children *nodes - nodes.flatten! - raise "Non-node child given!" if nodes.any? { |node| node.class != Node } - nodes.each do |node| - node.parent = self - @children << node - end + case @parent = parent + when nil then set_depth 0 + else + @parent.children << self + @parent.add_observer self + set_depth @parent.depth + 1 end + end - #Takes a hash {identity => property} and adds those to the current node. - #If a property already exists, it will append to it. - def add_properties hash - hash.each do |identity, property| - @properties[identity] ||= property.class.new - @properties[identity].concat property - end - update_human_readable_methods - end + alias :set_parent :parent= - #Iterate through each child. Yields a child node, if one exists. - def each_child - @children.each { |child| yield child } - end + def remove_parent + set_parent nil + end - #Compare to another node. - def == other_node - @properties == other_node.properties - end + def depth= new_depth + @depth = new_depth + changed + notify_observers :depth_change, @depth + end - #Syntactic sugar for node.properties["XX"] - def [] identity - identity = identity.to_s - @properties[identity] + alias :set_depth :depth= + + # Takes an arbitrary number of child nodes, adds them to the list of children, + # and make this node their parent. + def add_children *nodes + nodes.flatten.each do |node| + node.set_parent self end + changed + notify_observers :new_children, nodes.flatten + end - def to_s - out = "#<#{self.class}:#{self.object_id}, " - out << (@parent ? "Has a parent, " : "Has no parent, ") - out << "#{@children.size} Children, " - out << "#{@properties.keys.size} Properties" - out << ">" + # Takes a hash {identity => property} and adds those to the current node. + # If a property already exists, it will append to it. + def add_properties hash + hash.each do |identity, property| + @properties[flexible identity] ||= property.class.new + @properties[flexible identity].concat property end + update_human_readable_methods + end - alias :inspect :to_s + def each &block + preorder self, &block + end - def to_str(indent = 0) - properties = [] - @properties.each do |identity, property| - properties << stringify_identity_and_property(identity, property) - end - whitespace = leading_whitespace(indent) - "#{whitespace};#{properties.join("\n#{whitespace}")}" + # Iterate through and yield each child. + def each_child + @children.each { |child| yield child } + end + + # Compare to another node. + def == other_node + @properties == other_node.properties + end + + # Syntactic sugar for node.properties["XX"] + def [] identity + @properties[flexible(identity)] + end + + def []= identity, new_value + @properties[flexible(identity)] = new_value + end + + def inspect + out = "#<#{self.class}:#{self.object_id}, " + out << (@parent ? "Has a parent, " : "Has no parent, ") + out << "#{@children.size} Children, " + out << "#{@properties.keys.size} Properties" + out << ">" + end + + def to_s(indent = 0) + properties = [] + @properties.each do |identity, property| + properties << stringify_identity_and_property(identity, property) end + whitespace = leading_whitespace(indent) + "#{whitespace};#{properties.join("\n#{whitespace}")}" + end - def stringify_identity_and_property(identity, property) - new_property = property.instance_of?(Array) ? property.join("][") : property - new_property = new_property.gsub("]", "\\]") if identity == "C" - "#{identity.to_s}[#{new_property}]" + # Observer pattern + def update(message, data) + case message + when :depth_change then set_depth(data + 1) end + end - private + private - def update_human_readable_methods - SGF::Node::PROPERTIES.each do |human_readable_method, sgf_identity| - next if defined? human_readable_method.to_sym - define_method(human_readable_method.to_sym) do - @properties[sgf_identity] ? @properties[sgf_identity] : raise(SGF::NoIdentityError) - end + def flexible id + id.to_s.upcase + end + + def update_human_readable_methods + SGF::Node::PROPERTIES.reject do + |method_name, sgf_identity| defined? method_name + end.each do |human_readable_method, sgf_identity| + define_method(human_readable_method.to_sym) do + @properties[sgf_identity] ? @properties[sgf_identity] : raise(SGF::NoIdentityError, "This node does not have #{sgf_identity} available") end end + end - def leading_whitespace(indent) - "#{" " * indent}" + def preorder node=self, &block + yield node + node.each_child do |child| + preorder child, &block end + end - def method_missing method_name, *args - property = method_name.to_s.upcase - if property[/(.*?)=$/] - @properties[$1] = args[0] - else - output = @properties[property] - super(method_name, args) if output.nil? - output - end + def leading_whitespace(indent) + ' ' * indent + end + + def method_missing method_name, *args + property = flexible(method_name) + if property[/(.*?)=$/] + @properties[$1] = args[0] + else + @properties.fetch(property, nil) || super(method_name, args) end + end + def stringify_identity_and_property(identity, property) + new_property = property.instance_of?(Array) ? property.join("][") : property + new_id = flexible identity + new_property = new_property.gsub("]", "\\]") if new_id == "C" + "#{new_id}[#{new_property}]" end -end \ No newline at end of file +end