SgfParser 0.9.1 → 1.0.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.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.rdoc +44 -38
- data/SgfParser.gemspec +17 -15
- data/VERSION +1 -1
- data/lib/sgf.rb +8 -0
- data/lib/sgf/{sgf_indent.rb → indenter.rb} +108 -108
- data/lib/sgf/{parser/node.rb → node.rb} +61 -57
- data/lib/sgf/{parser/tree_parse.rb → parser.rb} +120 -111
- data/lib/sgf/{parser/properties.rb → properties.rb} +97 -97
- data/lib/sgf/{parser/tree.rb → tree.rb} +91 -107
- data/sample_usage/parsing_files.rb +18 -18
- data/{sample_sgf → spec/data}/ff4_ex.sgf +0 -0
- data/{sample_sgf → spec/data}/ff4_ex_saved.sgf +0 -0
- data/{sample_sgf → spec/data}/redrose-tartrate.sgf +1068 -1068
- data/{sample_sgf → spec/data}/simple.sgf +11 -11
- data/spec/data/simple_saved.sgf +7 -0
- data/spec/node_spec.rb +63 -33
- data/spec/parser_spec.rb +20 -0
- data/spec/spec_helper.rb +6 -6
- data/spec/tree_spec.rb +29 -41
- metadata +69 -87
- data/lib/sgf/sgfindent.rb +0 -118
- data/lib/sgf_parser.rb +0 -8
- data/sample_sgf/simple_saved.sgf +0 -7
- data/spec/tree_parser_spec.rb +0 -24
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -1,38 +1,44 @@
|
|
1
|
-
=INFORMATION
|
2
|
-
Author: Aldric Giacomoni
|
3
|
-
|
4
|
-
Email : aldric
|
5
|
-
|
6
|
-
SGF: all formats (but untested with FF < 4)
|
7
|
-
|
8
|
-
Ruby: >=1.8.7 (may work with 1.8.6)
|
9
|
-
|
10
|
-
=QUICK HOWTO
|
11
|
-
Example:
|
12
|
-
require '
|
13
|
-
tree =
|
14
|
-
|
15
|
-
|
16
|
-
All trees begin with an empty node ( @root) which allows a simple support of multiple gametrees.
|
17
|
-
|
18
|
-
Most games will just care about, say,
|
19
|
-
tree.root.children[0] which is the first node of the first gametree.
|
20
|
-
|
21
|
-
For any node, one can summon the properties as such:
|
22
|
-
node.properties # => returns a hash of the properties.
|
23
|
-
A single property can be called, like the comments, for instance, like so:
|
24
|
-
node.C # => returns the comments for this node.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
sgf =
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
1
|
+
=INFORMATION
|
2
|
+
Author: Aldric Giacomoni
|
3
|
+
|
4
|
+
Email : aldric@at@trevoke.net (feedback very welcome!)
|
5
|
+
|
6
|
+
SGF: all formats (but untested with FF < 4)
|
7
|
+
|
8
|
+
Ruby: >=1.8.7 (may work with 1.8.6)
|
9
|
+
|
10
|
+
=QUICK HOWTO
|
11
|
+
Example:
|
12
|
+
require 'sgf'
|
13
|
+
tree = SGF::Parser.new file_or_string
|
14
|
+
|
15
|
+
|
16
|
+
All trees begin with an empty node ( @root) which allows a simple support of multiple gametrees.
|
17
|
+
|
18
|
+
Most games will just care about, say,
|
19
|
+
tree.root.children[0] which is the first node of the first gametree.
|
20
|
+
|
21
|
+
For any node, one can summon the properties as such:
|
22
|
+
node.properties # => returns a hash of the properties.
|
23
|
+
A single property can be called, like the comments, for instance, like so:
|
24
|
+
node.C # => returns the comments for this node.
|
25
|
+
node.comments # => syntactic sugar
|
26
|
+
|
27
|
+
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.
|
28
|
+
|
29
|
+
There is also a SGF Indenter. Its purpose is to make SGF files more readable to humans at a glance.
|
30
|
+
require 'sgf/sgfindent' # Done automatically if you require 'sgf_parser'
|
31
|
+
sgf = SGF::Indenter.new 'some_ugly_file.sgf' # Will output to the console
|
32
|
+
sgf = SGF::Indenter.new 'some_ugly_file.sgf' 'pretty.sgf' # Sends the result to a new file.
|
33
|
+
|
34
|
+
___
|
35
|
+
|
36
|
+
TODO
|
37
|
+
- Create a "Game" class that wraps a complete set of () for ease of use
|
38
|
+
- implement node.next to go to node.children[0] because who wants to type that anyway?
|
39
|
+
- implement a 'current' node in Tree so we don't have to jump from node to node. This means..
|
40
|
+
- implement tree.next instead of node.next for tree.current_node.children[0]
|
41
|
+
- examine/fix the smell that when you use add_children, it also sets the parent on the passed-in nodes.
|
42
|
+
- fix the multiple properties bug in the SGF indenter as well
|
43
|
+
- see how much of the parser code the indenter can use, since it's basically the same logic
|
44
|
+
|
data/SgfParser.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{SgfParser}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Aldric Giacomoni"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-08-01}
|
13
13
|
s.description = %q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
|
14
14
|
s.email = %q{aldric@trevoke.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -27,22 +27,21 @@ Gem::Specification.new do |s|
|
|
27
27
|
"Rakefile",
|
28
28
|
"SgfParser.gemspec",
|
29
29
|
"VERSION",
|
30
|
-
"lib/sgf
|
31
|
-
"lib/sgf/
|
32
|
-
"lib/sgf/
|
33
|
-
"lib/sgf/parser
|
34
|
-
"lib/sgf/
|
35
|
-
"lib/sgf/
|
36
|
-
"lib/sgf_parser.rb",
|
37
|
-
"sample_sgf/ff4_ex.sgf",
|
38
|
-
"sample_sgf/ff4_ex_saved.sgf",
|
39
|
-
"sample_sgf/redrose-tartrate.sgf",
|
40
|
-
"sample_sgf/simple.sgf",
|
41
|
-
"sample_sgf/simple_saved.sgf",
|
30
|
+
"lib/sgf.rb",
|
31
|
+
"lib/sgf/indenter.rb",
|
32
|
+
"lib/sgf/node.rb",
|
33
|
+
"lib/sgf/parser.rb",
|
34
|
+
"lib/sgf/properties.rb",
|
35
|
+
"lib/sgf/tree.rb",
|
42
36
|
"sample_usage/parsing_files.rb",
|
37
|
+
"spec/data/ff4_ex.sgf",
|
38
|
+
"spec/data/ff4_ex_saved.sgf",
|
39
|
+
"spec/data/redrose-tartrate.sgf",
|
40
|
+
"spec/data/simple.sgf",
|
41
|
+
"spec/data/simple_saved.sgf",
|
43
42
|
"spec/node_spec.rb",
|
43
|
+
"spec/parser_spec.rb",
|
44
44
|
"spec/spec_helper.rb",
|
45
|
-
"spec/tree_parser_spec.rb",
|
46
45
|
"spec/tree_spec.rb"
|
47
46
|
]
|
48
47
|
s.homepage = %q{http://github.com/Trevoke/SGFParser}
|
@@ -56,15 +55,18 @@ Gem::Specification.new do |s|
|
|
56
55
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
56
|
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
58
57
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
59
59
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
60
60
|
else
|
61
61
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
62
62
|
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
63
64
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
64
65
|
end
|
65
66
|
else
|
66
67
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
67
68
|
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
68
70
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
69
71
|
end
|
70
72
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/sgf.rb
ADDED
@@ -1,108 +1,108 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module
|
4
|
-
|
5
|
-
# This indents an SGF file to make it more readable. It outputs to the screen
|
6
|
-
# by default, but can be given a file as output.
|
7
|
-
# Usage: SgfParser::Indenter.new infile, outfile
|
8
|
-
class Indenter
|
9
|
-
|
10
|
-
def initialize file, out=$stdout
|
11
|
-
@stream = load_file_to_stream(file)
|
12
|
-
@new_string = ""
|
13
|
-
@indentation = 0
|
14
|
-
@out = (out == $stdout) ? $stdout : File.open(out, 'w')
|
15
|
-
parse
|
16
|
-
end
|
17
|
-
|
18
|
-
def next_character
|
19
|
-
!@stream.eof? && @stream.sysread(1)
|
20
|
-
end
|
21
|
-
|
22
|
-
def parse
|
23
|
-
while char = next_character
|
24
|
-
case char
|
25
|
-
when '(' then open_branch
|
26
|
-
when ')' then close_branch
|
27
|
-
when ';' then new_node
|
28
|
-
when '[' then add_property
|
29
|
-
else add_identity(char)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
@out << @new_string
|
34
|
-
@out.close unless @out == $stdout
|
35
|
-
#if out == $stdout
|
36
|
-
# $stdout << @new_string
|
37
|
-
#else
|
38
|
-
# File.open(out, 'w') { |file| file << @new_string }
|
39
|
-
#end
|
40
|
-
end
|
41
|
-
|
42
|
-
def open_branch
|
43
|
-
next_line
|
44
|
-
@indentation += 2
|
45
|
-
@new_string << " " * @indentation
|
46
|
-
@new_string << "("
|
47
|
-
end
|
48
|
-
|
49
|
-
def close_branch
|
50
|
-
@new_string << ")"
|
51
|
-
next_line
|
52
|
-
@indentation -= 2
|
53
|
-
@indentation = 0 if @indentation < 0
|
54
|
-
@new_string << " " * @indentation
|
55
|
-
end
|
56
|
-
|
57
|
-
def new_node
|
58
|
-
next_line
|
59
|
-
@new_string << " " * @indentation
|
60
|
-
@new_string << ";"
|
61
|
-
end
|
62
|
-
|
63
|
-
def next_line
|
64
|
-
@new_string << "\n"
|
65
|
-
end
|
66
|
-
|
67
|
-
#TODO Fix it more. Add _ONE_ set of indentation if there are newlines,
|
68
|
-
#TODO Not one set for every newline.
|
69
|
-
def add_property
|
70
|
-
buffer = "["
|
71
|
-
while true
|
72
|
-
next_bit = @stream.sysread(1)
|
73
|
-
next_bit << @stream.sysread(1) if next_bit == "\\"
|
74
|
-
buffer << next_bit
|
75
|
-
buffer << " " * @indentation if next_bit == "\n"
|
76
|
-
break if next_bit == "]"
|
77
|
-
end
|
78
|
-
buffer << "\n"
|
79
|
-
buffer << " " * @indentation
|
80
|
-
@new_string << buffer
|
81
|
-
end
|
82
|
-
|
83
|
-
def add_identity(char)
|
84
|
-
@new_string << char unless char == "\n"
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def load_file_to_stream(file)
|
90
|
-
sgf = ""
|
91
|
-
File.open(file) { |f| sgf = f.read }
|
92
|
-
clean_string(sgf)
|
93
|
-
return StringIO.new(sgf, 'r')
|
94
|
-
end
|
95
|
-
|
96
|
-
def clean_string(sgf)
|
97
|
-
sgf.gsub! "\\\\n\\\\r", ""
|
98
|
-
sgf.gsub! "\\\\r\\\\n", ""
|
99
|
-
sgf.gsub! "\\\\r", ""
|
100
|
-
sgf.gsub! "\\\\n", ""
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module SGF
|
4
|
+
|
5
|
+
# This indents an SGF file to make it more readable. It outputs to the screen
|
6
|
+
# by default, but can be given a file as output.
|
7
|
+
# Usage: SgfParser::Indenter.new infile, outfile
|
8
|
+
class Indenter
|
9
|
+
|
10
|
+
def initialize file, out=$stdout
|
11
|
+
@stream = load_file_to_stream(file)
|
12
|
+
@new_string = ""
|
13
|
+
@indentation = 0
|
14
|
+
@out = (out == $stdout) ? $stdout : File.open(out, 'w')
|
15
|
+
parse
|
16
|
+
end
|
17
|
+
|
18
|
+
def next_character
|
19
|
+
!@stream.eof? && @stream.sysread(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse
|
23
|
+
while char = next_character
|
24
|
+
case char
|
25
|
+
when '(' then open_branch
|
26
|
+
when ')' then close_branch
|
27
|
+
when ';' then new_node
|
28
|
+
when '[' then add_property
|
29
|
+
else add_identity(char)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@out << @new_string
|
34
|
+
@out.close unless @out == $stdout
|
35
|
+
#if out == $stdout
|
36
|
+
# $stdout << @new_string
|
37
|
+
#else
|
38
|
+
# File.open(out, 'w') { |file| file << @new_string }
|
39
|
+
#end
|
40
|
+
end
|
41
|
+
|
42
|
+
def open_branch
|
43
|
+
next_line
|
44
|
+
@indentation += 2
|
45
|
+
@new_string << " " * @indentation
|
46
|
+
@new_string << "("
|
47
|
+
end
|
48
|
+
|
49
|
+
def close_branch
|
50
|
+
@new_string << ")"
|
51
|
+
next_line
|
52
|
+
@indentation -= 2
|
53
|
+
@indentation = 0 if @indentation < 0
|
54
|
+
@new_string << " " * @indentation
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_node
|
58
|
+
next_line
|
59
|
+
@new_string << " " * @indentation
|
60
|
+
@new_string << ";"
|
61
|
+
end
|
62
|
+
|
63
|
+
def next_line
|
64
|
+
@new_string << "\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
#TODO Fix it more. Add _ONE_ set of indentation if there are newlines,
|
68
|
+
#TODO Not one set for every newline.
|
69
|
+
def add_property
|
70
|
+
buffer = "["
|
71
|
+
while true
|
72
|
+
next_bit = @stream.sysread(1)
|
73
|
+
next_bit << @stream.sysread(1) if next_bit == "\\"
|
74
|
+
buffer << next_bit
|
75
|
+
buffer << " " * @indentation if next_bit == "\n"
|
76
|
+
break if next_bit == "]"
|
77
|
+
end
|
78
|
+
buffer << "\n"
|
79
|
+
buffer << " " * @indentation
|
80
|
+
@new_string << buffer
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_identity(char)
|
84
|
+
@new_string << char unless char == "\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def load_file_to_stream(file)
|
90
|
+
sgf = ""
|
91
|
+
File.open(file) { |f| sgf = f.read }
|
92
|
+
clean_string(sgf)
|
93
|
+
return StringIO.new(sgf, 'r')
|
94
|
+
end
|
95
|
+
|
96
|
+
def clean_string(sgf)
|
97
|
+
sgf.gsub! "\\\\n\\\\r", ""
|
98
|
+
sgf.gsub! "\\\\r\\\\n", ""
|
99
|
+
sgf.gsub! "\\\\r", ""
|
100
|
+
sgf.gsub! "\\\\n", ""
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
@@ -1,58 +1,62 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
class Node
|
4
|
-
|
5
|
-
attr_accessor :parent, :children, :properties
|
6
|
-
|
7
|
-
# Creates a new node.
|
8
|
-
# :parent => parent_node (nil by default)
|
9
|
-
# :
|
10
|
-
# :properties => {hash_of => properties} (empty hash by default)
|
11
|
-
def initialize args={}
|
12
|
-
@parent = args[:parent]
|
13
|
-
@children = []
|
14
|
-
add_children args[:children] if
|
15
|
-
@properties = Hash.new
|
16
|
-
@properties.merge! args[:properties] if
|
17
|
-
end
|
18
|
-
|
19
|
-
def add_children *nodes
|
20
|
-
nodes.flatten!
|
21
|
-
raise "Non-node child given!" if nodes.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@properties[key]
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
1
|
+
module SGF
|
2
|
+
|
3
|
+
class Node
|
4
|
+
|
5
|
+
attr_accessor :parent, :children, :properties
|
6
|
+
|
7
|
+
# Creates a new node. Arguments which can be passed in are:
|
8
|
+
# :parent => parent_node (nil by default)
|
9
|
+
# :children => [list, of, children] (empty array by default)
|
10
|
+
# :properties => {hash_of => properties} (empty hash by default)
|
11
|
+
def initialize args={}
|
12
|
+
@parent = args[:parent]
|
13
|
+
@children = []
|
14
|
+
add_children args[:children] if args[:children]
|
15
|
+
@properties = Hash.new
|
16
|
+
@properties.merge! args[:properties] if args[:properties]
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_children *nodes
|
20
|
+
nodes.flatten!
|
21
|
+
raise "Non-node child given!" if nodes.any? { |node| node.class != Node }
|
22
|
+
nodes.each { |node| node.parent = self }
|
23
|
+
@children.concat nodes
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_properties hash
|
27
|
+
hash.each do |key, value|
|
28
|
+
@properties[key] ||= ""
|
29
|
+
@properties[key].concat value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def each_child
|
34
|
+
@children.each { |child| yield child }
|
35
|
+
end
|
36
|
+
|
37
|
+
def == other_node
|
38
|
+
@properties == other_node.properties
|
39
|
+
end
|
40
|
+
|
41
|
+
def comments
|
42
|
+
@properties["C"]
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :comment :comments
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def method_missing method_name, *args
|
50
|
+
property = method_name.to_s.upcase
|
51
|
+
if property[/(.*?)=$/]
|
52
|
+
@properties[$1] = args[0]
|
53
|
+
else
|
54
|
+
output = @properties[property]
|
55
|
+
super(method_name, args) if output.nil?
|
56
|
+
output
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
58
62
|
end
|