SgfParser 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +26 -0
- data/.rvmrc +1 -1
- data/Gemfile +1 -11
- data/Gemfile.lock +20 -17
- data/README.rdoc +9 -35
- data/Rakefile +105 -44
- data/SgfParser.gemspec +14 -59
- data/bin/sgf_indent.rb +13 -0
- data/examples/simple_iteration_of_games_in_a_directory.rb +11 -0
- data/lib/sgf.rb +4 -2
- data/lib/sgf/error.rb +13 -0
- data/lib/sgf/game.rb +64 -0
- data/lib/sgf/node.rb +58 -11
- data/lib/sgf/parser.rb +121 -74
- data/lib/sgf/properties.rb +81 -88
- data/lib/sgf/tree.rb +39 -53
- data/lib/sgf/version.rb +3 -0
- data/lib/sgf/writer.rb +54 -0
- data/spec/data/example1.sgf +10 -0
- data/spec/game_spec.rb +70 -0
- data/spec/node_spec.rb +28 -2
- data/spec/parser_spec.rb +107 -10
- data/spec/spec_helper.rb +25 -1
- data/spec/tree_spec.rb +25 -18
- data/spec/writer_spec.rb +97 -0
- metadata +43 -28
- data/VERSION +0 -1
- data/lib/sgf/indenter.rb +0 -108
- data/sample_usage/parsing_files.rb +0 -19
- data/spec/data/ff4_ex_saved.sgf +0 -45
- data/spec/data/simple_saved.sgf +0 -7
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
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
|
+
doc
|
20
|
+
pkg
|
21
|
+
.bundle
|
22
|
+
|
23
|
+
## PROJECT::SPECIFIC
|
24
|
+
|
25
|
+
## Rubymine
|
26
|
+
.idea/*
|
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm 1.9.
|
1
|
+
rvm 1.9.3@sgfparser
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,29 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
SgfParser (1.0.1)
|
5
|
+
|
1
6
|
GEM
|
2
7
|
remote: http://rubygems.org/
|
3
8
|
specs:
|
4
|
-
diff-lcs (1.1.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rspec-mocks (~> 2.6.0)
|
17
|
-
rspec-core (2.6.4)
|
18
|
-
rspec-expectations (2.6.0)
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
json (1.6.3)
|
11
|
+
rake (0.9.2.2)
|
12
|
+
rcov (0.9.11)
|
13
|
+
rdoc (3.12)
|
14
|
+
json (~> 1.4)
|
15
|
+
rspec (2.7.0)
|
16
|
+
rspec-core (~> 2.7.0)
|
17
|
+
rspec-expectations (~> 2.7.0)
|
18
|
+
rspec-mocks (~> 2.7.0)
|
19
|
+
rspec-core (2.7.1)
|
20
|
+
rspec-expectations (2.7.0)
|
19
21
|
diff-lcs (~> 1.1.2)
|
20
|
-
rspec-mocks (2.
|
22
|
+
rspec-mocks (2.7.0)
|
21
23
|
|
22
24
|
PLATFORMS
|
23
25
|
ruby
|
24
26
|
|
25
27
|
DEPENDENCIES
|
26
|
-
|
28
|
+
SgfParser!
|
29
|
+
rake
|
27
30
|
rcov
|
28
31
|
rdoc
|
29
32
|
rspec
|
data/README.rdoc
CHANGED
@@ -1,44 +1,18 @@
|
|
1
1
|
=INFORMATION
|
2
|
-
Author: Aldric Giacomoni
|
2
|
+
Author: Aldric Giacomoni || Email : trevoke@gmail.com
|
3
3
|
|
4
|
-
|
4
|
+
Build status {<img src="https://secure.travis-ci.org/Trevoke/SGFParser.png" />}[http://travis-ci.org/Trevoke/SGFParser]
|
5
5
|
|
6
|
-
|
6
|
+
Are you using this gem? Is there functionality you wish it had? Is something hard to do?
|
7
7
|
|
8
|
-
|
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.
|
8
|
+
Let me know and I'll make it possible, or easier!
|
17
9
|
|
18
|
-
|
19
|
-
tree.root.children[0] which is the first node of the first gametree.
|
10
|
+
SGF: FF4 - may support earlier ones as well, but untested.
|
20
11
|
|
21
|
-
|
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.
|
12
|
+
Ruby: >=1.8.7 (may work with 1.8.6)
|
33
13
|
|
34
|
-
|
14
|
+
WARNING: An implementation requirement is to make sure any closing bracket ']' inside a comment is escaped: '\\]'. If this is not done, you will be one sad panda! This library will do this for you upon saving, but will most likely die horribly when parsing anything which does not follow this rule.
|
35
15
|
|
36
|
-
|
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
|
16
|
+
I'm honestly hoping that this is and remains the fastest SGF parser in Ruby. On my desktop, loading the SGF library and parsing Kogo's Joseki dictionary takes a little under six seconds.
|
44
17
|
|
18
|
+
Documentation available at the Github wiki: https://github.com/Trevoke/SGFParser/wiki
|
data/Rakefile
CHANGED
@@ -1,44 +1,105 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
desc 'Default: run specs.'
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc "Run specs"
|
10
|
+
RSpec::Core::RakeTask.new do |spec|
|
11
|
+
spec.pattern = "./spec/**/*_spec.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec::Core::RakeTask.new(:coverage) do |spec|
|
15
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
16
|
+
spec.rcov = true
|
17
|
+
spec.rcov_opts = ['--exclude', 'spec']
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rdoc/task'
|
21
|
+
RDoc::Task.new do |rdoc|
|
22
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
23
|
+
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = "SgfParser #{version}"
|
26
|
+
rdoc.rdoc_files.include('README*')
|
27
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Handle gem version"
|
31
|
+
namespace 'version' do
|
32
|
+
desc "Bump major version number"
|
33
|
+
task "bump:major" do
|
34
|
+
bump :major
|
35
|
+
end
|
36
|
+
desc "Bump minor version number"
|
37
|
+
task "bump:minor" do
|
38
|
+
bump :minor
|
39
|
+
end
|
40
|
+
desc "Bump patch version number"
|
41
|
+
task "bump:patch" do
|
42
|
+
bump :patch
|
43
|
+
end
|
44
|
+
desc "write out a specified version (rake version:write[\"x.y.z\"])"
|
45
|
+
task "write", :version do |task, args|
|
46
|
+
change_version_to(args.version)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
VERSION_ASSIGNMENT_REGEXP = /\A\s*?VERSION\s*?=\s*?['"](.*?)['"]\s*?\z/mx
|
51
|
+
|
52
|
+
def bump bit_to_increment
|
53
|
+
change_version_to incremented_version(bit_to_increment)
|
54
|
+
end
|
55
|
+
|
56
|
+
def change_version_to(new_version)
|
57
|
+
File.open(version_file, 'w') { |f| f << new_version_file( new_version ) }
|
58
|
+
puts "New version is now #{new_version}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def incremented_version bit_to_increment
|
62
|
+
version = {}
|
63
|
+
version[:major], version[:minor], version[:patch] = current_version.split('.')
|
64
|
+
version[bit_to_increment] = version[bit_to_increment].to_i + 1
|
65
|
+
"#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def new_version_file new_version
|
69
|
+
lines_of_version_file.map do |line|
|
70
|
+
is_line_with_version_assignment?(line) ? %Q{ VERSION = "#{new_version}"\n} : line
|
71
|
+
end.join
|
72
|
+
end
|
73
|
+
|
74
|
+
def current_version
|
75
|
+
array_of_value_versions = lines_of_version_file.grep(VERSION_ASSIGNMENT_REGEXP) { $1 }
|
76
|
+
raise_too_many_lines_matched if array_of_value_versions.size > 1
|
77
|
+
raise_no_lines_matched if array_of_value_versions.empty?
|
78
|
+
array_of_value_versions.first
|
79
|
+
end
|
80
|
+
|
81
|
+
def lines_of_version_file
|
82
|
+
@content_of_version_file ||= File.readlines(version_file)
|
83
|
+
end
|
84
|
+
|
85
|
+
def version_file
|
86
|
+
file_array = Dir['*/**/version.rb']
|
87
|
+
raise_too_many_files_found if file_array.size > 1
|
88
|
+
file_array.first
|
89
|
+
end
|
90
|
+
|
91
|
+
def is_line_with_version_assignment? line
|
92
|
+
!!(line[VERSION_ASSIGNMENT_REGEXP])
|
93
|
+
end
|
94
|
+
|
95
|
+
def raise_too_many_files_found
|
96
|
+
raise ArgumentError, "There are two files called version.rb and I do not know which one to use. Override the version_file method in your Rakefile and provide the correct path."
|
97
|
+
end
|
98
|
+
|
99
|
+
def raise_too_many_lines_matched
|
100
|
+
raise ArgumentError, "There are more than one line in version.rb which assign a value to VERSION. This is almost certainly a mistake. At the very least, I have no idea what I'm supposed to do. You must either increment the version manually in the file, or change the file so it only assigns VERSION once."
|
101
|
+
end
|
102
|
+
|
103
|
+
def raise_no_lines_matched
|
104
|
+
raise ArgumentError, "I did not find anything in the version file matching a version assignment."
|
105
|
+
end
|
data/SgfParser.gemspec
CHANGED
@@ -1,73 +1,28 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sgf/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
6
|
s.name = %q{SgfParser}
|
8
|
-
s.version =
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
7
|
+
s.version = SGF::VERSION
|
11
8
|
s.authors = ["Aldric Giacomoni"]
|
9
|
+
s.email = %q{trevoke@gmail.com}
|
10
|
+
s.homepage = %q{http://github.com/Trevoke/SGFParser}
|
12
11
|
s.date = %q{2011-08-01}
|
13
|
-
s.
|
14
|
-
s.
|
12
|
+
s.summary = %q{A library that parses and saves SGF (Smart Game Format) files.}
|
13
|
+
s.description = %q{SGF::Parser does standard stream parsing of the SGF file, instead of using an AG or some other auto-generated newfangled parser stuff. It is therefore faster to use, and hopefully will also be easier to use. Feedback helps :)}
|
15
14
|
s.extra_rdoc_files = [
|
16
15
|
"LICENSE",
|
17
16
|
"README.rdoc"
|
18
17
|
]
|
19
|
-
s.files
|
20
|
-
"
|
21
|
-
|
22
|
-
".rvmrc",
|
23
|
-
"Gemfile",
|
24
|
-
"Gemfile.lock",
|
25
|
-
"LICENSE",
|
26
|
-
"README.rdoc",
|
27
|
-
"Rakefile",
|
28
|
-
"SgfParser.gemspec",
|
29
|
-
"VERSION",
|
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",
|
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",
|
42
|
-
"spec/node_spec.rb",
|
43
|
-
"spec/parser_spec.rb",
|
44
|
-
"spec/spec_helper.rb",
|
45
|
-
"spec/tree_spec.rb"
|
46
|
-
]
|
47
|
-
s.homepage = %q{http://github.com/Trevoke/SGFParser}
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
48
21
|
s.require_paths = ["lib"]
|
49
|
-
s.rubygems_version = %q{1.6.2}
|
50
|
-
s.summary = %q{A library for working with SGF files.}
|
51
|
-
|
52
|
-
if s.respond_to? :specification_version then
|
53
|
-
s.specification_version = 3
|
54
22
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
-
else
|
61
|
-
s.add_dependency(%q<jeweler>, [">= 0"])
|
62
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
64
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
|
-
end
|
66
|
-
else
|
67
|
-
s.add_dependency(%q<jeweler>, [">= 0"])
|
68
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
70
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
71
|
-
end
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_development_dependency 'rcov'
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'rdoc'
|
72
27
|
end
|
73
28
|
|
data/bin/sgf_indent.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#Thankfully, whitespace doesn't matter in SGF, unless you're inside a comment.
|
2
|
+
#This makes it possible to indent an SGF file and let it be somewhat human-readable.
|
3
|
+
|
4
|
+
require '../lib/sgf'
|
5
|
+
|
6
|
+
if ARGV.size != 2
|
7
|
+
puts "Usage:\n sgf_indent source.sgf destination.sgf"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
parser = SGF::Parser.new
|
12
|
+
tree = parser.parse ARGV[0]
|
13
|
+
tree.save ARGV[1]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#Assuming you have the gem installed, of course
|
2
|
+
require 'sgf'
|
3
|
+
parser = SGF::Parser.new
|
4
|
+
|
5
|
+
Dir['*.sgf'].each do |file|
|
6
|
+
tree = parser.parse File.read(file)
|
7
|
+
tree.games.each do |game|
|
8
|
+
puts "White player: #{game.white_player} and Black player: #{game.black_player}"
|
9
|
+
end
|
10
|
+
tree.save file #Because may as well indent the files while I'm here, right?
|
11
|
+
end
|
data/lib/sgf.rb
CHANGED
data/lib/sgf/error.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module SGF
|
2
|
+
class MalformedDataError < StandardError
|
3
|
+
# error raised by parser
|
4
|
+
end
|
5
|
+
class FormatNotSpecifiedWarning < StandardError
|
6
|
+
# Warning if FF is not in first node
|
7
|
+
end
|
8
|
+
|
9
|
+
class NoIdentityError < StandardError
|
10
|
+
# Error if the user tries to read an identity that doesn't exist
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
data/lib/sgf/game.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module SGF
|
2
|
+
class Game
|
3
|
+
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
SGF::Game::PROPERTIES.each do |human_readable_method, sgf_identity|
|
7
|
+
define_method(human_readable_method.to_sym) do
|
8
|
+
@root[sgf_identity] ? @root[sgf_identity] : raise(SGF::NoIdentityError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :current_node, :root
|
13
|
+
|
14
|
+
#Takes a SGF::Node as an argument. It will be a problem if that node isn't
|
15
|
+
#really the first node of of a game (ie: no FF property)
|
16
|
+
def initialize node
|
17
|
+
raise ArgumentError, "Expected SGF::Node argument but received #{node.class}" unless node.instance_of? SGF::Node
|
18
|
+
@root = node
|
19
|
+
@current_node = node
|
20
|
+
end
|
21
|
+
|
22
|
+
#A simple way to go to the next node in the same branch of the tree
|
23
|
+
def next_node
|
24
|
+
@current_node = @current_node.children[0]
|
25
|
+
end
|
26
|
+
|
27
|
+
#Iterate through all the nodes in preorder fashion
|
28
|
+
def each &block
|
29
|
+
preorder @root, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
def node_count
|
33
|
+
count = 0
|
34
|
+
each { |node| count += 1 }
|
35
|
+
count
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"<SGF::Game:#{object_id}>"
|
40
|
+
end
|
41
|
+
|
42
|
+
alias :inspect :to_s
|
43
|
+
|
44
|
+
def to_str
|
45
|
+
SGF::Writer.new.stringify_tree_from @root
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def method_missing method_name, *args
|
51
|
+
human_readable_identity = method_name.to_s.downcase
|
52
|
+
identity = SGF::Game::PROPERTIES[human_readable_identity]
|
53
|
+
return @root[identity] if identity
|
54
|
+
super(method_name, args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def preorder node=@root, &block
|
58
|
+
yield node
|
59
|
+
node.each_child do |child|
|
60
|
+
preorder child, &block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|