Sutto-marvin 0.4.0 → 0.8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/bin/marvin +22 -156
  2. data/handlers/keiki_thwopper.rb +21 -0
  3. data/handlers/tweet_tweet.rb +1 -3
  4. data/lib/marvin/abstract_client.rb +75 -189
  5. data/lib/marvin/abstract_parser.rb +9 -11
  6. data/lib/marvin/base.rb +134 -101
  7. data/lib/marvin/client/actions.rb +104 -0
  8. data/lib/marvin/client/default_handlers.rb +97 -0
  9. data/lib/marvin/command_handler.rb +60 -49
  10. data/lib/marvin/console.rb +4 -31
  11. data/lib/marvin/core_commands.rb +30 -12
  12. data/lib/marvin/distributed/client.rb +225 -0
  13. data/lib/marvin/distributed/handler.rb +85 -0
  14. data/lib/marvin/distributed/protocol.rb +88 -0
  15. data/lib/marvin/distributed/server.rb +154 -0
  16. data/lib/marvin/distributed.rb +4 -10
  17. data/lib/marvin/dsl.rb +103 -0
  18. data/lib/marvin/exception_tracker.rb +7 -4
  19. data/lib/marvin/irc/client.rb +127 -99
  20. data/lib/marvin/irc/event.rb +14 -10
  21. data/lib/marvin/irc.rb +0 -1
  22. data/lib/marvin/middle_man.rb +1 -1
  23. data/lib/marvin/parsers/command.rb +10 -8
  24. data/lib/marvin/parsers/prefixes/host_mask.rb +12 -7
  25. data/lib/marvin/parsers/prefixes/server.rb +1 -1
  26. data/lib/marvin/parsers/ragel_parser.rb +59 -52
  27. data/lib/marvin/parsers/ragel_parser.rl +6 -7
  28. data/lib/marvin/parsers/simple_parser.rb +4 -9
  29. data/lib/marvin/parsers.rb +1 -2
  30. data/lib/marvin/settings.rb +29 -79
  31. data/lib/marvin/test_client.rb +20 -26
  32. data/lib/marvin/util.rb +10 -3
  33. data/lib/marvin.rb +42 -39
  34. data/templates/boot.erb +3 -0
  35. data/templates/connections.yml.erb +10 -0
  36. data/templates/debug_handler.erb +5 -0
  37. data/templates/hello_world.erb +10 -0
  38. data/templates/rakefile.erb +15 -0
  39. data/templates/settings.yml.erb +8 -0
  40. data/{config/setup.rb → templates/setup.erb} +8 -10
  41. data/templates/test_helper.erb +17 -0
  42. data/test/abstract_client_test.rb +63 -0
  43. data/test/parser_comparison.rb +2 -2
  44. data/test/parser_test.rb +3 -3
  45. data/test/test_helper.rb +58 -6
  46. metadata +51 -83
  47. data/README.textile +0 -105
  48. data/TUTORIAL.textile +0 -54
  49. data/VERSION.yml +0 -4
  50. data/config/boot.rb +0 -14
  51. data/config/connections.yml.sample +0 -5
  52. data/config/settings.yml.sample +0 -13
  53. data/handlers/logging_handler.rb +0 -89
  54. data/lib/marvin/core_ext.rb +0 -11
  55. data/lib/marvin/daemon.rb +0 -71
  56. data/lib/marvin/data_store.rb +0 -73
  57. data/lib/marvin/dispatchable.rb +0 -99
  58. data/lib/marvin/distributed/dispatch_handler.rb +0 -83
  59. data/lib/marvin/distributed/drb_client.rb +0 -78
  60. data/lib/marvin/distributed/ring_server.rb +0 -41
  61. data/lib/marvin/handler.rb +0 -12
  62. data/lib/marvin/irc/server/abstract_connection.rb +0 -84
  63. data/lib/marvin/irc/server/base_connection.rb +0 -66
  64. data/lib/marvin/irc/server/channel.rb +0 -115
  65. data/lib/marvin/irc/server/named_store.rb +0 -14
  66. data/lib/marvin/irc/server/remote_interface.rb +0 -77
  67. data/lib/marvin/irc/server/user/handle_mixin.rb +0 -140
  68. data/lib/marvin/irc/server/user.rb +0 -5
  69. data/lib/marvin/irc/server/user_connection.rb +0 -134
  70. data/lib/marvin/irc/server/virtual_user_connection.rb +0 -80
  71. data/lib/marvin/irc/server.rb +0 -71
  72. data/lib/marvin/loader.rb +0 -149
  73. data/lib/marvin/logger.rb +0 -86
  74. data/lib/marvin/options.rb +0 -42
  75. data/lib/marvin/parsers/regexp_parser.rb +0 -93
  76. data/lib/marvin/status.rb +0 -72
  77. data/script/client +0 -3
  78. data/script/console +0 -3
  79. data/script/distributed_client +0 -3
  80. data/script/install +0 -1
  81. data/script/ring_server +0 -4
  82. data/script/server +0 -4
  83. data/script/status +0 -3
  84. data/spec/marvin/abstract_client_test.rb +0 -38
  85. data/spec/spec_helper.rb +0 -14
