internet_scrabble_club 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: