Sutto-marvin 0.2.1 → 0.2.2
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/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
|