data/lib/marvin/dsl.rb ADDED
@@ -0,0 +1,103 @@
1
+ # Handy Dandy DSL style stuff for Marvin
2
+ module Marvin
3
+ class DSL
4
+
5
+ class Proxy < Perennial::Proxy
6
+
7
+ def initialize(klass)
8
+ @prototype_klass = Class.new(klass)
9
+ @mapping = {}
10
+ end
11
+
12
+ def define_shortcut(name, method_name)
13
+ @mapping[name] = method_name
14
+ end
15
+
16
+ def shortdef(hash = {})
17
+ hash.each_pair { |k,v| define_shortcut(k, v) }
18
+ end
19
+
20
+
21
+
22
+ def initialize_class!
23
+ @klass = Class.new(@prototype_klass)
24
+ end
25
+
26
+ def to_instance
27
+ @klass.new
28
+ end
29
+
30
+ def to_class
31
+ @klass
32
+ end
33
+
34
+ def method_missing(name, *args, &blk)
35
+ name = name.to_sym
36
+ if @mapping.has_key?(name)
37
+ @klass.define_method(@mapping[name], &blk)
38
+ else
39
+ @klass.send(name, *args, &blk)
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ def initialize(&blk)
46
+ instance_eval(&blk)
47
+ end
48
+
49
+ def logging(&blk)
50
+ call_prototype(:logging, &blk).register!
51
+ end
52
+
53
+ def handler(&blk)
54
+ call_prototype(:handler, &blk).register!
55
+ end
56
+
57
+ def commands(&blk)
58
+ call_prototype(:commands, &blk).register!
59
+ end
60
+
61
+ def configure(&blk)
62
+ Marvin::Settings.client.configure(&blk)
63
+ end
64
+
65
+ def server(name, port = nil)
66
+ name = name.to_s.dup
67
+ end
68
+
69
+
70
+
71
+ protected
72
+
73
+ def initialize_prototypes
74
+ prototype_for(:logging, Marvin::LoggingHandler) do
75
+ shortdef :setup => :setup_logging,
76
+ :teardown => :teardown_logging,
77
+ :incoming => :log_incoming,
78
+ :outgoing => :log_outgoing,
79
+ :message => :log_message
80
+ end
81
+ prototype_for(:handler, Marvin::Base) do
82
+ map :on => :on_event,
83
+ :numeric => :on_numeric
84
+ end
85
+ prototype_for(:handler, Marvin::CommandHandler)
86
+ end
87
+
88
+ def prototype_for(name, klass, &blk)
89
+ @prototypes ||= {}
90
+ p = Proxy.new(klass)
91
+ p.instance_eval(&blk)
92
+ @prototypes[name] = p
93
+ end
94
+
95
+ def call_prototype(name, &blk)
96
+ p = @prototypes[name]
97
+ p.initialize_class!
98
+ p.instance_eval(&blk)
99
+ return p.to_class
100
+ end
101
+
102
+ end
103
+ end
@@ -1,15 +1,18 @@
1
1
  module Marvin
2
2
  class ExceptionTracker
3
3
 
4
- cattr_accessor :logger
5
- self.logger = Marvin::Logger
4
+ is :loggable
5
+
6
+ cattr_accessor :log_exception_proc
7
+ self.log_exception_proc = proc { |e| e }
6
8
 
