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 CHANGED
@@ -1,183 +1,105 @@
1
1
  h1. Marvin
2
2
 
3
- Marvin is a simple IRC Framework for Rails suitable for building things
4
- such as simple IRC bots. Extracted from real use - we'd originally used
5
- a heavily modified version of MatzBot - it's been built to service a
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
- h2. Background
9
-
10
- The library is designed to be event driven in that it:
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
- The easiest way to get started with Marvin is by installing the Marvin gem. To
22
- do this, make sure Github is added to your gem sources (and you are using
23
- rubygems >= 1.2.0) (by default, substitute username for Sutto):
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
- $ gem sources -a http://gems.github.com
26
- $ sudo gem install username-marvin
27
-
18
+ $ rake gemspec
19
+ $ gem build marvin.gemspec
20
+ $ sudo gem install marvin.gem
28
21
 
29
- Once you have installed the gem, you should have access to the "marvin" command:
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 --help
26
+ $ sudo gem install Sutto-marvin
32
27
 
33
- You can create a new marvin folder:
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 my_marvin_project
32
+ $ marvin create path-to-my-bot
36
33
 
37
- Then simply edit your settings in the +config/settings.yml+
38
-
39
- default:
40
- name: Marvin
41
- use_logging: false
42
- datastore_location: tmp/datastore.json
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
- As defined in handlers/hello_world.rb
41
+ or, alternatively,
81
42
 
82
- h2. Thanks
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
- Thanks goes out to the following people / projects:
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
- * Jeff Rafter - contributed code and doc changes, now one of the co-developers.
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
- i.e., the above handler could be called in IRC as such:
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
- h2. Marvin::MiddleMan - Introducing middleware
61
+ YourNick: Hola from process with pid 12342
146
62
 
147
- Marvin::MiddleMan lets you insert middleware between handlers
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
- HelloWorld.register! Marvin::MiddleMan
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
- h2. Marvin::DataStore - A dead simple persistent hash store
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
- Want to save data between when you stop and start your IRC
159
- Client? With Marvin, it's really, really simple - Marvin::DataStore
160
- offers a simple syntax for persistent data stores.
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
- New datastores can be created with Marvin::DataStore.new("global-store-key").
163
- From there, you have a hash to do whatever the hell you want. Just
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
- When you start - stop a server (via Marvin::Loader.run! and Marvin::Loader.stop!)
167
- you're data will be loaded from and written to disk accordingly.
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
- If you're inside a Marvin::Base subclass it's even easier. You can get a cattr_access
170
- style accessor for free - just use the "uses_datastore" method. e.g:
83
+ Running a distributed client requires three things:
171
84
 
172
- class X < Marvin::Base
173
- uses_datastore "datastore-global-key", :something
174
- end
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
- def hello(data)
180
- (self.something[from] ||= 0) += 1
181
- end
182
-
183
- which will persist the count between each session.
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
@@ -1,4 +1,4 @@
1
1
  ---
2
- patch: 1
2
+ patch: 2
3
3
  major: 0
4
4
  minor: 2
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, :kill => :boolean
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
- Dir.chdir(@dest) { exec("script/#{name}", *extra_args) }
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], 5)
66
- dispatch(*event[1..-1]) unless event.blank?
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][1..2]
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
@@ -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[2][:dispatched_at]
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[2][:dispatched_at]
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[1].inspect] ||= 0
53
- mapping[i[1].inspect] += 1
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.1
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-05 00:00:00 -08:00
12
+ date: 2008-12-07 00:00:00 -08:00
13
13
  default_executable: marvin
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency