SgfParser 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 84fb3f7396b1239b5f0ca9e5df6c46e3990ecae0
4
+ data.tar.gz: 15e29afd726d23469558d382766447f0fe473b95
5
+ SHA512:
6
+ metadata.gz: d9780b3835dba470d819e8fbe99623806727b7188dd2c2e1c0745d7e778dadb38fbeeff767bb6996707b05b15cfd4c706a068d3fd3140d8d2478196ec062839c
7
+ data.tar.gz: 2f13b065d70201190e927d9da7f8100c2828f48abe7044fc555763a50efe0c964626fa655d54ac57dc9b090f05f79c7d5de88756598c9a02875c98801a6b534c
@@ -0,0 +1,13 @@
1
+ # Override autotest default magic to rerun all tests every time a
2
+ # change is detected on the file system.
3
+ class Autotest
4
+
5
+ def get_to_green
6
+ begin
7
+ rerun_all_tests
8
+ wait_for_changes unless all_good
9
+ end until all_good
10
+ end
11
+
12
+ end
13
+
data/.gitignore CHANGED
@@ -19,8 +19,17 @@ rdoc
19
19
  doc
20
20
  pkg
21
21
  .bundle
22
+ Gemfile.lock
22
23
 
23
24
  ## PROJECT::SPECIFIC
24
25
 
25
26
  ## Rubymine