7
9
  def self.log(e)
8
- logger.fatal "Exception raised inside Marvin Instance."
9
- logger.fatal "#{e} - #{e.message}"
10
+ logger.fatal "Oh noes cap'n - we have an exception!."
11
+ logger.fatal "#{e.class.name}: #{e.message}"
10
12
  e.backtrace.each do |line|
11
13
  logger.fatal line
12
14
  end
15
+ @@log_exception_proc.call(e)
13
16
  end
14
17
 
15
18
  end
@@ -1,138 +1,166 @@
1
1
  require 'eventmachine'
2
2
 
3
3
  module Marvin::IRC
4
-
5
- # == Marvin::IRC::Client
6
- # An EventMachine protocol implementation built to
7
- # serve as a basic, single server IRC client.
8
- #
9
- # Operates on the principal of Events as well
10
- # as handlers.
11
- #
12
- # === Events
13
- # Events are things that can happen (e.g. an
14
- # incoming message). All outgoing events are
15
- # automatically handled from within the client
16
- # class. Incoming events are currently based
17
- # on regular expression based matches of
18
- # incoming messages. the Client#register_event
19
- # method takes either an instance of Marvin::IRC::Event
20
- # or a set of arguments which will then be used
21
- # in the constructor of a new Marvin::IRC::Event
22
- # instance (see, for example, the source code for
23
- # this class for examples).
24
- #
25
- # === Handlers
26
- # Handlers on the other hand do as the name suggests
27
- # - they listen for dispatched events and act accordingly.
28
- # Handlers are simply objects which follow a certain
29
- # set of guidelines. Typically, a handler will at
30
- # minimum respond to #handle(event_name, details)
31
- # where event_name is a symbol for the current
32
- # event (e.g. :incoming_event) whilst details is a
33
- # a hash of details about the current event (e.g.
34
- # message target and the message itself).
35
- #
36
- # ==== Getting the current client instance
37
- # If the object responds to client=, The client will
38
- # call it with the current instance of itself
39
- # enabling the handler to do things such as respond.
40
- # Also, if a method handle_[message_name] exists,
41
- # it will be called instead of handle.
42
- #
43
- # ==== Adding handlers
44
- # To add an object as a handler, you simply call
45
- # the class method, register_handler with the
46
- # handler as the only argument.
47
4
  class Client < Marvin::AbstractClient
48
- cattr_accessor :stopped
49
- self.stopped = false
5
+
6
+ @@stopped = false
50
7
 
51
8
  attr_accessor :em_connection
52
9
 
53
10
  class EMConnection < EventMachine::Protocols::LineAndTextProtocol
54
- attr_accessor :client, :server, :port
11
+ is :loggable
12
+
13
+ attr_accessor :client, :port, :configuration
55
14
 
56
15
  def initialize(*args)
57
- opts = args.extract_options!
16
+ @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
58
17
  super(*args)
59
- self.client = Marvin::IRC::Client.new(opts)
60
- self.client.em_connection = self
18
+ @client = Marvin::IRC::Client.new(@configuration)
19
+ @client.em_connection = self
20
+ @connected = false
21
+ rescue Exception => e
22
+ Marvin::ExceptionTracker.log(e)
23
+ Marvin::IRC::Client.stop
61
24
  end
62
25
 
63
26
  def post_init
64
- client.process_connect
65
27
  super
28
+ if should_use_ssl?
29
+ logger.info "Starting SSL for #{host_with_port}"
30
+ start_tls
31
+ else
32
+ connected!
33
+ end
34
+ rescue Exception => e
35
+ Marvin::ExceptionTracker.log(e)
36
+ Marvin::IRC::Client.stop
37
+ end
38
+
39
+ def ssl_handshake_completed
40
+ logger.info "SSL handshake completed for #{host_with_port}"
41
+ connected! if should_use_ssl?
42
+ rescue Exception => e
43
+ Marvin::ExceptionTracker.log(e)
44
+ Marvin::IRC::Client.stop
66
45
  end
67
46
 
68
47
  def unbind
69
- client.process_disconnect
48
+ @client.process_disconnect
70
49
  super
