jeffrafter-marvin 0.1.20081115 → 0.1.20081120
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.
- data/README.textile +105 -32
- data/VERSION.yml +1 -1
- data/bin/marvin +10 -6
- data/config/connections.yml.sample +5 -0
- data/config/settings.yml.sample +2 -7
- data/config/setup.rb +6 -1
- data/handlers/debug_handler.rb +5 -0
- data/handlers/hello_world.rb +1 -1
- data/lib/marvin.rb +13 -9
- data/lib/marvin/abstract_client.rb +88 -43
- data/lib/marvin/abstract_parser.rb +14 -2
- data/lib/marvin/base.rb +44 -6
- data/lib/marvin/dispatchable.rb +9 -4
- data/lib/marvin/exception_tracker.rb +1 -1
- data/lib/marvin/exceptions.rb +3 -0
- data/lib/marvin/irc.rb +4 -5
- data/lib/marvin/irc/client.rb +39 -7
- data/lib/marvin/irc/event.rb +9 -4
- data/lib/marvin/irc/replies.rb +154 -0
- data/lib/marvin/loader.rb +26 -8
- data/lib/marvin/logger.rb +66 -3
- data/lib/marvin/options.rb +33 -0
- data/lib/marvin/parsers.rb +3 -0
- data/lib/marvin/parsers/command.rb +105 -0
- data/lib/marvin/parsers/prefixes.rb +8 -0
- data/lib/marvin/parsers/prefixes/host_mask.rb +30 -0
- data/lib/marvin/parsers/prefixes/server.rb +24 -0
- data/lib/marvin/parsers/ragel_parser.rb +713 -0
- data/lib/marvin/parsers/ragel_parser.rl +144 -0
- data/lib/marvin/parsers/regexp_parser.rb +0 -3
- data/lib/marvin/parsers/simple_parser.rb +20 -81
- data/lib/marvin/settings.rb +9 -9
- data/lib/marvin/test_client.rb +5 -1
- data/lib/marvin/util.rb +20 -3
- data/script/{run → client} +0 -0
- data/script/daemon-runner +1 -1
- data/script/install +3 -0
- data/test/parser_comparison.rb +62 -0
- data/test/parser_test.rb +264 -0
- data/test/test_helper.rb +10 -0
- metadata +19 -9
- data/lib/marvin/drb_handler.rb +0 -7
- data/lib/marvin/irc/abstract_server.rb +0 -4
- data/lib/marvin/irc/base_server.rb +0 -11
- data/lib/marvin/irc/socket_client.rb +0 -69
- data/lib/marvin/parsers/simple_parser/default_events.rb +0 -37
- data/lib/marvin/parsers/simple_parser/event_extensions.rb +0 -14
- data/lib/marvin/parsers/simple_parser/prefixes.rb +0 -34
@@ -0,0 +1,144 @@
|
|
1
|
+
# Ragel Parser comes from the Arrbot Guys -
|
2
|
+
# Kudos to Halogrium and Epitron.
|
3
|
+
|
4
|
+
%%{
|
5
|
+
machine irc;
|
6
|
+
|
7
|
+
action prefix_servername_start {
|
8
|
+
server = Marvin::Parsers::Prefixes::Server.new
|
9
|
+
}
|
10
|
+
|
11
|
+
action prefix_servername {
|
12
|
+
server.name << fc
|
13
|
+
}
|
14
|
+
|
15
|
+
action prefix_servername_finish {
|
16
|
+
command.prefix = server
|
17
|
+
}
|
18
|
+
|
19
|
+
action prefix_hostmask_start {
|
20
|
+
hostmask = Marvin::Parsers::Prefixes::HostMask.new
|
21
|
+
}
|
22
|
+
|
23
|
+
action hostmask_nickname {
|
24
|
+
hostmask.nickname << fc
|
25
|
+
}
|
26
|
+
|
27
|
+
action hostmask_user {
|
28
|
+
hostmask.user << fc
|
29
|
+
}
|
30
|
+
|
31
|
+
action hostmask_host {
|
32
|
+
hostmask.host << fc
|
33
|
+
}
|
34
|
+
|
35
|
+
action prefix_hostmask_finish {
|
36
|
+
command.prefix = hostmask
|
37
|
+
}
|
38
|
+
|
39
|
+
action message_code_start {
|
40
|
+
code = ""
|
41
|
+
}
|
42
|
+
|
43
|
+
action message_code {
|
44
|
+
code << fc
|
45
|
+
}
|
46
|
+
|
47
|
+
action message_code_finish {
|
48
|
+
command.code = code
|
49
|
+
}
|
50
|
+
|
51
|
+
action params_start {
|
52
|
+
params_1 = []
|
53
|
+
params_2 = []
|
54
|
+
}
|
55
|
+
|
56
|
+
action params {
|
57
|
+
}
|
58
|
+
|
59
|
+
action params_1_start {
|
60
|
+
params_1 << ""
|
61
|
+
}
|
62
|
+
|
63
|
+
action params_2_start {
|
64
|
+
params_2 << ""
|
65
|
+
}
|
66
|
+
|
67
|
+
action params_1 {
|
68
|
+
params_1.last << fc
|
69
|
+
}
|
70
|
+
|
71
|
+
action params_2 {
|
72
|
+
params_2.last << fc
|
73
|
+
}
|
74
|
+
|
75
|
+
action params_1_finish {
|
76
|
+
command.params = params_1
|
77
|
+
}
|
78
|
+
|
79
|
+
action params_2_finish {
|
80
|
+
command.params = params_2
|
81
|
+
}
|
82
|
+
|
83
|
+
SPACE = " ";
|
84
|
+
special = "[" | "\\" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "+";
|
85
|
+
nospcrlfcl = extend - ( 0 | SPACE | '\r' | '\n' | ':' );
|
86
|
+
crlf = "\r\n";
|
87
|
+
shortname = ( alnum ( alnum | "-" )* alnum* ) | "*";
|
88
|
+
multihostname = shortname ( ( "." | "/" ) shortname )*;
|
89
|
+
singlehostname = shortname ( "." | "/" );
|
90
|
+
hostname = multihostname | singlehostname;
|
91
|
+
servername = hostname;
|
92
|
+
nickname = ( alpha | special ) ( alnum | special | "-" ){,15};
|
93
|
+
user = (extend - ( 0 | "\n" | "\r" | SPACE | "@" ))+;
|
94
|
+
ip4addr = digit{1,3} "." digit{1,3} "." digit{1,3} "." digit{1,3};
|
95
|
+
ip6addr = ( xdigit+ ( ":" xdigit+ ){7} ) | ( "0:0:0:0:0:" ( "0" | "FFFF"i ) ":" ip4addr );
|
96
|
+
hostaddr = ip4addr | ip6addr;
|
97
|
+
host = hostname | hostaddr;
|
98
|
+
hostmask = nickname $ hostmask_nickname ( ( "!" user $ hostmask_user )? "@" host $ hostmask_host )?;
|
99
|
+
prefix = ( servername $ prefix_servername > prefix_servername_start % prefix_servername_finish ) | ( hostmask > prefix_hostmask_start % prefix_hostmask_finish );
|
100
|
+
code = alpha+ | digit{3};
|
101
|
+
middle = nospcrlfcl ( ":" | nospcrlfcl )*;
|
102
|
+
trailing = ( ":" | " " | nospcrlfcl )*;
|
103
|
+
params_1 = ( SPACE middle $ params_1 > params_1_start ){,14} ( SPACE ":" trailing $ params_1 > params_1_start )?;
|
104
|
+
params_2 = ( SPACE middle $ params_2 > params_2_start ){14} ( SPACE ":"? trailing $ params_2 > params_2_start )?;
|
105
|
+
params = ( params_1 % params_1_finish | params_2 % params_2_finish ) $ params > params_start;
|
106
|
+
message = ( ":" prefix SPACE )? ( code $ message_code > message_code_start % message_code_finish ) params? crlf;
|
107
|
+
|
108
|
+
main := message;
|
109
|
+
|
110
|
+
}%%
|
111
|
+
|
112
|
+
module Marvin
|
113
|
+
module Parsers
|
114
|
+
class RagelParser < Marvin::AbstractParser
|
115
|
+
|
116
|
+
%% write data;
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def self.parse!(line)
|
121
|
+
data = "#{line.strip}\r\n"
|
122
|
+
|
123
|
+
p = 0;
|
124
|
+
pe = data.length
|
125
|
+
cs = 0
|
126
|
+
|
127
|
+
hostmask = nil
|
128
|
+
server = nil
|
129
|
+
code = nil
|
130
|
+
command = Marvin::Parsers::Command.new(data)
|
131
|
+
|
132
|
+
%% write init;
|
133
|
+
%% write exec;
|
134
|
+
|
135
|
+
if cs >= irc_first_final
|
136
|
+
command
|
137
|
+
else
|
138
|
+
raise UnparseableMessage, "Failed to parse the message: #{input.inspect}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -81,9 +81,6 @@ module Marvin
|
|
81
81
|
register_event :quit, /^\:(.+)\!\~?(.+)\@(.+) QUIT :?(.+?)$/i,
|
82
82
|
:nick, :ident, :host, :message
|
83
83
|
|
84
|
-
register_event :nick_taken, /^\:(\S+) 433 \* (\w+) :(.+)$/,
|
85
|
-
:server, :target, :message
|
86
|
-
|
87
84
|
register_event :ping, /^\:(.+)\!\~?(.+)\@(.+) PING (.*)$/,
|
88
85
|
:nick, :ident, :host, :data
|
89
86
|
|
@@ -1,101 +1,40 @@
|
|
1
|
-
require File.dirname(__FILE__) / "simple_parser/prefixes"
|
2
|
-
require File.dirname(__FILE__) / "simple_parser/event_extensions"
|
3
|
-
require File.dirname(__FILE__) / "simple_parser/default_events"
|
4
|
-
|
5
1
|
module Marvin
|
6
2
|
module Parsers
|
7
3
|
class SimpleParser < Marvin::AbstractParser
|
8
4
|
|
9
|
-
cattr_accessor :events
|
10
|
-
self.events ||= {}
|
11
|
-
|
12
|
-
attr_accessor :arguments, :prefix, :current_line, :parts, :event
|
13
|
-
|
14
|
-
def initialize(line)
|
15
|
-
self.current_line = line
|
16
|
-
parse!
|
17
|
-
end
|
18
|
-
|
19
|
-
def to_event
|
20
|
-
if self.event.blank?
|
21
|
-
parse!
|
22
|
-
return nil
|
23
|
-
else
|
24
|
-
return self.event
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
5
|
private
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
6
|
+
|
7
|
+
# Parses an incoming message by using string
|
8
|
+
# Manipulation.
|
9
|
+
def self.parse!(line)
|
33
10
|
if line[0] == ?:
|
34
11
|
prefix_text, line = line.split(" ", 2)
|
35
12
|
else
|
36
13
|
prefix_text = nil
|
37
14
|
end
|
38
|
-
|
39
|
-
|
15
|
+
command = Marvin::Parsers::Command.new(line + "\r\n")
|
16
|
+
command.prefix = self.extract_prefix(prefix_text)
|
40
17
|
head, tail = line.split(":", 2)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
self.parts.unshift(command.to_s) # Reappend the command
|
47
|
-
process_event self.events[:numeric]
|
48
|
-
elsif self.events.has_key? command
|
49
|
-
# Registered Command
|
50
|
-
process_event self.events[command]
|
51
|
-
else
|
52
|
-
# Unknown Command
|
53
|
-
self.event = nil
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def process_event(prototype, skip_mutation = false)
|
58
|
-
self.event = prototype.dup
|
59
|
-
self.event.prefix = self.prefix
|
60
|
-
self.event.raw_arguments = self.parts
|
61
|
-
mutate_event! unless skip_mutation
|
18
|
+
parts = head.split(" ")
|
19
|
+
command.code = parts.shift
|
20
|
+
parts << tail unless tail.nil?
|
21
|
+
command.params = parts
|
22
|
+
return command
|
62
23
|
end
|
63
24
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
else
|
73
|
-
message = message[1..-2]
|
74
|
-
new_event = :ctcp
|
75
|
-
end
|
76
|
-
self.parts = [message]
|
77
|
-
process_event self.events[new_event], true
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def extract_prefix!(text)
|
82
|
-
return if text.blank?
|
83
|
-
full_prefix = text[1..-1]
|
84
|
-
prefix = full_prefix
|
85
|
-
# Ugly regexp for nick!ident@host format
|
86
|
-
# Officially this should be less-terse, but hey
|
87
|
-
# it's a simple parser.
|
88
|
-
if full_prefix =~ /^([^@!]+)\!\~?([^@]+)@(.*)$/
|
89
|
-
prefix = UserPrefix.new($1, $2, $3)
|
25
|
+
# From a given string, attempts to get the correct
|
26
|
+
# type of prefix (be it a HostMask or a Server name).
|
27
|
+
def self.extract_prefix(prefix_text)
|
28
|
+
return if prefix_text.blank?
|
29
|
+
prefix_text = prefix_text[1..-1] # Remove the leading :
|
30
|
+
# I think I just vomitted in my mouth a little...
|
31
|
+
if prefix_text =~ /^([A-Za-z0-9\-\[\]\\\`\^\{\}]+)(\!\~?([^@]+))?(@(.*))?$/
|
32
|
+
prefix = Prefixes::HostMask.new($1, $3, $5)
|
90
33
|
else
|
91
|
-
|
92
|
-
prefix = ServerNamePrefix.new(prefix.strip)
|
34
|
+
prefix = Prefixes::Server.new(prefix_text.strip)
|
93
35
|
end
|
94
|
-
self.prefix = prefix
|
95
36
|
return prefix
|
96
37
|
end
|
97
|
-
|
98
|
-
include DefaultEvents
|
99
38
|
end
|
100
39
|
end
|
101
40
|
end
|
data/lib/marvin/settings.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'eventmachine'
|
2
3
|
|
3
4
|
module Marvin
|
4
5
|
class Settings
|
5
6
|
|
6
|
-
cattr_accessor :environment, :configuration, :is_setup, :default_client,
|
7
|
+
cattr_accessor :environment, :configuration, :is_setup, :default_client,
|
8
|
+
:handler_folder, :default_parser, :log_level, :verbose
|
9
|
+
|
10
|
+
self.verbose = false
|
11
|
+
self.log_level = :info
|
7
12
|
|
8
13
|
class << self
|
9
14
|
|
10
15
|
def root
|
11
|
-
defined?(MARVIN_ROOT) ? MARVIN_ROOT : File.dirname(__FILE__) / "../.."
|
16
|
+
File.expand_path(defined?(MARVIN_ROOT) ? MARVIN_ROOT : File.dirname(__FILE__) / "../..")
|
12
17
|
end
|
13
18
|
|
14
19
|
def setup(options = {})
|
@@ -23,13 +28,8 @@ module Marvin
|
|
23
28
|
def setup!(options = {})
|
24
29
|
self.environment ||= "development"
|
25
30
|
self.configuration = {}
|
26
|
-
self.default_client ||=
|
27
|
-
|
28
|
-
Marvin::IRC::Client
|
29
|
-
rescue LoadError
|
30
|
-
Marvin::IRC::SocketClient
|
31
|
-
end
|
32
|
-
self.default_parser ||= Marvin::Parsers::RegexpParser
|
31
|
+
self.default_client ||= Marvin::IRC::Client
|
32
|
+
self.default_parser ||= Marvin::Parsers::SimpleParser
|
33
33
|
loaded_yaml = YAML.load_file(root / "config/settings.yml")
|
34
34
|
loaded_options = loaded_yaml["default"].
|
35
35
|
merge(loaded_yaml[self.environment]).
|
data/lib/marvin/test_client.rb
CHANGED
@@ -12,7 +12,7 @@ module Marvin
|
|
12
12
|
|
13
13
|
DispatchedEvents = Struct.new(:name, :options)
|
14
14
|
|
15
|
-
def initialize
|
15
|
+
def initialize(opts = {})
|
16
16
|
super
|
17
17
|
self.incoming_commands = []
|
18
18
|
self.outgoing_commands = []
|
@@ -56,5 +56,9 @@ module Marvin
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
def self.add_reconnect(opts = {})
|
60
|
+
Marvin::Logger.info "Added reconnect with options: #{opts.inspect}"
|
61
|
+
end
|
62
|
+
|
59
63
|
end
|
60
64
|
end
|
data/lib/marvin/util.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
module Marvin
|
2
2
|
module Util
|
3
3
|
|
4
|
+
GLOB_PATTERN_MAP = {
|
5
|
+
'*' => '.*',
|
6
|
+
'?' => '.',
|
7
|
+
'[' => '[',
|
8
|
+
']' => ']'
|
9
|
+
}
|
10
|
+
|
4
11
|
# Return the channel-name version of a string by
|
5
12
|
# appending "#" to the front if it doesn't already
|
6
13
|
# start with it.
|
@@ -10,9 +17,9 @@ module Marvin
|
|
10
17
|
alias chan channel_name
|
11
18
|
|
12
19
|
def arguments(input)
|
13
|
-
prefix,
|
20
|
+
prefix, ending = input.split(":", 2)
|
14
21
|
prefix = prefix.split(" ")
|
15
|
-
prefix << ending.
|
22
|
+
prefix << ending unless ending.blank?
|
16
23
|
return prefix
|
17
24
|
end
|
18
25
|
|
@@ -20,9 +27,19 @@ module Marvin
|
|
20
27
|
# specify parameters which have spaces etc (for example,
|
21
28
|
# the actual message part of a response).
|
22
29
|
def last_param(section)
|
23
|
-
section && ":#{section.to_s.strip}
|
30
|
+
section && ":#{section.to_s.strip}"
|
24
31
|
end
|
25
32
|
alias lp last_param
|
33
|
+
|
34
|
+
# Converts a glob-like pattern into a regular
|
35
|
+
# expression for easy / fast matching. Code is
|
36
|
+
# from PLEAC at http://pleac.sourceforge.net/pleac_ruby/patternmatching.html
|
37
|
+
def glob2pattern(glob_string)
|
38
|
+
inner_pattern = glob_string.gsub(/(.)/) do |c|
|
39
|
+
GLOB_PATTERN_MAP[c] || Regexp::escape(c)
|
40
|
+
end
|
41
|
+
return Regexp.new("^#{inner_pattern}$")
|
42
|
+
end
|
26
43
|
|
27
44
|
extend self
|
28
45
|
|
data/script/{run → client}
RENAMED
File without changes
|
data/script/daemon-runner
CHANGED
data/script/install
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require File.join(File.dirname(__FILE__),'../lib/marvin')
|
3
|
+
|
4
|
+
LINES = [
|
5
|
+
":irc.darth.vpn.spork.in 366 testbot #testing :End of NAMES list",
|
6
|
+
":Helsinki.FI.EU.Undernet.org PONG Helsinki.FI.EU.Undernet.org :Helsinki.FI.EU.Undernet.org",
|
7
|
+
":testnick USER guest tolmoon tolsun :Ronnie Reagan",
|
8
|
+
"LIST #twilight_zone,#42",
|
9
|
+
":WiZ LINKS *.bu.edu *.edu",
|
10
|
+
":Angel PRIVMSG Wiz :Hello are you receiving this message ?",
|
11
|
+
":RelayBot!n=MarvinBo@203.161.81.201.static.amnet.net.au JOIN :#relayrelay",
|
12
|
+
":SuttoL!n=SuttoL@li6-47.members.linode.com PRIVMSG #relayrelay :testing...",
|
13
|
+
":wolfe.freenode.net 004 MarvinBot3000 wolfe.freenode.net hyperion-1.0.2b aAbBcCdDeEfFGhHiIjkKlLmMnNopPQrRsStTuUvVwWxXyYzZ01234569*@ bcdefFhiIklmnoPqstv"
|
14
|
+
]
|
15
|
+
PARSERS = [Marvin::Parsers::RagelParser, Marvin::Parsers::SimpleParser, Marvin::Parsers::RegexpParser]
|
16
|
+
|
17
|
+
LINES.each do |line|
|
18
|
+
|
19
|
+
puts "Processing: #{line}"
|
20
|
+
puts ""
|
21
|
+
cmd = []
|
22
|
+
|
23
|
+
PARSERS.each do |p|
|
24
|
+
parser = p.new(line)
|
25
|
+
ev = parser.to_event
|
26
|
+
puts "Parser: #{p.name}"
|
27
|
+
if ev.nil?
|
28
|
+
puts "Unknown Event"
|
29
|
+
else
|
30
|
+
puts ev.to_hash.inspect
|
31
|
+
end
|
32
|
+
puts ""
|
33
|
+
end
|
34
|
+
puts ""
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
puts ""
|
39
|
+
puts ""
|
40
|
+
puts "==============="
|
41
|
+
puts "| SPEED TESTS |"
|
42
|
+
puts "==============="
|
43
|
+
puts ""
|
44
|
+
|
45
|
+
width = PARSERS.map { |p| p.name.length }.max + 2
|
46
|
+
|
47
|
+
ITERATIONS = 1000
|
48
|
+
|
49
|
+
Benchmark.bm(width) do |b|
|
50
|
+
PARSERS.each do |parser|
|
51
|
+
b.report("#{parser.name}: ") do
|
52
|
+
LINES.each do |l|
|
53
|
+
ITERATIONS.times do
|
54
|
+
e = parser.new(l).to_event
|
55
|
+
unless e.nil?
|
56
|
+
e.to_hash # Get a hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|