gotasku 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3a96fbb30bae291ebd35b1b6b7afb2ef18b1588a
4
+ data.tar.gz: 0ac6145dd789670da679de4e8e3b8f8d4ef6ac14
5
+ SHA512:
6
+ metadata.gz: 87b2eab023c391a130c2839167b7380e3e0a9371220f8a0e4e454c1d1b0d6b9e54b2e2330b9bf34b89736db79542041f1cc6480377bc5ec297bc40b3c7869fe5
7
+ data.tar.gz: 7c10a24044927d1dd28f6b70e7ed43a6a10160568e14c965eff37af71f783b252256250a945690ebf615b98026d50b7e29cdb55981d65846574ad3874ebe05e6
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 John Hager
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ gotasku
2
+ =======
3
+
4
+ Saving and opening tsumego
5
+
6
+ _Use_
7
+
8
+ *Problem*
9
+
10
+ New problem. You can provide "id", "sgf", or "data" options.
11
+ "id" => Integer (id of goproblem.com problem)
12
+ "sgf" => String or File of SGF
13
+ "data" => Hash with any of the following keys
14
+ ("id", "sgf", "difficulty",
15
+ "diff_num", "type", "rating")
16
+
17
+ Gotasku::Problem.new(options)
18
+
19
+ Gotasku::Problem.new("id" => 1000)
20
+
21
+ Gotasku::Problem.new("sgf" => "problem.sgf")
22
+ Gotasku::Problem.new("sgf" => "(;AB[aa]AW[ab]")
23
+
24
+ Gotasku::Problem.new("data" => {"id" => 1000,
25
+ "sgf" => "(;AB[aa]AW[ab])",
26
+ "difficulty" => "6 dan",
27
+ "diff_num" => -6,
28
+ "type" => "life and death",
29
+ "rating" => 3})
30
+
31
+ You can access Problem data with #sgf, #id, #difficulty, #type,
32
+ #rating, and #data instance methods
33
+
34
+ prob = Gotasku::Problem.new("id" => 10000)
35
+ prob.data
36
+ prob.difficulty
37
+
38
+ Gotasku use SgfParser to parse the SGF and save it.
39
+
40
+ You can get the tree from SGF with the #tree instance method
41
+
42
+ prob.tree
43
+
44
+ And you can save the file with the #save instance method
45
+
46
+ prob.save
47
+
48
+ The file will be saved as "#{id}.sgf" or using a time stamp if no id.
49
+
50
+ \*see MongoDB for more
51
+
52
+
53
+ *User*
54
+
55
+ You can create a user object.
56
+
57
+ user = Gotasku::User.new('username', 'password')
58
+
59
+ *Session*
60
+
61
+ You can login to goproblems.com by creating a new session object.
62
+ The current session is saved in the @@current\_session class variable
63
+
64
+ Gotasku::Session.new(user)
65
+
66
+
67
+ _*For MongoDB*_
68
+
69
+ If you have MongoDB installed, you can run the rake tasks from the
70
+ root directory of the gem:
71
+
72
+ rake db:create
73
+ rake problems
74
+
75
+ The first will create the database, the second will populate a
76
+ collection with all the go problems from goproblems.com.
77
+
78
+ _Querying_
79
+
80
+ You can build a query with Gotasku::Query.create (you will be
81
+ prompted on how you want to filter the problems collection.
82
+
83
+ Gotasku::Query.create
84
+
85
+ Or you can query the collection directly
86
+ Gotasku::Query.new(query\_hash)
87
+
88
+ Gotasku::Query.new({"id" => 1000})
89
+
90
+ This returns a Gotasku::Query object, with an instance variable
91
+ (@found) that holds the Mongo::Cursor object from the query.
92
+
93
+ query = Gotasku::Query.new({"type" => "life and death"})
94
+ query.found
95
+
96
+ Gotasku::Query has a #to\_a instance method to get an array from the
97
+ Mongo::Cursor object.
98
+
99
+ query.to\_a
100
+
101
+ *Problem*
102
+
103
+ index = 0
104
+ Gotasku::Problem.new("data", query.to\_a[index])
105
+
106
+ _Executable_
107
+
108
+ There is an executable in the bin folder, which you can add to your
109
+ PATH. It provides commands for getting data from or saving single
110
+ problems.
111
+
112
+ Use the help option to get more info:
113
+
114
+ $ path/to/gem/bin/gotasku --help
115
+
116
+
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ require_relative "../lib/gotasku"
6
+
7
+ def get_help
8
+ puts "\"gotasku #{ARGV.join(" ")}\" isn't a command I know."
9
+ puts "Try `gotasku --help`"
10
+ end
11
+
12
+ def problem(options)
13
+ Gotasku::Problem.new(options)
14
+ end
15
+
16
+ def prompt(text)
17
+ print(text)
18
+ $stdin.gets.chomp.strip
19
+ end
20
+
21
+ problem_commands = %w[ display sgf id save data difficulty
22
+ rating type ]
23
+ session_commands = %w[ login ]
24
+ rake_commands = %w[ test ]
25
+
26
+ options = {}
27
+ optparse = OptionParser.new do |opts|
28
+
29
+ opts.banner = "Gotasku"
30
+ opts.separator ""
31
+ opts.separator "Problem Commands"
32
+ problem_commands.each do |c|
33
+ opts.separator " #{c}"
34
+ end
35
+
36
+ opts.separator ""
37
+ opts.separator "Options"
38
+
39
+ options["id"] = nil
40
+ opts.on( '-i', '--id Num', Integer, "Provide the problem id" ) do |id|
41
+ options["id"] = id
42
+ end
43
+
44
+ options["sgf"] = nil
45
+ opts.on( '-s', '--sgf String|FILE',
46
+ "Provide the problem SGF" ) do |sgf|
47
+ options["sgf"] = sgf
48
+ end
49
+
50
+ opts.separator "Session Commands"
51
+ session_commands.each do |c|
52
+ opts.separator " #{c}"
53
+ end
54
+
55
+ opts.separator "Options"
56
+ options[:username] = nil
57
+ opts.on( '-u', '--username Username',
58
+ "Provide your Username" ) do |username|
59
+ options[:username] = username
60
+ end
61
+
62
+ options[:password] = nil
63
+ opts.on( '-p', '--password Password',
64
+ "Provide your Password" ) do |password|
65
+ options[:password] = password
66
+ end
67
+
68
+ opts.separator "Other Commands"
69
+ rake_commands.each do |c|
70
+ opts.separator " #{c}"
71
+ end
72
+
73
+ opts.separator "Help"
74
+ opts.on("-h","--help","help") do
75
+ puts optparse
76
+ end
77
+ end
78
+
79
+ optparse.parse!
80
+
81
+ # if multiple commands are given
82
+ if ARGV.length > 1
83
+ get_help
84
+ exit
85
+ end
86
+
87
+ if session_commands.include?(ARGV[0])
88
+ options[:username] ||= prompt("Username: ")
89
+ options[:password] ||= prompt("Password: ")
90
+
91
+ user = Gotasku::User.new(options[:username], options[:password])
92
+ p Gotasku::Session.new(user)
93
+ elsif problem_commands.include?(ARGV[0])
94
+ p problem(options).send(ARGV[0].to_sym)
95
+ elsif rake_commands.include?(ARGV[0])
96
+ puts `rake`
97
+ elsif ARGV[0].nil?
98
+ p problem(options)
99
+ else
100
+ get_help
101
+ end
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+ require 'mongo'
3
+
4
+ require_relative 'populate_database'
5
+
6
+ namespace :db do
7
+ desc "Create the database"
8
+ task :create do
9
+ client = Mongo::MongoClient.new
10
+ db = client.db('sgf')
11
+ collection = db.collection('owner')
12
+ collection.insert({name: "John"})
13
+ end
14
+
15
+ desc "Drop the database"
16
+ task :drop do
17
+ Mongo::MongoClient.new.drop_database('sgf')
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ require 'rake'
2
+ require 'mongo'
3
+ require_relative '../../lib/gotasku'
4
+
5
+ namespace :problems do
6
+ desc "Populate the problems collection"
7
+ task :populate do
8
+ client = Mongo::MongoClient.new
9
+ db = client.db('sgf')
10
+ collection = db.collection('problems')
11
+
12
+ (0..Gotasku::Problem.last_problem_id).each do |id|
13
+ if collection.find(id: id).first
14
+ print '_'
15
+ next
16
+ end
17
+
18
+ prob = Gotasku::Problem.new(id: id)
19
+
20
+ begin
21
+ if prob.blank? == false
22
+ collection.insert(prob.data)
23
+ print '.'
24
+ else
25
+ print '*'
26
+ end
27
+ rescue
28
+ print 'E'
29
+ end
30
+ end
31
+ end
32
+
33
+ desc "Structure the problems collection"
34
+ task :structure do
35
+ collection = Mongo::MongoClient.new.db('sgf').collection('problems')
36
+ collection.ensure_index(
37
+ {id: Mongo::ASCENDING},
38
+ {unique: true, dropDups: true})
39
+ collection.create_index(diff_num: Mongo::DESCENDING)
40
+ collection.create_index(rating: Mongo::DESCENDING)
41
+ collection.create_index("type")
42
+ end
43
+
44
+ desc "Populate and structure problems collection"
45
+ task default: [:populate, :structure]
46
+ end
@@ -0,0 +1,17 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'sgf'
4
+ require 'mechanize'
5
+ require 'mongo'
6
+
7
+ require_relative 'gotasku/gotasku'
8
+
9
+ require_relative 'gotasku/exceptions'
10
+ require_relative 'gotasku/problem'
11
+ require_relative 'gotasku/difficulty_string'
12
+ require_relative 'gotasku/rating_string'
13
+ require_relative 'gotasku/display'
14
+ require_relative 'gotasku/parser'
15
+ require_relative 'gotasku/user'
16
+ require_relative 'gotasku/session'
17
+ require_relative 'gotasku/query'
@@ -0,0 +1,22 @@
1
+ class Gotasku::DifficultyString < String
2
+
3
+ # convert difficulty string to number
4
+ def convert
5
+ case self
6
+ when /p/ #pro
7
+ # grab number, mulitply by -1 and subtract 10
8
+ (self.slice(/\d+/).to_i * -1) - 10
9
+ when /d/ #dan
10
+ # grab number, multiply by -1
11
+ (self.slice(/\d+/).to_i * -1)
12
+ when /k/ #kyu
13
+ # grab number
14
+ self.slice(/\d+/).to_i
15
+ else
16
+ nil
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+
@@ -0,0 +1,26 @@
1
+ class Gotasku::Display
2
+ def self.show_problem(problem)
3
+ puts "=" * 40
4
+ puts "id: #{problem.id}"
5
+ puts "type: #{problem.type}"
6
+ puts "rating: #{problem.rating}"
7
+ puts "difficulty: #{problem.difficulty}"
8
+ puts "=" * 40
9
+ end
10
+
11
+ def self.show_list(list)
12
+ list.each_with_index {|item, i| puts "#{i}...#{item}"}
13
+ end
14
+
15
+ def self.show(object)
16
+ case object
17
+ when ::Gotasku::Problem
18
+ self.show_problem(object)
19
+ when ::Array
20
+ self.show_list(object)
21
+ else
22
+ puts "Sorry, I can't show that.."
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,5 @@
1
+ class Gotasku::NotFound < Exception
2
+ end
3
+
4
+ class Gotasku::InvalidEntry < Exception
5
+ end
@@ -0,0 +1,4 @@
1
+ class Gotasku
2
+
3
+ end
4
+
@@ -0,0 +1,28 @@
1
+ class Gotasku::Parser < SGF::Parser
2
+
3
+ # validates and parses sgf string
4
+ def parse(sgf)
5
+ super(validate(sgf))
6
+ end
7
+
8
+ # validates sgf string for common goproblems.com issues
9
+ def validate(sgf)
10
+ validate_for(validate_for(sgf, :AB), :AW)
11
+ end
12
+
13
+ # validates for a property (AB or AW)
14
+ def validate_for(sgf, property)
15
+ regs = {AB: /AB/, AW: /AW/}
16
+
17
+ # find the first instance, all will be replaced and the first
18
+ # instance will be readded
19
+ start = sgf =~ regs[property]
20
+
21
+ if start
22
+ sgf.gsub!(regs[property], '')
23
+ sgf = sgf[0...start] + property.to_s + sgf[start..-1]
24
+ end
25
+
26
+ sgf
27
+ end
28
+ end
@@ -0,0 +1,151 @@
1
+ class Gotasku::Problem
2
+
3
+ @@uri = "http://www.goproblems.com/"
4
+
5
+ attr_reader :id
6
+
7
+ # initializes with a hash of options
8
+ def initialize(options = {})
9
+ @id = options["id"]
10
+ @sgf = options["sgf"]
11
+
12
+ if @sgf
13
+ # checks if sgf is a sgf file, and reads it if it is
14
+ if @sgf =~ /\.sgf/ && File.exists?(@sgf)
15
+ @sgf = File.open(@sgf, 'r').read
16
+ end
17
+
18
+ @data ||= {}
19
+ end
20
+
21
+ # overrides sgf and id if they are provided in the option data
22
+ if options["data"]
23
+ @data = options["data"]
24
+ @id = @data["id"] || @id
25
+ @sgf = @data["sgf"] || @sgf
26
+ end
27
+ end
28
+
29
+ # get the id of the last problem on goproblems.com
30
+ def self.last_problem_id
31
+ @@last_problem_id ||= begin
32
+ doc = Nokogiri::HTML(open('http://www.goproblems.com/').read)
33
+ link = doc.css('td a').select {|a| a[:href] =~ /^\/\d+\z/}[0]
34
+ link[:href].slice(/\d+/).to_i
35
+ end
36
+ end
37
+
38
+ # print out information on problem on screen
39
+ def display
40
+ Gotasku::Display.show(self)
41
+ end
42
+
43
+ # access sgf or strip problem sgf from goproblems.com
44
+ def sgf
45
+ @sgf ||= data["sgf"]
46
+ end
47
+
48
+ # strip problem difficulty from goproblems.com
49
+ def difficulty
50
+ data["difficulty"]
51
+ end
52
+
53
+ # strip problem type from goproblems.com
54
+ def type
55
+ data["type"]
56
+ end
57
+
58
+ # strip problem rating from goproblems.com
59
+ def rating
60
+ data["rating"]
61
+ end
62
+
63
+ # access or get the tree from the Parser
64
+ def tree
65
+ @tree ||= Gotasku::Parser.new.parse(sgf)
66
+ end
67
+
68
+ # saves the sgf
69
+ def save
70
+ tree.save("#{id || Time.now.to_i}.sgf")
71
+ end
72
+
73
+ # this method is not finished
74
+ # uploads sgf to goproblems.com
75
+ def upload
76
+ # don't upload a problem that came from goproblems
77
+ unless @id
78
+ sess = Gotasku::Session.current
79
+
80
+ add_info_page = sess.link_with(href: /add/).click
81
+ add_page = add_info_page.link_with(text: /direct/).click
82
+
83
+ add_page.form_with(
84
+ action: 'addtest.php3') do |f|
85
+ source_field = f.field_with(name: "source")
86
+ source_field.value = ''
87
+
88
+ name_field = f.field_with(name: "name")
89
+ name_field.value = ''
90
+
91
+ sgf_field = f.field_with(name: "sgf")
92
+ sgf_field.value = @sgf
93
+
94
+ intro_field = f.field_with(name: "intro")
95
+ intro_field.value = ''
96
+
97
+ type_select = f.field_with(name: "genre")
98
+ type_select.value = 'elementary'
99
+
100
+ group_select = f.field_with(name: "usergroup[]")
101
+ group_select.value = ['none']
102
+
103
+ @submit = f.submit
104
+ end
105
+
106
+ # after submit, goes to flash app to confirm and complete
107
+ # the problem
108
+ end
109
+ end
110
+
111
+ def blank?
112
+ sgf == '(;)'
113
+ end
114
+
115
+ def data
116
+ @data ||= begin
117
+ # get sgf from go problems
118
+ doc = Nokogiri::HTML(open("#{@@uri}#{@id}").read)
119
+ sgf_text = doc.css("div#player-container").text.gsub(/\r?\n/, '')
120
+
121
+ raise Gotasku::NotFound if sgf_text.empty?
122
+
123
+ difficulty = Gotasku::DifficultyString.new(
124
+ doc.css("div.difficulty a").text)
125
+ type = doc.css("div.prob_genre").text
126
+ rating = Gotasku::RatingString.new(
127
+ doc.css("div.probstars")[0][:class]).convert
128
+
129
+ {
130
+ "id" => @id,
131
+ "sgf" => sgf_text,
132
+ "difficulty" => difficulty,
133
+ "diff_num" => difficulty.convert,
134
+ "type" => type,
135
+ "rating" => rating
136
+ }
137
+
138
+ rescue Gotasku::NotFound
139
+ # puts "This problem cannot be found"
140
+
141
+ # at the moment, this seems the best solution, not ideal though
142
+ # because it allows for problems to be saved even if they are not
143
+ # found
144
+ {sgf: '(;)'}
145
+
146
+ rescue SocketError
147
+ puts "Poor internet connection"
148
+ {sgf: '(;)'}
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,106 @@
1
+ class Gotasku::Query
2
+ @@problems = Mongo::MongoClient.new.db('sgf').collection('problems')
3
+
4
+ attr_reader :found
5
+
6
+ # gets a Mongo::Cursor object
7
+ def initialize(query)
8
+ @found = @@problems.find(query)
9
+ end
10
+
11
+ # returns an array of items in @found
12
+ def to_a
13
+ @found.rewind!
14
+ @found.to_a
15
+ end
16
+
17
+ # returns a Mongo::Cursor object
18
+ def self.create
19
+ self.new(self.build_query)
20
+ end
21
+
22
+ =begin
23
+ # key is a string or symbol
24
+ def self.sort_by(key)
25
+ end
26
+
27
+ # query is a hash
28
+ def self.query_by(query)
29
+ end
30
+ =end
31
+
32
+ private
33
+ # prompts user for input
34
+ def self.prompt(text, tru = nil, fals = nil)
35
+ print text
36
+
37
+ response = gets.chomp.strip.downcase
38
+
39
+ # if values are given to verrifying input as true/false
40
+ if tru and fals
41
+ if response =~ /#{tru}/
42
+ true
43
+ elsif response =~ /#{fals}/
44
+ false
45
+ else
46
+ nil
47
+ end
48
+ else # else return an Array
49
+ response = response.split(',')
50
+
51
+ # throw an exception if the input is invalid
52
+ unless response.class == Array and
53
+ not(response.find {|item| item =~ /[a-z]/})
54
+ raise Gotasku::InvalidEntry
55
+ end
56
+
57
+ response.collect {|item| item.to_i}
58
+ end
59
+ end
60
+
61
+ #gets unique set of types from collection
62
+ def self.get_types
63
+ @@problems.distinct(:type).sort
64
+ end
65
+
66
+ #gets unique set of difficulties from collection
67
+ def self.get_difficulties
68
+ @@problems.distinct(:difficulty).sort do |x, y|
69
+ x = Gotasku::DifficultyString.new(x).convert
70
+ y = Gotasku::DifficultyString.new(y).convert
71
+ x <=> y
72
+ end
73
+ end
74
+
75
+ #gets unique set of ratings from collection
76
+ def self.get_ratings
77
+ @@problems.distinct(:rating).sort
78
+ end
79
+
80
+ # runs a prompt to build a query for the database
81
+ def self.build_query
82
+ query_build = {}
83
+ q = {
84
+ type: -> {self.get_types},
85
+ difficulty: -> {self.get_difficulties},
86
+ rating: -> {self.get_ratings}
87
+ }
88
+
89
+ q.keys.each do |key|
90
+ if self.prompt("Do you want to filter by #{key} (y/n)? ",
91
+ 'y', 'n')
92
+ list = q[key].call
93
+ Gotasku::Display.show(list)
94
+ puts '=' * 40
95
+ values = self.prompt(
96
+ "Please type the index/number for the values " +
97
+ "you would like to filter for (e.g. 0, 3, 4): ")
98
+
99
+ values.collect! {|value| list[value]}
100
+ query_build[key] = { "$in" => values }
101
+ end
102
+ end
103
+
104
+ query_build
105
+ end
106
+ end
@@ -0,0 +1,11 @@
1
+ class Gotasku::RatingString < String
2
+
3
+ # convert string to number
4
+ def convert
5
+ sum = self.slice(/sitesum:\d+/).slice(/\d+/).to_i
6
+ num = self.slice(/sitenum:\d+/).slice(/\d+/).to_i
7
+
8
+ # find the average or return the sum (if num is 0)
9
+ num == 0 ? 0 : sum / num
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ class Gotasku::Session
2
+
3
+ @@current = nil
4
+
5
+ attr_reader :account
6
+
7
+ # uses mechanize to login to goproblems.com and makes makes this the
8
+ # current session
9
+ def initialize(user)
10
+ a = Mechanize.new
11
+ a.get('http://www.goproblems.com/') do |page|
12
+ # Click the login link
13
+ login_page = a.click(page.link_with(href: /login/))
14
+
15
+ # Submit the login form
16
+ login_page.form_with(
17
+ action: 'login.php') do |f|
18
+ username_field = f.field_with(name: "name")
19
+ username_field.value = user.username
20
+
21
+ password_field = f.field_with(name: "password")
22
+ password_field.value = user.password
23
+
24
+ @account = f.submit
25
+ end
26
+ end
27
+
28
+ @@current = @account
29
+ end
30
+
31
+ # accesses current session
32
+ def self.current
33
+ @@current
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ class Gotasku::User
2
+
3
+ attr_reader :username, :password
4
+
5
+ # initializes a new user with all the info needed to create a new
6
+ # session
7
+ def initialize(username, password)
8
+ @username = username
9
+ @password = password
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'db/tasks/db-tasks.rb'
2
+
3
+ task :run_all_tests do
4
+ Dir.glob('test/*.rb').each {|file| load file}
5
+ end
6
+
7
+ task :delete_sgfs do
8
+ Dir.glob('*.sgf').each {|file| File.delete(file)}
9
+ end
10
+
11
+ task default: [:run_all_tests]
@@ -0,0 +1,77 @@
1
+ require_relative '../lib/gotasku'
2
+
3
+ require 'minitest/autorun'
4
+ require 'stringio'
5
+
6
+
7
+ load 'variables.rb' # username and password for testing
8
+
9
+ class GotaskuCLITest < MiniTest::Unit::TestCase
10
+
11
+ def test_get_problem
12
+ io = StringIO.new
13
+ io.puts(`./bin/gotasku`)
14
+ io.rewind
15
+ assert_match /Gotasku::Problem/, io.read
16
+ end
17
+
18
+ def test_get_problem_with_id
19
+ io = StringIO.new
20
+ io.puts(`./bin/gotasku --id 1000`)
21
+ io.rewind
22
+ assert_match /@id=1000/, io.read
23
+ end
24
+
25
+ def test_get_problem_with_sgf
26
+ io = StringIO.new
27
+ io.puts(`./bin/gotasku --sgf '(;AB[aa][bb][cc]AW[dd][ee])'`)
28
+ io.rewind
29
+ assert_includes io.read, "AB[aa][bb][cc]AW[dd][ee]"
30
+ end
31
+
32
+ def test_get_problem_with_sgf_from_file
33
+ File.open('./test/john.sgf', 'w') {|f| f.write('(;AW[cc][cd][ad]AB[aa])')}
34
+ io = StringIO.new
35
+ io.puts(`./bin/gotasku --sgf ./test/john.sgf`)
36
+ io.rewind
37
+ assert_includes io.read, "AW[cc]"
38
+ end
39
+
40
+ def test_save_problem
41
+ name = '1000.sgf'
42
+
43
+ File.delete(name) if File.exists?(name)
44
+ `./bin/gotasku save --id 1000 --sgf '(;AB[aa][bb][cc]AW[ab][bc][cd])'`
45
+ assert_includes Dir.glob('*.sgf'), name
46
+ end
47
+
48
+ def test_display_problem_data
49
+ io = StringIO.new
50
+ io.puts(`./bin/gotasku display --id 1000`)
51
+ io.rewind
52
+ output = io.read
53
+ assert_match /id: 1000/, output
54
+ assert_match /type: life and death/, output
55
+ assert_match /rating: 3/, output
56
+ assert_match /difficulty: 6 dan/, output
57
+ end
58
+
59
+ def test_login
60
+ io = StringIO.new
61
+ io.puts(`./bin/gotasku login --username #{::USERNAME} --password #{::PASSWORD}`)
62
+ io.rewind
63
+ assert_match /Gotasku::Session/, io.read
64
+ end
65
+
66
+ =begin
67
+ def test_get_problems_list_by_difficulty
68
+ end
69
+
70
+ def test_get_problems_list_by_rating
71
+ end
72
+
73
+ def test_get_problems_list_by_type
74
+ end
75
+ =end
76
+ end
77
+
@@ -0,0 +1,81 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/gotasku'
3
+
4
+ class GotaskuParserTest < MiniTest::Unit::TestCase
5
+ def setup
6
+ @parser = SGF::Parser.new
7
+ @gparser = Gotasku::Parser.new
8
+ end
9
+
10
+ def test_initialize_parser
11
+ assert_kind_of Gotasku::Parser, Gotasku::Parser.new
12
+ end
13
+
14
+ def test_inherits_for_sgf_parser
15
+ assert_includes Gotasku::Parser.ancestors, SGF::Parser
16
+ end
17
+
18
+ def test_parses_correct_sgf
19
+ sgf = '(;AB[lb][lc][ld][kc][jb][ka][ck][dg][ei])'
20
+
21
+ assert_equal @parser.parse(sgf), @gparser.parse(sgf)
22
+ end
23
+
24
+ def test_validates_sgf
25
+ correct_sgf = '(;AB[lb][lc][ld])'
26
+ incorrect_sgf = '(;AB[lb]AB[lc]AB[ld])'
27
+
28
+ assert_equal correct_sgf, @gparser.validate(incorrect_sgf)
29
+ end
30
+
31
+ def test_validates_sgf_with_AB_and_AW
32
+ correct_sgf = '(;AB[lb][lc][ld]AW[bb][cc])'
33
+ incorrect_sgf = '(;AB[lb]AB[lc]AB[ld]AW[bb]AW[cc])'
34
+
35
+ assert_equal correct_sgf, @gparser.validate(incorrect_sgf)
36
+ end
37
+
38
+ def test_parses_incorrect_sgf
39
+ correct_sgf = '(;AB[bb][bb][bb])'
40
+ incorrect_sgf = '(;AB[bb]AB[bb]AB[bb])'
41
+
42
+ assert_equal @parser.parse(correct_sgf),
43
+ @gparser.parse(incorrect_sgf)
44
+ end
45
+
46
+ def test_parses_incorrect_sgf_with_AB_and_AW
47
+ correct_sgf = '(;AB[lb][lc][ld]AW[bb][cc])'
48
+ incorrect_sgf = '(;AB[lb]AB[lc]AB[ld]AW[bb]AW[cc])'
49
+
50
+ assert_equal @parser.parse(correct_sgf),
51
+ @gparser.parse(incorrect_sgf)
52
+ end
53
+
54
+ def test_full_incorrect_sgf_AB
55
+ incorrect_sgf = '(;AW[pe]AW[pf]AW[qg]AW[qh]AW[oi]AW[ri]AW[oj]AW[pj]AW[rj]AW[sk]AW[rl]AW[sl]AW[rm]AB[pg]AB[ph]AB[rh]AB[pi]AB[qi]AB[qj]AB[pk]AB[qk]AB[rk]AB[pl]C[black to do something effective on the side]SZ[19]GM[1]FF[3]RU[Japanese]HA[0]KM[5.5]PW[White]PB[Black]GN[White (W) vs. Black (B)]DT[1999-07-27]SY[Cgoban 1.9.2]TM[30:00(5x1:00)](;B[sh](;W[sj](;B[qf];W[rg];B[rf]C[RIGHT])(;B[rg];W[qf]))(;W[rg];B[sj];W[si];B[sj]C[snapbackRIGHT]))(;B[sj];W[si](;B[sh];W[sj](;B[qf];W[rg])(;B[rg];W[qf]))(;B[qf];W[rg])(;B[rg];W[qf]))(;B[rg];W[qf]C[black has nothing now])(;B[qf];W[rg](;B[sh];W[sg])(;B[rf];W[sh]))(;B[sj];W[si](;B[sh];W[sj](;B[qf];W[rg])(;B[rg];W[qf]))(;B[qf];W[rg])(;B[rg];W[qf]))(;B[qn];W[rg])(;B[qm];W[rg])(;B[rn];W[rg])(;B[rf];W[rg])(;B[sg];W[rg]))'
56
+
57
+ validated_sgf = @gparser.validate(incorrect_sgf)
58
+
59
+ refute_match /AB\[..\]AB/, validated_sgf
60
+ assert_match /AB/, validated_sgf
61
+ end
62
+
63
+ def test_full_incorrect_sgf_AW
64
+ incorrect_sgf = '(;AW[pe]AW[pf]AW[qg]AW[qh]AW[oi]AW[ri]AW[oj]AW[pj]AW[rj]AW[sk]AW[rl]AW[sl]AW[rm]AB[pg]AB[ph]AB[rh]AB[pi]AB[qi]AB[qj]AB[pk]AB[qk]AB[rk]AB[pl]C[black to do something effective on the side]SZ[19]GM[1]FF[3]RU[Japanese]HA[0]KM[5.5]PW[White]PB[Black]GN[White (W) vs. Black (B)]DT[1999-07-27]SY[Cgoban 1.9.2]TM[30:00(5x1:00)](;B[sh](;W[sj](;B[qf];W[rg];B[rf]C[RIGHT])(;B[rg];W[qf]))(;W[rg];B[sj];W[si];B[sj]C[snapbackRIGHT]))(;B[sj];W[si](;B[sh];W[sj](;B[qf];W[rg])(;B[rg];W[qf]))(;B[qf];W[rg])(;B[rg];W[qf]))(;B[rg];W[qf]C[black has nothing now])(;B[qf];W[rg](;B[sh];W[sg])(;B[rf];W[sh]))(;B[sj];W[si](;B[sh];W[sj](;B[qf];W[rg])(;B[rg];W[qf]))(;B[qf];W[rg])(;B[rg];W[qf]))(;B[qn];W[rg])(;B[qm];W[rg])(;B[rn];W[rg])(;B[rf];W[rg])(;B[sg];W[rg]))'
65
+
66
+ validated_sgf = @gparser.validate(incorrect_sgf)
67
+
68
+ refute_match /AW\[..\]AW/, validated_sgf
69
+ assert_match /AW/, validated_sgf
70
+ end
71
+
72
+ def test_does_not_start_with_stone_positions
73
+ incorrect_sgf = '(;GM[1]FF[3]RU[Japanese]SZ[19]HA[0]KM[5.5]PW[White]PB[Black]GN[White (W) vs. Black (B)]DT[1999-07-28]SY[Cgoban 1.9.2]TM[30:00(5x1:00)];AW[ba]AW[ca]AW[ea]AW[bb]AW[eb]AW[cc]AW[ec]AW[ed]AW[ce]AW[de]AW[ee]AB[fa][fb][fc][bd][cd][dd][fd][be][fe][bf][cf][df][ef][ff](;B[dc];W[db];B[bc];W[ab]C[only ko])(;B[db];W[dc];B[ab];W[ac];B[bc]C[RIGHT])(;B[bc];W[db];B[dc];W[ab]C[only ko])(;B[ab];W[db](;B[dc];W[bc])(;B[bc];W[dc]))(;B[ac];W[ab]))'
74
+
75
+ validated_sgf = @gparser.validate(incorrect_sgf)
76
+
77
+ assert_match /AW/, validated_sgf
78
+ refute_match /AW\[..\]AW/, validated_sgf
79
+ end
80
+
81
+ end
@@ -0,0 +1,27 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/gotasku'
3
+
4
+ class GotaskuFeatureTest < MiniTest::Unit::TestCase
5
+ def setup
6
+ @problem = Gotasku::Problem.new("id" => 1000)
7
+ end
8
+
9
+ def test_gets_difficulty
10
+ # 30k-1k, 1d-9d, 1p-9p => 30 - 1, -1 - -9, -11 - -19
11
+ assert_equal '6 dan', @problem.difficulty
12
+ end
13
+
14
+ def test_gets_difficulty_conversion_for_sorting
15
+ # 30k-1k, 1d-9d, 1p-9p => 30 - 1, -1 - -9, -11 - -19
16
+ assert_equal -6, @problem.difficulty.convert
17
+ end
18
+
19
+ def test_gets_type
20
+ assert_equal @problem.type, 'life and death'
21
+ end
22
+
23
+ def test_gets_rating
24
+ assert_equal @problem.rating, 3
25
+ end
26
+ end
27
+
@@ -0,0 +1,52 @@
1
+ require_relative '../lib/gotasku'
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require 'mongo'
6
+ require 'stringio'
7
+
8
+ class GotaskuQueryTest < MiniTest::Unit::TestCase
9
+
10
+ def test_makes_query
11
+ query = Gotasku::Query.new({"id" => 1000})
12
+ assert_kind_of Mongo::Cursor, query.found
13
+ end
14
+
15
+ def test_builds_query
16
+ output = StringIO.new
17
+ file = StringIO.new
18
+ file << "y\n" << "1, 3\n" << "y\n"
19
+ file << "9, 15, 1\n" << "y\n" << "3\n"
20
+ file.rewind
21
+
22
+ temp_in = $stdin
23
+ temp_out = $stdout
24
+ $stdin = file
25
+ $stdout = output
26
+
27
+ query = Gotasku::Query.create
28
+
29
+ $stdin = temp_in
30
+ $stdout = temp_out
31
+
32
+ assert_kind_of Mongo::Cursor, query.found
33
+ end
34
+
35
+ def test_handles_big_query
36
+ query = Gotasku::Query.new({
37
+ "id" => {"$lt" => 15000},
38
+ "diff_num" => {"$gt" => 1, "$lt" => 9},
39
+ "rating" => {"$in" =>
40
+ ["life and death",
41
+ "elementary"]}
42
+ })
43
+ assert_kind_of Mongo::Cursor, query.found
44
+ end
45
+
46
+ def test_new_problem_from_database
47
+ query = Gotasku::Query.new({"id" => 17642})
48
+ prob = Gotasku::Problem.new("data" => query.to_a.first)
49
+ assert prob.sgf
50
+ assert prob.id
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/gotasku'
3
+
4
+ require_relative '../variables'
5
+
6
+ class GotaskuSessionTest < MiniTest::Unit::TestCase
7
+
8
+ def setup
9
+ @user = Gotasku::User.new(::USERNAME, ::PASSWORD)
10
+ @session = Gotasku::Session.new(@user)
11
+ end
12
+
13
+ def test_creates_session
14
+ assert_kind_of Gotasku::Session, @session
15
+ end
16
+ end
@@ -0,0 +1,79 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/gotasku'
3
+
4
+ class GotaskuProblemTest < MiniTest::Unit::TestCase
5
+ def setup
6
+ @problem = Gotasku::Problem.new("id" => 17625)
7
+ end
8
+
9
+ def test_gets_id
10
+ assert_equal 17625, @problem.id
11
+ end
12
+
13
+ def test_gets_sgf
14
+ sgf = '(;AB[se]AB[rd]AB[qd]AB[qe]AB[qf]AB[qg]AB[oe]AB[oc]AB[pc]AB[ob]AW[mb]AW[mc]AW[me]AW[mg]AW[og]AW[ph]AW[qh]AW[rg]AW[re]AW[rf]AW[sc]AW[qc]AW[qb]AW[pb]AW[rb]AP[goproblems]
15
+ (;B[ra]
16
+ (;W[rc]
17
+ (;B[sd];W[sa];B[sb]C[CHOICERIGHT])
18
+ (;B[pa];W[qa])
19
+ (;B[qa];W[pa])
20
+ (;B[sa];W[sd])
21
+ (;B[sb];W[qa]))
22
+ (;W[sd];B[sb]C[RIGHT]))
23
+ (;B[oa];W[sd])
24
+ (;B[pa];W[sd])
25
+ (;B[qa];W[pa])
26
+ (;B[sa];W[sd])
27
+ (;B[sb];W[rc]TR[sd]TR[sa])
28
+ (;B[rc];W[sb]TR[sd]TR[pa]))'.gsub(/\r?\n/, '')
29
+
30
+ assert_equal sgf, @problem.sgf
31
+ end
32
+
33
+ def test_gets_another_sgf
34
+ sgf = '(;GM[1]FF[4]VW[]AP[Many Faces of Go:10.0]
35
+ SZ[13]
36
+ HA[0]
37
+ ST[1]
38
+ DT[2001-08-13]
39
+ KM[0.0]
40
+ RU[GOE]
41
+ AB[lb][lc][ld][kc][jb][ka][ck][dg][ei][fb][fc][fd][ee][db][cd][ej][fk][fl][fh][eg][fg][lj][kk][jj][ij][md][lf][gm][fm][gc]
42
+ AW[ib][jc][kd][jd][le][jf][hc][fi][gb][gd][fe][ff][ia][fj][gl][gk][gh][gg][jk][kg][lh][kl][ll][ik][me][ke][hm][hl]
43
+ C[After the previous problem, Black has played at 1 rather than
44
+ protecting his corner. How can white punish him?
45
+ ]
46
+ PL[W]LB[gc:A]
47
+ (;W[mb]
48
+ (;B[ma]
49
+ (;W[la];B[ge];W[ma];B[mc];W[ja];B[ma];W[kb]
50
+ C[White has given up too much when black plays A
51
+ ]LB[ge:A]
52
+ )
53
+ (;W[ja];B[kb];W[la]
54
+ C[RIGHT
55
+ ]))
56
+ (;B[ja];W[la]))
57
+ (;W[ja];B[mb]
58
+ C[Black is unconditionally alive.
59
+ ])
60
+ (;W[ge];B[ja]
61
+ C[Black has had another chance to repair his defect.
62
+ ]))'.gsub(/\r?\n/, '')
63
+
64
+ assert_equal sgf, Gotasku::Problem.new("id" => 1000).sgf
65
+ end
66
+
67
+ def test_gets_tree_for_sgf
68
+ assert_kind_of ::SGF::Tree, @problem.tree
69
+ end
70
+
71
+ def test_saves_problem
72
+ file = Dir.pwd + '/17625.sgf'
73
+
74
+ File.delete(file) if File.exist?(file)
75
+ @problem.save
76
+
77
+ assert_includes Dir.glob(Dir.pwd + '/*.sgf'), file
78
+ end
79
+ end
@@ -0,0 +1,15 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/gotasku'
3
+
4
+ require_relative '../variables'
5
+
6
+ class GotaskuUserTest < MiniTest::Unit::TestCase
7
+
8
+ def setup
9
+ @user = Gotasku::User.new(::USERNAME, ::PASSWORD)
10
+ end
11
+
12
+ def test_creates_user
13
+ assert_kind_of Gotasku::User, @user
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ ::USERNAME = 'moax'
2
+ ::PASSWORD = 'regah2'
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gotasku
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - jphager2
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A gem to download SGFs of go problems
14
+ email: jphager2@gmail.com
15
+ executables:
16
+ - gotasku
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - bin/gotasku
23
+ - db/tasks/db-tasks.rb
24
+ - db/tasks/populate_database.rb
25
+ - lib/gotasku.rb
26
+ - lib/gotasku/difficulty_string.rb
27
+ - lib/gotasku/display.rb
28
+ - lib/gotasku/exceptions.rb
29
+ - lib/gotasku/gotasku.rb
30
+ - lib/gotasku/parser.rb
31
+ - lib/gotasku/problem.rb
32
+ - lib/gotasku/query.rb
33
+ - lib/gotasku/rating_string.rb
34
+ - lib/gotasku/session.rb
35
+ - lib/gotasku/user.rb
36
+ - rakefile
37
+ - test/cli.rb
38
+ - test/parser.rb
39
+ - test/problem_features.rb
40
+ - test/query.rb
41
+ - test/sessions.rb
42
+ - test/sgf.rb
43
+ - test/user.rb
44
+ - variables.rb
45
+ homepage: https://github.com/jphager2/gotasku
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.2.2
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Downloads SGFs of go problems
69
+ test_files: []