ircmachine 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -0
- data/Rakefile +8 -0
- data/ircmachine.gemspec +23 -0
- data/lib/ircmachine/command.rb +155 -0
- data/lib/ircmachine/ircsocketadapter.rb +71 -0
- data/lib/ircmachine/message.rb +53 -0
- data/lib/ircmachine/version.rb +3 -0
- data/lib/ircmachine.rb +3 -0
- data/spec/command_spec.rb +47 -0
- data/spec/ircsocketadapter_spec.rb +90 -0
- data/spec/message_spec.rb +59 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eb3c6abf09f3ce657496ebac2b2bb384a5fa9962
|
4
|
+
data.tar.gz: fff381302cd8132fe6412be5837f398d500a0d0f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed5df1caa8891bfa3da2e627d1eccfb21d85d06d08ed4dcce37d8d5eea72aa8b3f6b6551238801be388b836f57f6c0185eea881aefa0964177e10cff4cd52dfb
|
7
|
+
data.tar.gz: 1b677c17b260d8c4234967a689e0fa70891c11ccacb65c0c0ad1701995d33fe2e8be0c98b1854fef39a5f767a2d0b51d47e65912dc28e16e81e9892bdb0a74ce
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Chris Davies
|
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,25 @@
|
|
1
|
+
# IRCMachine
|
2
|
+
|
3
|
+
A very basic gem for parsing and dealing with the IRC protocol.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ircmachine'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ircmachine
|
18
|
+
|
19
|
+
## Contributing
|
20
|
+
|
21
|
+
1. Fork it ( http://github.com/north636/ircmachine/fork )
|
22
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
23
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
24
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
25
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/ircmachine.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ircmachine/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'ircmachine'
|
8
|
+
spec.version = IRCMachine::VERSION
|
9
|
+
spec.authors = ['Chris Davies']
|
10
|
+
spec.email = ['chris.davies.uk@member.mensa.org']
|
11
|
+
spec.summary = %q{A simple IRC protocol parser.}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'ircmachine/message'
|
2
|
+
|
3
|
+
module IRCMachine
|
4
|
+
module Command
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def cmds
|
8
|
+
@cmds ||= {}
|
9
|
+
end
|
10
|
+
private :cmds
|
11
|
+
|
12
|
+
def from_msg(msg)
|
13
|
+
if (klass = cmds[msg.command]).nil?
|
14
|
+
fail ArgumentError, "Unknown command: #{msg.command}"
|
15
|
+
else
|
16
|
+
klass.new(msg.prefix, *msg.params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(str)
|
21
|
+
if (msg = IRCMachine::Message.parse(str)).nil?
|
22
|
+
fail ArgumentError, 'Invalid format for IRC message.'
|
23
|
+
else
|
24
|
+
from_msg(msg)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def defcmd(cmd, *arg_names)
|
29
|
+
cmd = cmd.strip.upcase
|
30
|
+
|
31
|
+
klass = Class.new do
|
32
|
+
define_method(:initialize) do |prefix = nil, *args|
|
33
|
+
instance_variable_set('@prefix', prefix)
|
34
|
+
|
35
|
+
arg_names.zip(args).each do |e|
|
36
|
+
name, value = e[0], e[1]
|
37
|
+
instance_variable_set("@#{name}", value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create readers for the parameters.
|
42
|
+
arg_names.each do |name|
|
43
|
+
define_method(name) { instance_variable_get("@#{name}") }
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method(:prefix) { instance_variable_get('@prefix') }
|
47
|
+
|
48
|
+
define_method(:to_msg) do
|
49
|
+
param_values = arg_names.map { |k| instance_variable_get("@#{k}") }.join(' ')
|
50
|
+
|
51
|
+
IRCMachine::Message.new(
|
52
|
+
instance_variable_get('@prefix'),
|
53
|
+
cmd,
|
54
|
+
param_values
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method(:to_s) { self.to_msg.to_s }
|
59
|
+
end
|
60
|
+
|
61
|
+
const_set("#{cmd.capitalize}Command", klass)
|
62
|
+
cmds[cmd] = klass
|
63
|
+
end
|
64
|
+
private :defcmd
|
65
|
+
|
66
|
+
# Same as defcmd, but arguments are passed in reverse order. This is to
|
67
|
+
# support commands like WHOIS, which has an optional parameter in the
|
68
|
+
# middle, rather than at the end...
|
69
|
+
def defrcmd(cmd, *arg_names)
|
70
|
+
cmd = cmd.strip.upcase
|
71
|
+
|
72
|
+
klass = Class.new do
|
73
|
+
define_method(:initialize) do |prefix = nil, *args|
|
74
|
+
instance_variable_set('@prefix', prefix)
|
75
|
+
|
76
|
+
arg_names.reverse.zip(args.reverse).each do |e|
|
77
|
+
name, value = e[0], e[1]
|
78
|
+
instance_variable_set("@#{name}", value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create readers for the parameters.
|
83
|
+
arg_names.each do |name|
|
84
|
+
define_method(name) { instance_variable_get("@#{name}") }
|
85
|
+
end
|
86
|
+
|
87
|
+
define_method(:prefix) { instance_variable_get('@prefix') }
|
88
|
+
|
89
|
+
define_method(:to_msg) do
|
90
|
+
param_values = arg_names.reverse.map { |k| instance_variable_get("@#{k}") }.join(' ')
|
91
|
+
|
92
|
+
IRCMachine::Message.new(
|
93
|
+
instance_variable_get('@prefix'),
|
94
|
+
cmd,
|
95
|
+
param_values
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
define_method(:to_s) { self.to_msg.to_s }
|
100
|
+
end
|
101
|
+
|
102
|
+
const_set("#{cmd.capitalize}Command", klass)
|
103
|
+
cmds[cmd] = klass
|
104
|
+
end
|
105
|
+
private :defrcmd
|
106
|
+
end
|
107
|
+
|
108
|
+
defcmd 'PASS', :password
|
109
|
+
defcmd 'NICK', :nickname
|
110
|
+
defcmd 'USER', :user, :mode, :unused, :realname, :name, :password
|
111
|
+
defcmd 'MODE', :target, :modestring
|
112
|
+
defcmd 'SERVICE', :nickname, :reserved, :distribution, :type, :reserved2, :info
|
113
|
+
defcmd 'QUIT', :message
|
114
|
+
defcmd 'SQUIT', :server, :comment
|
115
|
+
defcmd 'JOIN', :channels, :keys
|
116
|
+
defcmd 'PART', :channels
|
117
|
+
defcmd 'TOPIC', :channel, :topic
|
118
|
+
defcmd 'NAMES', :channels, :target
|
119
|
+
defcmd 'LIST', :channels, :target
|
120
|
+
defcmd 'INVITE', :nickname, :channel
|
121
|
+
defcmd 'KICK', :channels, :users, :comment
|
122
|
+
defcmd 'PRIVMSG', :target, :message
|
123
|
+
defcmd 'NOTICE', :target, :message
|
124
|
+
defcmd 'MOTD', :target
|
125
|
+
defcmd 'LUSERS', :mask, :target
|
126
|
+
defcmd 'VERSION', :target
|
127
|
+
defcmd 'STATS', :query, :target
|
128
|
+
defcmd 'LINKS', :remote_server, :server_mask
|
129
|
+
defcmd 'TIME', :target
|
130
|
+
defcmd 'CONNECT', :server, :port, :remote_server
|
131
|
+
defcmd 'TRACE', :target
|
132
|
+
defcmd 'ADMIN', :target
|
133
|
+
defcmd 'INFO', :target
|
134
|
+
defcmd 'SERVLIST', :mask, :type
|
135
|
+
defcmd 'SQUERY', :service, :text
|
136
|
+
defcmd 'WHO', :mask, :o
|
137
|
+
defrcmd 'WHOIS', :target, :who
|
138
|
+
defcmd 'WHOWAS', :nicknames, :count, :target
|
139
|
+
defcmd 'KILL', :nickname, :comment
|
140
|
+
defcmd 'PING', :who, :server
|
141
|
+
defcmd 'PONG', :who, :server
|
142
|
+
defcmd 'ERROR', :message
|
143
|
+
defcmd 'AWAY', :text
|
144
|
+
defcmd 'REHASH'
|
145
|
+
defcmd 'DIE'
|
146
|
+
defcmd 'RESTART'
|
147
|
+
defcmd 'SUMMON', :user, :target, :channel
|
148
|
+
defcmd 'USERS', :target
|
149
|
+
defcmd 'WALLOPS', :text
|
150
|
+
|
151
|
+
# TODO: support multiple nicknames properly?
|
152
|
+
defcmd 'USERHOST', :nickname, :nickname2, :nickname3, :nickname4, :nickname5
|
153
|
+
defcmd 'ISON', :nickname, :nickname2, :nickname3, :nickname4, :nickname5
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module IRCMachine
|
2
|
+
class IRCSocketAdapter
|
3
|
+
attr_reader :socket
|
4
|
+
|
5
|
+
# As per RFC 2812:
|
6
|
+
IRC_MAX_DATA = 512
|
7
|
+
|
8
|
+
def initialize(socket)
|
9
|
+
@socket = socket
|
10
|
+
@messages = []
|
11
|
+
@messages_mtx = Mutex.new
|
12
|
+
@buffer = ''
|
13
|
+
@buffer_mtx = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def recv(blocking = true)
|
17
|
+
# If we have data to return already, return it.
|
18
|
+
@messages_mtx.synchronize { return @messages.shift unless @messages.empty? }
|
19
|
+
|
20
|
+
got_msg = false
|
21
|
+
msg = nil
|
22
|
+
until got_msg
|
23
|
+
data =
|
24
|
+
if blocking
|
25
|
+
@socket.recv(IRC_MAX_DATA)
|
26
|
+
else
|
27
|
+
@socket.recv_nonblock(IRC_MAX_DATA)
|
28
|
+
end
|
29
|
+
|
30
|
+
unless data.nil?
|
31
|
+
@buffer_mtx.synchronize do
|
32
|
+
data.each_char do |c|
|
33
|
+
@buffer << c
|
34
|
+
|
35
|
+
if c == "\n"
|
36
|
+
@messages_mtx.synchronize do
|
37
|
+
begin
|
38
|
+
msg = IRCMachine::Message.parse(@buffer)
|
39
|
+
@messages << msg
|
40
|
+
rescue ArgumentError
|
41
|
+
# => Not recognised; ignore it.
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@buffer = ''
|
45
|
+
elsif @buffer.size > IRC_MAX_DATA
|
46
|
+
# Too long. Drop it.
|
47
|
+
@buffer = ''
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
msg = @messages_mtx.synchronize { @messages.shift unless @messages.empty? }
|
54
|
+
got_msg = !blocking || !msg.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
msg
|
58
|
+
end
|
59
|
+
|
60
|
+
def recv_nonblock
|
61
|
+
recv(false)
|
62
|
+
end
|
63
|
+
|
64
|
+
def send(data)
|
65
|
+
msg = IRCMachine::Message.parse(data)
|
66
|
+
fail ArgumentError, "Invalid IRC data." if msg.nil?
|
67
|
+
|
68
|
+
@socket.send(msg.to_s)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module IRCMachine
|
2
|
+
class Message
|
3
|
+
# Based on RFC 2812, section 2.3.1
|
4
|
+
SHORTNAME = /[a-z0-9][a-z0-9-]*[a-z0-9]*/in
|
5
|
+
HOSTNAME = /#{SHORTNAME}(?:\.#{SHORTNAME})*/n
|
6
|
+
SERVERNAME = /#{HOSTNAME}/n
|
7
|
+
SPECIAL = /[\x5b-\x60\x7b-\x7d]/n
|
8
|
+
NICKNAME = /[a-z\x5b-\x60\x7b-\x7d][a-z0-9\x5b-\x60\x7b-\x7d-]{,8}/in
|
9
|
+
USER = /[\x01-\x09\x0b-\x0c\x0e-\x1f\x21-\x3f\x41-\xff]+/n
|
10
|
+
IP4ADDR = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/n
|
11
|
+
IP6ADDR = /(?:[0-9a-f]+(?::[0-9a-f]+){7})|(?:0:0:0:0:0:(?:0|ffff):#{IP4ADDR})/in
|
12
|
+
HOSTADDR = /#{IP4ADDR}|#{IP6ADDR}/n
|
13
|
+
HOST = /#{HOSTNAME}|#{HOSTADDR}/n
|
14
|
+
PREFIX = /#{SERVERNAME}|(?:#{NICKNAME}((?:!#{USER})?@#{HOST})?)/n
|
15
|
+
COMMAND = /[a-z]+|[0-9]{3}/in
|
16
|
+
NOSPCRLFCL = /[\x01-\x09\x0b-\x0c\x0e-\x1f\x21-\x39\x3b-\xff]/n
|
17
|
+
TRAILING = /(?:[: ]|#{NOSPCRLFCL})*/n
|
18
|
+
MIDDLE = /#{NOSPCRLFCL}(?::|#{NOSPCRLFCL})*/n
|
19
|
+
PARAMS = /(?:(?: #{MIDDLE}){,14}(?: :#{TRAILING})?)|(?:(?: #{MIDDLE}){,14}(?: :?#{TRAILING})?)/n
|
20
|
+
MESSAGE = /^(?<prefix>:#{PREFIX} )?(?<command>#{COMMAND})(?<params>#{PARAMS})?\r\n$/n
|
21
|
+
|
22
|
+
attr_reader :prefix, :command, :params
|
23
|
+
|
24
|
+
def initialize(prefix, command, params)
|
25
|
+
@prefix = prefix && prefix.strip
|
26
|
+
@command = command
|
27
|
+
|
28
|
+
params = (params || '').strip
|
29
|
+
@params =
|
30
|
+
if params[0] == ':'
|
31
|
+
[ params ]
|
32
|
+
elsif (trail_index = params.index(' :')).nil?
|
33
|
+
params.split(/ +/)
|
34
|
+
else
|
35
|
+
params[0...trail_index].split(/ +/).push(params[trail_index..-1].strip)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
str = ''
|
41
|
+
str << "#{prefix} " unless prefix.nil?
|
42
|
+
str << command
|
43
|
+
str << " #{params.join(' ')}" unless params.empty?
|
44
|
+
str << "\r\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse(str)
|
48
|
+
unless (m = MESSAGE.match(str)).nil?
|
49
|
+
Message.new(m[:prefix], m[:command], m[:params])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/ircmachine.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'ircmachine/command'
|
4
|
+
|
5
|
+
describe IRCMachine::Command do
|
6
|
+
describe 'defcmd' do
|
7
|
+
it 'should populate arguments correctly' do
|
8
|
+
c = IRCMachine::Command.parse("JOIN #channel key\r\n")
|
9
|
+
c.channels.must_equal '#channel'
|
10
|
+
c.keys.must_equal 'key'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should leave fields as nil if not given' do
|
14
|
+
c = IRCMachine::Command.parse("JOIN #channel\r\n")
|
15
|
+
c.channels.must_equal '#channel'
|
16
|
+
c.keys.must_be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should produce commands that themselves produce correct Message objects' do
|
20
|
+
c = IRCMachine::Command.parse(":foo JOIN #channel key\r\n")
|
21
|
+
m = c.to_msg
|
22
|
+
m.wont_be_nil
|
23
|
+
m.prefix.must_equal ':foo'
|
24
|
+
m.command.must_equal 'JOIN'
|
25
|
+
m.params.must_equal ['#channel', 'key']
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should produce commands that themselves produce valid IRC commands with to_s' do
|
29
|
+
c = IRCMachine::Command.parse(":foo QUIT :my quit message\r\n")
|
30
|
+
c.to_s.must_equal ":foo QUIT :my quit message\r\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'defrcmd' do
|
35
|
+
it 'should populate the last argument first' do
|
36
|
+
c = IRCMachine::Command.parse("WHOIS foo\r\n")
|
37
|
+
c.target.must_be_nil
|
38
|
+
c.who.must_equal 'foo'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should populate the arguments in reverse order' do
|
42
|
+
c = IRCMachine::Command.parse("WHOIS example.com foo\r\n")
|
43
|
+
c.target.must_equal 'example.com'
|
44
|
+
c.who.must_equal 'foo'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'ircmachine/ircsocketadapter'
|
4
|
+
|
5
|
+
describe IRCMachine::IRCSocketAdapter do
|
6
|
+
it 'sends valid IRC data' do
|
7
|
+
sock = MiniTest::Mock.new
|
8
|
+
sock.expect(:send, true, [ "JOIN #channel\r\n" ])
|
9
|
+
|
10
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
11
|
+
sa.send("JOIN #channel\r\n")
|
12
|
+
|
13
|
+
sock.verify
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'raises an ArgumentError when trying to send invalid IRC data' do
|
17
|
+
sock = MiniTest::Mock.new
|
18
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
19
|
+
|
20
|
+
proc {
|
21
|
+
sa.send("this isn't IRC data!")
|
22
|
+
}.must_raise ArgumentError
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns Message objects when receiving a single message' do
|
26
|
+
sock = MiniTest::Mock.new
|
27
|
+
sock.expect(:recv, ":foo@example.com QUIT :some message\r\n", [ Fixnum ])
|
28
|
+
|
29
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
30
|
+
msg = sa.recv
|
31
|
+
|
32
|
+
msg.wont_be_nil
|
33
|
+
msg.must_be_instance_of IRCMachine::Message
|
34
|
+
|
35
|
+
sock.verify
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'queues Message objects and returns them in order when receiving multiple messages' do
|
39
|
+
sock = MiniTest::Mock.new
|
40
|
+
sock.expect(:recv, ":foo@example.com QUIT :some message\r\n:bar@example.com PRIVMSG #chan :blah blah\r\n", [ Fixnum ])
|
41
|
+
|
42
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
43
|
+
msg = sa.recv
|
44
|
+
|
45
|
+
msg.wont_be_nil
|
46
|
+
msg.must_be_instance_of IRCMachine::Message
|
47
|
+
msg.command.must_equal 'QUIT'
|
48
|
+
|
49
|
+
msg = sa.recv
|
50
|
+
|
51
|
+
msg.wont_be_nil
|
52
|
+
msg.must_be_instance_of IRCMachine::Message
|
53
|
+
msg.command.must_equal 'PRIVMSG'
|
54
|
+
|
55
|
+
sock.verify
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'buffers messages if only partial data is received and does multiple recv if blocking' do
|
59
|
+
sock = MiniTest::Mock.new
|
60
|
+
sock.expect(:recv, ":foo@example.com QUIT :", [ Fixnum ])
|
61
|
+
sock.expect(:recv, "some message\r\n", [ Fixnum ])
|
62
|
+
|
63
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
64
|
+
msg = sa.recv
|
65
|
+
|
66
|
+
msg.wont_be_nil
|
67
|
+
msg.must_be_instance_of IRCMachine::Message
|
68
|
+
msg.command.must_equal 'QUIT'
|
69
|
+
|
70
|
+
sock.verify
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'buffers messages if only partial data is received, but only does on recv if non-blocking' do
|
74
|
+
|
75
|
+
sock = MiniTest::Mock.new
|
76
|
+
sock.expect(:recv_nonblock, ":foo@example.com QU", [ Fixnum ])
|
77
|
+
sock.expect(:recv_nonblock, "IT :blah\r\n", [ Fixnum ])
|
78
|
+
|
79
|
+
sa = IRCMachine::IRCSocketAdapter.new(sock)
|
80
|
+
msg = sa.recv_nonblock
|
81
|
+
msg.must_be_nil
|
82
|
+
|
83
|
+
msg = sa.recv_nonblock
|
84
|
+
msg.wont_be_nil
|
85
|
+
msg.must_be_instance_of IRCMachine::Message
|
86
|
+
msg.command.must_equal 'QUIT'
|
87
|
+
|
88
|
+
sock.verify
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'ircmachine/message'
|
4
|
+
|
5
|
+
describe IRCMachine::Message do
|
6
|
+
it 'should parse basic commands with no prefix and no parameters' do
|
7
|
+
m = IRCMachine::Message.parse("QUIT\r\n")
|
8
|
+
m.wont_be_nil
|
9
|
+
m.command.must_equal 'QUIT'
|
10
|
+
m.prefix.must_be_nil
|
11
|
+
m.params.must_be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should parse commands that have one parameter' do
|
15
|
+
m = IRCMachine::Message.parse("JOIN #hello\r\n")
|
16
|
+
m.wont_be_nil
|
17
|
+
m.command.must_equal 'JOIN'
|
18
|
+
m.prefix.must_be_nil
|
19
|
+
m.params.size.must_equal 1
|
20
|
+
m.params[0].must_equal '#hello'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should parse commands that have multiple parameters' do
|
24
|
+
m = IRCMachine::Message.parse("PRIVMSG #chan :this is my message\r\n")
|
25
|
+
m.wont_be_nil
|
26
|
+
m.command.must_equal 'PRIVMSG'
|
27
|
+
m.prefix.must_be_nil
|
28
|
+
m.params.size.must_equal 2
|
29
|
+
m.params[0].must_equal '#chan'
|
30
|
+
m.params[1].must_equal ':this is my message'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should parse commands with a prefix containing just a nickname' do
|
34
|
+
m = IRCMachine::Message.parse(":nick QUIT :blah\r\n")
|
35
|
+
m.wont_be_nil
|
36
|
+
m.command.must_equal 'QUIT'
|
37
|
+
m.prefix.must_equal ':nick'
|
38
|
+
m.params.size.must_equal 1
|
39
|
+
m.params[0].must_equal ':blah'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should parse commands with a prefix containing a nickname and a host' do
|
43
|
+
m = IRCMachine::Message.parse(":nick@host QUIT :blah\r\n")
|
44
|
+
m.wont_be_nil
|
45
|
+
m.command.must_equal 'QUIT'
|
46
|
+
m.prefix.must_equal ':nick@host'
|
47
|
+
m.params.size.must_equal 1
|
48
|
+
m.params[0].must_equal ':blah'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should parse commands with a prefix containing a nickname, username and a host' do
|
52
|
+
m = IRCMachine::Message.parse(":nick!user@host QUIT :blah\r\n")
|
53
|
+
m.wont_be_nil
|
54
|
+
m.command.must_equal 'QUIT'
|
55
|
+
m.prefix.must_equal ':nick!user@host'
|
56
|
+
m.params.size.must_equal 1
|
57
|
+
m.params[0].must_equal ':blah'
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ircmachine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Davies
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A simple IRC protocol parser.
|
42
|
+
email:
|
43
|
+
- chris.davies.uk@member.mensa.org
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- ircmachine.gemspec
|
54
|
+
- lib/ircmachine.rb
|
55
|
+
- lib/ircmachine/command.rb
|
56
|
+
- lib/ircmachine/ircsocketadapter.rb
|
57
|
+
- lib/ircmachine/message.rb
|
58
|
+
- lib/ircmachine/version.rb
|
59
|
+
- spec/command_spec.rb
|
60
|
+
- spec/ircsocketadapter_spec.rb
|
61
|
+
- spec/message_spec.rb
|
62
|
+
homepage: ''
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.0.3
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: A simple IRC protocol parser.
|
86
|
+
test_files:
|
87
|
+
- spec/command_spec.rb
|
88
|
+
- spec/ircsocketadapter_spec.rb
|
89
|
+
- spec/message_spec.rb
|