internet_scrabble_club 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +26 -0
  7. data/Rakefile +1 -0
  8. data/internet_scrabble_club.gemspec +29 -0
  9. data/lib/internet_scrabble_club/client/extensions/authentication.rb +21 -0
  10. data/lib/internet_scrabble_club/client/extensions/echo_ping.rb +17 -0
  11. data/lib/internet_scrabble_club/client/extensions/keep_alive.rb +16 -0
  12. data/lib/internet_scrabble_club/client/middleware/emit.rb +18 -0
  13. data/lib/internet_scrabble_club/client/middleware/parse.rb +24 -0
  14. data/lib/internet_scrabble_club/client/middleware/read.rb +19 -0
  15. data/lib/internet_scrabble_club/client/middleware/transform.rb +20 -0
  16. data/lib/internet_scrabble_club/client/middleware.rb +4 -0
  17. data/lib/internet_scrabble_club/client.rb +79 -0
  18. data/lib/internet_scrabble_club/message_parsers/base.rb +76 -0
  19. data/lib/internet_scrabble_club/message_parsers/close.rb +12 -0
  20. data/lib/internet_scrabble_club/message_parsers/examine/history.rb +49 -0
  21. data/lib/internet_scrabble_club/message_parsers/examine.rb +13 -0
  22. data/lib/internet_scrabble_club/message_parsers/history.rb +15 -0
  23. data/lib/internet_scrabble_club/message_parsers/login.rb +12 -0
  24. data/lib/internet_scrabble_club/message_parsers/ping/reply.rb +11 -0
  25. data/lib/internet_scrabble_club/message_parsers/ping.rb +11 -0
  26. data/lib/internet_scrabble_club/message_parsers/seek.rb +13 -0
  27. data/lib/internet_scrabble_club/message_parsers/unseek.rb +12 -0
  28. data/lib/internet_scrabble_club/message_parsers/who/list.rb +14 -0
  29. data/lib/internet_scrabble_club/message_parsers/who/move.rb +12 -0
  30. data/lib/internet_scrabble_club/message_parsers/who.rb +11 -0
  31. data/lib/internet_scrabble_club/message_parsers.rb +17 -0
  32. data/lib/internet_scrabble_club/message_transformers/base.rb +48 -0
  33. data/lib/internet_scrabble_club/message_transformers/examine/history.rb +32 -0
  34. data/lib/internet_scrabble_club/message_transformers/history.rb +14 -0
  35. data/lib/internet_scrabble_club/message_transformers/who/move.rb +14 -0
  36. data/lib/internet_scrabble_club/message_transformers.rb +3 -0
  37. data/lib/internet_scrabble_club/multi_queue.rb +20 -0
  38. data/lib/internet_scrabble_club.rb +3 -0
  39. metadata +167 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8a765c05c9d571f3d34382874e9d6a685a66f5cd
4
+ data.tar.gz: 86ec3896b32848b07ca04fe029d938aa36a22ba1
5
+ SHA512:
6
+ metadata.gz: 600ea4f6ac6284f56e9fa316547ecc3ee046f292de920ebef4222b92e1ea2792757e9b52a5ec02808a10bca27e49a9af564b87a4fea58bba74494e8001e2bbe1
7
+ data.tar.gz: 049623316e1dc70b325df7c9a951eed3d76900985f5d683944eb644374f47c79f464876f302a422fe97d9dd63d309b801a29ea16feccb952bd5a156fdf77b528
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ rbx-2.2.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scrabble_scraper.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Thomas Brus
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # ISC Ruby client
2
+
3
+ Ruby client for interacting with the [Internet Scrabble Club](http://www.isc.ro) server.
4
+
5
+ ## Example
6
+
7
+ ```ruby
8
+ require 'internet_scrabble_club'
9
+
10
+ client = InternetScrabbleClub::Client.new
11
+
12
+ client.authenticate('nickname', 'password') do |login|
13
+ # Authenticated ...
14
+ puts login.greeting # => "Welcome to the Internet Scrabble Club, <nickname>"
15
+
16
+ # Fetch history (last ten games)
17
+ client.send_message('HISTORY', 'thomas92') do |history|
18
+ puts history.entries.first # => #<OpenStruct index=1, first_player="thomas92" ... >
19
+ end
20
+
21
+ # Inspect a recent game
22
+ client.send_message('EXAMINE', 'HISTORY', 'thomas92', 0) do |game|
23
+ puts game.inspect # => #<OpenStruct date=#<Date ... >, players=["thomas92", ...] ... >
24
+ end
25
+ end
26
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'internet_scrabble_club'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "internet_scrabble_club"
8
+ spec.version = InternetScrabbleClub::VERSION
9
+ spec.authors = ["Thomas Brus"]
10
+ spec.email = ["thomas.brus@me.com"]
11
+ spec.summary = %q{Fetches scrabble games from the Internet Scrabble Club.}
12
+ spec.homepage = "https://github.com/thomasbrus/internet-scrabble-club"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+
23
+ spec.add_dependency "celluloid-io", "~> 0.15.0"
24
+ spec.add_dependency "parslet", "~> 1.5.0"
25
+ spec.add_dependency "descendants_tracker", "~> 0.0.4"
26
+ spec.add_dependency "events", "~> 0.9.8"
27
+ spec.add_dependency "anima", "~> 0.2.0"
28
+ spec.add_dependency "middleware", "~> 0.1.0"
29
+ end
@@ -0,0 +1,21 @@
1
+ module InternetScrabbleClub
2
+ class Client
3
+ module Extensions
4
+
5
+ module Authentication
6
+ def initialize(*args, &block)
7
+ super; @event_emitter.on(:message) do |message|
8
+ if message.command == 'CLOSE' && message.reason =~ /^Invalid password/
9
+ fail InvalidCredentials
10
+ end
11
+ end
12
+ end
13
+
14
+ def authenticate(nickname, password, &callback)
15
+ send_message('LOGIN', nickname, password, 1871, '?', &callback)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module InternetScrabbleClub
2
+ class Client
3
+ module Extensions
4
+
5
+ module EchoPing
6
+ def initialize(*args, &block)
7
+ super; @event_emitter.on(:message) do |message|
8
+ if [message.command, message.sub_command] == %w(PING REPLY)
9
+ send_message('PING', 'REPLY')
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module InternetScrabbleClub
2
+ class Client
3
+ module Extensions
4
+
5
+ module KeepAlive
6
+ def initialize(*args, &block)
7
+ every(10) { keep_alive }; super
8
+ end
9
+
10
+ def keep_alive
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module InternetScrabbleClub
2
+ class Client
3
+ module Middleware
4
+
5
+ class Emit
6
+ def initialize(stack, event_emitter)
7
+ @stack, @event_emitter = stack, event_emitter
8
+ end
9
+
10
+ def call(env)
11
+ @event_emitter.emit(:message, env[:message])
12
+ @stack.call(env)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ require 'parslet'
2
+ require 'celluloid/logger'
3
+ require_relative '../../message_parsers'
4
+
5
+ module InternetScrabbleClub
6
+ class Client
7
+ module Middleware
8
+
9
+ class Parse
10
+ def initialize(stack, message_parser = MessageParsers::Base.new)
11
+ @stack, @message_parser = stack, message_parser
12
+ end
13
+
14
+ def call(env)
15
+ env[:message] = @message_parser.parse(env[:message])
16
+ @stack.call(env)
17
+ rescue Parslet::ParseFailed
18
+ Celluloid::Logger.warn("Failed to parse message: #{env[:message]}")
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module InternetScrabbleClub
2
+ class Client
3
+ module Middleware
4
+
5
+ class Read
6
+ def initialize(stack, socket)
7
+ @stack, @socket = stack, socket
8
+ end
9
+
10
+ def call(env)
11
+ message_length = @socket.getc.ord * 256 + @socket.getc.ord
12
+ env[:message] = @socket.read(message_length)
13
+ @stack.call(env)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../../message_transformers'
2
+
3
+ module InternetScrabbleClub
4
+ class Client
5
+ module Middleware
6
+
7
+ class Transform
8
+ def initialize(stack, message_transformer = MessageTransformers::Base.new)
9
+ @stack, @message_transformer = stack, message_transformer
10
+ end
11
+
12
+ def call(env)
13
+ env[:message] = @message_transformer.apply(env[:message])
14
+ @stack.call(env)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'middleware/emit'
2
+ require_relative 'middleware/parse'
3
+ require_relative 'middleware/read'
4
+ require_relative 'middleware/transform'
@@ -0,0 +1,79 @@
1
+ require 'socket'
2
+
3
+ require 'celluloid/io'
4
+ require 'celluloid/autostart'
5
+
6
+ require 'events'
7
+ require 'middleware'
8
+
9
+ require_relative 'multi_queue'
10
+
11
+ require_relative 'client/extensions/authentication'
12
+ require_relative 'client/extensions/echo_ping'
13
+ require_relative 'client/extensions/keep_alive'
14
+
15
+ require_relative 'client/middleware'
16
+
17
+ module InternetScrabbleClub
18
+
19
+ class Client
20
+ include Celluloid::IO
21
+
22
+ prepend Extensions::Authentication
23
+ prepend Extensions::EchoPing
24
+ prepend Extensions::KeepAlive
25
+
26
+ finalizer :finalize
27
+
28
+ attr_writer :socket, :middleware
29
+ attr_writer :command_callback_queue, :event_emitter
30
+
31
+ class InvalidCredentials < StandardError
32
+ def initialize(message = nil)
33
+ super(message || "Could not authenticate; the provided credentials are invalid.")
34
+ end
35
+ end
36
+
37
+ def initialize(host = '50.97.175.138', port = 1330)
38
+ @socket = TCPSocket.new(host, port)
39
+ @command_callback_queue = MultiQueue.new
40
+ @event_emitter = Events::EventEmitter.new
41
+ @event_emitter.on(:message) { |message| yield_command_callback(message) }
42
+
43
+ async.run
44
+ end
45
+
46
+ def run
47
+ loop { middleware.call({}) }
48
+ end
49
+
50
+ def on_message(&callback)
51
+ @event_emitter.on(:message, &callback)
52
+ end
53
+
54
+ def send_message(command, *arguments, &callback)
55
+ message = ['0', command, *arguments].join(' ')
56
+ @command_callback_queue.enqueue(command.downcase.to_sym, callback)
57
+ @socket.write("\0" << message.length << message)
58
+ end
59
+
60
+ def finalize
61
+ @socket.close if @socket
62
+ end
63
+
64
+ private def yield_command_callback(message)
65
+ command_callback = @command_callback_queue.dequeue(message.command) { proc {} }
66
+ command_callback.call(message)
67
+ end
68
+
69
+ private def middleware
70
+ @middleware ||= ::Middleware::Builder.new.tap { |mw|
71
+ mw.use(Middleware::Read, @socket)
72
+ mw.use(Middleware::Parse)
73
+ mw.use(Middleware::Transform)
74
+ mw.use(Middleware::Emit, @event_emitter)
75
+ }
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,76 @@
1
+ require 'parslet'
2
+ require 'descendants_tracker'
3
+
4
+ module InternetScrabbleClub
5
+ module MessageParsers
6
+
7
+ class Base < Parslet::Parser
8
+ extend DescendantsTracker
9
+
10
+ root(:message)
11
+
12
+ rule(:message) { descendant_parsers.reduce(:|) or (digit >> space >>
13
+ ( command_with_sub_command_and_arguments |
14
+ command_with_arguments | command_with_sub_command)) }
15
+
16
+ rule(:command_with_arguments) { command.as(:command) >> space >>
17
+ arguments.as(:arguments) }
18
+
19
+ rule(:command_with_sub_command_and_arguments) { command_with_sub_command >>
20
+ space >> arguments.as(:arguments) }
21
+
22
+ rule(:command_with_sub_command) { command.as(:command) >>
23
+ space >> sub_command.as(:sub_command) }
24
+
25
+ rule(:command) { nothing }
26
+ rule(:sub_command) { nothing }
27
+ rule(:arguments) { nothing }
28
+
29
+ rule(:nothing) { str('') }
30
+
31
+ rule(:space) { str(' ') }
32
+ rule(:space?) { space.maybe }
33
+
34
+ rule(:newline) { match('\n') }
35
+ rule(:newline?) { newline.maybe }
36
+
37
+ rule(:colon) { str(':') }
38
+ rule(:semicolon) { str(';') }
39
+ rule(:minus) { str('-') }
40
+
41
+ rule(:digit) { match['0-9'] }
42
+ rule(:alpha) { match['A-Za-z'] }
43
+
44
+ rule(:int) { _int.as(:int) }
45
+ rule(:_int) { minus.maybe >> digit.repeat(1) }
46
+
47
+ rule(:dashes) { str('---').as(:dashes) }
48
+ rule(:null) { str('null').as(:null) }
49
+
50
+ rule(:word) { (digit | alpha).repeat(1).as(:word) }
51
+ rule(:sentence) { (match['^.'].repeat(1) >> str('.')).as(:sentence) }
52
+ rule(:text) { any.repeat(1).as(:text) }
53
+
54
+ rule(:tiles) { (alpha | str('?')).repeat(1).as(:tiles) }
55
+ rule(:rack) { dashes | tiles }
56
+
57
+ rule(:month_day) { digit.repeat(1, 2) }
58
+ rule(:short_month) { match['A-Z'] >> match['a-z'].repeat(2, 2) }
59
+ rule(:short_year) { digit >> digit }
60
+ rule(:date) { (delimited [month_day, short_month, short_year], colon).as(:date) }
61
+
62
+ rule(:dictionary) { str('TWL') | str('SOWPODS') | str('ODS') | str('LOC') |
63
+ str('SWL') | str('PARO') | str('MULTI') }
64
+
65
+ def delimited(sequence, seperator = space)
66
+ seperators = Array.new(sequence.length - 1) { seperator }
67
+ sequence.zip(seperators).flatten(1).compact.inject(:>>)
68
+ end
69
+
70
+ private def descendant_parsers
71
+ @descendant_parsers ||= self.class.descendants.map(&:new)
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Close < Base
7
+ rule(:command) { str('CLOSE') }
8
+ rule(:arguments) { text.as(:reason) }
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../examine'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Examine::History < Examine
7
+ rule(:sub_command) { str('HISTORY') }
8
+
9
+ rule(:arguments) { delimited([stats, settings,
10
+ (player_setup.as(:setup) >> newline_with_whitespace >> plays.as(:plays)
11
+ ).as(:first_player), str('STOP'),
12
+ (player_setup.as(:setup) >> newline_with_whitespace >> plays.as(:plays)
13
+ ).as(:second_player), str('STOP')
14
+ ], newline_with_whitespace) >> newline_with_whitespace }
15
+
16
+ rule(:newline_with_whitespace) { space.repeat >> newline >> space.repeat }
17
+
18
+ rule(:stats) { _int >> space >> date.as(:date) }
19
+ rule(:settings) { delimited [int.dictionary_code, _int, _int, _int] }
20
+
21
+ rule(:player_setup) { delimited [word.as(:nickname), int.as(:rating),
22
+ rack.as(:initial_rack), (int | null).as(:final_score)] }
23
+
24
+ rule(:plays) { ((move | change | pass) >> space?).repeat }
25
+
26
+ rule(:move) { delimited [str('MOVE').as(:word).as(:type), position.as(:position),
27
+ tiles.as(:word), int.as(:score), _int, _int, rack.as(:rack), _int] }
28
+
29
+ rule(:change) { delimited [str('CHANGE').as(:word).as(:type), rack.as(:rack),
30
+ _int, _int, (dashes | int).as(:swap_count)] }
31
+
32
+ rule(:pass) { delimited [str('PAS').as(:word).as(:type), _int, _int, (dashes |
33
+ suggestion).as(:suggestion)] }
34
+
35
+ rule(:suggestion) { delimited [position.as(:position), word.as(:word),
36
+ int.as(:score)], underscore }
37
+
38
+ rule(:underscore) { match('_') }
39
+ rule(:underscored_word) { word >> underscore >> word }
40
+
41
+ rule(:position) { horizontal_position.as(:horizontal) |
42
+ vertical_position.as(:vertical) }
43
+
44
+ rule(:horizontal_position) { alpha.as(:word).as(:column) >> int.as(:row) }
45
+ rule(:vertical_position) { int.as(:row) >> alpha.as(:word).as(:column) }
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Examine < Base
7
+ rule(:command) { str('EXAMINE') }
8
+ rule(:command_with_sub_command_and_arguments) { command_with_sub_command >>
9
+ space? >> newline >> arguments.as(:arguments) }
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class History < Base
7
+ rule(:command) { str('HISTORY') }
8
+ rule(:arguments) { word.as(:subject) >> space >> entries.as(:entries) }
9
+ rule(:entries) { (entry.as(:entry) >> space?).repeat(1) }
10
+ rule(:entry) { delimited [digit.as(:index), players, dictionary, _int, date.as(:date)] }
11
+ rule(:players) { delimited [_int, word.as(:first_player), _int, word.as(:second_player)] }
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Login < Base
7
+ rule(:command) { str('LOGIN') }
8
+ rule(:arguments) { sentence.as(:greeting) }
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../ping'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Ping::Reply < Ping
7
+ rule(:sub_command) { str('REPLY') }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Ping < Base
7
+ rule(:command) { str('PING') }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Seek < Base
7
+ rule(:command) { str('SEEK') }
8
+ rule(:arguments) { delimited [int.as(:rating), word.as(:nickname), settings] }
9
+ rule(:settings) { delimited([_int] * 7) }
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Unseek < Base
7
+ rule(:command) { str('UNSEEK') }
8
+ rule(:arguments) { word.as(:nickname) }
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ require_relative '../who'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Who::List < Who
7
+ rule(:sub_command) { str('LIST') }
8
+ rule(:arguments) { int.as(:count) >> space >> space >> users.as(:users) }
9
+ rule(:users) { (user.as(:user) >> space?).repeat(1) }
10
+ rule(:user) { delimited [int.as(:rating), word.as(:nickname), any.as(:status), digit, digit] }
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ require_relative '../who'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Who::Move< Who
7
+ rule(:sub_command) { str('MOVE') }
8
+ rule(:arguments) { delimited [_int, word.as(:nickname), alpha, _int, _int] }
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'base'
2
+
3
+ module InternetScrabbleClub
4
+ module MessageParsers
5
+
6
+ class Who < Base
7
+ rule(:command) { str('WHO') }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'message_parsers/close'
2
+
3
+ require_relative 'message_parsers/examine'
4
+ require_relative 'message_parsers/examine/history'
5
+
6
+ require_relative 'message_parsers/who'
7
+ require_relative 'message_parsers/who/list'
8
+ require_relative 'message_parsers/who/move'
9
+
10
+ require_relative 'message_parsers/history'
11
+ require_relative 'message_parsers/login'
12
+
13
+ require_relative 'message_parsers/ping'
14
+ require_relative 'message_parsers/ping/reply'
15
+
16
+ require_relative 'message_parsers/seek'
17
+ require_relative 'message_parsers/unseek'
@@ -0,0 +1,48 @@
1
+ require 'date'
2
+ require 'parslet'
3
+ require 'descendants_tracker'
4
+
5
+ module InternetScrabbleClub
6
+ class MessageTransformers
7
+
8
+ class Base < Parslet::Transform
9
+ extend DescendantsTracker
10
+
11
+ rule(command: simple(:cmd), arguments: subtree(:args)) do
12
+ OpenStruct.new(args.to_h.merge(command: cmd.to_s))
13
+ end
14
+
15
+ rule(command: simple(:cmd), sub_command: simple(:sub_cmd)) do
16
+ OpenStruct.new(command: cmd.to_s, sub_command: sub_cmd.to_s)
17
+ end
18
+
19
+ rule(command: simple(:cmd), sub_command: simple(:sub_cmd), arguments: subtree(:args)) do
20
+ OpenStruct.new(args.to_h.merge(command: cmd.to_s, sub_command: sub_cmd.to_s))
21
+ end
22
+
23
+ rule(int: simple(:int)) { Integer(int) }
24
+
25
+ rule(word: simple(:word)) { word.to_s }
26
+ rule(sentence: simple(:sentence)) { sentence.to_s }
27
+ rule(text: simple(:text)) { text.to_s }
28
+
29
+ rule(date: simple(:date)) { Date.parse(date.to_s) }
30
+
31
+ rule(null: simple(:null)) { 0 }
32
+ rule(tiles: simple(:tiles)) { tiles.to_s }
33
+
34
+ rule(dashes: simple(:dashes)) { nil }
35
+
36
+ def apply(tree, context = nil)
37
+ descendent_transformers.reduce(super) do |tree, transformer|
38
+ transformer.apply(tree, context)
39
+ end
40
+ end
41
+
42
+ def descendent_transformers
43
+ @descendent_transformers ||= self.class.descendants.map(&:new)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ require 'ostruct'
2
+ require_relative '../base'
3
+
4
+ module InternetScrabbleClub
5
+ class MessageTransformers
6
+ module Examine
7
+
8
+ class History < Base
9
+ rule(date: simple(:date), dictionary_code: simple(:dictionary_code),
10
+ first_player: subtree(:first_player), second_player: subtree(:second_player)
11
+ ) do
12
+ { date: date, dictionary_code: dictionary_code,
13
+ first_player: first_player, second_player: second_player
14
+ }
15
+ end
16
+
17
+ rule(setup: subtree(:setup), plays: subtree(:plays)) do
18
+ OpenStruct.new(setup: setup, plays: plays.map { |play| OpenStruct.new(play) })
19
+ end
20
+
21
+ rule(horizontal: { column: simple(:column), row: simple(:row) }) do
22
+ {direction: :horizontal, column: column, row: row}
23
+ end
24
+
25
+ rule(vertical: { column: simple(:column), row: simple(:row) }) do
26
+ {direction: :vertical, column: column, row: row}
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ require 'ostruct'
2
+ require_relative 'base'
3
+
4
+ module InternetScrabbleClub
5
+ class MessageTransformers
6
+
7
+ class History < Base
8
+ rule(entry: subtree(:entry)) do
9
+ OpenStruct.new(entry)
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'ostruct'
2
+ require_relative '../base'
3
+
4
+ module InternetScrabbleClub
5
+ class MessageTransformers
6
+ module Who
7
+
8
+ class Move < Base
9
+ rule(nickname: simple(:nickname)) { OpenStruct.new(nickname: nickname) }
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'message_transformers/examine/history'
2
+ require_relative 'message_transformers/who/move'
3
+ require_relative 'message_transformers/history'
@@ -0,0 +1,20 @@
1
+ module InternetScrabbleClub
2
+
3
+ class MultiQueue
4
+ def initialize
5
+ @queues = Hash.new { Array.new }
6
+ end
7
+
8
+ def enqueue(name, item)
9
+ @queues[name] += [item]
10
+ end
11
+
12
+ def dequeue(name, default = nil)
13
+ return @queues[name].shift if @queues[name].any?
14
+ return yield(name) if block_given?
15
+ return default unless default.nil?
16
+ fail ArgumentError, "Cannot dequeue item from empty queue"
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,3 @@
1
+ module InternetScrabbleClub
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: internet_scrabble_club
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Brus
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2014-08-29 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: "1.5"
22
+ type: :development
23
+ version_requirements: *id001
24
+ - !ruby/object:Gem::Dependency
25
+ name: rake
26
+ prerelease: false
27
+ requirement: &id002 !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - &id009
30
+ - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id002
35
+ - !ruby/object:Gem::Dependency
36
+ name: celluloid-io
37
+ prerelease: false
38
+ requirement: &id003 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ version: 0.15.0
43
+ type: :runtime
44
+ version_requirements: *id003
45
+ - !ruby/object:Gem::Dependency
46
+ name: parslet
47
+ prerelease: false
48
+ requirement: &id004 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: 1.5.0
53
+ type: :runtime
54
+ version_requirements: *id004
55
+ - !ruby/object:Gem::Dependency
56
+ name: descendants_tracker
57
+ prerelease: false
58
+ requirement: &id005 !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: 0.0.4
63
+ type: :runtime
64
+ version_requirements: *id005
65
+ - !ruby/object:Gem::Dependency
66
+ name: events
67
+ prerelease: false
68
+ requirement: &id006 !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ version: 0.9.8
73
+ type: :runtime
74
+ version_requirements: *id006
75
+ - !ruby/object:Gem::Dependency
76
+ name: anima
77
+ prerelease: false
78
+ requirement: &id007 !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.0
83
+ type: :runtime
84
+ version_requirements: *id007
85
+ - !ruby/object:Gem::Dependency
86
+ name: middleware
87
+ prerelease: false
88
+ requirement: &id008 !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ version: 0.1.0
93
+ type: :runtime
94
+ version_requirements: *id008
95
+ description:
96
+ email:
97
+ - thomas.brus@me.com
98
+ executables: []
99
+
100
+ extensions: []
101
+
102
+ extra_rdoc_files: []
103
+
104
+ files:
105
+ - .gitignore
106
+ - .ruby-version
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - internet_scrabble_club.gemspec
112
+ - lib/internet_scrabble_club.rb
113
+ - lib/internet_scrabble_club/client.rb
114
+ - lib/internet_scrabble_club/client/extensions/authentication.rb
115
+ - lib/internet_scrabble_club/client/extensions/echo_ping.rb
116
+ - lib/internet_scrabble_club/client/extensions/keep_alive.rb
117
+ - lib/internet_scrabble_club/client/middleware.rb
118
+ - lib/internet_scrabble_club/client/middleware/emit.rb
119
+ - lib/internet_scrabble_club/client/middleware/parse.rb
120
+ - lib/internet_scrabble_club/client/middleware/read.rb
121
+ - lib/internet_scrabble_club/client/middleware/transform.rb
122
+ - lib/internet_scrabble_club/message_parsers.rb
123
+ - lib/internet_scrabble_club/message_parsers/base.rb
124
+ - lib/internet_scrabble_club/message_parsers/close.rb
125
+ - lib/internet_scrabble_club/message_parsers/examine.rb
126
+ - lib/internet_scrabble_club/message_parsers/examine/history.rb
127
+ - lib/internet_scrabble_club/message_parsers/history.rb
128
+ - lib/internet_scrabble_club/message_parsers/login.rb
129
+ - lib/internet_scrabble_club/message_parsers/ping.rb
130
+ - lib/internet_scrabble_club/message_parsers/ping/reply.rb
131
+ - lib/internet_scrabble_club/message_parsers/seek.rb
132
+ - lib/internet_scrabble_club/message_parsers/unseek.rb
133
+ - lib/internet_scrabble_club/message_parsers/who.rb
134
+ - lib/internet_scrabble_club/message_parsers/who/list.rb
135
+ - lib/internet_scrabble_club/message_parsers/who/move.rb
136
+ - lib/internet_scrabble_club/message_transformers.rb
137
+ - lib/internet_scrabble_club/message_transformers/base.rb
138
+ - lib/internet_scrabble_club/message_transformers/examine/history.rb
139
+ - lib/internet_scrabble_club/message_transformers/history.rb
140
+ - lib/internet_scrabble_club/message_transformers/who/move.rb
141
+ - lib/internet_scrabble_club/multi_queue.rb
142
+ homepage: https://github.com/thomasbrus/internet-scrabble-club
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+
147
+ post_install_message:
148
+ rdoc_options: []
149
+
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - *id009
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - *id009
158
+ requirements: []
159
+
160
+ rubyforge_project:
161
+ rubygems_version: 2.2.2
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Fetches scrabble games from the Internet Scrabble Club.
165
+ test_files: []
166
+
167
+ has_rdoc: