Sutto-marvin 0.1.0.20081014
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/bin/marvin +60 -0
- data/config/settings.yml.sample +13 -0
- data/config/setup.rb +15 -0
- data/handlers/hello_world.rb +14 -0
- data/handlers/logging_handler.rb +87 -0
- data/handlers/tweet_tweet.rb +19 -0
- data/lib/marvin/abstract_client.rb +250 -0
- data/lib/marvin/abstract_parser.rb +19 -0
- data/lib/marvin/base.rb +121 -0
- data/lib/marvin/command_handler.rb +62 -0
- data/lib/marvin/core_ext.rb +11 -0
- data/lib/marvin/data_store.rb +73 -0
- data/lib/marvin/drb_handler.rb +7 -0
- data/lib/marvin/exception_tracker.rb +16 -0
- data/lib/marvin/exceptions.rb +8 -0
- data/lib/marvin/irc/abstract_server.rb +4 -0
- data/lib/marvin/irc/client.rb +107 -0
- data/lib/marvin/irc/event.rb +29 -0
- data/lib/marvin/irc/socket_client.rb +63 -0
- data/lib/marvin/irc.rb +8 -0
- data/lib/marvin/loader.rb +55 -0
- data/lib/marvin/logger.rb +22 -0
- data/lib/marvin/middle_man.rb +105 -0
- data/lib/marvin/parsers/regexp_parser.rb +94 -0
- data/lib/marvin/parsers.rb +7 -0
- data/lib/marvin/settings.rb +73 -0
- data/lib/marvin/test_client.rb +60 -0
- data/lib/marvin/util.rb +30 -0
- data/lib/marvin.rb +33 -0
- data/script/run +18 -0
- metadata +85 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Marvin
|
4
|
+
# Implements a simple datastore interface, designed to make
|
5
|
+
# it easy to develop handlers which have persistent data.
|
6
|
+
class DataStore
|
7
|
+
|
8
|
+
cattr_accessor :logger, :registered_stores
|
9
|
+
self.logger = Marvin::Logger.logger
|
10
|
+
self.registered_stores = {}
|
11
|
+
|
12
|
+
# Returns the path to the data store relative to this file.
|
13
|
+
# Used when loading / dumping the data.
|
14
|
+
def self.datastore_location
|
15
|
+
path = Marvin::Settings[:datastore_location] ? Marvin::Settings.datastore_location : "tmp/datastore.json"
|
16
|
+
return Marvin::Settings.root / path
|
17
|
+
end
|
18
|
+
|
19
|
+
# Dump the current data store contents to file.
|
20
|
+
def self.dump!
|
21
|
+
File.open(self.datastore_location, "w+") do |f|
|
22
|
+
f.write self.registered_stores.to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Load the current data store contents from file.
|
27
|
+
def self.load!
|
28
|
+
results = {}
|
29
|
+
if File.exists?(self.datastore_location)
|
30
|
+
begin
|
31
|
+
json = JSON.load(File.read(self.datastore_location))
|
32
|
+
results = json if json.is_a?(Hash)
|
33
|
+
rescue JSON::ParserError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
self.registered_stores = results
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# For each individual datastore.
|
41
|
+
|
42
|
+
attr_accessor :name
|
43
|
+
|
44
|
+
def initialize(name)
|
45
|
+
self.name = name.to_s
|
46
|
+
self.registered_stores ||= {}
|
47
|
+
self.registered_stores[self.name] ||= {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def [](key)
|
51
|
+
((self.registered_stores||={})[self.name]||={})[key.to_s]
|
52
|
+
end
|
53
|
+
|
54
|
+
def []=(key,value)
|
55
|
+
self.registered_stores[self.name][key.to_s] = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(name, *args, &blk)
|
59
|
+
if name.to_s =~ /^(.*)=$/i
|
60
|
+
self[$1.to_s] = args.first
|
61
|
+
elsif self.registered_stores[self.name].has_key?(name.to_s)
|
62
|
+
return self.registered_stores[self.name][name.to_s]
|
63
|
+
else
|
64
|
+
super(name, *args, &blk)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_hash
|
69
|
+
self.registered_stores[self.name]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Marvin
|
2
|
+
class ExceptionTracker
|
3
|
+
|
4
|
+
cattr_accessor :logger
|
5
|
+
self.logger = Marvin::Logger.logger
|
6
|
+
|
7
|
+
def self.log(e)
|
8
|
+
logger.fatal "Exception raised inside Marvin Instance."
|
9
|
+
logger.fatal "#{e} - #{e.message}"
|
10
|
+
e.backtrace.each do |line|
|
11
|
+
logger.fatal line
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
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
|
+
class Client < Marvin::AbstractClient
|
48
|
+
attr_accessor :em_connection
|
49
|
+
|
50
|
+
class EMConnection < EventMachine::Protocols::LineAndTextProtocol
|
51
|
+
attr_accessor :client
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
super
|
55
|
+
self.client = Marvin::IRC::Client.new
|
56
|
+
self.client.em_connection = self
|
57
|
+
end
|
58
|
+
|
59
|
+
def post_init
|
60
|
+
super
|
61
|
+
client.process_connect
|
62
|
+
end
|
63
|
+
|
64
|
+
def unbind
|
65
|
+
super
|
66
|
+
client.process_disconnect
|
67
|
+
end
|
68
|
+
|
69
|
+
def receive_line(line)
|
70
|
+
self.client.receive_line(line)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_line(*args)
|
76
|
+
em_connection.send_data *args
|
77
|
+
end
|
78
|
+
|
79
|
+
## Client specific details
|
80
|
+
|
81
|
+
# Starts the EventMachine loop and hence starts up the actual
|
82
|
+
# networking portion of the IRC Client.
|
83
|
+
def self.run
|
84
|
+
self.setup # So we have options etc
|
85
|
+
EventMachine::run do
|
86
|
+
logger.debug "Connecting to #{self.configuration.server}:#{self.configuration.port}"
|
87
|
+
EventMachine::connect self.configuration.server, self.configuration.port, Marvin::IRC::Client::EMConnection
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.stop
|
92
|
+
logger.debug "Telling all connections to quit"
|
93
|
+
self.connections.dup.each { |connection| connection.quit }
|
94
|
+
logger.debug "Telling Event Machine to Stop"
|
95
|
+
EventMachine::stop_event_loop
|
96
|
+
logger.debug "Stopped."
|
97
|
+
end
|
98
|
+
|
99
|
+
# Registers a callback handle that will be periodically run.
|
100
|
+
def periodically(timing, event_callback)
|
101
|
+
callback = proc { self.dispatch_event event_callback.to_sym }
|
102
|
+
EventMachine::add_periodic_timer(timing, &callback)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Marvin::IRC
|
2
|
+
class Event
|
3
|
+
attr_accessor :keys, :name, :raw_arguments
|
4
|
+
|
5
|
+
def initialize(name, *args)
|
6
|
+
self.name = name.to_sym
|
7
|
+
self.keys = args.flatten.map { |k| k.to_sym }
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
return {} unless self.raw_arguments
|
12
|
+
results = {}
|
13
|
+
values = self.raw_arguments.to_a
|
14
|
+
self.keys.each do |key|
|
15
|
+
results[key] = values.shift
|
16
|
+
end
|
17
|
+
return results
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#<Marvin::IRC::Event name=#{self.name} attributes=[#{keys * ","}] >"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_incoming_event_name
|
25
|
+
:"incoming_#{self.name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Marvin::IRC
|
4
|
+
class SocketClient < Marvin::AbstractClient
|
5
|
+
attr_accessor :socket
|
6
|
+
|
7
|
+
def run
|
8
|
+
@socket = TCPSocket.new(self.configuration.server, self.configuration.port)
|
9
|
+
self.process_connect
|
10
|
+
self.enter_loop
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_line(*args)
|
14
|
+
args.each { |l| @socket.write l } if !@socket.closed?
|
15
|
+
end
|
16
|
+
|
17
|
+
def disconnect_processed?
|
18
|
+
@disconnect_processed
|
19
|
+
end
|
20
|
+
|
21
|
+
def enter_loop
|
22
|
+
until @socket.closed?
|
23
|
+
line = @socket.readline.strip
|
24
|
+
receive_line line
|
25
|
+
end
|
26
|
+
self.process_disconnect unless self.disconnect_processed?
|
27
|
+
@disconnect_processed = true
|
28
|
+
rescue SystemExit
|
29
|
+
self.process_disconnect unless self.disconnect_processed?
|
30
|
+
@disconnect_processed = true
|
31
|
+
rescue Exception => e
|
32
|
+
Marvin::ExceptionTracker.log(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def quit(*args)
|
36
|
+
super(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
## Client specific details
|
40
|
+
|
41
|
+
def self.run
|
42
|
+
self.setup # So we have options etc
|
43
|
+
logger.debug "Connecting to #{self.configuration.server}:#{self.configuration.port}"
|
44
|
+
self.new.run
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.stop
|
48
|
+
logger.debug "Telling all connections to quit"
|
49
|
+
self.connections.each do |connection|
|
50
|
+
connection.quit
|
51
|
+
logger.debug "Preparing to close socket"
|
52
|
+
connection.socket.close
|
53
|
+
end
|
54
|
+
logger.debug "Stopped."
|
55
|
+
end
|
56
|
+
|
57
|
+
# Registers a callback handle that will be periodically run.
|
58
|
+
def periodically(timing, event_callback)
|
59
|
+
callback = proc { self.dispatch_event event_callback.to_sym }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/marvin/irc.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Marvin
|
2
|
+
class Loader
|
3
|
+
|
4
|
+
cattr_accessor :setup_block
|
5
|
+
|
6
|
+
def self.before_connecting(&blk)
|
7
|
+
self.setup_block = blk
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup_defaults
|
11
|
+
Marvin::Logger.setup
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_handlers
|
15
|
+
handlers = Dir[Marvin::Settings.root / "handlers/**/*.rb"].map { |h| h[0..-4] }
|
16
|
+
handlers.each do |handler|
|
17
|
+
require handler
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_settings
|
22
|
+
Marvin::Settings.setup
|
23
|
+
Marvin::Settings.default_client.configuration = Marvin::Settings.to_hash
|
24
|
+
Marvin::Settings.default_client.setup
|
25
|
+
end
|
26
|
+
|
27
|
+
def pre_connect_setup
|
28
|
+
Marvin::DataStore.load!
|
29
|
+
require(Marvin::Settings.root / "config/setup")
|
30
|
+
self.setup_block.call unless self.setup_block.blank?
|
31
|
+
end
|
32
|
+
|
33
|
+
def run!
|
34
|
+
self.setup_defaults
|
35
|
+
self.load_settings
|
36
|
+
self.load_handlers
|
37
|
+
self.pre_connect_setup
|
38
|
+
Marvin::Settings.default_client.run
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop!
|
42
|
+
Marvin::Settings.default_client.stop
|
43
|
+
Marvin::DataStore.dump!
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.run!
|
47
|
+
self.new.run!
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.stop!
|
51
|
+
self.new.stop!
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Marvin
|
4
|
+
class Logger
|
5
|
+
|
6
|
+
cattr_accessor :logger
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def setup
|
11
|
+
self.logger ||= ::Logger.new(STDOUT)
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(name, *args, &blk)
|
15
|
+
self.setup # Ensure the logger is setup
|
16
|
+
self.logger.send(name, *args, &blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,105 @@
|
|
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 do |sh|
|
89
|
+
sh.client = self.client if sh.respond_to?(:client=)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# This should probably be extracted into some sort of Util's library as
|
94
|
+
# it's shared across a couple of classes but I really can't be bothered
|
95
|
+
# at the moment - I just want to test the concept.
|
96
|
+
def forward_message_to_handler(handler, message, options, full_handler_name)
|
97
|
+
if handler.respond_to?(full_handler_name)
|
98
|
+
handler.send(full_handler_name, options)
|
99
|
+
elsif handler.respond_to?(:handle)
|
100
|
+
handler.handle message, options
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,94 @@
|
|
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 :numeric, /^\:(\S+) ([0-9]+) (.*)$/,
|
91
|
+
:host, :code, :data
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,73 @@
|
|
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 setup!(options = {})
|
20
|
+
self.environment ||= "development"
|
21
|
+
self.configuration = {}
|
22
|
+
self.default_client ||= begin
|
23
|
+
require 'eventmachine'
|
24
|
+
Marvin::IRC::Client
|
25
|
+
rescue LoadError
|
26
|
+
Marvin::IRC::SocketClient
|
27
|
+
end
|
28
|
+
self.default_parser ||= Marvin::Parsers::RegexpParser
|
29
|
+
loaded_yaml = YAML.load_file(root / "config/settings.yml")
|
30
|
+
loaded_options = loaded_yaml["default"].
|
31
|
+
merge(loaded_yaml[self.environment]).
|
32
|
+
merge(options)
|
33
|
+
self.configuration.merge!(loaded_options)
|
34
|
+
self.configuration.symbolize_keys!
|
35
|
+
mod = Module.new do
|
36
|
+
Settings.configuration.keys.each do |k|
|
37
|
+
define_method(k) do
|
38
|
+
return Settings.configuration[k]
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method("#{k}=") do |val|
|
42
|
+
Settings.configuration[k] = val
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Extend and include.
|
48
|
+
|
49
|
+
extend mod
|
50
|
+
include mod
|
51
|
+
|
52
|
+
self.is_setup = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](key)
|
56
|
+
self.setup
|
57
|
+
return self.configuration[key.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []=(key, value)
|
61
|
+
self.setup
|
62
|
+
self.configuration[key.to_sym] = value
|
63
|
+
return value
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_hash
|
67
|
+
self.configuration
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|