71
50
  end
72
51
 
73
52
  def receive_line(line)
74
- Marvin::Logger.debug "<< #{line.strip}"
75
- self.client.receive_line(line)
53
+ return unless @connected
54
+ line = line.strip
55
+ logger.debug "<< #{line}"
56
+ @client.receive_line(line)
57
+ rescue Exception => e
58
+ logger.warn "Uncaught exception raised; Likely in Marvin"
59
+ Marvin::ExceptionTracker.log(e)
76
60
  end
77
61
 
78
- end
79
-
80
- def send_line(*args)
81
- args.each { |line| Marvin::Logger.debug ">> #{line.strip}" }
82
- em_connection.send_data *args
83
- end
84
-
85
- ## Client specific details
86
-
87
- # Starts the EventMachine loop and hence starts up the actual
88
- # networking portion of the IRC Client.
89
- def self.run(force = false)
90
- return if self.stopped && !force
91
- self.setup # So we have options etc
92
- settings = YAML.load_file(Marvin::Settings.root / "config/connections.yml")
93
- if settings.is_a?(Hash)
94
- # Use epoll if available
95
- EventMachine.epoll
96
- EventMachine::run do
97
- settings.each do |name, options|
98
- settings = options.symbolize_keys!
99
- settings[:server] ||= name
100
- settings.reverse_merge!(:port => 6667, :channels => [])
101
- connect settings
102
- end
62
+ def send_line(*lines)
63
+ return unless @connected
64
+ lines.each do |line|
65
+ logger.debug ">> #{line.strip}"
66
+ send_data line
103
67
  end
104
- else
105
- logger.fatal "config/connections.yml couldn't be loaded. Exiting"
106
68
  end
69
+
70
+ def connected!
71
+ @connected = true
72
+ @client.process_connect
73
+ end
74
+
75
+ def should_use_ssl?
76
+ @should_use_ssl ||= @configuration.ssl?
77
+ end
78
+
79
+ def host_with_port
80
+ "#{@configuration.host}:#{@configuration.port}"
81
+ end
82
+
107
83
  end
108
84
 
109
- def self.connect(opts = {})
110
- logger.info "Connecting to #{opts[:server]}:#{opts[:port]} - Channels: #{opts[:channels].join(", ")}"
111
- EventMachine::connect(opts[:server], opts[:port], EMConnection, opts)
112
- end
85
+ ## Client specific details
113
86
 
114
- def self.stop
115
- return if self.stopped
116
- logger.debug "Telling all connections to quit"
117
- self.connections.dup.each { |connection| connection.quit }
118
- logger.debug "Telling Event Machine to Stop"
119
- EventMachine::stop_event_loop
120
- logger.debug "Stopped."
121
- self.stopped = true
87
+ def send_line(*args)
88
+ @em_connection.send_line(*args)
122
89
  end
123
90
 
124
- def self.add_reconnect(opts = {})
125
- Marvin::Logger.warn "Adding entry to reconnect to #{opts[:server]}:#{opts[:port]} in 15 seconds"
126
- EventMachine::add_timer(15) do
127
- Marvin::Logger.warn "Attempting to reconnect to #{opts[:server]}:#{opts[:port]}"
128
- Marvin::IRC::Client.connect(opts)
91
+ class << self
92
+
93
+ # Starts the EventMachine loop and hence starts up the actual
94
+ # networking portion of the IRC Client.
95
+ def run(opts = {}, force = false)
96
+ self.development = opts[:development]
97
+ return if @stopped && !force
98
+ self.setup # So we have options etc
99
+ connections_file = Marvin::Settings.root / "config" / "connections.yml"
100
+ connections = Marvin::Nash.load_file(connections_file) rescue nil
101
+ if connections.present?
102
+ # Use epoll if available
103
+ EventMachine.kqueue
104
+ EventMachine.epoll
105
+ EventMachine.run do
106
+ connections.each_pair do |server, configuration|
107
+ connect(configuration.merge(:server => server.to_s))
108
+ end
109
+ Marvin::Distributed::Server.start if Marvin::Distributed::Handler.registered?
110
+ @@stopped = false
111
+ end
112
+ else
113
+ logger.fatal "config/connections.yml couldn't be loaded."
114
+ end
129
115
  end
