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.
Files changed (48) hide show
  1. data/README.textile +105 -32
  2. data/VERSION.yml +1 -1
  3. data/bin/marvin +10 -6
  4. data/config/connections.yml.sample +5 -0
  5. data/config/settings.yml.sample +2 -7
  6. data/config/setup.rb +6 -1
  7. data/handlers/debug_handler.rb +5 -0
  8. data/handlers/hello_world.rb +1 -1
  9. data/lib/marvin.rb +13 -9
  10. data/lib/marvin/abstract_client.rb +88 -43
  11. data/lib/marvin/abstract_parser.rb +14 -2
  12. data/lib/marvin/base.rb +44 -6
  13. data/lib/marvin/dispatchable.rb +9 -4
  14. data/lib/marvin/exception_tracker.rb +1 -1
  15. data/lib/marvin/exceptions.rb +3 -0
  16. data/lib/marvin/irc.rb +4 -5
  17. data/lib/marvin/irc/client.rb +39 -7
  18. data/lib/marvin/irc/event.rb +9 -4
  19. data/lib/marvin/irc/replies.rb +154 -0
  20. data/lib/marvin/loader.rb +26 -8
  21. data/lib/marvin/logger.rb +66 -3
  22. data/lib/marvin/options.rb +33 -0
  23. data/lib/marvin/parsers.rb +3 -0
  24. data/lib/marvin/parsers/command.rb +105 -0
  25. data/lib/marvin/parsers/prefixes.rb +8 -0
  26. data/lib/marvin/parsers/prefixes/host_mask.rb +30 -0
  27. data/lib/marvin/parsers/prefixes/server.rb +24 -0
  28. data/lib/marvin/parsers/ragel_parser.rb +713 -0
  29. data/lib/marvin/parsers/ragel_parser.rl +144 -0
  30. data/lib/marvin/parsers/regexp_parser.rb +0 -3
  31. data/lib/marvin/parsers/simple_parser.rb +20 -81
  32. data/lib/marvin/settings.rb +9 -9
  33. data/lib/marvin/test_client.rb +5 -1
  34. data/lib/marvin/util.rb +20 -3
  35. data/script/{run → client} +0 -0
  36. data/script/daemon-runner +1 -1
  37. data/script/install +3 -0
  38. data/test/parser_comparison.rb +62 -0
  39. data/test/parser_test.rb +264 -0
  40. data/test/test_helper.rb +10 -0
  41. metadata +19 -9
  42. data/lib/marvin/drb_handler.rb +0 -7
  43. data/lib/marvin/irc/abstract_server.rb +0 -4
  44. data/lib/marvin/irc/base_server.rb +0 -11
  45. data/lib/marvin/irc/socket_client.rb +0 -69
  46. data/lib/marvin/parsers/simple_parser/default_events.rb +0 -37
  47. data/lib/marvin/parsers/simple_parser/event_extensions.rb +0 -14
  48. 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
- def parse!
31
- # Split the message
32
- line = self.current_line
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
- extract_prefix! prefix_text
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
- self.parts = head.split(" ")
42
- self.parts << tail
43
- command = self.parts.shift.upcase.to_sym
44
- if command.to_s =~ /^[0-9]{3}$/
45
- # Other Command
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
- def mutate_event!
65
- # Do nothing by default
66
- name, contents = self.event.name, self.event.raw_arguments.last
67
- # mutate for ctcp and actions
68
- if name == :message && contents[0..0] == "\001" && contents[-1..-1] == "\001"
69
- if message.index("ACTION: ") == 1
70
- message = message[9..-2]
71
- new_event = :action
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
- # TODO: Validate the hostname here.
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
@@ -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, :handler_folder, :default_parser
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 ||= begin
27
- require 'eventmachine'
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]).
@@ -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
@@ -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, *ending = input.split(":")
20
+ prefix, ending = input.split(":", 2)
14
21
  prefix = prefix.split(" ")
15
- prefix << ending.join(":").strip
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
 
File without changes
@@ -4,7 +4,7 @@ require 'daemons'
4
4
 
5
5
  DIR = File.join(File.dirname(__FILE__), "..")
6
6
 
7
- Daemons.run(File.join(DIR, "script/run --is-daemon"),
7
+ Daemons.run(File.join(DIR, "script/client"),
8
8
  {:mode => :exec,
9
9
  :dir => DIR,
10
10
  :dir_mode => :normal,
@@ -0,0 +1,3 @@
1
+ gem uninstall marvin --quiet
2
+ gem build marvin.gemspec
3
+ gem install marvin-0.1.*.gem
@@ -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