ircparser 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/ircparser.rb +3 -1
- data/lib/ircparser/error.rb +1 -1
- data/lib/ircparser/message.rb +10 -164
- data/lib/ircparser/prefix.rb +8 -21
- data/lib/ircparser/stream.rb +46 -0
- data/lib/ircparser/wire/rfc.rb +248 -0
- data/test/test_message.rb +25 -0
- data/test/test_prefix.rb +46 -17
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3b3ba0c73061105629280badf2eda78d434a53c09bca59f77f73397740a6f062
|
4
|
+
data.tar.gz: b49d3de744a1fec6d9c9fabb17ca2df3c4416dc38f8d0afe953e9d0f4587d7b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04633fd5973f4b80cfdfa50b7c01c75683e9ecea0fe4e087c9d410d3a6cce649158eecc57e4902909fee8900031ebe50c3b5a6c9f070363120b711d762d29de8
|
7
|
+
data.tar.gz: fc2a8e5cadb156963f16743d857703c3bdf4900da0f86a18a9814e13ef52532282d7eee4ba7d1ee761439d46ee7107a62a5da1eb833de330ddc8bf7e33c22762
|
data/lib/ircparser.rb
CHANGED
@@ -16,9 +16,11 @@
|
|
16
16
|
module IRCParser
|
17
17
|
|
18
18
|
# Public: The version of IRCParser in use.
|
19
|
-
VERSION = '0.
|
19
|
+
VERSION = '0.6.0'
|
20
20
|
end
|
21
21
|
|
22
22
|
require_relative 'ircparser/error'
|
23
23
|
require_relative 'ircparser/message'
|
24
24
|
require_relative 'ircparser/prefix'
|
25
|
+
require_relative 'ircparser/stream'
|
26
|
+
require_relative 'ircparser/wire/rfc'
|
data/lib/ircparser/error.rb
CHANGED
@@ -21,7 +21,7 @@ module IRCParser
|
|
21
21
|
# Public: The value which failed to parse correctly.
|
22
22
|
attr_reader :value
|
23
23
|
|
24
|
-
# Public:
|
24
|
+
# Public: Initialises a new parse error with the specified invalid value.
|
25
25
|
#
|
26
26
|
# value - The value which failed to parse correctly.
|
27
27
|
def initialize value
|
data/lib/ircparser/message.rb
CHANGED
@@ -18,31 +18,24 @@ module IRCParser
|
|
18
18
|
# Public: Represents an IRC message.
|
19
19
|
class Message
|
20
20
|
|
21
|
-
# Internal: A regular expression which matches a tag.
|
22
|
-
MATCH_TAG = /^(?<name>[^\s=]+?)(?:=(?<value>[^\s;]+))?$/
|
23
|
-
|
24
|
-
# Internal: The characters which need to be escaped in tag values.
|
25
|
-
TAG_ESCAPES = {
|
26
|
-
'\\' => '\\\\',
|
27
|
-
';' => '\:',
|
28
|
-
"\s" => '\s',
|
29
|
-
"\r" => '\r',
|
30
|
-
"\n" => '\n'
|
31
|
-
}
|
32
|
-
|
33
21
|
# Public: Command name (e.g. PRIVMSG).
|
34
22
|
attr_reader :command
|
35
23
|
|
36
24
|
# Public: An array of command parameters.
|
37
25
|
attr_reader :parameters
|
38
26
|
|
39
|
-
# Public: The prefix of this command or nil if
|
27
|
+
# Public: The prefix of this command or nil if unprefixed.
|
40
28
|
attr_reader :prefix
|
41
29
|
|
42
30
|
# Public: A hash of IRCv3 message tags.
|
43
31
|
attr_reader :tags
|
44
32
|
|
45
|
-
# Public:
|
33
|
+
# Public: Initializes a new message.
|
34
|
+
#
|
35
|
+
# command - Command name (e.g. PRIVMSG).
|
36
|
+
# parameters - An array of command parameters.
|
37
|
+
# prefix - The prefix of this command or nil if unprefixed.
|
38
|
+
# tags - A hash of IRCv3 message tags.
|
46
39
|
def initialize command: String.new, parameters: Array.new, prefix: nil, tags: Hash.new
|
47
40
|
@command = command
|
48
41
|
@parameters = parameters
|
@@ -50,163 +43,16 @@ module IRCParser
|
|
50
43
|
@tags = tags
|
51
44
|
end
|
52
45
|
|
53
|
-
# Public: Parses an IRC message from
|
46
|
+
# Public: Parses an IRC message from wire form.
|
54
47
|
#
|
55
48
|
# line - The line to attempt to parse.
|
56
49
|
def self.parse line
|
57
|
-
|
58
|
-
# Ruby really needs some kind of basic type checking.
|
59
|
-
raise IRCParser::Error.new(line), "line is not a string" unless line.is_a? String
|
60
|
-
|
61
|
-
# Split the message up into an array of tokens.
|
62
|
-
buffer = line.clone
|
63
|
-
current_token = __get_token buffer
|
64
|
-
components = Hash.new
|
65
|
-
|
66
|
-
# Have we encountered IRCv3 message tags?
|
67
|
-
components[:tags] = Hash.new
|
68
|
-
if current_token != nil && current_token[0] == '@'
|
69
|
-
components[:tags] = __parse_tags current_token
|
70
|
-
current_token = __get_token buffer
|
71
|
-
end
|
72
|
-
|
73
|
-
# Have we encountered the prefix of this message?
|
74
|
-
if current_token != nil && current_token[0] == ':'
|
75
|
-
components[:prefix] = IRCParser::Prefix.new current_token[1..-1]
|
76
|
-
current_token = __get_token buffer
|
77
|
-
end
|
78
|
-
|
79
|
-
# The command parameter is mandatory.
|
80
|
-
if current_token != nil
|
81
|
-
components[:command] = current_token.upcase
|
82
|
-
current_token = __get_final_token buffer
|
83
|
-
else
|
84
|
-
raise IRCParser::Error.new(line), 'message is missing the command name'
|
85
|
-
end
|
86
|
-
|
87
|
-
# Try to parse all of the remaining parameters.
|
88
|
-
components[:parameters] = Array.new
|
89
|
-
while current_token != nil
|
90
|
-
components[:parameters] << current_token
|
91
|
-
current_token = __get_final_token buffer
|
92
|
-
end
|
93
|
-
|
94
|
-
return IRCParser::Message.new components
|
50
|
+
return IRCParser::RFCWireFormat.objectify line.clone
|
95
51
|
end
|
96
52
|
|
97
|
-
|
98
53
|
# Public: Serializes the message to a string.
|
99
54
|
def to_s
|
100
|
-
|
101
|
-
|
102
|
-
# Serialize the tags.
|
103
|
-
unless tags.empty?
|
104
|
-
buffer += '@'
|
105
|
-
buffer += __serialize_tags
|
106
|
-
buffer += ' '
|
107
|
-
end
|
108
|
-
|
109
|
-
# Serialize the prefix.
|
110
|
-
unless prefix.nil?
|
111
|
-
buffer += ':'
|
112
|
-
buffer += prefix.to_s
|
113
|
-
buffer += ' '
|
114
|
-
end
|
115
|
-
|
116
|
-
# Serialize the command.
|
117
|
-
buffer += command
|
118
|
-
|
119
|
-
# Serialize the parameters
|
120
|
-
buffer += __serialize_parameters
|
121
|
-
|
122
|
-
# We're done!
|
123
|
-
return buffer
|
124
|
-
end
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
# Internal: Retrieves a space delimited token from the buffer.
|
129
|
-
#
|
130
|
-
# data - The buffer to retrieve the token from.
|
131
|
-
def self.__get_token buffer
|
132
|
-
return nil if buffer.empty?
|
133
|
-
position = buffer.index ' '
|
134
|
-
if position == nil
|
135
|
-
token = buffer.clone
|
136
|
-
buffer.clear
|
137
|
-
return token
|
138
|
-
end
|
139
|
-
token = buffer.slice! 0...position
|
140
|
-
position = buffer.index /\S+/
|
141
|
-
if position == nil
|
142
|
-
buffer.clear
|
143
|
-
else
|
144
|
-
buffer.slice! 0...position
|
145
|
-
end
|
146
|
-
return token
|
147
|
-
end
|
148
|
-
|
149
|
-
# Internal: Retrieves a space delimited token that may be a <trailing> parameter.
|
150
|
-
#
|
151
|
-
# data - The buffer to retrieve the token from.
|
152
|
-
def self.__get_final_token buffer
|
153
|
-
return nil if buffer.empty?
|
154
|
-
if buffer[0] == ':'
|
155
|
-
token = buffer[1..-1]
|
156
|
-
buffer.clear
|
157
|
-
return token
|
158
|
-
end
|
159
|
-
return __get_token buffer
|
160
|
-
end
|
161
|
-
|
162
|
-
# Internal: Parse tags from network form to a Hash.
|
163
|
-
#
|
164
|
-
# token - A list of tags in network form.
|
165
|
-
def self.__parse_tags token
|
166
|
-
tags = Hash.new
|
167
|
-
token[1..-1].split(';').each do |tag|
|
168
|
-
if tag =~ MATCH_TAG
|
169
|
-
name, value = $~['name'], $~['value']
|
170
|
-
TAG_ESCAPES.each do |unescaped, escaped|
|
171
|
-
value.gsub! escaped, unescaped
|
172
|
-
end unless value.nil?
|
173
|
-
tags[name] = value
|
174
|
-
else
|
175
|
-
raise IRCParser::Error.new(tag), "tag is malformed"
|
176
|
-
end
|
177
|
-
end
|
178
|
-
return tags
|
179
|
-
end
|
180
|
-
|
181
|
-
# Internal: Serializes parameters from an Array to network form.
|
182
|
-
def __serialize_parameters
|
183
|
-
buffer = String.new
|
184
|
-
@parameters.each_with_index do |parameter, index|
|
185
|
-
trailing = parameter.include? ' '
|
186
|
-
if trailing && index != @parameters.size-1
|
187
|
-
raise IRCParser::Error.new(parameter), "only the last parameter may contain spaces"
|
188
|
-
end
|
189
|
-
|
190
|
-
buffer += ' '
|
191
|
-
if trailing || parameter.empty?
|
192
|
-
buffer += ':'
|
193
|
-
buffer += parameter
|
194
|
-
break
|
195
|
-
end
|
196
|
-
buffer += parameter
|
197
|
-
end
|
198
|
-
return buffer
|
199
|
-
end
|
200
|
-
|
201
|
-
# Internal: Serializes tags from a Hash to network form.
|
202
|
-
def __serialize_tags
|
203
|
-
buffer = @tags.dup.map do |key, value|
|
204
|
-
TAG_ESCAPES.each do |unescaped, escaped|
|
205
|
-
value.gsub! unescaped, Regexp.escape(escaped)
|
206
|
-
end unless value.nil?
|
207
|
-
value.nil? ? key : key + '=' + value
|
208
|
-
end.join ';'
|
209
|
-
return buffer
|
55
|
+
return IRCParser::RFCWireFormat.stringify self.clone
|
210
56
|
end
|
211
57
|
end
|
212
58
|
end
|
data/lib/ircparser/prefix.rb
CHANGED
@@ -18,9 +18,6 @@ module IRCParser
|
|
18
18
|
# Public: Represents the prefix of an IRC message.
|
19
19
|
class Prefix
|
20
20
|
|
21
|
-
# Internal: A regular expression which matches a n!u@h mask.
|
22
|
-
MATCH_PREFIX = /^(?<nick>[^@!]+) (?:!(?<user>[^@]+))? (?:@(?<host>.+))?$/x
|
23
|
-
|
24
21
|
# Public: The hostname of this prefix or nil if no hostname was given.
|
25
22
|
attr_reader :host
|
26
23
|
|
@@ -30,25 +27,15 @@ module IRCParser
|
|
30
27
|
# Public: The username of this prefix or nil if no username was given.
|
31
28
|
attr_reader :user
|
32
29
|
|
33
|
-
# Public:
|
30
|
+
# Public: Initialises a new message prefix.
|
34
31
|
#
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
raise IRCParser::Error.new(prefix), 'prefix is not a user mask or server name'
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Public: serialises this prefix to the network form.
|
47
|
-
def to_s
|
48
|
-
buffer = @nick
|
49
|
-
buffer += "!#{@user}" unless @user.nil?
|
50
|
-
buffer += "@#{@host}" unless @host.nil?
|
51
|
-
return buffer
|
32
|
+
# nick - The nickname of this user.
|
33
|
+
# user - The username of this prefix or nil if no username was given.
|
34
|
+
# host - The hostname of this prefix or nil if no hostname was given.
|
35
|
+
def initialize nick: nil, user: nil, host: nil
|
36
|
+
@nick = nick
|
37
|
+
@user = user
|
38
|
+
@host = host
|
52
39
|
end
|
53
40
|
end
|
54
41
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# IRCParser - Internet Relay Chat Message Parser
|
2
|
+
#
|
3
|
+
# Copyright (C) 2015-2017 Peter "SaberUK" Powell <petpow@saberuk.com>
|
4
|
+
#
|
5
|
+
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without
|
6
|
+
# fee is hereby granted, provided that the above copyright notice and this permission notice appear
|
7
|
+
# in all copies.
|
8
|
+
#
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
10
|
+
# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
11
|
+
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
12
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
13
|
+
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
14
|
+
# OF THIS SOFTWARE.
|
15
|
+
|
16
|
+
module IRCParser
|
17
|
+
|
18
|
+
# Public: Parses IRC messages from a stream of data.
|
19
|
+
class Stream
|
20
|
+
|
21
|
+
# Public: The contents of the stream buffer.
|
22
|
+
attr_reader :buffer
|
23
|
+
|
24
|
+
# Public: The block which is called when a message is parsed.
|
25
|
+
attr_reader :block
|
26
|
+
|
27
|
+
# Public: Initialize a new stream.
|
28
|
+
def initialize &block
|
29
|
+
unless block.is_a? Proc
|
30
|
+
raise TypeError, "Wrong argument type #{block.class} (expected Proc)"
|
31
|
+
end
|
32
|
+
@block = block
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Appends data to the stream buffer.
|
36
|
+
#
|
37
|
+
# data - The data to append.
|
38
|
+
def append data
|
39
|
+
(@buffer ||= '') << data
|
40
|
+
while @buffer.slice! /(.*?)\r?\n/
|
41
|
+
message = IRCParser::Message.parse $1
|
42
|
+
@block.call message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# IRCParser - Internet Relay Chat Message Parser
|
2
|
+
#
|
3
|
+
# Copyright (C) 2015-2017 Peter "SaberUK" Powell <petpow@saberuk.com>
|
4
|
+
#
|
5
|
+
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without
|
6
|
+
# fee is hereby granted, provided that the above copyright notice and this permission notice appear
|
7
|
+
# in all copies.
|
8
|
+
#
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
10
|
+
# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
11
|
+
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
12
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
13
|
+
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
14
|
+
# OF THIS SOFTWARE.
|
15
|
+
|
16
|
+
module IRCParser
|
17
|
+
|
18
|
+
# Internal: Implements objectification and stringification for the RFC wire format.
|
19
|
+
module RFCWireFormat
|
20
|
+
|
21
|
+
# Internal: Objectifies a message from the RFC wire format to an IRCParser::Message.
|
22
|
+
#
|
23
|
+
# str - A String containing a message in the RFC wire format.
|
24
|
+
def self.objectify str
|
25
|
+
|
26
|
+
# Ruby really needs some kind of basic type checking.
|
27
|
+
unless str.is_a? String
|
28
|
+
raise IRCParser::Error.new(line), "message is not a string"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Split the message up into an array of tokens.
|
32
|
+
current_token = self.__get_token str
|
33
|
+
components = Hash.new
|
34
|
+
|
35
|
+
# Have we encountered IRCv3 message tags?
|
36
|
+
components[:tags] = Hash.new
|
37
|
+
if current_token != nil && current_token[0] == '@'
|
38
|
+
components[:tags] = self.__objectify_tags current_token
|
39
|
+
current_token = self.__get_token str
|
40
|
+
end
|
41
|
+
|
42
|
+
# Have we encountered the prefix of this message?
|
43
|
+
if current_token != nil && current_token[0] == ':'
|
44
|
+
components[:prefix] = self.__objectify_prefix current_token
|
45
|
+
current_token = self.__get_token str
|
46
|
+
end
|
47
|
+
|
48
|
+
# The command parameter is mandatory.
|
49
|
+
if current_token != nil
|
50
|
+
components[:command] = current_token.upcase
|
51
|
+
current_token = self.__get_final_token str
|
52
|
+
else
|
53
|
+
raise IRCParser::Error.new(line), 'message is missing the command name'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Try to parse all of the remaining parameters.
|
57
|
+
components[:parameters] = Array.new
|
58
|
+
while current_token != nil
|
59
|
+
components[:parameters] << current_token
|
60
|
+
current_token = self.__get_final_token str
|
61
|
+
end
|
62
|
+
|
63
|
+
return IRCParser::Message.new components
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Stringifies a message from an IRCParser::Message to the RFC wire format.
|
67
|
+
#
|
68
|
+
# obj - An IRCParser::Message to stringify to the RFC wire format.
|
69
|
+
def self.stringify obj
|
70
|
+
|
71
|
+
# Ruby really needs some kind of basic type checking.
|
72
|
+
unless obj.is_a? IRCParser::Message
|
73
|
+
raise IRCParser::Error.new(obj), "message is not an IRCParser::Message"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Stringify the tags.
|
77
|
+
buffer = String.new
|
78
|
+
unless obj.tags.empty?
|
79
|
+
buffer += '@'
|
80
|
+
buffer += self.__stringify_tags obj.tags
|
81
|
+
buffer += ' '
|
82
|
+
end
|
83
|
+
|
84
|
+
# Stringify the prefix.
|
85
|
+
unless obj.prefix.nil?
|
86
|
+
buffer += ':'
|
87
|
+
buffer += self.__stringify_prefix obj.prefix
|
88
|
+
buffer += ' '
|
89
|
+
end
|
90
|
+
|
91
|
+
# Stringify the command.
|
92
|
+
buffer += obj.command
|
93
|
+
|
94
|
+
# Stringify the parameters
|
95
|
+
buffer += self.__stringify_parameters obj.parameters
|
96
|
+
|
97
|
+
# We're done!
|
98
|
+
return buffer
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Internal: A regular expression which matches a n!u@h mask.
|
104
|
+
MATCH_PREFIX = /^:(?<nick>[^@!]+) (?:!(?<user>[^@]+))? (?:@(?<host>.+))?$/x
|
105
|
+
|
106
|
+
# Internal: A regular expression which matches a tag.
|
107
|
+
MATCH_TAG = /^(?<name>[^\s=]+?)(?:=(?<value>[^\s;]+))?$/
|
108
|
+
|
109
|
+
# Internal: The characters which need to be escaped in tag values.
|
110
|
+
TAG_ESCAPES = {
|
111
|
+
'\\\\' => '\\',
|
112
|
+
'\:' => ';',
|
113
|
+
'\s' => "\s",
|
114
|
+
'\r' => "\r",
|
115
|
+
'\n' => "\n",
|
116
|
+
}
|
117
|
+
|
118
|
+
# Internal: Retrieves a space delimited token from the buffer.
|
119
|
+
#
|
120
|
+
# buffer - The buffer to retrieve the token from.
|
121
|
+
def self.__get_token buffer
|
122
|
+
return nil if buffer.empty?
|
123
|
+
position = buffer.index ' '
|
124
|
+
if position == nil
|
125
|
+
token = buffer.clone
|
126
|
+
buffer.clear
|
127
|
+
return token
|
128
|
+
end
|
129
|
+
token = buffer.slice! 0...position
|
130
|
+
position = buffer.index /\S+/
|
131
|
+
if position == nil
|
132
|
+
buffer.clear
|
133
|
+
else
|
134
|
+
buffer.slice! 0...position
|
135
|
+
end
|
136
|
+
return token
|
137
|
+
end
|
138
|
+
|
139
|
+
# Internal: Retrieves a space delimited token that may be a <trailing> parameter.
|
140
|
+
#
|
141
|
+
# buffer - The buffer to retrieve the token from.
|
142
|
+
def self.__get_final_token buffer
|
143
|
+
return nil if buffer.empty?
|
144
|
+
if buffer[0] == ':'
|
145
|
+
token = buffer[1..-1]
|
146
|
+
buffer.clear
|
147
|
+
return token
|
148
|
+
end
|
149
|
+
return self.__get_token buffer
|
150
|
+
end
|
151
|
+
|
152
|
+
# Internal: Objectifies the prefix from the RFC wire format to an IRCParser::Prefix.
|
153
|
+
#
|
154
|
+
# token - A String containing the prefix in the RFC wire format.
|
155
|
+
def self.__objectify_prefix prefix
|
156
|
+
unless MATCH_PREFIX =~ prefix
|
157
|
+
raise IRCParser::Error.new(prefix), 'prefix is not a user mask or server name'
|
158
|
+
end
|
159
|
+
return IRCParser::Prefix.new nick: $~[:nick], user: $~[:user], host: $~[:host]
|
160
|
+
end
|
161
|
+
|
162
|
+
# Internal: Objectifies tags from the RFC wire format to a Hash.
|
163
|
+
#
|
164
|
+
# token - A String containing tags in the RFC wire format.
|
165
|
+
def self.__objectify_tags token
|
166
|
+
tags = Hash.new
|
167
|
+
token[1..-1].split(';').each do |tag|
|
168
|
+
if tag =~ MATCH_TAG
|
169
|
+
value = nil
|
170
|
+
value_index = 0
|
171
|
+
while $~['value'] != nil && value_index < $~['value'].size
|
172
|
+
value ||= String.new
|
173
|
+
if $~['value'][value_index] == '\\'
|
174
|
+
escape = $~['value'].slice(value_index, 2)
|
175
|
+
if TAG_ESCAPES.include? escape
|
176
|
+
value += TAG_ESCAPES[escape]
|
177
|
+
value_index += 1
|
178
|
+
end
|
179
|
+
else
|
180
|
+
value += $~['value'][value_index]
|
181
|
+
end
|
182
|
+
value_index += 1
|
183
|
+
end
|
184
|
+
tags[$~['name']] = value
|
185
|
+
else
|
186
|
+
raise IRCParser::Error.new(tag), 'tag is malformed'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
return tags
|
190
|
+
end
|
191
|
+
|
192
|
+
# Internal: Stringifies parameters from an Array to the RFC wire format.
|
193
|
+
#
|
194
|
+
# parameters - An Array to stringify to the RFC wire format.
|
195
|
+
def self.__stringify_parameters parameters
|
196
|
+
buffer = String.new
|
197
|
+
parameters.each_with_index do |parameter, index|
|
198
|
+
trailing = parameter.include? ' '
|
199
|
+
if trailing && index != parameters.size-1
|
200
|
+
raise IRCParser::Error.new(parameter), 'only the last parameter may contain spaces'
|
201
|
+
end
|
202
|
+
|
203
|
+
buffer += ' '
|
204
|
+
if trailing || parameter.empty?
|
205
|
+
buffer += ':'
|
206
|
+
buffer += parameter
|
207
|
+
break
|
208
|
+
end
|
209
|
+
buffer += parameter
|
210
|
+
end
|
211
|
+
return buffer
|
212
|
+
end
|
213
|
+
|
214
|
+
# Internal: Stringifies the prefix from an IRCParser::Prefix to the RFC wire format.
|
215
|
+
#
|
216
|
+
# tags - An IRCParser::Prefix to stringify to the RFC wire format.
|
217
|
+
def self.__stringify_prefix prefix
|
218
|
+
buffer = prefix.nick
|
219
|
+
buffer += "!#{prefix.user}" unless prefix.user.nil?
|
220
|
+
buffer += "@#{prefix.host}" unless prefix.host.nil?
|
221
|
+
return buffer
|
222
|
+
end
|
223
|
+
|
224
|
+
# Internal: Stringifies tags from a Hash to the RFC wire format.
|
225
|
+
#
|
226
|
+
# tags - A Hash of tags to stringify to the RFC wire format.
|
227
|
+
def self.__stringify_tags tags
|
228
|
+
buffer = String.new
|
229
|
+
tags.each.with_index do |tag, idx|
|
230
|
+
key, value = tag
|
231
|
+
buffer += key
|
232
|
+
unless value.nil?
|
233
|
+
buffer += '='
|
234
|
+
value.each_char do |chr|
|
235
|
+
if TAG_ESCAPES.has_value? chr
|
236
|
+
buffer += TAG_ESCAPES.key chr
|
237
|
+
else
|
238
|
+
buffer += chr
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
buffer += ';' if idx < tags.size - 1
|
243
|
+
end
|
244
|
+
return buffer
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
data/test/test_message.rb
CHANGED
@@ -168,6 +168,31 @@ describe IRCParser::Message do
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
describe 'when checking we can handle parsing malformed tags' do
|
172
|
+
before do
|
173
|
+
@text = '@foo=wibble\Zwobble\\ COMMAND'
|
174
|
+
@message = IRCParser::Message.parse @text
|
175
|
+
end
|
176
|
+
it 'should strip invalid and trailing escapes' do
|
177
|
+
@message.tags['foo'].must_equal 'wibbleZwobble'
|
178
|
+
end
|
179
|
+
it 'should serialise back to a well formed value' do
|
180
|
+
@message.to_s.must_equal '@foo=wibbleZwobble COMMAND'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'when checking we can handle serialising without creating malformed tags' do
|
185
|
+
before do
|
186
|
+
@tags = {
|
187
|
+
'foo' => 'wibble\Zwobble\\'
|
188
|
+
}
|
189
|
+
@message = IRCParser::Message.new tags: @tags, command: 'COMMAND'
|
190
|
+
end
|
191
|
+
it 'should serialise without creating malformed tags' do
|
192
|
+
@message.to_s.must_equal '@foo=wibble\\\\Zwobble\\\\ COMMAND'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
171
196
|
describe 'when checking we can handle serialising malformed parameters' do
|
172
197
|
it 'should throw an IRCParser::Error when a non <trailing> parameter contains spaces' do
|
173
198
|
proc {
|
data/test/test_prefix.rb
CHANGED
@@ -20,41 +20,70 @@ require 'minitest/autorun'
|
|
20
20
|
|
21
21
|
describe IRCParser::Prefix do
|
22
22
|
PREFIXES = {
|
23
|
-
'nick!user@host'
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
'nick!user@host' => {
|
24
|
+
nick: 'nick',
|
25
|
+
user: 'user',
|
26
|
+
host: 'host'
|
27
|
+
},
|
28
|
+
'nick!user' => {
|
29
|
+
nick: 'nick',
|
30
|
+
user: 'user',
|
31
|
+
host: nil
|
32
|
+
},
|
33
|
+
'nick@host' => {
|
34
|
+
nick: 'nick',
|
35
|
+
user: nil,
|
36
|
+
host: 'host'
|
37
|
+
},
|
38
|
+
'nick' => {
|
39
|
+
nick: 'nick',
|
40
|
+
user: nil,
|
41
|
+
host: nil
|
42
|
+
},
|
43
|
+
'irc.example.com' => {
|
44
|
+
nick: 'irc.example.com',
|
45
|
+
user: nil,
|
46
|
+
host: nil
|
47
|
+
},
|
28
48
|
}
|
29
49
|
|
30
50
|
PREFIXES.each do |serialized, deserialized|
|
31
51
|
describe 'when checking a valid prefix' do
|
32
52
|
before do
|
33
|
-
@
|
53
|
+
@text = ":#{serialized} COMMAND"
|
54
|
+
@message = IRCParser::Message.parse @text
|
34
55
|
end
|
35
56
|
it 'should consist of the correct components' do
|
36
57
|
%i(nick user host).each do |component|
|
37
58
|
if deserialized[component].nil?
|
38
|
-
@prefix.send(component).must_be_nil
|
59
|
+
@message.prefix.send(component).must_be_nil
|
39
60
|
else
|
40
|
-
@prefix.send(component).must_equal deserialized[component]
|
61
|
+
@message.prefix.send(component).must_equal deserialized[component]
|
41
62
|
end
|
42
63
|
end
|
43
64
|
end
|
44
65
|
it 'should serialise back to the same text' do
|
45
|
-
@
|
66
|
+
@message.to_s.must_equal @text
|
46
67
|
end
|
47
68
|
end
|
48
69
|
end
|
49
70
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
71
|
+
MALFORMED = [
|
72
|
+
'nick!@',
|
73
|
+
'!user@',
|
74
|
+
'!@host',
|
75
|
+
'nick!user@',
|
76
|
+
'nick!@host',
|
77
|
+
'!user@host',
|
78
|
+
]
|
79
|
+
|
80
|
+
MALFORMED.each do |prefix|
|
81
|
+
describe 'when checking an invalid user prefix' do
|
82
|
+
it 'should throw an IRCParser::Error when components are missing' do
|
83
|
+
proc {
|
84
|
+
IRCParser::Message.parse ":#{prefix} COMMAND"
|
85
|
+
}.must_raise IRCParser::Error
|
86
|
+
end
|
58
87
|
end
|
59
88
|
end
|
60
89
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ircparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter "SaberUK" Powell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -68,6 +68,8 @@ files:
|
|
68
68
|
- lib/ircparser/error.rb
|
69
69
|
- lib/ircparser/message.rb
|
70
70
|
- lib/ircparser/prefix.rb
|
71
|
+
- lib/ircparser/stream.rb
|
72
|
+
- lib/ircparser/wire/rfc.rb
|
71
73
|
- test/test_message.rb
|
72
74
|
- test/test_prefix.rb
|
73
75
|
homepage: https://github.com/SaberUK/ircparser
|
@@ -90,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
92
|
version: '0'
|
91
93
|
requirements: []
|
92
94
|
rubyforge_project:
|
93
|
-
rubygems_version: 2.
|
95
|
+
rubygems_version: 2.7.5
|
94
96
|
signing_key:
|
95
97
|
specification_version: 4
|
96
98
|
summary: An IRCv3 message parser.
|