SgfParser 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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](;)(;))
|