can_has_bots 0.1.0
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/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
|
+
|