SgfParser 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.2@jrun
1
+ rvm 1.9.3@sgfparser
data/Gemfile CHANGED
@@ -1,13 +1,3 @@
1
- # A sample Gemfile
2
1
  source "http://rubygems.org"
3
2
 
4
-
5
- group :development do
6
- gem "jeweler"
7
- gem "rcov"
8
- gem "rdoc"
9
- end
10
-
11
- group :test do
12
- gem "rspec"
13
- end
3
+ gemspec
@@ -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.2)
5
- git (1.2.5)
6
- jeweler (1.6.4)
7
- bundler (~> 1.0)
8
- git (>= 1.2.5)
9
- rake
10
- rake (0.9.2)
11
- rcov (0.9.9)
12
- rdoc (3.9)
13
- rspec (2.6.0)
14
- rspec-core (~> 2.6.0)
15
- rspec-expectations (~> 2.6.0)
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.6.0)
22
+ rspec-mocks (2.7.0)
21
23
 
22
24
  PLATFORMS
23
25
  ruby
24
26
 
25
27
  DEPENDENCIES
26
- jeweler
28
+ SgfParser!
29
+ rake
27
30
  rcov
28
31
  rdoc
29
32
  rspec
@@ -1,44 +1,18 @@
1
1
  =INFORMATION
2
- Author: Aldric Giacomoni
2
+ Author: Aldric Giacomoni || Email : trevoke@gmail.com
3
3
 
4
- Email : aldric@at@trevoke.net (feedback very welcome!)
4
+ Build status {<img src="https://secure.travis-ci.org/Trevoke/SGFParser.png" />}[http://travis-ci.org/Trevoke/SGFParser]
5
5
 
6
- SGF: all formats (but untested with FF < 4)
6
+ Are you using this gem? Is there functionality you wish it had? Is something hard to do?
7
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.
8
+ Let me know and I'll make it possible, or easier!
17
9
 
18
- Most games will just care about, say,
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
- 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.
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
- 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
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
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "SgfParser"
8
- gem.summary = %Q{A library for working with SGF files.}
9
- gem.description = %Q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
10
- gem.email = "aldric@trevoke.net"
11
- gem.homepage = "http://github.com/Trevoke/SGFParser"
12
- gem.authors = ["Aldric Giacomoni"]
13
- gem.add_development_dependency "rspec", ">= 1.2.9"
14
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
- end
16
- Jeweler::GemcutterTasks.new
17
- rescue LoadError
18
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
- end
20
-
21
- require 'rspec/core/rake_task'
22
- desc 'Default: run specs.'
23
- task :default => :spec
24
-
25
- desc "Run specs"
26
- RSpec::Core::RakeTask.new do |spec|
27
- spec.pattern = "./spec/**/*_spec.rb"
28
- end
29
-
30
- RSpec::Core::RakeTask.new(:coverage) do |spec|
31
- spec.pattern = 'spec/**/*_spec.rb'
32
- spec.rcov = true
33
- spec.rcov_opts = ['--exclude', 'spec']
34
- end
35
-
36
- require 'rdoc/task'
37
- RDoc::Task.new do |rdoc|
38
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
-
40
- rdoc.rdoc_dir = 'rdoc'
41
- rdoc.title = "SgfParser #{version}"
42
- rdoc.rdoc_files.include('README*')
43
- rdoc.rdoc_files.include('lib/**/*.rb')
44
- end
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
@@ -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 = "1.0.0"
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.description = %q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
14
- s.email = %q{aldric@trevoke.net}
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
- ".document",
21
- ".rspec",
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
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
- s.add_development_dependency(%q<jeweler>, [">= 0"])
57
- s.add_development_dependency(%q<rcov>, [">= 0"])
58
- s.add_development_dependency(%q<rdoc>, [">= 0"])
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
 
@@ -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
@@ -1,8 +1,10 @@
1
1
  $: << File.dirname(__FILE__)
2
2
 
3
- require 'yaml'
3
+ require 'sgf/error'
4
+ require 'sgf/version'
4
5
  require 'sgf/properties'
5
6
  require 'sgf/node'
6
7
  require 'sgf/tree'
7
8
  require 'sgf/parser'
8
- require 'sgf/indenter'
9
+ require 'sgf/game'
10
+ require 'sgf/writer'
@@ -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
@@ -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