Sutto-marvin 0.4.0 → 0.8.0.0

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 (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