can_has_bots 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/LICENSE +25 -0
- data/README +44 -0
- data/Rakefile +75 -0
- data/TODO +0 -0
- data/adapters/data_mapper/bot.rb +48 -0
- data/adapters/jabber/bot.rb +59 -0
- data/adapters/starling/bot.rb +96 -0
- data/examples/tweecrets/init.rb +10 -0
- data/examples/tweecrets/message_parser_bot.rb +14 -0
- data/examples/tweecrets/models/tweecret.rb +4 -0
- data/examples/tweecrets/models/user.rb +4 -0
- data/examples/tweecrets/run.rb +30 -0
- data/examples/tweecrets/store_tweecret_bot.rb +15 -0
- data/examples/tweecrets/tweecrets.db +0 -0
- data/examples/tweecrets/tweet_receiver_bot.rb +14 -0
- data/lib/can_has_bots.rb +15 -0
- data/lib/can_has_bots/adapter.rb +13 -0
- data/lib/can_has_bots/base.rb +133 -0
- data/lib/can_has_bots/core_ext/class.rb +192 -0
- data/lib/can_has_bots/core_ext/module.rb +35 -0
- data/lib/can_has_bots/core_ext/object.rb +28 -0
- data/lib/can_has_bots/core_ext/thread_group.rb +5 -0
- data/lib/can_has_bots/version.rb +3 -0
- metadata +83 -0
data/HISTORY
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2008 Ben Burkert <ben@benburkert.com>
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of this project nor the names of its contributors may be
|
13
|
+
used to endorse or promote products derived from this software without
|
14
|
+
specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
20
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
21
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
22
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
25
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= can_has_bots
|
2
|
+
|
3
|
+
"The box is locked, the lights are on, it's robot fightin' time!"
|
4
|
+
|
5
|
+
Author:: Ben Burkert (mailto:ben@benburkert.com)
|
6
|
+
Version:: 0.1.0
|
7
|
+
Copyright:: Copyright (c) 2008 Ben Burkert. All rights reserved.
|
8
|
+
License:: New BSD License (http://opensource.org/licenses/bsd-license.php)
|
9
|
+
Website:: http://canhasgems.rubyforge.org/can_has_bots
|
10
|
+
Repository:: git://github.com/benburkert/can_has_bots.git
|
11
|
+
|
12
|
+
== Dependencies
|
13
|
+
|
14
|
+
== Basic Usage
|
15
|
+
|
16
|
+
can_has_bots started off as helpers for writing long running processes, and quickly turned into a DSL
|
17
|
+
for creating said bots. The author never got around to finishing the Erlang book, so the design is
|
18
|
+
based on what little he understood of the Actor pattern. Here is an example bot:
|
19
|
+
|
20
|
+
class TweetReceiverBot < CanHasBots::Base
|
21
|
+
acts_as_bot
|
22
|
+
|
23
|
+
input_type Jabber
|
24
|
+
output_type Starling
|
25
|
+
|
26
|
+
register_input_message
|
27
|
+
register_output_queue(:tweets)
|
28
|
+
|
29
|
+
worker do |message|
|
30
|
+
info "Message received: #{message.from}: #{message.body}"
|
31
|
+
output(message.body) if message.from == "twitter@twitter.com"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
This bot, like all others, is comprised of three parts: inputs, outputs, and the worker. Input's are
|
36
|
+
the interface into the bot; message arrives in an input, and is sent to the worker for processing. In
|
37
|
+
this example, the input listens for xmpp messages. Naturally, the output receivers are the interfaces
|
38
|
+
out of the bot. A Starling queue, in this case
|
39
|
+
|
40
|
+
The worker is a method that takes all input, does something, then sends it to the output. All inputs
|
41
|
+
send their packages to the worker, and all outputs receive the same package from the worker. There can
|
42
|
+
be only one input type, but many output types (for now, at least).
|
43
|
+
|
44
|
+
Browse the examples folder for more examples of bots.
|
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/clean"
|
5
|
+
require "rake/gempackagetask"
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require "spec"
|
8
|
+
require "spec/rake/spectask"
|
9
|
+
|
10
|
+
DIR = File.dirname(__FILE__)
|
11
|
+
NAME = 'can_has_bots'
|
12
|
+
SUMMARY =<<-EOS
|
13
|
+
DSL for creating bots.
|
14
|
+
EOS
|
15
|
+
|
16
|
+
require "lib/#{NAME}/version"
|
17
|
+
|
18
|
+
spec = Gem::Specification.new do |s|
|
19
|
+
s.name = NAME
|
20
|
+
s.summary = SUMMARY
|
21
|
+
|
22
|
+
s.version = CanHasBots::VERSION
|
23
|
+
s.platform = Gem::Platform::RUBY
|
24
|
+
|
25
|
+
s.require_path = "lib"
|
26
|
+
s.files = %w(Rakefile LICENSE HISTORY README TODO) + Dir["{lib,adapters,examples}/**/*"]
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::GemPackageTask.new(spec) do |package|
|
30
|
+
package.gem_spec = spec
|
31
|
+
package.need_zip = true
|
32
|
+
package.need_tar = true
|
33
|
+
end
|
34
|
+
|
35
|
+
##############################################################################
|
36
|
+
# Documentation
|
37
|
+
##############################################################################
|
38
|
+
task :doc => "doc:rerdoc"
|
39
|
+
namespace :doc do
|
40
|
+
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
files = %w(Rakefile README) + Dir["{lib,adapters,examples}/**/*"]
|
43
|
+
rdoc.rdoc_files.add(files)
|
44
|
+
rdoc.main = 'README'
|
45
|
+
rdoc.title = 'I CAN HAS BOTS? HUZZAH!'
|
46
|
+
rdoc.template = 'tools/allison-2.0.3/lib/allison'
|
47
|
+
rdoc.rdoc_dir = "doc/#{NAME}/rdoc"
|
48
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "rdoc to rubyforge"
|
52
|
+
task :rubyforge => :doc do
|
53
|
+
sh %{chmod -R 755 doc}
|
54
|
+
sh %{/usr/bin/scp -r -p doc/#{NAME}/rdoc/* benburkert@rubyforge.org:/var/www/gforge-projects/canhasgems/#{NAME}}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##############################################################################
|
59
|
+
# rSpec & rcov
|
60
|
+
##############################################################################
|
61
|
+
desc "Run all specs"
|
62
|
+
Spec::Rake::SpecTask.new("specs") do |t|
|
63
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
64
|
+
t.spec_files = Dir["spec/**/*_spec.rb"].sort
|
65
|
+
end
|
66
|
+
|
67
|
+
##############################################################################
|
68
|
+
# release
|
69
|
+
##############################################################################
|
70
|
+
task :release => [:specs, :package] do
|
71
|
+
sh %{rubyforge add_release canhasgems #{NAME} "#{CanHasBots::VERSION}" pkg/#{NAME}-#{CanHasBots::VERSION}.gem}
|
72
|
+
%w[zip tgz].each do |ext|
|
73
|
+
sh %{rubyforge add_file canhasgems #{NAME} #{CanHasBots::VERSION} pkg/#{NAME}-#{CanHasBots::VERSION}.#{ext}}
|
74
|
+
end
|
75
|
+
end
|
data/TODO
ADDED
File without changes
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'data_mapper'
|
3
|
+
|
4
|
+
module DataMapper::Bot
|
5
|
+
module Common
|
6
|
+
include CanHasBots::Adapter
|
7
|
+
attr_accessor :config_file, :db_config, :environment, :models
|
8
|
+
|
9
|
+
def connect_database
|
10
|
+
info "Attempting to connect to #{@db_config[:adapter]}://#{@db_config[:host] || :localhost}/#{@db_config[:database]}"
|
11
|
+
DataMapper::Database.setup(@db_config)
|
12
|
+
info "Connection to #{@db_config[:adapter]}://#{@db_config[:host] || :localhost}/#{@db_config[:database]} established successfully"
|
13
|
+
end
|
14
|
+
|
15
|
+
mixins do
|
16
|
+
initializer do |config|
|
17
|
+
@adapter ||= @config.delete(:adapter)
|
18
|
+
@database ||= @config.delete(:database)
|
19
|
+
@username ||= @config.delete(:username)
|
20
|
+
|
21
|
+
raise "You must supply an adapter" unless @adapter
|
22
|
+
raise "You must supply a database name" unless @database
|
23
|
+
raise "You must supply a username" unless @username
|
24
|
+
|
25
|
+
@db_config = {
|
26
|
+
:adapter => @adapter,
|
27
|
+
:database => @database,
|
28
|
+
:username => @username
|
29
|
+
}
|
30
|
+
|
31
|
+
connect_database
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Output
|
37
|
+
include Common
|
38
|
+
|
39
|
+
macros do
|
40
|
+
def register_output_query(&block)
|
41
|
+
raise "register_output_query expects a block" if block.nil?
|
42
|
+
register_output_receiver do |*args|
|
43
|
+
block.call(*args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'xmpp4r'
|
3
|
+
|
4
|
+
module Jabber::Bot
|
5
|
+
module Input
|
6
|
+
include Jabber
|
7
|
+
include CanHasBots::Adapter
|
8
|
+
|
9
|
+
attr_accessor :node, :domain, :resource, :passwd, :presence
|
10
|
+
|
11
|
+
initializer do
|
12
|
+
@@resource_count ||= 0
|
13
|
+
@node ||= @config.delete(:node)
|
14
|
+
@domain ||= @config.delete(:domain)
|
15
|
+
@resource ||= @config.delete(:resource) || generate_resource
|
16
|
+
@passwd ||= @config.delete(:passwd)
|
17
|
+
@presence ||= @config.delete(:presence) || :available
|
18
|
+
|
19
|
+
raise "You must supply a node" unless @node
|
20
|
+
raise "You must supply a domain" unless @domain
|
21
|
+
raise "You must supply a passwd" unless @passwd
|
22
|
+
|
23
|
+
info "Attempting to connect Jabber ID: #{@node}@#{@domain}/#{resource}"
|
24
|
+
@jid = JID::new(@node, @domain, @resource)
|
25
|
+
@client = Client.new(@jid)
|
26
|
+
@client.connect
|
27
|
+
|
28
|
+
info "Jabber connection made, authenticating"
|
29
|
+
@client.auth(@passwd)
|
30
|
+
@client.send(Presence.new.set_type(@presence))
|
31
|
+
|
32
|
+
info "#{@node}@#{@domain}/#{resource} is now #{@presence}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_resource
|
36
|
+
"#{`hostname`.chomp}-#{Process.pid}-#{@@resource_count += 1}"
|
37
|
+
end
|
38
|
+
|
39
|
+
mixins do
|
40
|
+
runner do
|
41
|
+
Thread.new(self) do |instance|
|
42
|
+
instance.input_receivers.each do |proc|
|
43
|
+
instance.instance_eval(&proc)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
macros do
|
50
|
+
def register_input_message(&block)
|
51
|
+
register_input_receiver do
|
52
|
+
@client.add_message_callback do |message|
|
53
|
+
input(message)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'starling'
|
3
|
+
|
4
|
+
module Starling::Bot
|
5
|
+
module Common
|
6
|
+
include CanHasBots::Adapter
|
7
|
+
|
8
|
+
attr_accessor :starling_host, :starling_port, :starling
|
9
|
+
|
10
|
+
mixins do
|
11
|
+
initializer do
|
12
|
+
@starling_host ||= @config.delete(:starling_host) || 'localhost'
|
13
|
+
@starling_port ||= @config.delete(:starling_port) || 22122
|
14
|
+
|
15
|
+
unless @starling
|
16
|
+
info("Connecting to Starling server at '#{starling_server}'")
|
17
|
+
@starling = Starling.new(starling_server)
|
18
|
+
test_starling_connection
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def starling_server
|
23
|
+
"#{@starling_host}:#{@starling_port}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_starling_connection
|
27
|
+
info("Testing connection to Starling server")
|
28
|
+
begin
|
29
|
+
@starling.set("test_queue", :connection_test)
|
30
|
+
@starling.get("test_queue")
|
31
|
+
rescue Exception => e
|
32
|
+
raise "Error connecting to the Starling server at '#{starling_server}': #{e}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Input
|
39
|
+
include Common
|
40
|
+
|
41
|
+
mixins do
|
42
|
+
runner do
|
43
|
+
threads = []
|
44
|
+
input_receivers.each do |proc|
|
45
|
+
threads << Thread.new(self, proc) do |instance, proc|
|
46
|
+
instance.instance_eval(&proc)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
threads
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
macros do
|
55
|
+
def register_input_queue(name, &block)
|
56
|
+
register_input_receiver do
|
57
|
+
loop do
|
58
|
+
begin
|
59
|
+
if block.nil?
|
60
|
+
input(@starling.get(name.to_s))
|
61
|
+
else
|
62
|
+
input(block.call(@starling.get(name.to_s)))
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
error "Error while reading from Starling #{e}: \"#{e.message}\""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module Output
|
74
|
+
include Common
|
75
|
+
|
76
|
+
def send_to_queue(name, *args)
|
77
|
+
begin
|
78
|
+
@starling.set(name.to_s, *args)
|
79
|
+
rescue Exception => e
|
80
|
+
error "Error while writing to Starling #{e}: \"#{e.message}\""
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
macros do
|
85
|
+
def register_output_queue(name, &block)
|
86
|
+
register_output_receiver do |*args|
|
87
|
+
if block.nil?
|
88
|
+
send_to_queue(name, *args)
|
89
|
+
else
|
90
|
+
send_to_queue(name, block.call(*args))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class MessageParserBot < CanHasBots::Base
|
2
|
+
acts_as_bot
|
3
|
+
|
4
|
+
input_type Starling
|
5
|
+
output_type Starling
|
6
|
+
|
7
|
+
register_input_queue(:tweets)
|
8
|
+
register_output_queue(:tweecrets)
|
9
|
+
|
10
|
+
worker do |message|
|
11
|
+
info "Message received: #{message}"
|
12
|
+
output(:username => $1, :message => $2) if message =~ /^Direct from (\w+):\n(.*)\n\nReply\swith\s'd\s(\w+)\shi\.'$/m
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../../lib/can_has_bots"
|
2
|
+
|
3
|
+
require 'data_mapper/bot'
|
4
|
+
require 'jabber/bot'
|
5
|
+
require 'starling/bot'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
8
|
+
|
9
|
+
require "message_parser_bot"
|
10
|
+
require "store_tweecret_bot"
|
11
|
+
require "tweet_receiver_bot"
|
12
|
+
|
13
|
+
autoload :User, "models/user"
|
14
|
+
autoload :Tweecret, "models/tweecret"
|
15
|
+
|
16
|
+
MessageParserBot.new.run
|
17
|
+
|
18
|
+
StoreTweecretBot.new do
|
19
|
+
@adapter = :sqlite3
|
20
|
+
@database = "tweecrets.db"
|
21
|
+
@username = "ben"
|
22
|
+
end.run
|
23
|
+
|
24
|
+
TweetReceiverBot.new do
|
25
|
+
@node = "tweecrets"
|
26
|
+
@domain = "gmail.com"
|
27
|
+
@passwd = "matsumoto"
|
28
|
+
end.run
|
29
|
+
|
30
|
+
Thread.stop
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class StoreTweecretBot < CanHasBots::Base
|
2
|
+
acts_as_bot
|
3
|
+
|
4
|
+
input_type Starling
|
5
|
+
output_type DataMapper
|
6
|
+
|
7
|
+
register_input_queue(:tweecrets)
|
8
|
+
|
9
|
+
worker do |username_and_message|
|
10
|
+
info "Finding or creating #{username_and_message[:username]}"
|
11
|
+
user = User.find_or_create(:username => username_and_message[:username])
|
12
|
+
info "Creating #{username_and_message[:message]}"
|
13
|
+
Tweecret.create(:user => user, :message => username_and_message[:message])
|
14
|
+
end
|
15
|
+
end
|
Binary file
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class TweetReceiverBot < CanHasBots::Base
|
2
|
+
acts_as_bot
|
3
|
+
|
4
|
+
input_type Jabber
|
5
|
+
output_type Starling
|
6
|
+
|
7
|
+
register_input_message
|
8
|
+
register_output_queue(:tweets)
|
9
|
+
|
10
|
+
worker do |message|
|
11
|
+
info "Message received: #{message.from}: #{message.body}"
|
12
|
+
output(message.body) if message.from == "twitter@twitter.com"
|
13
|
+
end
|
14
|
+
end
|
data/lib/can_has_bots.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../adapters"
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
6
|
+
|
7
|
+
require "can_has_bots/core_ext/class"
|
8
|
+
require "can_has_bots/core_ext/module"
|
9
|
+
require "can_has_bots/core_ext/object"
|
10
|
+
require "can_has_bots/core_ext/thread_group"
|
11
|
+
|
12
|
+
module CanHasBots; end
|
13
|
+
|
14
|
+
require "can_has_bots/adapter"
|
15
|
+
require "can_has_bots/base"
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module CanHasBots
|
2
|
+
class Base
|
3
|
+
|
4
|
+
klass.class_eval do
|
5
|
+
def acts_as_bot
|
6
|
+
self.class_eval do
|
7
|
+
cattr_accessor :initializers, :runners, :input_receivers, :output_receivers, :worker_proc
|
8
|
+
self.initializers = []
|
9
|
+
self.runners = []
|
10
|
+
self.input_receivers = []
|
11
|
+
self.output_receivers = []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.register_initializer(&block)
|
17
|
+
initializers << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register_runner(&block)
|
21
|
+
runners << block
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.input_type(mod)
|
25
|
+
include mod.const_get(:Bot).const_get(:Input)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.output_type(*mods)
|
29
|
+
mods.each {|mod| include mod.const_get(:Bot).const_get(:Output)}
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
alias_method :output_types, :output_type
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.register_input_receiver(&block)
|
37
|
+
input_receivers << block
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.register_output_receiver(&block)
|
41
|
+
output_receivers << block
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.worker(&block)
|
45
|
+
self.worker_proc = block
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_accessor :input_procs, :output_procs
|
49
|
+
attr_accessor :log_file, :log_level
|
50
|
+
attr_accessor :logger
|
51
|
+
|
52
|
+
def initialize(config = {}, &block)
|
53
|
+
@input_procs, @output_procs = [], []
|
54
|
+
|
55
|
+
@log_file ||= config[:log_file] || $stdout
|
56
|
+
@environment ||= config[:environment] || :development
|
57
|
+
@log_level ||= config[:log_level] || @environment
|
58
|
+
|
59
|
+
instance_eval(&block) unless block.nil?
|
60
|
+
|
61
|
+
@logger = Logger.new(@log_file)
|
62
|
+
@logger.level = case @log_level
|
63
|
+
when :development then Logger::DEBUG
|
64
|
+
when :test then Logger::WARN
|
65
|
+
when :production then Logger::ERROR
|
66
|
+
end
|
67
|
+
|
68
|
+
@config = config
|
69
|
+
initializers.each {|initializer| instance_eval(config, &initializer) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def unknown(*args)
|
73
|
+
args.each {|m| @logger.unknown(klass.name) {m} }
|
74
|
+
end
|
75
|
+
|
76
|
+
def fatal(*args)
|
77
|
+
args.each {|m| @logger.fatal(klass.name) {m} }
|
78
|
+
end
|
79
|
+
|
80
|
+
def error(*args)
|
81
|
+
args.each {|m| @logger.error(klass.name) {m} }
|
82
|
+
end
|
83
|
+
|
84
|
+
def warn(*args)
|
85
|
+
args.each {|m| @logger.warn(klass.name) {m} }
|
86
|
+
end
|
87
|
+
|
88
|
+
def info(*args)
|
89
|
+
args.each {|m| @logger.info(klass.name) {m} }
|
90
|
+
end
|
91
|
+
|
92
|
+
def debug(*args)
|
93
|
+
args.each {|m| @logger.debug(klass.name) {m} }
|
94
|
+
end
|
95
|
+
|
96
|
+
def run
|
97
|
+
threads = ThreadGroup.new
|
98
|
+
klass.runners.each do |proc|
|
99
|
+
[instance_eval(&proc)].flatten.each do |thread|
|
100
|
+
threads.add thread
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
threads
|
105
|
+
end
|
106
|
+
|
107
|
+
def input(*args)
|
108
|
+
begin
|
109
|
+
instance_eval(*args, &worker_proc)
|
110
|
+
rescue Exception => e
|
111
|
+
fatal "exception #{e.class} #{e}, #{e.message}: #{e.backtrace}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def output(*args)
|
116
|
+
begin
|
117
|
+
klass.output_receivers.each do |output_receiver|
|
118
|
+
instance_eval(*args, &output_receiver)
|
119
|
+
end
|
120
|
+
rescue Exception => e
|
121
|
+
fatal "exception #{e.class} #{e}, #{e.message}: #{e.backtrace}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def raise_with_logging(*args)
|
126
|
+
fatal(*args)
|
127
|
+
raise_without_logging(*args)
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_method :raise_without_logging, :raise
|
131
|
+
alias_method :raise, :raise_with_logging
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where
|
2
|
+
# each descendant gets a copy of their parents' attributes, instead of just a
|
3
|
+
# pointer to the same. This means that the child can add elements to, for
|
4
|
+
# example, an array without those additions being shared with either their
|
5
|
+
# parent, siblings, or children, which is unlike the regular class-level
|
6
|
+
# attributes that are shared across the entire hierarchy.
|
7
|
+
class Class # :nodoc:
|
8
|
+
|
9
|
+
def cattr_reader(*syms)
|
10
|
+
syms.flatten.each do |sym|
|
11
|
+
next if sym.is_a?(Hash)
|
12
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
13
|
+
unless defined? @@#{sym}
|
14
|
+
@@#{sym} = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.#{sym}
|
18
|
+
@@#{sym}
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{sym}
|
22
|
+
@@#{sym}
|
23
|
+
end
|
24
|
+
EOS
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cattr_writer(*syms)
|
29
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
30
|
+
syms.flatten.each do |sym|
|
31
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
32
|
+
unless defined? @@#{sym}
|
33
|
+
@@#{sym} = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.#{sym}=(obj)
|
37
|
+
@@#{sym} = obj
|
38
|
+
end
|
39
|
+
|
40
|
+
#{"
|
41
|
+
|
42
|
+
def #{sym}=(obj)
|
43
|
+
@@#{sym} = obj
|
44
|
+
end
|
45
|
+
" unless options[:instance_writer] == false }
|
46
|
+
EOS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def cattr_accessor(*syms)
|
51
|
+
cattr_reader(*syms)
|
52
|
+
cattr_writer(*syms)
|
53
|
+
end
|
54
|
+
|
55
|
+
def class_inheritable_reader(*syms)
|
56
|
+
syms.each do |sym|
|
57
|
+
next if sym.is_a?(Hash)
|
58
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
59
|
+
|
60
|
+
def self.#{sym}
|
61
|
+
read_inheritable_attribute(:#{sym})
|
62
|
+
end
|
63
|
+
|
64
|
+
def #{sym}
|
65
|
+
self.class.#{sym}
|
66
|
+
end
|
67
|
+
EOS
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def class_inheritable_writer(*syms)
|
72
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
73
|
+
syms.each do |sym|
|
74
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
75
|
+
|
76
|
+
def self.#{sym}=(obj)
|
77
|
+
write_inheritable_attribute(:#{sym}, obj)
|
78
|
+
end
|
79
|
+
|
80
|
+
#{"
|
81
|
+
|
82
|
+
def #{sym}=(obj)
|
83
|
+
self.class.#{sym} = obj
|
84
|
+
end
|
85
|
+
" unless options[:instance_writer] == false }
|
86
|
+
EOS
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def class_inheritable_array_writer(*syms)
|
91
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
92
|
+
syms.each do |sym|
|
93
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
94
|
+
|
95
|
+
def self.#{sym}=(obj)
|
96
|
+
write_inheritable_array(:#{sym}, obj)
|
97
|
+
end
|
98
|
+
|
99
|
+
#{"
|
100
|
+
|
101
|
+
def #{sym}=(obj)
|
102
|
+
self.class.#{sym} = obj
|
103
|
+
end
|
104
|
+
" unless options[:instance_writer] == false }
|
105
|
+
EOS
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def class_inheritable_hash_writer(*syms)
|
110
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
111
|
+
syms.each do |sym|
|
112
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
113
|
+
|
114
|
+
def self.#{sym}=(obj)
|
115
|
+
write_inheritable_hash(:#{sym}, obj)
|
116
|
+
end
|
117
|
+
|
118
|
+
#{"
|
119
|
+
|
120
|
+
def #{sym}=(obj)
|
121
|
+
self.class.#{sym} = obj
|
122
|
+
end
|
123
|
+
" unless options[:instance_writer] == false }
|
124
|
+
EOS
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def class_inheritable_accessor(*syms)
|
129
|
+
class_inheritable_reader(*syms)
|
130
|
+
class_inheritable_writer(*syms)
|
131
|
+
end
|
132
|
+
|
133
|
+
def class_inheritable_array(*syms)
|
134
|
+
class_inheritable_reader(*syms)
|
135
|
+
class_inheritable_array_writer(*syms)
|
136
|
+
end
|
137
|
+
|
138
|
+
def class_inheritable_hash(*syms)
|
139
|
+
class_inheritable_reader(*syms)
|
140
|
+
class_inheritable_hash_writer(*syms)
|
141
|
+
end
|
142
|
+
|
143
|
+
def inheritable_attributes
|
144
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
145
|
+
end
|
146
|
+
|
147
|
+
def write_inheritable_attribute(key, value)
|
148
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
149
|
+
@inheritable_attributes = {}
|
150
|
+
end
|
151
|
+
inheritable_attributes[key] = value
|
152
|
+
end
|
153
|
+
|
154
|
+
def write_inheritable_array(key, elements)
|
155
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
156
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
157
|
+
end
|
158
|
+
|
159
|
+
def write_inheritable_hash(key, hash)
|
160
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
161
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
162
|
+
end
|
163
|
+
|
164
|
+
def read_inheritable_attribute(key)
|
165
|
+
inheritable_attributes[key]
|
166
|
+
end
|
167
|
+
|
168
|
+
def reset_inheritable_attributes
|
169
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
# Prevent this constant from being created multiple times
|
174
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
175
|
+
|
176
|
+
def inherited_with_inheritable_attributes(child)
|
177
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
178
|
+
|
179
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
180
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
181
|
+
else
|
182
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
183
|
+
memo.update(key => (value.dup rescue value))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
188
|
+
end
|
189
|
+
|
190
|
+
alias inherited_without_inheritable_attributes inherited
|
191
|
+
alias inherited inherited_with_inheritable_attributes
|
192
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Module
|
2
|
+
attr_accessor :macro_blocks, :mixin_blocks, :included_callbacks
|
3
|
+
|
4
|
+
def macros(&block)
|
5
|
+
@macro_blocks ||= [] << block
|
6
|
+
end
|
7
|
+
|
8
|
+
def mixins(&block)
|
9
|
+
@mixin_blocks ||= [] << block
|
10
|
+
end
|
11
|
+
|
12
|
+
def included_with_meta(base, &block)
|
13
|
+
(@macro_blocks ||= []).each do |block|
|
14
|
+
base.class.class_eval(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
(@mixin_blocks ||= []).each do |block|
|
18
|
+
base.class_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
case base.class
|
22
|
+
when Class then (@included_callbacks ||= []).each {|ic| base.instance_eval(&ic)}
|
23
|
+
when Module then (@included_callbacks ||= []).each {|ic| base.included_callbacks << ic}
|
24
|
+
end
|
25
|
+
|
26
|
+
included_without_meta(base, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :included_without_meta, :included
|
30
|
+
alias_method :included, :included_with_meta
|
31
|
+
|
32
|
+
def included_callback(&block)
|
33
|
+
@included_callbacks ||= [] << block
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Object
|
2
|
+
def klass
|
3
|
+
self.class
|
4
|
+
end
|
5
|
+
|
6
|
+
def instance_eval_with_args_check(*args, &block)
|
7
|
+
unless args.empty? || block.nil?
|
8
|
+
instance_eval_with_args(*args, &block)
|
9
|
+
else
|
10
|
+
instance_eval_without_args_check(*args, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :instance_eval_without_args_check, :instance_eval
|
15
|
+
alias_method :instance_eval, :instance_eval_with_args_check
|
16
|
+
|
17
|
+
def instance_eval_with_args(*args, &block)
|
18
|
+
while respond_to?(random_name = "temp_method#{rand(10)}"); end
|
19
|
+
|
20
|
+
klass.class_eval do
|
21
|
+
define_method(random_name, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
self.send(random_name, *args)
|
25
|
+
|
26
|
+
klass.class_eval("undef #{random_name}")
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: can_has_bots
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors: []
|
7
|
+
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-03-22 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- LICENSE
|
27
|
+
- HISTORY
|
28
|
+
- README
|
29
|
+
- TODO
|
30
|
+
- lib/can_has_bots
|
31
|
+
- lib/can_has_bots/adapter.rb
|
32
|
+
- lib/can_has_bots/base.rb
|
33
|
+
- lib/can_has_bots/core_ext
|
34
|
+
- lib/can_has_bots/core_ext/class.rb
|
35
|
+
- lib/can_has_bots/core_ext/module.rb
|
36
|
+
- lib/can_has_bots/core_ext/object.rb
|
37
|
+
- lib/can_has_bots/core_ext/thread_group.rb
|
38
|
+
- lib/can_has_bots/version.rb
|
39
|
+
- lib/can_has_bots.rb
|
40
|
+
- adapters/data_mapper
|
41
|
+
- adapters/data_mapper/bot.rb
|
42
|
+
- adapters/jabber
|
43
|
+
- adapters/jabber/bot.rb
|
44
|
+
- adapters/starling
|
45
|
+
- adapters/starling/bot.rb
|
46
|
+
- examples/tweecrets
|
47
|
+
- examples/tweecrets/init.rb
|
48
|
+
- examples/tweecrets/message_parser_bot.rb
|
49
|
+
- examples/tweecrets/models
|
50
|
+
- examples/tweecrets/models/tweecret.rb
|
51
|
+
- examples/tweecrets/models/user.rb
|
52
|
+
- examples/tweecrets/run.rb
|
53
|
+
- examples/tweecrets/store_tweecret_bot.rb
|
54
|
+
- examples/tweecrets/tweecrets.db
|
55
|
+
- examples/tweecrets/tweet_receiver_bot.rb
|
56
|
+
has_rdoc: false
|
57
|
+
homepage:
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.0.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 2
|
81
|
+
summary: DSL for creating bots.
|
82
|
+
test_files: []
|
83
|
+
|