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