Sutto-marvin 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +76 -154
- data/VERSION.yml +1 -1
- data/bin/marvin +17 -2
- data/config/setup.rb +3 -0
- data/lib/marvin/base.rb +3 -0
- data/lib/marvin/distributed/dispatch_handler.rb +1 -1
- data/lib/marvin/distributed/drb_client.rb +2 -2
- data/lib/marvin/distributed/ring_server.rb +2 -2
- data/lib/marvin/settings.rb +5 -1
- data/lib/marvin/status.rb +5 -5
- metadata +2 -2
data/README.textile
CHANGED
@@ -1,183 +1,105 @@
|
|
1
1
|
h1. Marvin
|
2
2
|
|
3
|
-
Marvin is a
|
4
|
-
|
5
|
-
|
6
|
-
particular need.
|
3
|
+
Marvin is a ruby irc framework / library built on top of event machine.
|
4
|
+
It's been build from scratch to be evented - you build "handlers" which
|
5
|
+
are called whenever an event occurs.
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Uses the EventMachine library for all network connections
|
13
|
-
# It uses an architecture based on event listeners - called 'handlers'
|
14
|
-
|
15
|
-
It's been heavily influenced by rack in terms of design, making it easy
|
16
|
-
to do things like chain handlers, write your own functionality and most
|
17
|
-
of all making it easy to implement.
|
7
|
+
A single client instance can handle multiple IRC connections (and will
|
8
|
+
automatically reconnect in the case that a connection is lost). Distributed
|
9
|
+
support (e.g. 1 client => multiple handler backends) is built in out of
|
10
|
+
the box on top of DRb.
|
18
11
|
|
19
12
|
h2. Getting Started
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
Starting out with Marvin is simple. You can either go the "edge" route -
|
15
|
+
cloning from the GitHub repository (in this case, [here](http://github.com/sutto/marvin))
|
16
|
+
and then running the following:
|
24
17
|
|
25
|
-
$
|
26
|
-
$
|
27
|
-
|
18
|
+
$ rake gemspec
|
19
|
+
$ gem build marvin.gemspec
|
20
|
+
$ sudo gem install marvin.gem
|
28
21
|
|
29
|
-
|
22
|
+
Or, for a generally more stable release you can install it from the GitHub gem
|
23
|
+
server (requiring Rubygems >= 1.2.0 with the GitHub sources added), by running
|
24
|
+
the following:
|
30
25
|
|
31
|
-
$ marvin
|
26
|
+
$ sudo gem install Sutto-marvin
|
32
27
|
|
33
|
-
|
28
|
+
Installing the gem will make available a new executable - "+marvin+" - which is
|
29
|
+
used as an easy way to do a variety of tasks. To get started, you can create
|
30
|
+
a project located at given path using the following command:
|
34
31
|
|
35
|
-
$ marvin create
|
32
|
+
$ marvin create path-to-my-bot
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
development:
|
44
|
-
user: MarvinBot
|
45
|
-
name: MarvinBot
|
46
|
-
nick: Marvin
|
47
|
-
|
48
|
-
You can use the defaults or configure it. The datastore location
|
49
|
-
specifies a relative path where a simple json-backed key value
|
50
|
-
store will store persistent information for your client (if chosen).
|
51
|
-
Once that's been done, you'll want to setup some connections by editing
|
52
|
-
+config/connections.yml+, using the following format:
|
53
|
-
|
54
|
-
"server-address":
|
55
|
-
post: 6667 # Defaults to 6667
|
56
|
-
channels:
|
57
|
-
- "#marvin-testing"
|
58
|
-
- "#relayrelay"
|
59
|
-
nicks:
|
60
|
-
- List
|
61
|
-
- Of
|
62
|
-
- Alternative
|
63
|
-
- Nicks
|
64
|
-
"another-server-address":
|
65
|
-
post: 6667 # Defaults to 6667
|
66
|
-
channels:
|
67
|
-
- "#helloworld"
|
68
|
-
|
69
|
-
Which will let marvin connect to multiple servers - autojoining the specific rooms.
|
70
|
-
Next, to get started you can simply type:
|
71
|
-
|
72
|
-
$ ./script/client
|
73
|
-
|
74
|
-
The bot should join the specified channel and will respond to some simple
|
75
|
-
commands by default:
|
76
|
-
|
77
|
-
*YourName*: MarvinBot3000: hello
|
78
|
-
*MarvinBot3000*: YourName: Hola!
|
34
|
+
Once that's done, you'll have a blank slate loaded with the default marvin handlers -
|
35
|
+
+HelloWorld+ (which will respond to any addressed "hello"'s) and an empty debug handler
|
36
|
+
which you can use for generic debugging. To run the new app, you can use either of the
|
37
|
+
following:
|
38
|
+
|
39
|
+
$ cd path-to-my-bot && script/client
|
79
40
|
|
80
|
-
|
41
|
+
or, alternatively,
|
81
42
|
|
82
|
-
|
43
|
+
$ marvin client path-to-my-bot
|
44
|
+
|
45
|
+
There are a couple of options available for the client (as well as the marvin library),
|
46
|
+
Each of which can be found by appending the "--help" option to the command.
|
83
47
|
|
84
|
-
|
48
|
+
Once your client has been started (assuming the name wasn't taken / it could connect),
|
49
|
+
simply join the chat room your bot was instructed to join and say the following (substiting
|
50
|
+
BotNick for the nick name your bot connected with):
|
85
51
|
|
86
|
-
|
87
|
-
* epitron / halogrium - For the ragel state machine used in Marvin::Parsers::RagelParser
|
88
|
-
* The creator of Ruby-IRCD - the server component is heavily influenced by / part derivative of said work.
|
89
|
-
|
90
|
-
h2. Marvin::Base - A handler starting point
|
91
|
-
|
92
|
-
The first, Marvin::Base provides a base set of methods (e.g. say,
|
93
|
-
reply etc etc.) which make writing a client easier. You can simply
|
94
|
-
inherit from Marvin::Base, write some logic and then use the class
|
95
|
-
method on_event to define responses to events. The passed in meta
|
96
|
-
data for each event is then usable via options.attribute_name - an
|
97
|
-
openstruct version of the details. e.g.
|
98
|
-
|
99
|
-
class NinjaStuff < Marvin::Base
|
100
|
-
on_event :incoming_message do
|
101
|
-
do_something
|
102
|
-
end
|
103
|
-
def do_something
|
104
|
-
reply options.message # Will echo back the message
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
Or the like. Also, the halt! method can be called in any subclass to
|
109
|
-
halt the handler callback chain.
|
110
|
-
|
111
|
-
You also get access to the class method +on_numeric+ which makes
|
112
|
-
it relatively easy to respond to a specific numeric reply.
|
113
|
-
|
114
|
-
h2. Marvin::CommandHandler - Ridiculously easy Bots
|
115
|
-
|
116
|
-
With Marvin::CommandHandler, you get to define seriously
|
117
|
-
simple classes which can act as a simple bot. It takes
|
118
|
-
great inspiration from "MatzBot":http://github.com/defunkt/matzbot/tree/master
|
119
|
-
to make it as easy as possible to make a simple bot
|
120
|
-
|
121
|
-
To write a CommandHandler, you simply create a subclass
|
122
|
-
(ala ActiveRecord::Base), define a few methods and then
|
123
|
-
just use the "exposes" class method. e.g.
|
124
|
-
|
125
|
-
class MySecondExample < Marvin::CommandHandler
|
126
|
-
exposes :hello
|
127
|
-
def hello(data)
|
128
|
-
reply "Hello!"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
Where data is an array of parameters. exposed methods will be called
|
133
|
-
when they match the following pattern:
|
134
|
-
|
135
|
-
Botname: *exposed-method* *space-seperated-list-meaning-data*
|
52
|
+
BotNick: hello
|
136
53
|
|
137
|
-
|
54
|
+
Or even easier, by PM'ing the bot with:
|
138
55
|
|
139
|
-
YourBotsName: hello
|
140
|
-
|
141
|
-
or, even easier, by PM'ing the bot with:
|
142
|
-
|
143
56
|
hello
|
57
|
+
|
58
|
+
Assuming all went well, your bot should reply back with something akin to (where YourNick)
|
59
|
+
if the nickname you connected with):
|
144
60
|
|
145
|
-
|
61
|
+
YourNick: Hola from process with pid 12342
|
146
62
|
|
147
|
-
|
148
|
-
and you're client - letting you do things such as translating
|
149
|
-
all messages on the fly. It's build to be extensible and is
|
150
|
-
relatively simple to use. On any Marvin::Base subclass (baring
|
151
|
-
the MiddleMan itself), using a middle man is easy - you simply
|
152
|
-
call the register! class method with an option argument. e.g:
|
63
|
+
h2. Distributed Bots
|
153
64
|
|
154
|
-
|
65
|
+
One of the relatively unique features of Marvin is the ability to write bots
|
66
|
+
which use DRb and Rinda which can grow with relative ease.
|
155
67
|
|
156
|
-
|
68
|
+
It's important to keep in mind that keeping state is discouraged in this case
|
69
|
+
as it can not be ensured that clients are still active or that you will always
|
70
|
+
get messages from the same client.
|
157
71
|
|
158
|
-
|
159
|
-
|
160
|
-
|
72
|
+
For a start, take a look at the default +config/setup.rb+ file which contains
|
73
|
+
an example of registering handlers on a distributed client as well as setting
|
74
|
+
up the distributed handler which needs to be setup on the main client.
|
161
75
|
|
162
|
-
|
163
|
-
|
164
|
-
make sure the data you store is JSON-serializable.
|
76
|
+
By default, the messages will be dispatched to the first discovered tuple
|
77
|
+
space (using Rinda::RingFinger) and will be of the format:
|
165
78
|
|
166
|
-
|
167
|
-
|
79
|
+
[:marvin_format, :your_namespace, :message_name, {:message => "options"}, client_reference]
|
80
|
+
|
81
|
+
You can change the namespace (which defaults to +:default+) by setting +Marvin::Settings.distributed_namespace+
|
168
82
|
|
169
|
-
|
170
|
-
style accessor for free - just use the "uses_datastore" method. e.g:
|
83
|
+
Running a distributed client requires three things:
|
171
84
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
Then, self.something will point to the data store - letting you do
|
177
|
-
things like:
|
85
|
+
* 1 Ring server instance (+script/ring_server+ or +marvin ring_server+ or +marvin rs+)
|
86
|
+
* 1+ Client instances (+script/client+ or +marvin client+ or +marvin cl+)
|
87
|
+
* 1+ Distributed Client instances (+script/distributed_client+ or +marvin distributed_client+ or +marvin dc+)
|
178
88
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
89
|
+
Each of which takes the default options of:
|
90
|
+
* -v - Be verbose and print to STDOUT if not daemonized
|
91
|
+
* -level=something - set level, defaults to info
|
92
|
+
* -d - daemonize the process
|
93
|
+
* -k - kill all daemonized instances of this specific kind
|
94
|
+
|
95
|
+
h2. Example Bots
|
96
|
+
|
97
|
+
Coming soon.
|
98
|
+
|
99
|
+
h2. Thanks
|
100
|
+
|
101
|
+
Thanks go to:
|
102
|
+
|
103
|
+
* Jeff Rafter - contributed code and doc changes, now one of the co-developers.
|
104
|
+
* epitron / halogrium - For the ragel state machine used in Marvin::Parsers::RagelParser
|
105
|
+
* The creator of Ruby-IRCD - the server component is heavily influenced by / part derivative of said work.
|
data/VERSION.yml
CHANGED
data/bin/marvin
CHANGED
@@ -75,7 +75,8 @@ class Marvin < Thor
|
|
75
75
|
end
|
76
76
|
|
77
77
|
desc "distributed_client [PATH]", "starts a distributed client from the given location"
|
78
|
-
method_options :verbose => :boolean, :daemon => :boolean, :level => :optional,
|
78
|
+
method_options :verbose => :boolean, :daemon => :boolean, :level => :optional,
|
79
|
+
:kill => :boolean, :nodes => :numeric
|
79
80
|
def distributed_client(path = ".")
|
80
81
|
@dest = File.expand_path(path)
|
81
82
|
start_script(:distributed_client)
|
@@ -124,7 +125,21 @@ class Marvin < Thor
|
|
124
125
|
extra_args << "-v" if options[:verbose]
|
125
126
|
extra_args << "-d" if options[:daemon]
|
126
127
|
extra_args << "--level=#{options[:level]}" if options[:level]
|
127
|
-
|
128
|
+
if options[:daemon] && options[:nodes]
|
129
|
+
nodes = options[:nodes]
|
130
|
+
else
|
131
|
+
nodes = 1
|
132
|
+
end
|
133
|
+
Dir.chdir(@dest) do
|
134
|
+
# Lets you start a number of different processes.
|
135
|
+
# uses system if there are more than 1 nodes, exec
|
136
|
+
# otherwise.
|
137
|
+
if nodes > 1
|
138
|
+
nodes.times { system("script/#{name}", *extra_args) }
|
139
|
+
else
|
140
|
+
exec("script/#{name}", *extra_args)
|
141
|
+
end
|
142
|
+
end
|
128
143
|
else
|
129
144
|
STDOUT.puts "Woop! #{@dest.gsub(" ", "\\ ")} isn't a marvin app."
|
130
145
|
end
|
data/config/setup.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
# any connections are created
|
5
5
|
Marvin::Loader.before_run do
|
6
6
|
|
7
|
+
# Want a non-default namespace? Choose something simple
|
8
|
+
# Marvin::Settings.distributed_namespace = :some_namespace
|
9
|
+
|
7
10
|
# E.G.
|
8
11
|
# MyHandler.register! (Marvin::Base subclass) or
|
9
12
|
# Marvin::Settings.default_client.register_handler my_handler (a handler instance)
|
data/lib/marvin/base.rb
CHANGED
@@ -76,6 +76,9 @@ module Marvin
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def uses_datastore(datastore_name, local_name)
|
79
|
+
if Marvin::Loader.type == :distributed_client
|
80
|
+
Marvin::Logger.warn "Using datastores inside of a distributed client is a bad idea, mmmkay?"
|
81
|
+
end
|
79
82
|
cattr_accessor local_name.to_sym
|
80
83
|
self.send("#{local_name}=", Marvin::DataStore.new(datastore_name))
|
81
84
|
rescue Exception => e
|
@@ -46,7 +46,7 @@ module Marvin
|
|
46
46
|
# TODO: Improve it to dump messages to disk at a predefined limit.
|
47
47
|
def dispatch(name, options)
|
48
48
|
options[:dispatched_at] ||= Time.now
|
49
|
-
tuple = [:marvin_event, name, options, self.client]
|
49
|
+
tuple = [:marvin_event, Marvin::Settings.distributed_namespace, name, options, self.client]
|
50
50
|
begin
|
51
51
|
(@queued_messages ||= []) << tuple
|
52
52
|
if self.ring_server.nil?
|
@@ -62,8 +62,8 @@ module Marvin
|
|
62
62
|
while !self.stopped
|
63
63
|
begin
|
64
64
|
unless self.ring_server.blank?
|
65
|
-
event = self.ring_server.take([:marvin_event, nil, nil, nil],
|
66
|
-
dispatch(*event[
|
65
|
+
event = self.ring_server.take([:marvin_event, Marvin::Settings.distributed_namespace, nil, nil, nil], 2)
|
66
|
+
dispatch(*event[2..-1]) unless event.blank?
|
67
67
|
end
|
68
68
|
rescue
|
69
69
|
# Reset the ring server on event of connection refused etc.
|
@@ -12,10 +12,10 @@ module Marvin
|
|
12
12
|
def initialize
|
13
13
|
self.tuple_space = Rinda::TupleSpace.new
|
14
14
|
if Marvin::Settings.log_level == :debug
|
15
|
-
observer = self.tuple_space.notify('write', [:marvin_event, nil, nil, nil])
|
15
|
+
observer = self.tuple_space.notify('write', [:marvin_event, Marvin::Settings.distributed_namespace, nil, nil, nil])
|
16
16
|
Thread.start do
|
17
17
|
observer.each do |i|
|
18
|
-
event_name, args = i[1][
|
18
|
+
event_name, args = i[1][2..3]
|
19
19
|
Marvin::Logger.logger.debug "Marvin event added - #{event_name.inspect} w/ #{args.inspect}"
|
20
20
|
end
|
21
21
|
end
|
data/lib/marvin/settings.rb
CHANGED
@@ -4,7 +4,7 @@ require 'eventmachine'
|
|
4
4
|
module Marvin
|
5
5
|
class Settings
|
6
6
|
|
7
|
-
cattr_accessor :environment, :configuration, :is_setup, :default_client,
|
7
|
+
cattr_accessor :environment, :configuration, :is_setup, :default_client, :distributed_namespace,
|
8
8
|
:handler_folder, :default_parser, :log_level, :verbose, :daemon
|
9
9
|
|
10
10
|
self.verbose = false
|
@@ -26,6 +26,10 @@ module Marvin
|
|
26
26
|
self.setup!(options)
|
27
27
|
end
|
28
28
|
|
29
|
+
def distributed_namespace
|
30
|
+
@@distributed_namespace ||= :default
|
31
|
+
end
|
32
|
+
|
29
33
|
def setup!(options = {})
|
30
34
|
self.environment ||= "development"
|
31
35
|
self.configuration = {}
|
data/lib/marvin/status.rb
CHANGED
@@ -39,18 +39,18 @@ module Marvin
|
|
39
39
|
begin
|
40
40
|
rs = Rinda::RingFinger.finger.lookup_ring(3)
|
41
41
|
STDOUT.puts " Ring Server: #{rs.__drburi}"
|
42
|
-
items = rs.read_all([:marvin_event, nil, nil, nil])
|
42
|
+
items = rs.read_all([:marvin_event, Marvin::Settings.distributed_namespace, nil, nil, nil])
|
43
43
|
STDOUT.puts " Unprocessed Items: #{items.size}"
|
44
44
|
STDOUT.puts ""
|
45
45
|
unless items.empty?
|
46
|
-
start_time = items.first[
|
46
|
+
start_time = items.first[3][:dispatched_at]
|
47
47
|
STDOUT.puts " Earliest Item: #{start_time ? start_time.strftime("%I:%M%p %d/%m/%y") : "Never"}"
|
48
|
-
end_time = items.last[
|
48
|
+
end_time = items.last[3][:dispatched_at]
|
49
49
|
STDOUT.puts " Latest Item: #{end_time ? end_time.strftime("%I:%M%p %d/%m/%y") : "Never"}"
|
50
50
|
mapping = {}
|
51
51
|
items.each do |i|
|
52
|
-
mapping[i[
|
53
|
-
mapping[i[
|
52
|
+
mapping[i[2].inspect] ||= 0
|
53
|
+
mapping[i[2].inspect] += 1
|
54
54
|
end
|
55
55
|
width = mapping.keys.map { |k| k.length }.max
|
56
56
|
STDOUT.puts ""
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: Sutto-marvin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-12-
|
12
|
+
date: 2008-12-07 00:00:00 -08:00
|
13
13
|
default_executable: marvin
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|