theoros-rubysgf 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +19 -0
- data/lib/rubysgf.rb +76 -0
- data/lib/sgf/compressed_list.rb +30 -0
- data/lib/sgf/coordinates.rb +74 -0
- data/lib/sgf/errors.rb +14 -0
- data/lib/sgf/node.rb +68 -0
- data/lib/sgf/property.rb +102 -0
- data/lib/sgf/record.rb +66 -0
- data/rubysgf.gemspec +25 -0
- metadata +61 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Adam Prescott <disaffect@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/lib/rubysgf.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
7
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
8
|
+
|
9
|
+
require "strscan"
|
10
|
+
require "sgf/errors"
|
11
|
+
require "sgf/coordinates"
|
12
|
+
require "sgf/compressed_list"
|
13
|
+
require "sgf/property"
|
14
|
+
require "sgf/node"
|
15
|
+
require "sgf/record"
|
16
|
+
|
17
|
+
module RubySGF
|
18
|
+
VERSION = "0.0.2"
|
19
|
+
|
20
|
+
module SGF # Go-specific under FF[4]
|
21
|
+
|
22
|
+
ALL_PROPERTY_NAMES = %w{ B KO MN W AB AE AW PL C DM GB GW HO N UC V BM DO IT TE AR CR DD LB LN MA SL SQ TR AP CA FF GM ST SZ AN BR BT CP DT EV GN GC ON OT PB PC PW RE RO RU SO TM US WR WT BL OB OW WL FG PM VW HA KM TB TW }
|
23
|
+
|
24
|
+
# properties which have values which are coordinates and implemented by this program
|
25
|
+
COORDINATE_PROPERTIES = %w{ B W AB AW AE CR MA SQ TR TW TB }
|
26
|
+
|
27
|
+
# the properties going to be implemented by this program
|
28
|
+
PROPERTY_NAMES_HASH = {
|
29
|
+
:black_move => "B",
|
30
|
+
:white_move => "W",
|
31
|
+
:black_stones => "AB",
|
32
|
+
:white_stones => "AW",
|
33
|
+
:neutral_points => "AE",
|
34
|
+
:comment => "C",
|
35
|
+
:node_name => "N",
|
36
|
+
:circled_points => "CR",
|
37
|
+
:label => "LB",
|
38
|
+
:marked_x => "MA",
|
39
|
+
:squared_points => "SQ",
|
40
|
+
:triangled_points => "TR",
|
41
|
+
:application => "AP",
|
42
|
+
:character_set => "CA",
|
43
|
+
:file_format => "FF",
|
44
|
+
:game_type => "GM",
|
45
|
+
:board_size => "SZ",
|
46
|
+
:annotator => "AN",
|
47
|
+
:black_rank => "BR",
|
48
|
+
:copyright_info => "CP",
|
49
|
+
:game_date => "DT",
|
50
|
+
:game_name => "GN",
|
51
|
+
:overtime => "OT",
|
52
|
+
:black_player => "PB",
|
53
|
+
:game_location => "PC",
|
54
|
+
:white_player => "PW",
|
55
|
+
:game_result => "RE",
|
56
|
+
:ruleset => "RU",
|
57
|
+
:source => "SO",
|
58
|
+
:time_limits => "TM",
|
59
|
+
:white_rank => "WR",
|
60
|
+
:black_time_left => "BL",
|
61
|
+
:black_overtime => "OB",
|
62
|
+
:white_time_left => "WL",
|
63
|
+
:white_overtime => "OW",
|
64
|
+
:handicap => "HA",
|
65
|
+
:komi => "KM",
|
66
|
+
:white_territory => "TW",
|
67
|
+
:black_territory => "TB",
|
68
|
+
:markup_style => "ST"
|
69
|
+
}
|
70
|
+
|
71
|
+
# implemented by this program
|
72
|
+
PROPERTY_NAMES = PROPERTY_NAMES_HASH.values
|
73
|
+
|
74
|
+
NOT_IMPLEMENTED_PROPERTY_NAMES = ALL_PROPERTY_NAMES - PROPERTY_NAMES
|
75
|
+
end # SGF
|
76
|
+
end # RSGF
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
module RubySGF
|
7
|
+
module SGF
|
8
|
+
class CompressedList
|
9
|
+
attr_reader :ulx, :uly, :lrx, :lry
|
10
|
+
def initialize(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
|
11
|
+
@ulx, @uly, @lrx, @lry = upper_left_x, upper_left_y, lower_right_x, lower_right_y
|
12
|
+
end
|
13
|
+
|
14
|
+
# expand compressed list to array
|
15
|
+
def to_a
|
16
|
+
smallx, bigx = [ulx, lrx].sort.map { |i| Coordinates::HORIZONTAL.index(i) }
|
17
|
+
xgap = bigx - smallx
|
18
|
+
smally, bigy = [uly, lry].sort.map { |i| Coordinates::VERTICAL.index(i) }
|
19
|
+
ygap = bigy - smally
|
20
|
+
coord_list = []
|
21
|
+
Coordinates::VERTICAL[smally..bigy].each do |y|
|
22
|
+
Coordinates::HORIZONTAL[smallx..bigx].each do |x|
|
23
|
+
coord_list << [x,y]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
coord_list.sort
|
27
|
+
end
|
28
|
+
end # compressedlist
|
29
|
+
end # sgf
|
30
|
+
end # rsgf
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
module RubySGF
|
7
|
+
module SGF
|
8
|
+
class Coordinates
|
9
|
+
VERTICAL = HORIZONTAL = [*"a".."z"] + [*"A".."Z"]
|
10
|
+
|
11
|
+
attr_reader :x, :y
|
12
|
+
def initialize(x, y)
|
13
|
+
@x = x
|
14
|
+
@y = y
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(second_coordinates)
|
18
|
+
@x == second_coordinates.x && @y == second_coordinates.y
|
19
|
+
end
|
20
|
+
|
21
|
+
def neighbours(size)
|
22
|
+
[up, right, down, left].compact.map do |c|
|
23
|
+
c.inside?(size) ? c : nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def inside?(size)
|
28
|
+
vert = VERTICAL[0,size]
|
29
|
+
horiz = HORIZONTAL[0,size]
|
30
|
+
horiz.include?(@x) && vert.include?(@y)
|
31
|
+
end
|
32
|
+
|
33
|
+
def outside?(size)
|
34
|
+
!inside?(size)
|
35
|
+
end
|
36
|
+
|
37
|
+
def up
|
38
|
+
idx = VERTICAL.index(y) - 1
|
39
|
+
return nil unless idx >= 0
|
40
|
+
new_y = VERTICAL[idx]
|
41
|
+
return nil unless new_y
|
42
|
+
Coordinates.new(x, new_y)
|
43
|
+
end
|
44
|
+
|
45
|
+
def right
|
46
|
+
idx = HORIZONTAL.index(x) + 1
|
47
|
+
return nil unless idx >= 0
|
48
|
+
new_x = HORIZONTAL[idx]
|
49
|
+
return nil unless new_x
|
50
|
+
Coordinates.new(new_x, y)
|
51
|
+
end
|
52
|
+
|
53
|
+
def down
|
54
|
+
idx = VERTICAL.index(y) + 1
|
55
|
+
return nil unless idx >= 0
|
56
|
+
new_y = VERTICAL[idx]
|
57
|
+
return nil unless new_y
|
58
|
+
Coordinates.new(x, new_y)
|
59
|
+
end
|
60
|
+
|
61
|
+
def left
|
62
|
+
idx = HORIZONTAL.index(x) - 1
|
63
|
+
return nil unless idx >= 0
|
64
|
+
new_x = HORIZONTAL[idx]
|
65
|
+
return nil unless new_x
|
66
|
+
Coordinates.new(new_x, y)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
@x + @y
|
71
|
+
end
|
72
|
+
end # coordinates
|
73
|
+
end # SGF
|
74
|
+
end # RSGF
|
data/lib/sgf/errors.rb
ADDED
data/lib/sgf/node.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
module RubySGF
|
7
|
+
module SGF
|
8
|
+
#--
|
9
|
+
# this class is partially inspired by code written by
|
10
|
+
# Stefan Rusterholz, stefan.rusterholz <AT> gmail <DOT> com
|
11
|
+
#++
|
12
|
+
class Node
|
13
|
+
|
14
|
+
attr_reader :parent, :children, :number
|
15
|
+
attr_accessor :properties
|
16
|
+
|
17
|
+
def initialize(parent, properties = [])
|
18
|
+
@parent = parent
|
19
|
+
@properties = properties
|
20
|
+
@children = []
|
21
|
+
|
22
|
+
@parent << self if parent
|
23
|
+
|
24
|
+
if parent
|
25
|
+
@number = parent.number + 1
|
26
|
+
else
|
27
|
+
@number = 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def prev
|
32
|
+
@parent
|
33
|
+
end
|
34
|
+
|
35
|
+
def next
|
36
|
+
@children[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](index)
|
40
|
+
@children[index]
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(node)
|
44
|
+
@children << node
|
45
|
+
end
|
46
|
+
|
47
|
+
def leaf?
|
48
|
+
@children.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def root?
|
52
|
+
@parent.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
" "*@number << @number.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_tree(string="")
|
60
|
+
string << self.to_s << "\n"
|
61
|
+
@children.each do |child|
|
62
|
+
child.write_tree(string)
|
63
|
+
end
|
64
|
+
string
|
65
|
+
end # write_tree
|
66
|
+
end # class
|
67
|
+
end # sgf
|
68
|
+
end # rsgf
|
data/lib/sgf/property.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
module RubySGF
|
7
|
+
module SGF
|
8
|
+
class Property
|
9
|
+
|
10
|
+
DEFAULTS = {"AP" => "rGo:0.1a", "GM" => "1", "FF" => "4", "SZ" => "19", "PW" => "White", "PB" => "Black"}
|
11
|
+
|
12
|
+
attr_reader :name, :value
|
13
|
+
|
14
|
+
# /(PN\[((?:[^\]\\]+|\\[\]\[]?)*?)\])/ --> /(PN\[((?:[^\]\\]+|\\[\]\[]?)*?)\])+/
|
15
|
+
# allows lists to be taken into account [aa][ev][gg]
|
16
|
+
# returns PN[everything here including escaped \] like this]
|
17
|
+
MATCH = /(\[((?:[^\]\\]+|\\[\]\[]?)*?)\])/
|
18
|
+
MATCH_MANY = /(\[((?:[^\]\\]+|\\[\]\[]?)*?)\])+/
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(name, value)
|
22
|
+
@name = name
|
23
|
+
@value = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def human_name
|
27
|
+
Property.human_name_of(@name)
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
def create_many_from_raw(text, properties)
|
33
|
+
properties.map! do |pn|
|
34
|
+
Property.create_from_raw(text,pn)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_from_raw(text, property_name)
|
39
|
+
prop_value = Property.parse_sgf(text, property_name) # Array, may be size 1 or more
|
40
|
+
prop_value.map! do |v|
|
41
|
+
if COORDINATE_PROPERTIES.include? property_name
|
42
|
+
Coordinates.new(v[0,1], v[1,1])
|
43
|
+
elsif property_name == "LB"
|
44
|
+
v = v.split(":")
|
45
|
+
v[0] = Coordinates.new(v[0][0,1], v[0][1,1])
|
46
|
+
v
|
47
|
+
else
|
48
|
+
v
|
49
|
+
end
|
50
|
+
end
|
51
|
+
new(property_name, prop_value)
|
52
|
+
end
|
53
|
+
|
54
|
+
# take a text of properties (NAME[VALUE], or otherwise) and return the value(s)
|
55
|
+
def parse_sgf(input, property_name)
|
56
|
+
$stderr << "invalid property detected (will be ignored): #{property_name} -- \"#{input}\"\n" unless Property.valid?(property_name)
|
57
|
+
match_many = MATCH_MANY.source
|
58
|
+
match = MATCH.source
|
59
|
+
output = ""
|
60
|
+
case property_name
|
61
|
+
when "C"
|
62
|
+
output = input[/#{property_name}#{match}/m][2..-2]
|
63
|
+
when "GN"
|
64
|
+
output = input[/#{property_name}#{match}/m][3..-2]
|
65
|
+
else
|
66
|
+
output = input.gsub("\n","")[/#{property_name}#{match_many}/][2..-1].scan(/[^\[\]]+/)
|
67
|
+
end
|
68
|
+
[*output]
|
69
|
+
end
|
70
|
+
|
71
|
+
def valid?(name)
|
72
|
+
ALL_PROPERTY_NAMES.include?(name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def compressed_list?(val)
|
76
|
+
val =~ /^[a-z][a-z]\:[a-z][a-z]$/i
|
77
|
+
end
|
78
|
+
|
79
|
+
def expand_list(val)
|
80
|
+
ulx, uly, lrx, lry = val.scan(/^([a-z])([a-z])\:([a-z])([a-z])$/i).flatten
|
81
|
+
CompressedList.new(ulx, uly, lrx, lry).to_a
|
82
|
+
end
|
83
|
+
|
84
|
+
def can_be_nil?(property_name)
|
85
|
+
["W","B"].include?(property_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
class << self
|
92
|
+
def human_name_of(name)
|
93
|
+
PROPERTY_NAMES_HASH.invert[name]
|
94
|
+
end
|
95
|
+
|
96
|
+
def sgf_name_of(sym)
|
97
|
+
PROPERTY_NAMES_HASH[sym]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/sgf/record.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Adam Prescott
|
3
|
+
# licensed as specified under LICENSE.txt
|
4
|
+
#++
|
5
|
+
|
6
|
+
module RubySGF
|
7
|
+
module SGF
|
8
|
+
class Record
|
9
|
+
attr_reader :data, :root, :info
|
10
|
+
|
11
|
+
def initialize(data = nil)
|
12
|
+
@data = data
|
13
|
+
if data
|
14
|
+
@root = Record.create_tree(@data)
|
15
|
+
else
|
16
|
+
n = Node.new(nil)
|
17
|
+
n.properties = Property::DEFAULTS.map do |name, value|
|
18
|
+
Property.new(name, [value])
|
19
|
+
end
|
20
|
+
@root = n
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@root.write_tree
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def create_tree(str)
|
32
|
+
str = StringScanner.new(str)
|
33
|
+
root_node = node = nil
|
34
|
+
old_nodes = []
|
35
|
+
until str.eos?
|
36
|
+
case s = str.getch
|
37
|
+
when "("
|
38
|
+
old_nodes << node
|
39
|
+
node = Node.new(node)
|
40
|
+
root_node = node if node.root?
|
41
|
+
when ";"
|
42
|
+
# pass along all of the properties of the current node to Node for creation
|
43
|
+
# we ignore ; ( ) chars inside property values
|
44
|
+
node_text = str.scan(/[A-Z]+\[.*?\]\s*[;\(\)]/m).strip[0..-2]
|
45
|
+
# look for all property names
|
46
|
+
property_names = node_text.scan(/((?:[A-Z]+)(?:[^\\]|))\[/).flatten
|
47
|
+
$stderr << "Error around \"#{str.matched}\": multiple properties of the same type -- all but the first instance will be ignored\n" if property_names.uniq!
|
48
|
+
properties = Property.create_many_from_raw(node_text, property_names)
|
49
|
+
if node.properties.empty? # every node must have a property
|
50
|
+
node.properties = properties
|
51
|
+
else
|
52
|
+
node = Node.new(node, properties)
|
53
|
+
end
|
54
|
+
str.pos -= 1 # move back before the ; or ( or )
|
55
|
+
when ")"
|
56
|
+
node = old_nodes.pop
|
57
|
+
else
|
58
|
+
next
|
59
|
+
end # case
|
60
|
+
end # until
|
61
|
+
root_node
|
62
|
+
end # create_tree
|
63
|
+
end # singleton
|
64
|
+
end # record
|
65
|
+
end # SGF
|
66
|
+
end # RSGF
|
data/rubysgf.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "rubysgf"
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.date = "2008-08-22"
|
5
|
+
s.summary = "SGF file reader for Ruby"
|
6
|
+
s.email = "disaffect@gmail.com"
|
7
|
+
s.homepage = "http://github.com/theoros/rubysgf"
|
8
|
+
s.description = "A Ruby library to read SGF files and create objects out of them."
|
9
|
+
s.required_ruby_version = "~> 1.8.6"
|
10
|
+
s.has_rdoc = false
|
11
|
+
s.authors = ["Adam Prescott"]
|
12
|
+
s.files = ["LICENSE.txt",
|
13
|
+
"rubysgf.gemspec",
|
14
|
+
"lib/rubysgf.rb",
|
15
|
+
"lib/sgf/compressed_list.rb",
|
16
|
+
"lib/sgf/coordinates.rb",
|
17
|
+
"lib/sgf/errors.rb",
|
18
|
+
"lib/sgf/node.rb",
|
19
|
+
"lib/sgf/property.rb",
|
20
|
+
"lib/sgf/record.rb",
|
21
|
+
]
|
22
|
+
s.rdoc_options = []#["--main", "README.txt"]
|
23
|
+
s.extra_rdoc_files = []#["History.txt", "Manifest.txt", "README.txt"]
|
24
|
+
#s.add_dependency("mime-types", ["> 0.0.0"])
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: theoros-rubysgf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Prescott
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-08-22 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A Ruby library to read SGF files and create objects out of them.
|
17
|
+
email: disaffect@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- LICENSE.txt
|
26
|
+
- rubysgf.gemspec
|
27
|
+
- lib/rubysgf.rb
|
28
|
+
- lib/sgf/compressed_list.rb
|
29
|
+
- lib/sgf/coordinates.rb
|
30
|
+
- lib/sgf/errors.rb
|
31
|
+
- lib/sgf/node.rb
|
32
|
+
- lib/sgf/property.rb
|
33
|
+
- lib/sgf/record.rb
|
34
|
+
has_rdoc: false
|
35
|
+
homepage: http://github.com/theoros/rubysgf
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.8.6
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: SGF file reader for Ruby
|
60
|
+
test_files: []
|
61
|
+
|