116
+
117
+ def connect(c, &blk)
118
+ c = normalize_connection_options(c)
119
+ raise ArgumentError, "Your connection options must specify a server" if !c.server?
120
+ raise ArgumentError, "Your connection options must specify a port" if !c.port?
121
+ real_block = blk.present? ? proc { |c| blk.call(connection.client) } : nil
122
+ logger.info "Connecting to #{c.server}:#{c.port} (using ssl: #{c.ssl?}) - Channels: #{c.channels.join(", ")}"
123
+ EventMachine.connect(c.server, c.port, EMConnection, c, &real_block)
124
+ end
125
+
126
+ def stop
127
+ return if @@stopped
128
+ logger.debug "Telling all connections to quit"
129
+ connections.dup.each { |connection| connection.quit }
130
+ logger.debug "Telling Event Machine to Stop"
131
+ EventMachine.stop_event_loop
132
+ logger.debug "Stopped."
133
+ @@stoped = true
134
+ end
135
+
136
+ def add_reconnect(c)
137
+ logger.warn "Adding timer to reconnect to #{c.server}:#{c.port} in 15 seconds"
138
+ EventMachine.add_timer(15) do
139
+ logger.warn "Preparing to reconnect to #{c.server}:#{c.port}"
140
+ connect(c)
141
+ end
142
+ end
143
+
144
+ protected
145
+
146
+ def normalize_connection_options(config)
147
+ config = Marvin::Nash.new(config) if !config.is_a?(Marvin::Nash)
148
+ config = config.normalized
149
+ channels = config.channels
150
+ if channels.present?
151
+ config.channels = [*channels].compact.reject { |c| c.blank? }
152
+ else
153
+ config.channels = []
154
+ end
155
+ config.host ||= config.server
156
+ return config
157
+ end
158
+
130
159
  end
131
160
 
132
161
  # Registers a callback handle that will be periodically run.
133
162
  def periodically(timing, event_callback)
134
- callback = proc { self.dispatch event_callback.to_sym }
135
- EventMachine::add_periodic_timer(timing, &callback)
163
+ EventMachine.add_periodic_timer(timing) { dispatch(event_callback.to_sym) }
136
164
  end
137
165
 
138
166
  end
@@ -3,32 +3,36 @@ module Marvin::IRC
3
3
  attr_accessor :keys, :name, :raw_arguments, :prefix
4
4
 
5
5
  def initialize(name, *args)
6
- self.name = name.to_sym
7
- self.keys = args.flatten.map { |k| k.to_sym }
6
+ @name = name.to_sym
7
+ @keys = args.flatten.map { |k| k.to_sym }
8
8
  end
9
9
 
10
10
  def to_hash
11
11
  return @hash_value unless @hash_value.blank?
12
12
  results = {}
13
- values = self.raw_arguments.to_a
14
- last_index = self.keys.size - 1
15
- self.keys.each_with_index do |key, i|
13
+ values = @raw_arguments.to_a
14
+ last_index = @keys.size - 1
15
+ @keys.each_with_index do |key, i|
16
16
  results[key] = (i == last_index ? values.join(" ").strip : values.shift)
17
17
  end
18
- results.merge!(prefix.to_hash) unless prefix.blank?
19
- return (@hash_value = results)
18
+ results.merge!(@prefix.to_hash) unless @prefix.blank?
19
+ @hash_value = results
20
20
  end
21
21
 
22
22
  def inspect
23
- "#<Marvin::IRC::Event name=#{self.name} attributes=#{self.to_hash.inspect} >"
23
+ "#<Marvin::IRC::Event name=#{@name} attributes=#{to_hash.inspect} >"
24
+ end
25
+
26
+ def to_event_name(prefix = nil)
27
+ [prefix, @name].join("_").to_sym
24
28
  end
25
29
 
26
30
  def to_incoming_event_name
27
- :"incoming_#{self.name}"
31
+ to_event_name :incoming
28
32
  end
29
33
 
30
34
  def to_outgoing_event_name
31
- :"outgoing_#{self.name}"
35
+ to_event_name :outgoing
32
36
  end
33
37
 
34
38
  end
