SgfParser 0.8.0 → 0.9.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/.document +5 -5
- data/.gitignore +24 -24
- data/README.rdoc +19 -9
- data/SgfParser.gemspec +67 -66
- data/VERSION +1 -1
- data/lib/sgf/parser/node.rb +4 -1
- data/lib/sgf/parser/tree.rb +21 -27
- data/lib/sgf/parser/tree_parse.rb +82 -96
- data/lib/sgf/sgf_indent.rb +108 -0
- data/lib/sgf_parser.rb +1 -1
- data/spec/tree_parser_spec.rb +13 -0
- data/spec/tree_spec.rb +0 -1
- data/tryme.rb +3 -0
- metadata +23 -11
- data/lib/sgf/sgfindent.rb +0 -118
- data/sample_sgf/ff4_ex_saved.sgf +0 -45
data/.document
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
README.rdoc
|
2
|
-
lib/**/*.rb
|
3
|
-
bin/*
|
4
|
-
features/**/*.feature
|
5
|
-
LICENSE
|
1
|
+
README.rdoc
|
2
|
+
lib/**/*.rb
|
3
|
+
bin/*
|
4
|
+
features/**/*.feature
|
5
|
+
LICENSE
|
data/.gitignore
CHANGED
@@ -1,25 +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
|
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
25
|
.idea/*
|
data/README.rdoc
CHANGED
@@ -1,24 +1,34 @@
|
|
1
|
+
=INFORMATION
|
2
|
+
Author: Aldric Giacomoni
|
3
|
+
|
4
|
+
Email : aldric~at~trevoke.net (feedback very welcome!)
|
5
|
+
|
1
6
|
SGF: all formats (but untested with FF < 4)
|
2
|
-
Ruby: >1.8.7 (may work with 1.8.6)
|
3
7
|
|
8
|
+
Ruby: >=1.8.7 (may work with 1.8.6)
|
9
|
+
|
10
|
+
=QUICK HOWTO
|
4
11
|
Example:
|
5
|
-
require 'sgf_parser'
|
6
|
-
tree = SgfParser::Tree.new :filename => File
|
7
|
-
tree = SgfParser::Tree.new :
|
12
|
+
require 'sgf_parser'
|
13
|
+
tree = SgfParser::Tree.new :filename => File
|
14
|
+
tree = SgfParser::Tree.new :string => String
|
8
15
|
|
9
16
|
All trees begin with an empty node ( @root) which allows a simple support of multiple gametrees.
|
17
|
+
|
10
18
|
Most games will just care about, say,
|
11
|
-
tree.root.children[0] which is the first node of the first gametree.
|
19
|
+
tree.root.children[0] which is the first node of the first gametree.
|
12
20
|
|
13
21
|
For any node, one can summon the properties as such:
|
14
|
-
node.properties # => returns a hash of the properties.
|
22
|
+
node.properties # => returns a hash of the properties.
|
15
23
|
A single property can be called, like the comments, for instance, like so:
|
16
|
-
node.C # => returns the comments for this node.
|
24
|
+
node.C # => returns the comments for this node.
|
17
25
|
|
18
26
|
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
27
|
|
20
|
-
|
21
|
-
|
28
|
+
There is also a SGF Indenter. Its purpose is to make SGF files more readable to humans at a glance.
|
29
|
+
require 'sgf_parser/sgfindent' # Done automatically if you require 'sgf_parser'
|
30
|
+
sgf = SgfParser::Indenter.new 'some_ugly_file.sgf' # Will output to the console
|
31
|
+
sgf = SgfParser::Indenter.new 'some_ugly_file.sgf' 'pretty.sgf' # Sends the result to a new file.
|
22
32
|
|
23
33
|
___
|
24
34
|
|
data/SgfParser.gemspec
CHANGED
@@ -1,66 +1,67 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{SgfParser}
|
8
|
-
s.version = "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-
|
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/
|
32
|
-
"lib/sgf_parser.rb",
|
33
|
-
"sample_sgf/ff4_ex.sgf",
|
34
|
-
"sample_sgf/
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"spec/
|
38
|
-
"spec/
|
39
|
-
"spec/
|
40
|
-
"
|
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.
|
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/
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
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.9.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-04-30}
|
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/sgf_indent.rb",
|
32
|
+
"lib/sgf_parser.rb",
|
33
|
+
"sample_sgf/ff4_ex.sgf",
|
34
|
+
"sample_sgf/redrose-tartrate.sgf",
|
35
|
+
"sample_usage/parsing_files.rb",
|
36
|
+
"spec/node_spec.rb",
|
37
|
+
"spec/spec.opts",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"spec/tree_spec.rb",
|
40
|
+
"tryme.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.6}
|
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_parser_spec.rb",
|
51
|
+
"spec/tree_spec.rb"
|
52
|
+
]
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
56
|
+
s.specification_version = 3
|
57
|
+
|
58
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
59
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/lib/sgf/parser/node.rb
CHANGED
@@ -23,8 +23,11 @@ module SgfParser
|
|
23
23
|
# list or an array of node children. Will raise an error if one of the
|
24
24
|
# arguments is not of class Node.
|
25
25
|
def add_children *nodes
|
26
|
+
nodes.flatten!
|
26
27
|
raise "Non-node child given!" if nodes.find { |node| node.class != Node }
|
27
|
-
|
28
|
+
# This node becomes the proud parent of one or more node!
|
29
|
+
nodes.each { |node| node.parent = self }
|
30
|
+
@children.concat nodes
|
28
31
|
end
|
29
32
|
|
30
33
|
# Adds one or more properties to the node.
|
data/lib/sgf/parser/tree.rb
CHANGED
@@ -13,20 +13,19 @@ module SgfParser
|
|
13
13
|
# options: \n
|
14
14
|
# :filename => filename \n
|
15
15
|
# !!! OR !!! \n
|
16
|
-
# :
|
16
|
+
# :string => string \n
|
17
17
|
def initialize args={}
|
18
18
|
@root = Node.new
|
19
19
|
@sgf = ""
|
20
|
-
raise ArgumentError, "Both file and string provided" if args[:filename] &&
|
21
|
-
|
22
|
-
if !args[:filename].nil?
|
20
|
+
raise ArgumentError, "Both file and string provided" if args[:filename] && args[:string]
|
21
|
+
if args[:filename]
|
23
22
|
load_file args[:filename]
|
24
|
-
elsif
|
25
|
-
load_string args[:
|
23
|
+
elsif args[:string]
|
24
|
+
load_string args[:string]
|
26
25
|
end
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
parse unless @sgf.empty?
|
28
|
+
end
|
30
29
|
|
31
30
|
# Iterates over the tree, node by node, in preorder fashion.
|
32
31
|
# Does not support other types of iteration, but may in the future.
|
@@ -36,70 +35,65 @@ module SgfParser
|
|
36
35
|
when :preorder
|
37
36
|
preorder @root, &block
|
38
37
|
end
|
39
|
-
end
|
38
|
+
end
|
40
39
|
|
41
40
|
# Compares a tree to another tree, node by node.
|
42
|
-
# Nodes must
|
41
|
+
# Nodes must be the same (same properties, parents and children).
|
43
42
|
def == other_tree
|
44
43
|
one = []
|
45
44
|
two = []
|
46
45
|
each { |node| one << node }
|
47
46
|
other_tree.each { |node| two << node }
|
48
47
|
one == two
|
49
|
-
end
|
48
|
+
end
|
50
49
|
|
51
50
|
# Saves the tree as an SGF file. raises an error if a filename is not given.
|
52
51
|
# tree.save :filename => file_name
|
53
52
|
def save args={}
|
54
53
|
raise ArgumentError, "No file name provided" if args[:filename].nil?
|
55
54
|
# SGF files are trees stored in pre-order traversal.
|
56
|
-
@
|
55
|
+
@savable_sgf = "("
|
57
56
|
@root.children.each { |child| write_node child }
|
58
57
|
# write_node @root
|
59
|
-
@
|
58
|
+
@savable_sgf << ")"
|
60
59
|
|
61
|
-
File.open(args[:filename], 'w') { |f| f << @
|
62
|
-
end
|
60
|
+
File.open(args[:filename], 'w') { |f| f << @savable_sgf }
|
61
|
+
end
|
63
62
|
|
64
63
|
private
|
65
64
|
|
66
|
-
# Adds a stringified node to the variable @
|
65
|
+
# Adds a stringified node to the variable @savable_sgf.
|
67
66
|
def write_node node=@root
|
68
|
-
@
|
67
|
+
@savable_sgf << ";"
|
69
68
|
unless node.properties.empty?
|
70
69
|
properties = ""
|
71
70
|
node.properties.each do |k, v|
|
72
71
|
v_escaped = v.gsub("]", "\\]")
|
73
72
|
properties += "#{k.to_s}[#{v_escaped}]"
|
74
73
|
end
|
75
|
-
@
|
74
|
+
@savable_sgf << "#{properties}"
|
76
75
|
end
|
77
76
|
|
78
77
|
case node.children.size
|
79
78
|
when 0
|
80
|
-
@
|
79
|
+
@savable_sgf << ")"
|
81
80
|
when 1
|
82
81
|
write_node node.children[0]
|
83
82
|
else
|
84
83
|
node.each_child do |child|
|
85
|
-
@
|
84
|
+
@savable_sgf << "("
|
86
85
|
write_node child
|
87
86
|
end
|
88
87
|
end
|
89
88
|
end
|
90
89
|
|
91
|
-
# Used to load and parse a string if a string was given.
|
92
90
|
def load_string string
|
93
91
|
@sgf = string
|
94
|
-
|
95
|
-
end # load_string
|
92
|
+
end
|
96
93
|
|
97
|
-
# Used to load and parse a file if a file was given.
|
98
94
|
def load_file filename
|
99
|
-
@sgf = ""
|
100
95
|
File.open(filename, 'r') { |f| @sgf = f.read }
|
101
|
-
|
102
|
-
end # load_file
|
96
|
+
end
|
103
97
|
|
104
98
|
# Traverse the tree in preorder fashion, starting with the @root node if
|
105
99
|
# no node is given, and activating the passed block on each.
|
@@ -1,111 +1,97 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
1
3
|
module SgfParser
|
2
4
|
class Tree
|
3
5
|
|
4
6
|
private
|
5
7
|
|
6
|
-
#
|
8
|
+
# Creates a tree (truly, a linked list) from @sgf.
|
7
9
|
def parse
|
10
|
+
while char = next_character
|
11
|
+
case char
|
12
|
+
when '(' then store_branch
|
13
|
+
when ')' then fetch_branch
|
14
|
+
when ';' then store_node_and_create_new_node
|
15
|
+
when '[' then get_and_store_property
|
16
|
+
else store_character(char)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_character
|
22
|
+
character_available? && @stream.sysread(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def character_available?
|
26
|
+
@stream ||= StringIO.new clean_string, 'r'
|
27
|
+
!@stream.eof?
|
28
|
+
end
|
29
|
+
|
30
|
+
def clean_string
|
8
31
|
@sgf.gsub! "\\\\n\\\\r", ""
|
9
32
|
@sgf.gsub! "\\\\r\\\\n", ""
|
10
33
|
@sgf.gsub! "\\\\r", ""
|
11
34
|
@sgf.gsub! "\\\\n", ""
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
else
|
52
|
-
# Make the current node the old node, make new node, store data
|
53
|
-
parent = current_node
|
54
|
-
current_node = Node.new :parent => parent
|
55
|
-
parent.add_properties content
|
56
|
-
parent.add_children current_node
|
57
|
-
param, property = "", ""
|
58
|
-
content.clear
|
59
|
-
end
|
60
|
-
when '[' # Open identprop?
|
61
|
-
if identprop
|
62
|
-
property << char
|
63
|
-
else # If we're not inside an identprop, then now we are.
|
64
|
-
identprop = true
|
65
|
-
end_of_a_series = false
|
66
|
-
end
|
67
|
-
when ']' # Close identprop
|
68
|
-
# Cleverness : checking for this first, then for the backslash.
|
69
|
-
# This means that if we encounter this, it must be closing an identprop.
|
70
|
-
# Because the "\\" code handles the logic to see if we're inside an identprop,
|
71
|
-
# And for skipping the bracket if necessary.
|
72
|
-
end_of_a_series = true # Maybe end of a series of identprop.
|
73
|
-
identprop = false # That's our cue to close an identprop.
|
74
|
-
content[param] = property
|
75
|
-
property = ""
|
76
|
-
when "\\"
|
77
|
-
# If we're inside a comment, then maybe we're about to escape a ].
|
78
|
-
# This is the whole reason we need this ugly loop.
|
79
|
-
if identprop
|
80
|
-
# If the next character is a closing bracket, then it's just
|
81
|
-
# escaped and the identprop continues.
|
82
|
-
if sgf_array[iterator + 1] == "]"
|
83
|
-
property << "]"
|
84
|
-
# On the next pass through, we will skip that bracket.
|
85
|
-
iterator += 1
|
86
|
-
else
|
87
|
-
property << "\\"
|
88
|
-
end
|
89
|
-
else
|
90
|
-
#This should never happen - a backslash outside an identprop ?!
|
91
|
-
#But let's not have it be told that I'm not prepared.
|
92
|
-
param << "\\"
|
93
|
-
end
|
94
|
-
when "\n"
|
95
|
-
property << "\n" if identprop
|
96
|
-
|
97
|
-
else
|
98
|
-
# Well, I guess it's "just" a character after all.
|
99
|
-
if end_of_a_series
|
100
|
-
end_of_a_series = false
|
101
|
-
param, property = "", ""
|
102
|
-
end
|
103
|
-
identprop ? (property << char) : param << char
|
104
|
-
end
|
105
|
-
iterator += 1
|
35
|
+
@sgf
|
36
|
+
end
|
37
|
+
|
38
|
+
def store_branch
|
39
|
+
@branches ||= []
|
40
|
+
@branches.unshift @current_node
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_node
|
44
|
+
@current_node ||= @root
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_branch
|
48
|
+
@current_node = @branches.shift
|
49
|
+
clear_temporary_data
|
50
|
+
end
|
51
|
+
|
52
|
+
def store_node_and_create_new_node
|
53
|
+
parent = current_node
|
54
|
+
@current_node = Node.new :parent => parent
|
55
|
+
parent.add_properties content
|
56
|
+
parent.add_children @current_node
|
57
|
+
clear_temporary_data
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_and_store_property
|
61
|
+
@content[@identity] ||= ""
|
62
|
+
@content[@identity] << get_property
|
63
|
+
@identity = ""
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_property
|
67
|
+
buffer = ""
|
68
|
+
while true
|
69
|
+
next_bit = @stream.sysread(1)
|
70
|
+
break if next_bit == "]"
|
71
|
+
next_bit << @stream.sysread(1) if next_bit == "\\"
|
72
|
+
next_bit = "]" if next_bit == "\\]"
|
73
|
+
buffer << next_bit
|
106
74
|
end
|
75
|
+
buffer
|
76
|
+
end
|
107
77
|
|
78
|
+
def store_character(char)
|
79
|
+
@identity << char unless char == "\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear_temporary_data
|
83
|
+
@content.clear
|
84
|
+
@identity = ""
|
85
|
+
end
|
86
|
+
|
87
|
+
def content
|
88
|
+
@content ||= {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def identity
|
92
|
+
@identity ||= ""
|
108
93
|
end
|
109
94
|
|
110
95
|
end
|
111
|
-
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module SgfParser
|
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
|
+
|
data/lib/sgf_parser.rb
CHANGED
data/spec/tree_spec.rb
CHANGED
data/tryme.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SgfParser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Aldric Giacomoni
|
@@ -9,19 +14,23 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-04-30 00:00:00 -04:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: rspec
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 9
|
23
31
|
version: 1.2.9
|
24
|
-
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
25
34
|
description: SGFParser is a library that parses and saves SGF (Smart Game Format) files.
|
26
35
|
email: aldric@trevoke.net
|
27
36
|
executables: []
|
@@ -43,16 +52,16 @@ files:
|
|
43
52
|
- lib/sgf/parser/properties.rb
|
44
53
|
- lib/sgf/parser/tree.rb
|
45
54
|
- lib/sgf/parser/tree_parse.rb
|
46
|
-
- lib/sgf/
|
55
|
+
- lib/sgf/sgf_indent.rb
|
47
56
|
- lib/sgf_parser.rb
|
48
57
|
- sample_sgf/ff4_ex.sgf
|
49
|
-
- sample_sgf/ff4_ex_saved.sgf
|
50
58
|
- sample_sgf/redrose-tartrate.sgf
|
51
59
|
- sample_usage/parsing_files.rb
|
52
60
|
- spec/node_spec.rb
|
53
61
|
- spec/spec.opts
|
54
62
|
- spec/spec_helper.rb
|
55
63
|
- spec/tree_spec.rb
|
64
|
+
- tryme.rb
|
56
65
|
has_rdoc: true
|
57
66
|
homepage: http://github.com/Trevoke/SGFParser
|
58
67
|
licenses: []
|
@@ -66,22 +75,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
75
|
requirements:
|
67
76
|
- - ">="
|
68
77
|
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
69
80
|
version: "0"
|
70
|
-
version:
|
71
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
82
|
requirements:
|
73
83
|
- - ">="
|
74
84
|
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
75
87
|
version: "0"
|
76
|
-
version:
|
77
88
|
requirements: []
|
78
89
|
|
79
90
|
rubyforge_project:
|
80
|
-
rubygems_version: 1.3.
|
91
|
+
rubygems_version: 1.3.6
|
81
92
|
signing_key:
|
82
93
|
specification_version: 3
|
83
94
|
summary: A library for working with SGF files.
|
84
95
|
test_files:
|
85
96
|
- spec/node_spec.rb
|
86
97
|
- spec/spec_helper.rb
|
98
|
+
- spec/tree_parser_spec.rb
|
87
99
|
- spec/tree_spec.rb
|
data/lib/sgf/sgfindent.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
module SgfParser
|
2
|
-
|
3
|
-
# This indents an SGF file to make it more readable. It outputs to the screen
|
4
|
-
# by default, but can be given a file as output.
|
5
|
-
# Usage: SgfParser::Indenter.new infile, outfile
|
6
|
-
class Indenter
|
7
|
-
|
8
|
-
def initialize file, out=$stdout
|
9
|
-
sgf = ""
|
10
|
-
File.open(file) { |f| sgf = f.read }
|
11
|
-
@new_string = ""
|
12
|
-
sgf.gsub! "\\\\n\\\\r", ""
|
13
|
-
sgf.gsub! "\\\\r\\\\n", ""
|
14
|
-
sgf.gsub! "\\\\r", ""
|
15
|
-
sgf.gsub! "\\\\n", ""
|
16
|
-
#sgf.gsub! "\n", ""
|
17
|
-
|
18
|
-
end_of_a_series = false
|
19
|
-
identprop = false # We are not in the middle of an identprop value.
|
20
|
-
# An identprop is an identity property - a value.
|
21
|
-
|
22
|
-
sgf_array = sgf.split(//)
|
23
|
-
iterator = 0
|
24
|
-
array_length = sgf_array.size
|
25
|
-
indent = 0
|
26
|
-
|
27
|
-
while iterator < array_length - 1
|
28
|
-
char = sgf_array[iterator]
|
29
|
-
|
30
|
-
case char
|
31
|
-
when '(' # Opening a new branch
|
32
|
-
if !identprop
|
33
|
-
@new_string << "\n"
|
34
|
-
indent += 2
|
35
|
-
#tabulate indent
|
36
|
-
@new_string << " " * indent
|
37
|
-
end
|
38
|
-
@new_string << char
|
39
|
-
|
40
|
-
when ')' # Closing a branch
|
41
|
-
@new_string << char
|
42
|
-
if !identprop
|
43
|
-
@new_string << "\n"
|
44
|
-
indent -= 2
|
45
|
-
@new_string << " " * indent
|
46
|
-
#tabulate indent
|
47
|
-
end
|
48
|
-
|
49
|
-
when ';' # Opening a new node
|
50
|
-
if !identprop
|
51
|
-
@new_string << "\n"
|
52
|
-
@new_string << " " * indent
|
53
|
-
#tabulate indent
|
54
|
-
end
|
55
|
-
@new_string << char
|
56
|
-
|
57
|
-
when '[' # Open comment?
|
58
|
-
if !identprop #If we're not inside a comment, then now we are.
|
59
|
-
identprop = true
|
60
|
-
end_of_a_series = false
|
61
|
-
end
|
62
|
-
@new_string << char
|
63
|
-
|
64
|
-
when ']' # Close comment
|
65
|
-
end_of_a_series = true # Maybe end of a series of comments.
|
66
|
-
identprop = false # That's our cue to close a comment.
|
67
|
-
@new_string << char
|
68
|
-
|
69
|
-
when "\\" # If we're inside a comment, then maybe we're about to escape a ].
|
70
|
-
# This is the whole reason we need this ugly charade of a loop.
|
71
|
-
if identprop
|
72
|
-
if sgf_array[iterator + 1] == "]"
|
73
|
-
@new_string << "\\]"
|
74
|
-
iterator += 1
|
75
|
-
else
|
76
|
-
@new_string << "\\"
|
77
|
-
end
|
78
|
-
else
|
79
|
-
#This should never happen - a backslash outside a comment ?!
|
80
|
-
#But let's not have it be told that I'm not prepared.
|
81
|
-
@new_string << "\\"
|
82
|
-
end
|
83
|
-
|
84
|
-
when "\n"
|
85
|
-
@new_string << "\n"
|
86
|
-
@new_string << " " * indent
|
87
|
-
#tabulate indent
|
88
|
-
|
89
|
-
else
|
90
|
-
# Well, I guess it's "just" a character after all.
|
91
|
-
if end_of_a_series
|
92
|
-
end_of_a_series = false
|
93
|
-
end
|
94
|
-
@new_string << char
|
95
|
-
end
|
96
|
-
|
97
|
-
iterator += 1
|
98
|
-
end
|
99
|
-
|
100
|
-
if out == $stdout
|
101
|
-
$stdout << @new_string
|
102
|
-
else
|
103
|
-
File.open(out, 'w') { |file| file << @new_string }
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
private
|
109
|
-
|
110
|
-
def tabulate indent
|
111
|
-
indent.times { print " " }
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
data/sample_sgf/ff4_ex_saved.sgf
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
(;FF[4]AP[Primiview:3.1]GM[1]SZ[19]GN[Gametree 1: properties]US[Arno Hollosi](;B[pd]N[Moves, comments, annotations]C[Nodename set to: "Moves, comments, annotations"];W[dp]C[Marked as "Good for White"]GW[1];B[pp]C[Marked as "Very good for Black"]GB[2];W[dc]C[Marked as "Very good for White"]GW[2];B[pj]C[Marked as "Even position"]DM[1];W[ci]UC[1]C[Marked as "Unclear position"];B[jd]C[Marked as "Tesuji" or "Good move"]TE[1];BM[2]W[jp]C[Marked as "Very bad move"];DO[]B[gd]C[Marked as "Doubtful move"];IT[]W[de]C[Marked as "Interesting move"];B[jj];W[]C[White "Pass" move];)(;AB[do:gq]N[Setup]C[Black & white stones at the top are added as single stones.
|
2
|
-
|
3
|
-
Black & white stones at the bottom are added using compressed point lists.] AW[pn:pq];C[AddEmpty
|
4
|
-
|
5
|
-
Black stones & stones of left white group are erased in FF[3\] way.
|
6
|
-
|
7
|
-
White stones at bottom right were erased using compressed point list.]AE[pn:pq];AW[pp]AB[pd]C[Added two stones.
|
8
|
-
|
9
|
-
Node marked with "Black to play".]PL[B];)(;AW[er]AB[mr]N[Markup]C[Position set up without compressed point lists.]; SL[kh:kj] TW[lr:ns] TB[ao]C[Markup at top partially using compressed point lists (for markup on white stones); listed clockwise, starting at upper left:
|
10
|
-
- TR (triangle)
|
11
|
-
- CR (circle)
|
12
|
-
- SQ (square)
|
13
|
-
- SL (selected points)
|
14
|
-
- MA ('X')
|
15
|
-
|
16
|
-
Markup at bottom: black & white territory (using compressed point lists)] CR[pd:pf]TR[fd:ff] SQ[ph:pj] MA[fh:fj];LB[ms:12345678]C[Label (LB property)
|
17
|
-
|
18
|
-
Top: 8 single char labels (1-4, a-d)
|
19
|
-
|
20
|
-
Bottom: Labels up to 8 char length.];)(;B[qd]N[Style & text type]C[There are hard linebreaks & soft linebreaks.
|
21
|
-
Soft linebreaks are linebreaks preceeded by '\\' like this one >o\
|
22
|
-
k<. Hard line breaks are all other linebreaks.
|
23
|
-
Soft linebreaks are converted to >nothing<, i.e. removed.
|
24
|
-
|
25
|
-
Note that linebreaks are coded differently on different systems.
|
26
|
-
|
27
|
-
Examples (>ok< shouldn't be split):
|
28
|
-
|
29
|
-
linebreak 1 "": >o\
|
30
|
-
k<
|
31
|
-
linebreak 2 "": >o\
|
32
|
-
|
33
|
-
linebreak 3 "": >o\
|
34
|
-
k<
|
35
|
-
linebreak 4 "": >o\
|
36
|
-
Black stones left (in this byo-yomi period): 10]OB[10]BL[105.6];W[qq]C[White time left: 200 sec
|
37
|
-
White stones left: 2]OW[2]WL[200];B[sr]C[Black time left: 87 sec
|
38
|
-
Black stones left: 9]OB[9]BL[87.00];W[qs]C[White time left: 13.2 sec
|
39
|
-
White stones left: 1]OW[1]WL[13.20];B[rs]C[One white stone at s2 captured];W[ps];B[pr];W[or]MN[2]C[Set move number to 2];B[os]C[Two white stones captured
|
40
|
-
(at q1 & r1)];MN[112]W[pq]C[Set move number to 112];B[sq];W[rp];B[ps];W[ns];B[ss];W[nr];B[rr];W[sp];);FF[4]C[Gametree 2: game-info
|
41
|
-
|
42
|
-
Game-info properties are usually stored in the root node.
|
43
|
-
If games are merged into a single game-tree, they are stored in the node\
|
44
|
-
where the game first becomes distinguishable from all other games in\
|
45
|
-
the tree.]AP[Primiview:3.1]GM[1]SZ[19];B[pd](;)(;)(;W[ep];B[pp](;)(;))
|