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