ircbgb 0.0.1.pre

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,84 @@
1
+ # RFC 1459 Message Parser to be processed by Ragel
2
+ %%{
3
+ machine irc_message_parser;
4
+
5
+ action start_parser { eof = pe }
6
+ action clear_command { cmd = '' }
7
+ action clear_server { server = '' }
8
+ action clear_rest { rest = '' }
9
+ action clear_param { param = '' }
10
+ action clear_user_mask { user_mask = '' }
11
+ action append_user_mask { user_mask << data[p] }
12
+ action append_server { server << data[p] }
13
+ action append_param { param << data[p] }
14
+ action append_params { params << param }
15
+ action append_rest { rest << data[p] }
16
+ action append_command { cmd << data[p] }
17
+ action make_source_user {
18
+ source = ::Ircbgb::User.parse(user_mask)
19
+ }
20
+ action make_source_server {
21
+ source = ::Ircbgb::Server.new(server)
22
+ }
23
+ action show_me {
24
+ puts "Currently parsing data[#{p}/#{data.length}]: '#{data[p]}'"
25
+ }
26
+ action message_error {
27
+ raise ::Ircbgb::MessageFormatError, "invalid form #{data.inspect} at #{p} '#{data[p]}'"
28
+ }
29
+
30
+ sspace = ' ';
31
+ crlf = '\r\n';
32
+ whites = sspace | '\0' | '\r' | '\n';
33
+ nospcrlfcl = any - whites - ':';
34
+ nonwhite = any - whites;
35
+ userchar = any - '@' - whites;
36
+ ip4addr = digit{1,3} '.' digit{1,3} '.' digit{1,3} '.' digit{1,3};
37
+ ip6addr = (xdigit+ (':' xdigit+){7})
38
+ | ('0:0:0:0:0:' ('0' | [fF]{4}) ':' ip4addr);
39
+ hostaddr = ip4addr | ip6addr;
40
+ shortname = (alpha | digit) (alpha | digit | '-')* (alpha | digit)*;
41
+ hostname = shortname ('.' shortname)*;
42
+ host = hostname | hostaddr;
43
+ user = userchar+;
44
+ nick_special = '[' | ']' | '\\' | '`' | '_' | '^' | '{' | '|' | '}';
45
+ nick = (alpha | nick_special) (alpha | digit | nick_special | '-')*;
46
+ servername = hostname >clear_server $append_server %make_source_server;
47
+ nick_mask = (nick ('!' user ('@' host)?)?) >clear_user_mask $append_user_mask %make_source_user;
48
+
49
+ prefix = servername | nick_mask;
50
+ command = (alpha+ | digit+) >clear_command @append_command;
51
+ middle = (nospcrlfcl (':' | nospcrlfcl)*) >clear_param @append_param %append_params;
52
+ trailing = ((nospcrlfcl | ' ' | ':')*) >clear_rest @append_rest;
53
+ params = ((sspace+ middle){0,14} (sspace+ ':' trailing)?);
54
+
55
+ message = (':' prefix sspace+)? command params? crlf;
56
+
57
+ main := message >start_parser $err(message_error);
58
+ }%%
59
+
60
+ module Ircbgb
61
+ class MessageParser
62
+
63
+ %% write data;
64
+
65
+ def self.parse str
66
+ data = str
67
+ server = ''
68
+ user_mask = ''
69
+ para = ''
70
+ rest = ''
71
+ params = []
72
+ source = nil
73
+ cmd = ''
74
+
75
+ %% write init;
76
+ %% write exec;
77
+
78
+ params << rest unless rest.empty?
79
+
80
+ ::Ircbgb::Message.new source, cmd, params
81
+ end
82
+ end
83
+ end
84
+
@@ -0,0 +1,13 @@
1
+ module Ircbgb
2
+ class Server
3
+ attr_reader :host
4
+ alias :user :host
5
+ alias :nick :host
6
+
7
+ def initialize h
8
+ @host = h
9
+ end
10
+
11
+ def to_s; @host.dup; end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Ircbgb
2
+ class User
3
+ attr_reader :nick, :user, :host
4
+
5
+ def initialize n, u, h
6
+ @nick = n.empty? ? '*' : n
7
+ @user = u.empty? ? '*' : u
8
+ @host = h.empty? ? '*' : h
9
+ @mask = "#{@nick}!#{@user}@#{@host}"
10
+ end
11
+
12
+ def to_s; @mask.dup; end
13
+
14
+ def =~ other
15
+ return Matcher.new(other) =~ @mask if String === other
16
+ @mask =~ other
17
+ end
18
+
19
+ class << self
20
+ def parse nick_mask
21
+ case nick_mask
22
+ when Ircbgb::User
23
+ nick_mask
24
+ when /\A([^!]+)!([^@]*)@(.*)\Z/
25
+ new $1, $2, $3
26
+ else
27
+ new nick_mask, '', ''
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Ircbgb
2
+ VERSION = "0.0.1.pre"
3
+ end
@@ -0,0 +1,171 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Ircbgb::Client do
4
+ include Stubz
5
+
6
+ def client
7
+ @client ||= Ircbgb::Client.new { |c|
8
+ c.servers << 'irc://server1.example.org'
9
+ c.servers << 'irc://server2.example.com:7001'
10
+ c.nicks = ['bot1', 'bot2', 'bot3']
11
+ c.realname = 'I am a bot'
12
+ c.username = 'botty'
13
+ }
14
+ end
15
+
16
+ describe "initialize" do
17
+ it "sets up servers" do
18
+ client.servers.must_equal [
19
+ 'irc://server1.example.org',
20
+ 'irc://server2.example.com:7001'
21
+ ]
22
+ end
23
+
24
+ it "sets up nicknames" do
25
+ client.nicks.must_equal [ 'bot1', 'bot2', 'bot3' ]
26
+ end
27
+
28
+ it "sets up a realname" do
29
+ client.realname.must_equal 'I am a bot'
30
+ end
31
+
32
+ it "sets up a username" do
33
+ client.username.must_equal 'botty'
34
+ end
35
+ end
36
+
37
+ describe "attribs" do
38
+ it "sets servers from a single string" do
39
+ client.servers = 'irc://foo.bar.bazz'
40
+ client.servers.must_equal [ 'irc://foo.bar.bazz' ]
41
+ end
42
+
43
+ it "sets servers from an array" do
44
+ client.servers = [ 'frosty', 'snowman' ]
45
+ client.servers.must_equal [ 'frosty', 'snowman' ]
46
+ end
47
+
48
+ it "sets nicks from a single string" do
49
+ client.nicks = 'peaches'
50
+ client.nicks.must_equal [ 'peaches' ]
51
+ end
52
+
53
+ it "sets nicks from an array" do
54
+ client.nicks = ['meaty', 'meaty', 'moo']
55
+ client.nicks.must_equal ['meaty', 'meaty', 'moo']
56
+ end
57
+
58
+ it "parses server uris" do
59
+ client.uris.first.host.must_equal 'server1.example.org'
60
+ client.uris.first.port.must_equal 6667
61
+ client.uris.last.host.must_equal 'server2.example.com'
62
+ client.uris.last.port.must_equal 7001
63
+ end
64
+ end
65
+
66
+ describe "starting and stopping" do
67
+ before do
68
+ @mock_io = MiniTest::Mock.new
69
+ stub(::IoUnblock::TcpSocket, :new, @mock_io)
70
+ end
71
+
72
+ it "starts and stops an unblocked tcp socket and waits until its running" do
73
+ @mock_io.expect(:start, nil)
74
+ @mock_io.expect(:running?, true)
75
+ @mock_io.expect(:stop, nil)
76
+ client.start
77
+ client.stop
78
+ @mock_io.verify
79
+ end
80
+ end
81
+
82
+ describe "io" do
83
+ before do
84
+ @socket = socket = UnblockedTcpSocket.new
85
+ stub(::IoUnblock::TcpSocket, :new) { |*a|
86
+ socket.init_with(*a)
87
+ }
88
+ client.start
89
+ end
90
+
91
+ after do
92
+ client.stop
93
+ end
94
+
95
+ describe "connecting" do
96
+ it "is connected once it's sent the user nick and pong" do
97
+ client.connected?.must_equal false
98
+ @socket.trigger_start
99
+ @socket.server_write 'PING :give tHi:S baCK!'
100
+ client.connected?.must_equal true
101
+ @socket.written.must_equal [
102
+ 'USER botty 0 * :I am a bot',
103
+ 'NICK bot1',
104
+ 'PONG :give tHi:S baCK!'
105
+ ]
106
+ end
107
+
108
+ it "chooses alternate nicknames" do
109
+ @socket.trigger_start
110
+ @socket.server_write '433 * bot1 :Nickname is already in use.'
111
+ client.connected?.must_equal false
112
+ @socket.server_write '433 * bot2 :Nickname is already in use.'
113
+ client.connected?.must_equal false
114
+ @socket.server_write 'PING :0123456'
115
+ client.connected?.must_equal true
116
+ @socket.written.must_equal [
117
+ 'USER botty 0 * :I am a bot',
118
+ 'NICK bot1',
119
+ 'NICK bot2',
120
+ 'NICK bot3',
121
+ 'PONG :0123456'
122
+ ]
123
+ end
124
+
125
+ it "shuts down if all nicknames are taken" do
126
+ @socket.trigger_start
127
+ @socket.server_write '433 * bot1 :Nickname is already in use.'
128
+ @socket.server_write '433 * bot2 :Nickname is already in use.'
129
+ @socket.server_write '433 * bot3 :Nickname is already in use.'
130
+ client.connected?.must_equal false
131
+ @socket.written.must_equal [
132
+ 'USER botty 0 * :I am a bot',
133
+ 'NICK bot1',
134
+ 'NICK bot2',
135
+ 'NICK bot3',
136
+ 'QUIT'
137
+ ]
138
+ end
139
+ end
140
+
141
+ describe "events" do
142
+ it "binds and triggers an event based upon the command" do
143
+ params = nil
144
+ msg = nil
145
+ client.on_403 do |ps, m|
146
+ params = ps
147
+ msg = m
148
+ end
149
+ @socket.server_write '403 these are :my various arguments'
150
+ params.must_equal ['these', 'are', 'my various arguments']
151
+ msg.command.must_equal '403'
152
+ msg.source.nick.must_equal 'server1.example.org'
153
+ end
154
+
155
+ it "triggers an event split across multiple reads" do
156
+ params = nil
157
+ msg = nil
158
+ client.on_ping do |ps, m|
159
+ params = ps
160
+ msg = m
161
+ end
162
+
163
+ @socket.server_write_raw ':server1.example.org PI'
164
+ @socket.server_write_raw 'NG :echo this bac'
165
+ @socket.server_write_raw "k to me\r\n"
166
+ params.must_equal ['echo this back to me']
167
+ msg.command.must_equal 'PING'
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Ircbgb::Matcher do
4
+ def matcher str; Ircbgb::Matcher.new str; end
5
+
6
+ it "treats ? as any single character (/./)" do
7
+ match = matcher('?lAm?')
8
+ 'flame'.must_match match
9
+ 'bLAMe'.must_match match
10
+ 'lLaMa'.must_match match
11
+ 'claMer'.wont_match match
12
+ 'lam'.wont_match match
13
+ 'filame'.wont_match match
14
+ 'filament'.wont_match match
15
+ end
16
+
17
+ it "treats * as 0 or more characters (/.*/)" do
18
+ match = matcher('*laM*')
19
+ 'lam'.must_match match
20
+ 'LaMabcdefghijklmnopqrstuvwxyz1234567890[]{}()!@#$%^&*'.must_match match
21
+ 'abcdefghijklmnopqrstuvwxyz1234567890[]{}()!@#$%^&*lAM'.must_match match
22
+ 'abcdefghijklmnopqrsLAMtuvwxyz1234567890[]{}()!@#$%^&*'.must_match match
23
+ 'flMa'.wont_match match
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Ircbgb::MessageParser do
4
+ def parse_it str
5
+ Ircbgb::MessageParser.parse ":#{str}\r\n"
6
+ end
7
+
8
+ it "parses user message without args" do
9
+ msg = parse_it 'my_nick!~my|user_[nam{@some.host KiCk'
10
+ msg.source.nick.must_equal 'my_nick' # just to enusre we've got a user proper
11
+ msg.source.to_s.must_equal 'my_nick!~my|user_[nam{@some.host'
12
+ msg.command.must_equal 'KICK'
13
+ msg.params.must_equal []
14
+ end
15
+
16
+ it "parses user message with args" do
17
+ msg = parse_it '[slappy-!~figgle@scrappy.dappy.doo 919 a1,a11 b2.3 c3:88'
18
+ msg.source.to_s.must_equal '[slappy-!~figgle@scrappy.dappy.doo'
19
+ msg.command.must_equal '919'
20
+ msg.params.must_equal ['a1,a11', 'b2.3', 'c3:88']
21
+ end
22
+
23
+ it "parses user message with rest" do
24
+ msg = parse_it 'my_nick!my.user@my.host PRIVmsg :this maintains the spaces '
25
+ msg.source.to_s.must_equal 'my_nick!my.user@my.host'
26
+ msg.command.must_equal 'PRIVMSG'
27
+ msg.params.must_equal ['this maintains the spaces ']
28
+ end
29
+
30
+ it "parses user message with args and rest" do
31
+ msg = parse_it 'my_nick!my.user@my.host part #channel1,&channel2 limey :MOA:R SP:ACES!'
32
+ msg.source.to_s.must_equal 'my_nick!my.user@my.host'
33
+ msg.command.must_equal 'PART'
34
+ msg.params.must_equal ['#channel1,&channel2', 'limey', 'MOA:R SP:ACES!']
35
+ end
36
+
37
+ it "parses server message without args" do
38
+ msg = parse_it 'sweet.server 302'
39
+ msg.source.nick.must_equal 'sweet.server'
40
+ msg.source.to_s.must_equal 'sweet.server'
41
+ msg.command.must_equal '302'
42
+ msg.params.must_equal []
43
+ end
44
+
45
+ it "parses server message with args" do
46
+ msg = parse_it 'sweet.server noticed a1 a2,a3 a4:5'
47
+ msg.source.to_s.must_equal 'sweet.server'
48
+ msg.command.must_equal 'NOTICED'
49
+ msg.params.must_equal ['a1', 'a2,a3', 'a4:5']
50
+ end
51
+
52
+ it "parses server message with rest" do
53
+ msg = parse_it 'sweet.server quit :this has spaces !'
54
+ msg.source.to_s.must_equal 'sweet.server'
55
+ msg.command.must_equal 'QUIT'
56
+ msg.params.must_equal ['this has spaces !']
57
+ end
58
+
59
+ it "parses server message with args and rest" do
60
+ msg = parse_it 'sweet.server Pezz fright,:clap beddy pram :this : has : colons!'
61
+ msg.source.to_s.must_equal 'sweet.server'
62
+ msg.command.must_equal 'PEZZ'
63
+ msg.params.must_equal ['fright,:clap', 'beddy', 'pram', 'this : has : colons!']
64
+ end
65
+
66
+ it "parses ipv4 user hosts" do
67
+ msg = parse_it 'me!~notyou@123.45.67.89 mklame'
68
+ msg.source.host.must_equal '123.45.67.89'
69
+ end
70
+
71
+ it "parses ipv6 user hosts" do
72
+ msg = parse_it 'me!~notyou@00f:a221:03:e:1234:420b:f6:9a mklame'
73
+ msg.source.host.must_equal '00f:a221:03:e:1234:420b:f6:9a'
74
+ end
75
+
76
+ it "parses ipv4 over ipv6 hosts" do
77
+ msg = parse_it 'me!~notyou@0:0:0:0:0:0:1.2.3.4 mklame'
78
+ msg.source.host.must_equal '0:0:0:0:0:0:1.2.3.4'
79
+ msg = parse_it 'me!~notyou@0:0:0:0:0:FffF:1.2.3.4 mklame'
80
+ msg.source.host.must_equal '0:0:0:0:0:FffF:1.2.3.4'
81
+ end
82
+
83
+ it "does not parse malformed messages" do
84
+ lambda {
85
+ parse_it 'invalid'
86
+ }.must_raise ::Ircbgb::MessageFormatError
87
+
88
+ lambda {
89
+ parse_it 'nick!user@hostname.com '
90
+ }.must_raise ::Ircbgb::MessageFormatError
91
+
92
+ lambda {
93
+ parse_it ''
94
+ }.must_raise ::Ircbgb::MessageFormatError
95
+
96
+ lambda {
97
+ parse_it ' '
98
+ }.must_raise ::Ircbgb::MessageFormatError
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Ircbgb::Message do
4
+ def client_message *args
5
+ @client_message ||= Ircbgb::Message.new(
6
+ Ircbgb::User.parse("nick!~user@some.host.name"),
7
+ 'CMD',
8
+ ['all', 'the', 'pretty little args']
9
+ )
10
+ end
11
+
12
+ def server_message
13
+ @server_message ||= Ircbgb::Message.new(
14
+ Ircbgb::Server.new('host.domain.tld'),
15
+ '303',
16
+ []
17
+ )
18
+ end
19
+
20
+ it "parses the source" do
21
+ client_message.source.to_s.must_equal "nick!~user@some.host.name"
22
+ server_message.source.to_s.must_equal "host.domain.tld"
23
+ end
24
+
25
+ it "parses the command" do
26
+ client_message.command.must_equal 'CMD'
27
+ server_message.command.must_equal '303'
28
+ end
29
+
30
+ it "parses the parameters" do
31
+ client_message.params.must_equal ['all', 'the', 'pretty little args']
32
+ server_message.params.must_equal []
33
+ end
34
+
35
+ it "identifies a numeric reply" do
36
+ client_message.numeric?.must_equal false
37
+ server_message.numeric?.must_equal true
38
+ end
39
+ end