jeffrafter-marvin 0.1.20081115

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 (43) hide show
  1. data/README.textile +110 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/marvin +67 -0
  4. data/config/settings.yml.sample +13 -0
  5. data/config/setup.rb +14 -0
  6. data/handlers/hello_world.rb +9 -0
  7. data/handlers/logging_handler.rb +87 -0
  8. data/handlers/tweet_tweet.rb +21 -0
  9. data/lib/marvin/abstract_client.rb +210 -0
  10. data/lib/marvin/abstract_parser.rb +19 -0
  11. data/lib/marvin/base.rb +121 -0
  12. data/lib/marvin/command_handler.rb +62 -0
  13. data/lib/marvin/core_ext.rb +11 -0
  14. data/lib/marvin/data_store.rb +73 -0
  15. data/lib/marvin/dispatchable.rb +94 -0
  16. data/lib/marvin/drb_handler.rb +7 -0
  17. data/lib/marvin/exception_tracker.rb +16 -0
  18. data/lib/marvin/exceptions.rb +8 -0
  19. data/lib/marvin/handler.rb +12 -0
  20. data/lib/marvin/irc/abstract_server.rb +4 -0
  21. data/lib/marvin/irc/base_server.rb +11 -0
  22. data/lib/marvin/irc/client.rb +105 -0
  23. data/lib/marvin/irc/event.rb +30 -0
  24. data/lib/marvin/irc/socket_client.rb +69 -0
  25. data/lib/marvin/irc.rb +9 -0
  26. data/lib/marvin/loader.rb +68 -0
  27. data/lib/marvin/logger.rb +23 -0
  28. data/lib/marvin/middle_man.rb +103 -0
  29. data/lib/marvin/parsers/regexp_parser.rb +96 -0
  30. data/lib/marvin/parsers/simple_parser/default_events.rb +37 -0
  31. data/lib/marvin/parsers/simple_parser/event_extensions.rb +14 -0
  32. data/lib/marvin/parsers/simple_parser/prefixes.rb +34 -0
  33. data/lib/marvin/parsers/simple_parser.rb +101 -0
  34. data/lib/marvin/parsers.rb +7 -0
  35. data/lib/marvin/settings.rb +77 -0
  36. data/lib/marvin/test_client.rb +60 -0
  37. data/lib/marvin/util.rb +30 -0
  38. data/lib/marvin.rb +44 -0
  39. data/script/daemon-runner +12 -0
  40. data/script/run +25 -0
  41. data/spec/marvin/abstract_client_test.rb +38 -0
  42. data/spec/spec_helper.rb +14 -0
  43. metadata +99 -0
