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 ADDED
@@ -0,0 +1,4 @@
1
+ can_has_fixtures Release History
2
+
3
+ Version 0.1.0 (22 March 2008)
4
+ * Initial release.
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.
@@ -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,10 @@
1
+ require "#{File.dirname(__FILE__)}/../../lib/can_has_bots"
2
+
3
+ require 'data_mapper'
4
+
5
+ DataMapper::Database.setup(:adapter => :sqlite3, :database => "tweecrets.db")
6
+
7
+ require "models/user"
8
+ require "models/tweecret"
9
+
10
+ DataMapper::Base.auto_migrate!
@@ -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,4 @@
1
+ class Tweecret < DataMapper::Base
2
+ property :message, :string
3
+ belongs_to :user
4
+ end
@@ -0,0 +1,4 @@
1
+ class User < DataMapper::Base
2
+ property :username, :string
3
+ has_many :tweecrets
4
+ 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
@@ -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
@@ -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,13 @@
1
+ module CanHasBots::Adapter
2
+ macros do
3
+ def initializer(&block)
4
+ included_callback do |base|
5
+ base.register_initializer(&block)
6
+ end
7
+ end
8
+
9
+ def runner(&block)
10
+ self.register_runner(&block)
11
+ end
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ class ThreadGroup
2
+ def join
3
+ list.each {|t| t.join if t != Thread.current}
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module CanHasBots
2
+ VERSION = "0.1.0"
3
+ 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
+