26
27
  .idea/*
28
+
29
+ # ctags
30
+ tags
31
+ vendor/cache/
32
+
33
+ # rspec
34
+
35
+ .spec-examples.txt
data/.rspec CHANGED
@@ -1 +1,3 @@
1
- --color --format doc
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
@@ -0,0 +1 @@
1
+ 2.3.0
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rbenv:
3
+ - 2.2.0
@@ -0,0 +1,12 @@
1
+ == 3.0.0
2
+ - Rename Tree to Collection
3
+ - Rename Game to Gametree
4
+ - `SGF.parse` takes a filename and creates a collection
5
+ - Collection has `errors` properties collecting parsing problems
6
+ - Fix a bug preventing `node[:prop] = new_value` from working
7
+ - Fix a bug causing a collection to always generate new gametree objects
8
+
9
+ == 2.1.0
10
+ - Adding children and adding parents all works beautifully
11
+ - Node depth is set properly and updated properly
12
+ - Node.new takes an optional hash of properties with special keys :children and :parent
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
@@ -1,32 +1,58 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- SgfParser (1.0.1)
4
+ SgfParser (3.0.0)
5
5
 
6
6
  GEM
7
- remote: http://rubygems.org/
7
+ remote: https://rubygems.org/
8
8
  specs:
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)
9
+ byebug (8.2.4)
10
+ coderay (1.1.1)
11
+ diff-lcs (1.2.5)
12
+ docile (1.1.5)
13
+ json (1.8.3)
14
+ method_source (0.8.2)
15
+ pry (0.10.3)
16
+ coderay (~> 1.1.0)
17
+ method_source (~> 0.8.1)
18
+ slop (~> 3.4)
19
+ pry-byebug (3.3.0)
20
+ byebug (~> 8.0)
21
+ pry (~> 0.10)
22
+ rake (11.1.2)
23
+ rdoc (4.2.2)
14
24
  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)
21
- diff-lcs (~> 1.1.2)
22
- rspec-mocks (2.7.0)
25
+ rspec (3.4.0)
26
+ rspec-core (~> 3.4.0)
27
+ rspec-expectations (~> 3.4.0)
28
+ rspec-mocks (~> 3.4.0)
29
+ rspec-core (3.4.4)
30
+ rspec-support (~> 3.4.0)
31
+ rspec-expectations (3.4.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.4.0)
34
+ rspec-mocks (3.4.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.4.0)
37
+ rspec-support (3.4.1)
38
+ simplecov (0.11.2)
39
+ docile (~> 1.1.0)
40
+ json (~> 1.8)
41
+ simplecov-html (~> 0.10.0)
42
+ simplecov-html (0.10.0)
43
+ slop (3.6.0)
23
44
 
24
45
  PLATFORMS
25
46
  ruby
47
+ x64-mingw32
26
48
 
27
49
  DEPENDENCIES
28
50
  SgfParser!
51
+ pry-byebug
29
52
  rake
30
- rcov
31
53
  rdoc
32
54
  rspec
55
+ simplecov
56
+
57
+ BUNDLED WITH
58
+ 1.11.2
@@ -0,0 +1,97 @@
1
+ # SGFParser
2
+ [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Trevoke/SGFParser?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3
+
4
+ Build status {<img src="https://secure.travis-ci.org/Trevoke/SGFParser.png" />}[http://travis-ci.org/Trevoke/SGFParser]
5
+
6
+ # Intro
7
+ I'm 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. It's a 3MB file and the average SGF is maybe 10k, so on average it's rather snappy.
8
+
9
+ Are you using this gem? Is there functionality you wish it had? Is something hard to do? Does the documentation not make sense, and you know how to make it more helpful? Let me know and I'll make it possible, or easier!
10
+
11
+ # Supported versions
12
+ SGF: FF4 - may support earlier ones as well, but untested.
13
+ Ruby: >=2.1
14
+
15
+
16
+ # Intro to SGF
17
+ According to the standard, An SGF file holds a `Collection` of one or more `Gametree` objects. Each of those is made of a tree of `Node` objects.
18
+
19
+ In other words: FILE (1 ↔ ∞) Collection (1 ↔ ∞) Gametree (1 ↔ ∞) Node
20
+
21
+ ## Basics of our data structure
22
+
23
+ In this implementation, when you parse a file, you get a `Collection` back. This object has a root `Node` used as the top-level node for all gametrees. The children of that node are the root nodes of the actual games.
24
+
25
+ Assuming a common SGF file with a single game, you could get to the game by doing this:
26
+
27
+ ```ruby
28
+ SGF.parse(file).gametrees.first # => <SGF::Game:70180384181460>
29
+ ```
30
+
31
+ ## Basics of properties
32
+
33
+ Some properties belong on the root node of a game only, such as the identity of the players. For convenience, some human-readable methods are defined on the gametree object itself to reach this information, for instance
34
+
35
+ ```ruby
36
+ gametree.black_player # => "tartrate"
37
+ ```
38
+
39
+ Calling a property that is not defined in the current tree will result in an error. For instance, a property that does not exist in the game of Go:
40
+
41
+ ```ruby
42
+ gametree.black_octisquares # => SGF::NoIdentityError
43
+ ```
44
+
45
+ ## Basics of navigating
46
+
47
+ Since a game is a tree (each node can be the source of many variations), a convenience method is defined to help you traverse the main branch one node at a time.
48
+
49
+ ```ruby
50
+ gametree.current_node # => starts as root node, e.g. #<SGF::Node:70180384857820, Has a parent, 1 Children, 16 Properties>
51
+ gametree.next_node # => #<SGF::Node:70180384839420, Has a parent, 1 Children, 4 Properties>
52
+ gametree.current_node # => #<SGF::Node:70180384839420, Has a parent, 1 Children, 4 Properties>
53
+ ```
54
+
55
+ Since it's easy to get lost when you're looking at things one node at a time (or because sometimes you don't want to iterate with an index), we also provide a convenience `depth` method on a given node to tell you how far down the tree you are.
56
+
57
+ And since this is Ruby, all of the objects (`Collection`, `Gametree` and `Node`) provide iteration through `each`. Note that in this example, we are using a gametree, and iteration on a gametree starts from the gametree's root, so the depth is 1. Iteration on a collection starts from the collection's root, and that node's depth would be 0. Iteration on any node starts from that node and goes through all its children.
58
+
59
+ NOTE: iteration is done as preorder tree traversal. You shouldn't have to care about this, but you might.
60
+
61
+ ```ruby
62
+ gametree.each do |node|
63
+ puts "Node at depth #{node.depth} has #{node.properties.count} properties"
64
+ end
65
+ =begin
66
+ Node at depth 1 has 16 properties
67
+ Node at depth 2 has 4 properties
68
+ Node at depth 3 has 3 properties
69
+ Node at depth 4 has 4 properties
70
+ Node at depth 5 has 3 properties
71
+ Node at depth 6 has 3 properties
72
+ Node at depth 7 has 3 properties
73
+ Node at depth 8 has 4 properties
74
+ ... And so on
75
+ =end
76
+ ```
77
+
78
+ ## Basics of saving
79
+
80
+ There is `SGF::Writer`, which you can use starting from any node. There is also a convenience method on collection:
81
+
82
+ ```ruby
83
+ collection.save(filename) # => Shiny new text file
84
+ SGF::Writer.new.stringify_tree_from(node) # => Shiny string
85
+ SGF::Writer.new.save(node, filename) # => File with tree starting at node
86
+ ```
87
+
88
+ If you need a raw SGF version of your data, you can use `to_s`:
89
+
90
+ ```ruby
91
+ node.to_s
92
+ gametree.to_s
93
+ collection.to_s
94
+ ```
95
+
96
+ # SGF Parsing warning (À bon entendeur…)
97
+ 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.
@@ -10,19 +10,16 @@ Gem::Specification.new do |s|
10
10
  s.homepage = %q{http://github.com/Trevoke/SGFParser}
11
11
  s.date = %q{2011-08-01}
12
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 :)}
14
- s.extra_rdoc_files = [
15
- "LICENSE",
16
- "README.rdoc"
17
- ]
13
+ s.description = %q{SGF::Parser does standard stream parsing of the SGF file, instead of using an AG or some other auto-generated parser. It is therefore faster to use. It also intends to be very object-oriented and hopefully will also be easier to use.}
14
+ s.extra_rdoc_files = %w(LICENSE README.md)
18
15
  s.files = `git ls-files`.split("\n")
19
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
- s.require_paths = ["lib"]
18
+ s.require_paths = %w(lib)
22
19
 
23
20
  s.add_development_dependency 'rspec'
24
- s.add_development_dependency 'rcov'
21
+ s.add_development_dependency 'simplecov'
25
22
  s.add_development_dependency 'rake'
26
23
  s.add_development_dependency 'rdoc'
24
+ s.add_development_dependency 'pry-byebug'
27
25
  end
28
-
@@ -1,13 +1,14 @@
1
+ #!/usr/bin/env ruby
1
2
  #Thankfully, whitespace doesn't matter in SGF, unless you're inside a comment.
2
3
  #This makes it possible to indent an SGF file and let it be somewhat human-readable.
3
4
 
4
- require '../lib/sgf'
5
+ require_relative '../lib/sgf'
5
6
 
6
7
  if ARGV.size != 2
7
8
  puts "Usage:\n sgf_indent source.sgf destination.sgf"
8
- exit
9
+ exit 1
9
10
  end
10
11
 
11
12
  parser = SGF::Parser.new
12
- tree = parser.parse ARGV[0]
13
- tree.save ARGV[1]
13
+ collection = parser.parse ARGV[0]
14
+ collection.save ARGV[1]
@@ -3,9 +3,9 @@ require 'sgf'
3
3
  parser = SGF::Parser.new
4
4
 
5
5
  Dir['*.sgf'].each do |file|
6
- tree = parser.parse File.read(file)
7
- tree.games.each do |game|
6
+ collection = parser.parse File.read(file)
7
+ collection.gametrees.each do |game|
8
8
  puts "White player: #{game.white_player} and Black player: #{game.black_player}"
9
9
  end
10
- tree.save file #Because may as well indent the files while I'm here, right?
10
+ collection.save file #Because may as well indent the files while I'm here, right?
11
11
  end
data/lib/sgf.rb CHANGED
@@ -1,10 +1,19 @@
1
- $: << File.dirname(__FILE__)
1
+ module SGF
2
+ class FileDoesNotExistError < StandardError ; end
3
+ def self.parse(filename)
4
+ SGF::Parser.new.parse File.read(filename)
5
+ rescue Errno::ENOENT
6
+ raise FileDoesNotExistError
7
+ end
2
8
 
3
- require 'sgf/error'
4
- require 'sgf/version'
5
- require 'sgf/properties'
6
- require 'sgf/node'
7
- require 'sgf/tree'
8
- require 'sgf/parser'
9
- require 'sgf/game'
10
- require 'sgf/writer'
9
+ end
10
+
11
+ require_relative 'sgf/error'
12
+ require_relative 'sgf/version'
13
+ require_relative 'sgf/properties'
14
+ require_relative 'sgf/variation'
15
+ require_relative 'sgf/node'
16
+ require_relative 'sgf/collection'
17
+ require_relative 'sgf/parser'
18
+ require_relative 'sgf/gametree'
19
+ require_relative 'sgf/writer'
@@ -0,0 +1,75 @@
1
+ # Collection holds most of the logic, for now.
2
+ # It has all the nodes, can iterate over them, and can even save to a file!
3
+ class SGF::Collection
4
+ include Enumerable, Observable
5
+
6
+ attr_accessor :current_node, :errors, :gametrees
7
+ attr_reader :root
8
+
9
+ def initialize(root = SGF::Node.new)
10
+ @root = root
11
+ @current_node = @root
12
+ @root.add_observer(self)
13
+ @errors = []
14
+ @gametrees = @root.children.map do |root_of_tree|
15
+ SGF::Gametree.new(root_of_tree)
16
+ end
17
+ end
18
+
19
+ def each
20
+ gametrees.each do |game|
21
+ game.each do |node|
22
+ yield node
23
+ end
24
+ end
25
+ end
26
+
27
+ def <<(gametree)
28
+ unless gametree.instance_of?(SGF::Gametree)
29
+ raise ArgumentError, "Expected instance of class SGF::Gametree but was instance of #{gametree.class}"
30
+ end
31
+ @root.add_children gametree.root
32
+ end
33
+
34
+ # Compares a tree to another tree, node by node.
35
+ # Nodes must be the same (same properties, parents and children).
36
+ def == other
37
+ self.map { |node| node } == other.map { |node| node }
38
+ end
39
+
40
+ def inspect
41
+ out = "#<SGF::Collection:#{self.object_id}, "
42
+ out << "#{gametrees.count} Games, "
43
+ out << "#{node_count} Nodes"
44
+ out << ">"
45
+ end
46
+
47
+ def to_s
48
+ SGF::Writer.new.stringify_tree_from @root
49
+ end
50
+
51
+ # Saves the Collection as an SGF file. Takes a filename as argument.
52
+ def save filename
53
+ SGF::Writer.new.save(@root, filename)
54
+ end
55
+
56
+ def update(message, data)
57
+ case message
58
+ when :new_children
59
+ data.each do |new_gametree_root|
60
+ @gametrees << SGF::Gametree.new(new_gametree_root)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def node_count
68
+ gametrees.inject(0) { |sum, game| sum + game.count }
69
+ end
70
+
71
+ def method_missing method_name, *args
72
+ super(method_name, args) if @root.children.empty? || !@root.children[0].properties.has_key?(method_name)
73
+ @root.children[0].properties[method_name]
74
+ end
75
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'node'
2
+ require_relative 'collection'
3
+
4
+ class SGF::CollectionAssembler
5
+ attr_reader :collection
6
+
7
+ def initialize
8
+ @collection = SGF::Collection.new
9
+ @current_node = @collection.root
10
+ @branches = []
11
+ end
12
+
13
+ def open_branch
14
+ @branches.unshift @current_node
15
+ end
16
+
17
+ def close_branch
18
+ @current_node = @branches.shift
19
+ end
20
+
21
+ def create_node_with_properties properties
22
+ node = SGF::Node.new
23
+ @current_node.add_children node
24
+ @current_node = node
25
+ @current_node.add_properties properties
26
+ end
27
+
28
+ def add_error message
29
+ collection.errors << message
30
+ end
31
+
32
+ end
@@ -9,5 +9,4 @@ module SGF
9
9
  class NoIdentityError < StandardError
10
10
  # Error if the user tries to read an identity that doesn't exist
11
11
  end
12
-
13
- end
12
+ end