data/lib/marvin/irc.rb CHANGED
@@ -2,7 +2,6 @@ module Marvin
2
2
  module IRC
3
3
  autoload :Client, 'marvin/irc/client'
4
4
  autoload :Event, 'marvin/irc/event'
5
- autoload :Server, 'marvin/irc/server'
6
5
  autoload :Replies, 'marvin/irc/replies'
7
6
  end
8
7
  end
@@ -58,7 +58,7 @@ module Marvin
58
58
  # Forcefully do the setup routine.
59
59
  def setup!
60
60
  # Register ourselves as a new handler.
61
- Marvin::Settings.default_client.register_handler self.new
61
+ Marvin::Settings.client.register_handler self.new
62
62
  @@setup = true
63
63
  end
64
64
 
@@ -20,16 +20,18 @@ module Marvin
20
20
  # attempt to recognize the command and convert
21
21
  # it to an event which can be used for other stuff.
22
22
  def to_event
23
- if self.code =~ /^\d+$/
23
+ # If we have a numeric...
24
+ if @code =~ /^\d+$/
24
25
  ev = @@commands[:numeric].dup
25
26
  data = @params[0..-2]
26
27
  data << "#{@params.last.include?(" ") ? ":" : ""}#{@params.last}"
27
28
  ev.raw_arguments = [self.code.to_s, data.join(" ")]
28
- elsif code == "PRIVMSG" && params.last[0] == 1 && params.last[-1] == 1
29
- if params.last[0..8] == "\001ACTION: "
30
- name, value = :action, params.last[9..-2]
29
+ elsif code == "PRIVMSG" && params.last =~ /^\001(.*)\001$/
30
+ results = $1.to_s
31
+ if results =~ /^ACTION /
32
+ name, value = :action, results.gsub(/^ACTION /, '')
31
33
  else
32
- name, value = :ctcp, params.last[1..-2]
34
+ name, value = :ctcp, results
33
35
  end
34
36
  self.params[-1] = value
35
37
  ev = @@commands[name].dup
@@ -96,9 +98,9 @@ module Marvin
96
98
  register_event :ison, :ISON, :nick
97
99
  # Add the default numeric event
98
100
  register_event :numeric, :numeric, :code, :data
99
- # And a few others reserved for special purposed
100
- register_event :action, :action, :message
101
- register_event :ctcp, :ctcp, :message
101
+ # And a few others reserved for special purposes
102
+ register_event :action, :action, :target, :message
103
+ register_event :ctcp, :ctcp, :target, :message
102
104
 
103
105
  end
104
106
  end
@@ -3,25 +3,30 @@ module Marvin
3
3
  module Prefixes
4
4
  # A Generic host mask prefix for a message.
5
5
  class HostMask
6
- attr_accessor :nickname, :user, :host
6
+ attr_accessor :nick, :user, :host
7
7
 
8
- def initialize(nickname = nil, user = nil, host = nil)
9
- self.nickname = nickname || ""
10
- self.user = user || ""
11
- self.host = host || ""
8
+ def initialize(nick = nil, user = nil, host = nil)
9
+ @nick = nick || ""
10
+ @user = user || ""
11
+ @host = host || ""
12
12
  end
13
13
 
14
14
  # Convert it to a usable hash.
15
15
  def to_hash
16
- {:nick => @nickname.freeze, :ident => @user.freeze, :host => @host.freeze}
16
+ {
17
+ :nick => @nick.dup.freeze,
18
+ :ident => @user.dup.freeze,
19
+ :host => @host.dup.freeze
20
+ }
17
21
  end
18
22
 
19
23
  # Converts it back to a nicer form / a string.
20
24
  def to_s
21
25
  str = ""
22
- str << @nickname.to_s
26
+ str << @nick.to_s
23
27
  str << "!#{@user}" unless @user.blank?
24
28
  str << "@#{@host}" unless @host.blank?
29
+ str
25
30
  end
26
31
 
27
32
  end
@@ -7,7 +7,7 @@ module Marvin
7
7
  attr_accessor :name
8
8
 
9
9
  def initialize(name = nil)
10
- self.name = name || ""
10
+ @name = name.to_s
11
11
  end
12
12
 
13
13
  def to_hash