@@ -0,0 +1,103 @@
1
+ module Marvin
2
+ # The middle man is a class you can use to register
3
+ # other handlers on. e.g. it acts as a way to 'filter'
4
+ # incoming and outgoing messages. Akin to Rack / WSGI
5
+ # middleware.
6
+ class MiddleMan
7
+
8
+ # Set the logger cattr to the default marvin logger.
9
+ cattr_accessor :logger
10
+ self.logger ||= Marvin::Logger
11
+
12
+ # By default, we are *not* setup.
13
+ @@setup = false
14
+
15
+ # Our list of subhandlers. We make sure
16
+ # the list is unique to our subclass / class.
17
+ class_inheritable_accessor :subhandlers
18
+ self.subhandlers = []
19
+
20
+ # Finally, the client.
21
+ attr_reader :client
22
+
23
+ # When we're told to set the client,
24
+ # not only do we set out own instance
25
+ # but we also echo the command down
26
+ # to all of our sub-clients.
27
+ def client=(new_client)
28
+ @client = new_client
29
+ setup_subhandler_clients
30
+ end
31
+
32
+ def process_event(message, options)
33
+ return message, options
34
+ end
35
+
36
+ # Filter incoming events.
37
+ def handle(message, options)
38
+ # Process the current event.
39
+ message, options = process_event(message, options)
40
+ full_handler_name = "handle_#{message}"
41
+ self.send(full_handler_name, opts) if respond_to?(full_handler_name)
42
+ self.subhandlers.each do |sh|
43
+ forward_message_to_handler(sh, message, options, full_handler_name)
44
+ end
45
+ rescue HaltHandlerProcessing
46
+ logger.info "Asked to halt the filter processing chain inside a middleman."
47
+ rescue Exception => e
48
+ logger.fatal "Exception processing handle #{message}"
49
+ Marvin::ExceptionTracker.log(e)
50
+ end
51
+
52
+ class << self
53
+
54
+ def setup?
55
+ @@setup
56
+ end
57
+
58
+ # Forcefully do the setup routine.
59
+ def setup!
60
+ # Register ourselves as a new handler.
61
+ Marvin::Settings.default_client.register_handler self.new
62
+ @@setup = true
63
+ end
64
+
65
+ # Setup iff setup hasn't been done.
66
+ def setup
67
+ return if self.setup?
68
+ self.setup!
69
+ end
70
+
71
+ # Register a single subhandler.
72
+ def register_handler(handler, run_setup = true)
73
+ self.setup if run_setup
74
+ self.subhandlers << handler unless handler.blank?
75
+ end
76
+
77
+ # Registers a group of subhandlers.
78
+ def register_handlers(*args)
79
+ self.setup
80
+ args.each { |h| self.register_handler(h, false) }
81
+ end
82
+
83
+ end
84
+
85
+ private
86
+
87
+ def setup_subhandler_clients
88
+ self.subhandlers.each { |sh| sh.client = self.client if sh.respond_to?(:client=) }
89
+ end
90
+
91
+ # This should probably be extracted into some sort of Util's library as
92
+ # it's shared across a couple of classes but I really can't be bothered
93
+ # at the moment - I just want to test the concept.
94
+ def forward_message_to_handler(handler, message, options, full_handler_name)
95
+ if handler.respond_to?(full_handler_name)
96
+ handler.send(full_handler_name, options)
97
+ elsif handler.respond_to?(:handle)
98
+ handler.handle message, options
99
+ end
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,96 @@
1
+ module Marvin
2
+ module Parsers
3
+ class RegexpParser < Marvin::AbstractParser
4
+
5
+ cattr_accessor :regexp_matchers, :events
6
+ # Since we cbf implemented an ordered hash, just use regexp => event at the same
7
+ # index.
8
+ self.regexp_matchers = []
9
+ self.events = []
10
+
11
+ attr_accessor :current_line
12
+
13
+ # Appends an event to the end of the the events callback
14
+ # chain. It will be search in order of first-registered
15
+ # when used to match a URL (hence, order matters).
16
+ def self.register_event(*args)
17
+ matcher = args.delete_at(1) # Extract regexp.
18
+ if args.first.is_a?(Marvin::IRC::Event)
19
+ event = args.first
20
+ else
21
+ event = Marvin::IRC::Event.new(*args)
22
+ end
23
+ self.regexp_matchers << matcher
24
+ self.events << event
25
+ end
26
+
27
+
28
+ # Initialize a new RegexpParser from the given line.
29
+ def initialize(line)
30
+ self.current_line = line
31
+ end
32
+
33
+ def to_event
34
+ self.regexp_matchers.each_with_index do |matcher, offset|
35
+ if (match_data = matcher.match(self.current_line))
36
+ event = self.events[offset].dup
37
+ event.raw_arguments = match_data.to_a[1..-1]
38
+ return event
39
+ end
40
+ end
41
+ # otherwise, return nil
42
+ return nil
43
+ end
44
+
45
+ ## The Default IRC Events
46
+
47
+ # Note that some of these Regexp's are from Net::YAIL,
48
+ # which apparantly sources them itself from the IRCSocket
49
+ # library.
50
+
51
+ register_event :invite, /^\:(.+)\!\~?(.+)\@(.+) INVITE (\S+) :?(.+?)$/i,
52
+ :nick, :ident, :host, :target, :channel
53
+
54
+ register_event :action, /^\:(.+)\!\~?(.+)\@(.+) PRIVMSG (\S+) :?\001ACTION (.+?)\001$/i,
55
+ :nick, :ident, :host, :target, :message
56
+
57
+ register_event :ctcp, /^\:(.+)\!\~?(.+)\@(.+) PRIVMSG (\S+) :?\001(.+?)\001$/i,
58
+ :nick, :ident, :host, :target, :message
59
+
60
+ register_event :message, /^\:(.+)\!\~?(.+)\@(.+) PRIVMSG (\S+) :?(.+?)$/i,
61
+ :nick, :ident, :host, :target, :message
62
+
63
+ register_event :join, /^\:(.+)\!\~?(.+)\@(.+) JOIN (\S+)/i,
64
+ :nick, :ident, :host, :target
65
+
66
+ register_event :part, /^\:(.+)\!\~?(.+)\@(.+) PART (\S+)\s?:?(.+?)$/i,
67
+ :nick, :ident, :host, :target, :message
68
+
69
+ register_event :mode, /^\:(.+)\!\~?(.+)\@(.+) MODE (\S+) :?(.+?)$/i,
70
+ :nick, :ident, :host, :target, :mode
71
+
72
+ register_event :kick, /^\:(.+)\!\~?(.+)\@(.+) KICK (\S+) (\S+)\s?:?(.+?)$/i,
73
+ :nick, :ident, :host, :target, :channel, :reason
74
+
75
+ register_event :topic, /^\:(.+)\!\~?(.+)\@(.+) TOPIC (\S+) :?(.+?)$/i,
76
+ :nick, :ident, :host, :target, :topic
77
+
78
+ register_event :nick, /^\:(.+)\!\~?(.+)\@(.+) NICK :?(.+?)$/i,
79
+ :nick, :ident, :host, :new_nick
80
+
81
+ register_event :quit, /^\:(.+)\!\~?(.+)\@(.+) QUIT :?(.+?)$/i,
82
+ :nick, :ident, :host, :message
83
+
84
+ register_event :nick_taken, /^\:(\S+) 433 \* (\w+) :(.+)$/,
85
+ :server, :target, :message
86
+
87
+ register_event :ping, /^\:(.+)\!\~?(.+)\@(.+) PING (.*)$/,
88
+ :nick, :ident, :host, :data
89
+
90
+ register_event :ping, /PING (.*)$/, :data
91
+
92
+ register_event :numeric, /^\:(\S+) ([0-9]+) (.*)$/,
93
+ :server, :code, :data
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,37 @@
1
+ class Marvin::Parsers::SimpleParser < Marvin::AbstractParser
2
+ module DefaultEvents
3
+
4
+ def self.included(parent)
5
+ parent.class_eval do
6
+ extend ClassMethods
7
+ # Register the default set of events with commands
8
+ register_event :nick, :NICK, :new_nick
9
+ register_event :quit, :QUIT, :message
10
+ register_event :ping, :PING, :data
11
+ register_event :join, :JOIN, :target
12
+ register_event :invite, :INVITE, :target, :channel
13
+ register_event :message, :PRIVMSG, :target, :message
14
+ register_event :part, :PART, :target, :message
15
+ register_event :mode, :MODE, :target, :mode
16
+ register_event :kick, :KICK, :target, :channel, :reason
17
+ register_event :topic, :TOPIC, :target, :topic
18
+ # Add the default numeric event
19
+ register_event :numeric, :numeric, :code, :data
20
+ # And a few others reserved for special purposed
21
+ register_event :action, :action, :message
22
+ register_event :ctcp, :ctcp, :message
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+
28
+ # Register an event from a given name,
29
+ # command as well as a set of arguments.
30
+ def register_event(name, command, *args)
31
+ event = Marvin::Parsers::SimpleParser::EventWithPrefix.new(name, *args)
32
+ self.events[command] = event
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ # An extension to Marvin::IRC::Event which
2
+ # lets a user specify a prefix to use.
3
+ class Marvin::Parsers::SimpleParser < Marvin::AbstractParser
4
+
5
+ class EventWithPrefix < Marvin::IRC::Event
6
+ attr_accessor :prefix
7
+
8
+ def to_hash
9
+ super.merge(prefix.blank? ? {} : prefix.to_hash)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,34 @@
1
+ # A Set of prefixes for a given IRC line
2
+ # as parsed from an incoming line.
3
+ class Marvin::Parsers::SimpleParser < Marvin::AbstractParser
4
+
5
+ class Prefix; end
6
+
7
+ class ServerNamePrefix < Prefix
8
+ attr_accessor :server_name
9
+
10
+ def initialize(name)
11
+ self.server_name = name
12
+ end
13
+
14
+ def to_hash
15
+ {:server => self.server_name}
16
+ end
17
+
18
+ end
19
+
20
+ class UserPrefix < Prefix
21
+ attr_accessor :nick, :ident, :host
22
+
23
+ def initialize(nick, ident = nil, host = nil)
24
+ self.nick = nick
25
+ self.ident = ident
26
+ self.host = host
27
+ end
28
+
29
+ def to_hash
30
+ {:host => self.host, :nick => self.nick, :ident => self.ident}
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,101 @@
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
+ module Marvin
6
+ module Parsers
7
+ class SimpleParser < Marvin::AbstractParser
8
+
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
+ private
29
+
30
+ def parse!
31
+ # Split the message
32
+ line = self.current_line
33
+ if line[0] == ?:
34
+ prefix_text, line = line.split(" ", 2)
35
+ else
36
+ prefix_text = nil
37
+ end
38
+ extract_prefix! prefix_text
39
+
40
+ 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
62
+ end
63
+
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)
90
+ else
91
+ # TODO: Validate the hostname here.
92
+ prefix = ServerNamePrefix.new(prefix.strip)
93
+ end
94
+ self.prefix = prefix
95
+ return prefix
96
+ end
97
+
98
+ include DefaultEvents
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,7 @@
1
+ module Marvin
2
+ module Parsers
3
+ # Default Parsers
4
+ autoload :RegexpParser, 'marvin/parsers/regexp_parser'
5
+ autoload :SimpleParser, 'marvin/parsers/simple_parser'
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ require 'yaml'
2
+
3
+ module Marvin
4
+ class Settings
5
+
6
+ cattr_accessor :environment, :configuration, :is_setup, :default_client, :handler_folder, :default_parser
7
+
8
+ class << self
9
+
10
+ def root
11
+ defined?(MARVIN_ROOT) ? MARVIN_ROOT : File.dirname(__FILE__) / "../.."
12
+ end
13
+
14
+ def setup(options = {})
15
+ return if self.is_setup
16
+ self.setup!(options)
17
+ end
18
+
19
+ def daemon?
20
+ defined?(IS_DAEMON) && IS_DAEMON
21
+ end
22
+
23
+ def setup!(options = {})
24
+ self.environment ||= "development"
25
+ 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
33
+ loaded_yaml = YAML.load_file(root / "config/settings.yml")
34
+ loaded_options = loaded_yaml["default"].
35
+ merge(loaded_yaml[self.environment]).
36
+ merge(options)
37
+ self.configuration.merge!(loaded_options)
38
+ self.configuration.symbolize_keys!
39
+ mod = Module.new do
40
+ Settings.configuration.keys.each do |k|
41
+ define_method(k) do
42
+ return Settings.configuration[k]
43
+ end
44
+
45
+ define_method("#{k}=") do |val|
46
+ Settings.configuration[k] = val
47
+ end
48
+ end
49
+ end
50
+
51
+ # Extend and include.
52
+
53
+ extend mod
54
+ include mod
55
+
56
+ self.is_setup = true
57
+ end
58
+
59
+ def [](key)
60
+ self.setup
61
+ return self.configuration[key.to_sym]
62
+ end
63
+
64
+ def []=(key, value)
65
+ self.setup
66
+ self.configuration[key.to_sym] = value
67
+ return value
68
+ end
69
+
70
+ def to_hash
71
+ self.configuration
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,60 @@
1
+ require 'ostruct'
2
+ require 'active_support'
3
+
4
+ module Marvin
5
+ # Marvin::TestClient is a simple client used for testing
6
+ # Marvin::Base derivatives in a non-network-reliant setting.
7
+ class TestClient < AbstractClient
8
+ attr_accessor :incoming_commands, :outgoing_commands, :last_sent, :dispatched_events, :connection_open
9
+
10
+ cattr_accessor :instances
11
+ self.instances = []
12
+
13
+ DispatchedEvents = Struct.new(:name, :options)
14
+
15
+ def initialize
16
+ super
17
+ self.incoming_commands = []
18
+ self.outgoing_commands = []
19
+ self.dispatched_events = []
20
+ self.connection_open = false
21
+ self.instances << self
22
+ end
23
+
24
+ def connection_open?
25
+ self.connection_open
26
+ end
27
+
28
+ def send_line(*args)
29
+ self.outgoing_commands += args
30
+ self.last_sent = args.last
31
+ end
32
+
33
+ def test_command(name, *args)
34
+ options = args.extract_options!
35
+ host_mask = options.delete(:host_mask) || ":WiZ!jto@tolsun.oulu.fi"
36
+ name = name.to_s.upcase
37
+ args = args.flatten.compact
38
+ irc_command = "#{host_mask} #{name} #{args.join(" ").strip}"
39
+ self.receive_line irc_command
40
+ end
41
+
42
+ def dispatch(name, opts = {})
43
+ self.dispatched_events << [name, opts]
44
+ super(name, opts)
45
+ end
46
+
47
+ def self.run
48
+ self.instances.each do |i|
49
+ i.connection_open = true
50
+ end
51
+ end
52
+
53
+ def self.stop
54
+ self.instances.each do |i|
55
+ i.connection_open = false
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ module Marvin
2
+ module Util
3
+
4
+ # Return the channel-name version of a string by
5
+ # appending "#" to the front if it doesn't already
6
+ # start with it.
7
+ def channel_name(name)
8
+ return name.to_s[0..0] == "#" ? name.to_s : "##{name}"
9
+ end
10
+ alias chan channel_name
11
+
12
+ def arguments(input)
13
+ prefix, *ending = input.split(":")
14
+ prefix = prefix.split(" ")
15
+ prefix << ending.join(":").strip
16
+ return prefix
17
+ end
18
+
19
+ # Specifies the last parameter of a response, used to
20
+ # specify parameters which have spaces etc (for example,
21
+ # the actual message part of a response).
22
+ def last_param(section)
23
+ section && ":#{section.to_s.strip} "
24
+ end
25
+ alias lp last_param
26
+
27
+ extend self
28
+
29
+ end
30
+ end
data/lib/marvin.rb ADDED
@@ -0,0 +1,44 @@
1
+ $:.unshift File.dirname(__FILE__) # Append the current working dir to the front of the line.
2
+
3
+ require 'rubygems'
4
+ require 'active_support'
5
+ require 'marvin/core_ext'
6
+
7
+ # Make all exceptions available
8
+ require 'marvin/exceptions'
9
+
10
+ module Marvin
11
+ autoload :Util, 'marvin/util'
12
+ autoload :Dispatchable, 'marvin/dispatchable'
13
+ autoload :AbstractClient, 'marvin/abstract_client'
14
+ autoload :Base, 'marvin/base'
15
+ autoload :ClientMixin, 'marvin/client_mixin'
16
+ autoload :Settings, 'marvin/settings'
17
+ autoload :Logger, 'marvin/logger'
18
+ autoload :IRC, 'marvin/irc'
19
+ autoload :TestClient, 'marvin/test_client'
20
+ autoload :Loader, 'marvin/loader'
21
+ autoload :MiddleMan, 'marvin/middle_man'
22
+ autoload :DRBHandler, 'marvin/drb_handler'
23
+ autoload :DataStore, 'marvin/data_store'
24
+ autoload :ExceptionTracker, 'marvin/exception_tracker'
25
+ # Parsers
26
+ autoload :AbstractParser, 'marvin/abstract_parser'
27
+ autoload :Parsers, 'marvin/parsers.rb'
28
+
29
+ # Default Handlers
30
+ autoload :CommandHandler, 'marvin/command_handler'
31
+
32
+ Settings.setup # Load Settings etc.
33
+
34
+ end
35
+
36
+ def p(text)
37
+ res = Marvin::Parsers::SimpleParser.parse(text)
38
+ if res.blank?
39
+ puts "Unrecognized Result"
40
+ else
41
+ STDOUT.puts "Event: #{res.to_incoming_event_name}"
42
+ STDOUT.puts "Args: #{res.to_hash.inspect}"
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'daemons'
4
+
5
+ DIR = File.join(File.dirname(__FILE__), "..")
6
+
7
+ Daemons.run(File.join(DIR, "script/run --is-daemon"),
8
+ {:mode => :exec,
9
+ :dir => DIR,
10
+ :dir_mode => :normal,
11
+ :log_output => true,
12
+ :app_name => 'marvin'})
data/script/run ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+ if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
5
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
6
+ end
7
+
8
+ MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
9
+ IS_DAEMON = ARGV.include?("--is-daemon")
10
+
11
+ # And Require Marvin.
12
+ require 'marvin'
13
+
14
+ ["INT", "TERM"].each do |sig|
15
+
16
+ # Trap a given signal and run all
17
+ # of our callbacks etc,
18
+ trap sig do
19
+ Marvin::Loader.stop!
20
+ exit
21
+ end
22
+
23
+ end
24
+
25
+ Marvin::Loader.run!
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ # Tests the behaviour of the AbstractClient functionality
4
+ # via a thin wrapper of the class via Marvin::TestClient.
5
+
6
+ describe "the base Marvin::TestClient functionality" do
7
+
8
+ it "should dispatch :client_connected as the first event on process_connect" do
9
+ client(true).dispatched_events.should == []
10
+ client.process_connect
11
+ client.dispatched_events.first.should == [:client_connected, {}]
12
+ end
13
+
14
+ it "should dispatch :client_connected as the first event on process_connect" do
15
+ client(true).dispatched_events.should == []
16
+ client.process_connect
17
+ Marvin::Logger.info client.outgoing_commands.inspect
18
+ client.dispatched_events[-2].first.should == :outgoing_nick
19
+ client.dispatched_events[-1].first.should == :outgoing_join
20
+ client.outgoing_commands.length.should == 3
21
+ client.outgoing_commands[0].should =~ /^USER \w+ 0 \* :\w+ \r\n$/
22
+ client.outgoing_commands[1].should =~ /^NICK \w+ \r\n$/
23
+ client.outgoing_commands[2].should =~ /^JOIN \#[A-Za-z0-9\-\_]+ \r\n$/
24
+ end
25
+
26
+ it "should dispatch :client_disconnect on process_disconnect" do
27
+ client(true).dispatched_events.should == []
28
+ client.process_disconnect
29
+ client.dispatched_events.last.should == [:client_disconnected, {}]
30
+ end
31
+
32
+ it "should add an :incoming_line event for each incoming line" do
33
+ client(true).dispatched_events.should == []
34
+ client.receive_line "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"
35
+ client.dispatched_events.first.should == [:incoming_line, {:line => "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"}]
36
+ end
37
+
38
+ end