Sutto-marvin 0.1.0.20081014
Sign up to get free protection for your applications and to get access to all the features.
- 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
|