SgfParser 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
23
+
24
+ ## Rubymine
25
+ .idea/*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Aldric Giacomoni
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ SGF: all formats (but untested with FF < 4)
2
+ Ruby: >1.8.7 (may work with 1.8.6)
3
+
4
+ Example:
5
+ require 'sgf_parser'
6
+ tree = SgfParser::Tree.new :filename => File
7
+ tree = SgfParser::Tree.new :sgf_string => String
8
+
9
+ All trees begin with an empty node ( @root) which allows a simple support of multiple gametrees.
10
+ Most games will just care about, say,
11
+ tree.root.children[0] which is the first node of the first gametree.
12
+
13
+ For any node, one can summon the properties as such:
14
+ node.properties # => returns a hash of the properties.
15
+ A single property can be called, like the comments, for instance, like so:
16
+ node.C # => returns the comments for this node.
17
+
18
+ The library currently uses method_missing to painlessly return the data. I must admit that this is both clever coding and laziness on my part.
19
+
20
+ The 'SGF Indenter', the purpose of which is to make the actual SGF file more
21
+ human readable, is working.
22
+
23
+ ___
24
+
25
+ TODO
26
+ ? Create a "Game" class, and if a whole set of () exists, then I have a game?
27
+ That way maybe I can easily go to multiple games stored in a single SGF file?
28
+ Mostly syntactic sugar, but may be worth implementing.
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "SgfParser"
8
+ gem.summary = %Q{A library for working with SGF files.}
9
+ gem.description = %Q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
10
+ gem.email = "aldric@trevoke.net"
11
+ gem.homepage = "http://github.com/Trevoke/SGFParser"
12
+ gem.authors = ["Aldric Giacomoni"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ # In case I ever want to go back to cucumber.
36
+ #begin
37
+ # require 'cucumber/rake/task'
38
+ # Cucumber::Rake::Task.new(:features)
39
+ #
40
+ # task :features => :check_dependencies
41
+ #rescue LoadError
42
+ # task :features do
43
+ # abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
44
+ # end
45
+ #end
46
+
47
+ #task :default => [:spec, :features]
48
+
49
+ task :default => [:spec]
50
+
51
+ require 'rake/rdoctask'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "SgfParser #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{SgfParser}
8
+ s.version = "0.8.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aldric Giacomoni"]
12
+ s.date = %q{2010-01-05}
13
+ s.description = %q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
14
+ s.email = %q{aldric@trevoke.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "SgfParser.gemspec",
26
+ "VERSION",
27
+ "lib/sgf/parser/node.rb",
28
+ "lib/sgf/parser/properties.rb",
29
+ "lib/sgf/parser/tree.rb",
30
+ "lib/sgf/parser/tree_parse.rb",
31
+ "lib/sgf/sgfindent.rb",
32
+ "lib/sgf_parser.rb",
33
+ "sample_sgf/ff4_ex.sgf",
34
+ "sample_sgf/ff4_ex_saved.sgf",
35
+ "sample_sgf/redrose-tartrate.sgf",
36
+ "sample_usage/parsing_files.rb",
37
+ "spec/node_spec.rb",
38
+ "spec/spec.opts",
39
+ "spec/spec_helper.rb",
40
+ "spec/tree_spec.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/Trevoke/SGFParser}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{A library for working with SGF files.}
47
+ s.test_files = [
48
+ "spec/node_spec.rb",
49
+ "spec/spec_helper.rb",
50
+ "spec/tree_spec.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
59
+ else
60
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
64
+ end
65
+ end
66
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.0
@@ -0,0 +1,67 @@
1
+ module SgfParser
2
+
3
+ # Each part of the SGF Tree is a node. This holds links to the parent node,
4
+ # to the next node(s) in the tree, and of course, the properties of said node.
5
+ # Accessors : node.parent, node.children, node.properties
6
+ class Node
7
+
8
+ attr_accessor :parent, :children, :properties
9
+
10
+ # Creates a new node. Options which can be passed in are:
11
+ # :parent => parent_node (nil by default)
12
+ # : children => [list, of, children] (empty array by default)
13
+ # :properties => {hash_of => properties} (empty hash by default)
14
+ def initialize args={}
15
+ @parent = args[:parent]
16
+ @children = []
17
+ add_children args[:children] if !args[:children].nil?
18
+ @properties = Hash.new
19
+ @properties.merge! args[:properties] if !args[:properties].nil?
20
+ end
21
+
22
+ # Adds one child or several children. Can be passed in as a comma-separated
23
+ # list or an array of node children. Will raise an error if one of the
24
+ # arguments is not of class Node.
25
+ def add_children *nodes
26
+ raise "Non-node child given!" if nodes.find { |node| node.class != Node }
27
+ @children.concat nodes.flatten
28
+ end
29
+
30
+ # Adds one or more properties to the node.
31
+ # Argument: a hash {'property' => 'value'}
32
+ def add_properties hash
33
+ hash.each do |key, value|
34
+ @properties[key] ||= ""
35
+ @properties[key].concat value
36
+ end
37
+ end
38
+
39
+ # Iterates over each child of the given node
40
+ # node.each_child { |child| puts child.properties }
41
+ def each_child
42
+ @children.each { |child| yield child }
43
+ end
44
+
45
+ # Compares one node's properties to another node's properties
46
+ def == other_node
47
+ @properties == other_node.properties
48
+ end
49
+
50
+ # Making comments easier to access.
51
+ def comments
52
+ @properties["C"]
53
+ end
54
+
55
+ alias :comment :comments
56
+
57
+ private
58
+
59
+ def method_missing method_name, *args
60
+ output = @properties[method_name.to_s.upcase]
61
+ super(method_name, args) if output.nil?
62
+ output
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,97 @@
1
+ # A parser for SGF Files. Main usage: SGF::Tree.new :filename => file_name
2
+ module SgfParser
3
+
4
+ # http://www.red-bean.com/sgf/proplist.html
5
+
6
+ # Here we define SGF::Properties, so we can figure out what each property
7
+ # is and does.
8
+
9
+ property_string = %Q{AB Add Black setup list of stone
10
+ AE Add Empty setup list of point
11
+ AN Annotation game-info simpletext
12
+ AP Application root composed simpletext ':' simpletext
13
+ AR Arrow - list of composed point ':' point
14
+ AS Who adds stones - (LOA) simpletext
15
+ AW Add White setup list of stone
16
+ B Black move move
17
+ BL Black time left move real
18
+ BM Bad move move double
19
+ BR Black rank game-info simpletext
20
+ BT Black team game-info simpletext
21
+ C Comment - text
22
+ CA Charset root simpletext
23
+ CP Copyright game-info simpletext
24
+ CR Circle - list of point
25
+ DD Dim points - (inherit) elist of point
26
+ DM Even position - double
27
+ DO Doubtful move none
28
+ DT Date game-info simpletext
29
+ EV Event game-info simpletext
30
+ FF Fileformat root number (range: 1-4)
31
+ FG Figure - none | composed number ":" simpletext
32
+ GB Good for Black - double
33
+ GC Game comment game-info text
34
+ GM Game root number (range: 1-5,7-16)
35
+ GN Game name game-info simpletext
36
+ GW Good for White - double
37
+ HA Handicap game-info (Go) number
38
+ HO Hotspot - double
39
+ IP Initial pos. game-info (LOA) simpletext
40
+ IT Interesting move none
41
+ IY Invert Y-axis game-info (LOA) simpletext
42
+ KM Komi game-info (Go) real
43
+ KO Ko move none
44
+ LB Label - list of composed point ':' simpletext
45
+ LN Line - list of composed point ':' point
46
+ MA Mark - list of point
47
+ MN set move number move number
48
+ N Nodename - simpletext
49
+ OB OtStones Black move number
50
+ ON Opening game-info simpletext
51
+ OT Overtime game-info simpletext
52
+ OW OtStones White move number
53
+ PB Player Black game-info simpletext
54
+ PC Place game-info simpletext
55
+ PL Player to play setup color
56
+ PM Print move mode - (inherit) number
57
+ PW Player White game-info simpletext
58
+ RE Result game-info simpletext
59
+ RO Round game-info simpletext
60
+ RU Rules game-info simpletext
61
+ SE Markup - (LOA) point
62
+ SL Selected - list of point
63
+ SO Source game-info simpletext
64
+ SQ Square - list of point
65
+ ST Style root number (range: 0-3)
66
+ SU Setup type game-info (LOA) simpletext
67
+ SZ Size root (number | composed number ':' number)
68
+ TB Territory Black - (Go) elist of point
69
+ TE Tesuji move double
70
+ TM Timelimit game-info real
71
+ TR Triangle - list of point
72
+ TW Territory White - (Go) elist of point
73
+ UC Unclear pos - double
74
+ US User game-info simpletext
75
+ V Value - real
76
+ VW View - (inherit) elist of point
77
+ W White move move
78
+ WL White time left move real
79
+ WR White rank game-info simpletext
80
+ WT White team game-info simpletext }
81
+
82
+ property_array = property_string.split("\n")
83
+ hash = {}
84
+ property_array.each do |set|
85
+ temp = set.gsub("\t", " ")
86
+ id = temp[0..3].strip
87
+ desc = temp[4..19].strip
88
+ property_type = temp[20..35].strip
89
+ property_value = temp[37..-1].strip
90
+ hash[id] = [desc, property_type, property_value]
91
+ end
92
+
93
+ # All this work for this minuscule line!
94
+ PROPERTIES = hash
95
+
96
+ end
97
+
@@ -0,0 +1,124 @@
1
+ module SgfParser
2
+
3
+ # This is a placeholder for the root of the gametree(s). It's an abstraction,
4
+ # but it allows an easy way to save, iterate over, and compare other trees.
5
+ # Accessors: tree.root
6
+ class Tree
7
+ include Enumerable
8
+
9
+ attr_accessor :root
10
+
11
+ # Create a new tree. Can also be used to load a tree from either a file or
12
+ # a string. Raises an error if both are provided.
13
+ # options: \n
14
+ # :filename => filename \n
15
+ # !!! OR !!! \n
16
+ # :sgf_string => string \n
17
+ def initialize args={}
18
+ @root = Node.new
19
+ @sgf = ""
20
+ raise ArgumentError, "Both file and string provided" if args[:filename] &&
21
+ args[:sgf_string]
22
+ if !args[:filename].nil?
23
+ load_file args[:filename]
24
+ elsif !args[:sgf_string].nil?
25
+ load_string args[:sgf_string]
26
+ end
27
+
28
+ end # initialize
29
+
30
+
31
+ # Iterates over the tree, node by node, in preorder fashion.
32
+ # Does not support other types of iteration, but may in the future.
33
+ # tree.each { |node| puts "I am node. Hear me #{node.properties} !"}
34
+ def each order=:preorder, &block
35
+ case order
36
+ when :preorder
37
+ preorder @root, &block
38
+ end
39
+ end # each
40
+
41
+ # Compares a tree to another tree, node by node.
42
+ # Nodes must by the same (same properties, parents and children).
43
+ def == other_tree
44
+ one = []
45
+ two = []
46
+ each { |node| one << node }
47
+ other_tree.each { |node| two << node }
48
+ one == two
49
+ end # ==
50
+
51
+ # Saves the tree as an SGF file. raises an error if a filename is not given.
52
+ # tree.save :filename => file_name
53
+ def save args={}
54
+ raise ArgumentError, "No file name provided" if args[:filename].nil?
55
+ # SGF files are trees stored in pre-order traversal.
56
+ @sgf_string = "("
57
+ @root.children.each { |child| write_node child }
58
+ # write_node @root
59
+ @sgf_string << ")"
60
+
61
+ File.open(args[:filename], 'w') { |f| f << @sgf_string }
62
+ end #save
63
+
64
+ private
65
+
66
+ # Adds a stringified node to the variable @sgf_string - for saving purposes.
67
+ def write_node node=@root
68
+ @sgf_string << ";"
69
+ unless node.properties.empty?
70
+ properties = ""
71
+ node.properties.each do |k, v|
72
+ v_escaped = v.gsub("]", "\\]")
73
+ properties += "#{k.to_s}[#{v_escaped}]"
74
+ end
75
+ @sgf_string << "#{properties}"
76
+ end
77
+
78
+ case node.children.size
79
+ when 0
80
+ @sgf_string << ")"
81
+ when 1
82
+ write_node node.children[0]
83
+ else
84
+ node.each_child do |child|
85
+ @sgf_string << "("
86
+ write_node child
87
+ end
88
+ end
89
+ end
90
+
91
+ # Used to load and parse a string if a string was given.
92
+ def load_string string
93
+ @sgf = string
94
+ parse unless @sgf.empty?
95
+ end # load_string
96
+
97
+ # Used to load and parse a file if a file was given.
98
+ def load_file filename
99
+ @sgf = ""
100
+ File.open(filename, 'r') { |f| @sgf = f.read }
101
+ parse unless @sgf.empty?
102
+ end # load_file
103
+
104
+ # Traverse the tree in preorder fashion, starting with the @root node if
105
+ # no node is given, and activating the passed block on each.
106
+ def preorder node=@root, &block
107
+ # stop processing if the block returns false
108
+ if yield node then
109
+ node.each_child do |child|
110
+ preorder(child, &block)
111
+ end
112
+ end
113
+ end # preorder
114
+
115
+ def method_missing method_name, *args
116
+ output = @root.children[0].properties[method_name]
117
+ super(method_name, args) if output.nil?
118
+ output
119
+ end # method_missing
120
+
121
+ end
122
+
123
+ end
124
+