SgfParser 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|