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 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