ircbgb 0.0.1.pre

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