jabbot 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ require 'optparse'
2
+
3
+ module Jabbot
4
+ #
5
+ # Jabbot configuration. Use either Jabbot::CliConfig.new or
6
+ # JabbotFileConfig.new setup a new bot from either command line or file
7
+ # (respectively). Configurations can be chained so they override each other:
8
+ #
9
+ # config = Jabbot::FileConfig.new
10
+ # config << Jabbot::CliConfig.new
11
+ # config.to_hash
12
+ #
13
+ # The preceding example will create a configuration which is based on a
14
+ # configuration file but have certain values overridden from the command line.
15
+ # This can be used for instance to store everything but the Twitter account
16
+ # password in your configuration file. Then you can just provide the password
17
+ # when running the bot.
18
+ #
19
+ class Config
20
+ attr_reader :settings
21
+
22
+ DEFAULT = {
23
+ :log_level => 'info',
24
+ :log_file => nil,
25
+ :login => nil,
26
+ :password => nil,
27
+ :nick => 'jabbot',
28
+ :channel => nil,
29
+ :server => nil,
30
+ :resource => nil
31
+ }
32
+
33
+ def initialize(settings = {})
34
+ @configs = []
35
+ @settings = settings
36
+ end
37
+
38
+ #
39
+ # Add a configuration object to override given settings
40
+ #
41
+ def add(config)
42
+ @configs << config
43
+ self
44
+ end
45
+
46
+ alias_method :<<, :add
47
+
48
+ #
49
+ # Makes it possible to access configuration settings as attributes
50
+ #
51
+ def method_missing(name, *args, &block)
52
+ regex = /=$/
53
+ attr_name = name.to_s.sub(regex, '').to_sym
54
+ return super if name == attr_name && !@settings.key?(attr_name)
55
+
56
+ if name != attr_name
57
+ @settings[attr_name] = args.first
58
+ end
59
+
60
+ @settings[attr_name]
61
+ end
62
+
63
+ #
64
+ # Merges configurations and returns a hash with all options
65
+ #
66
+ def to_hash
67
+ hash = {}.merge(@settings)
68
+ @configs.each { |conf| hash.merge!(conf.to_hash) }
69
+ hash
70
+ end
71
+
72
+ def self.default
73
+ Config.new({}.merge(DEFAULT))
74
+ end
75
+ end
76
+
77
+ #
78
+ # Configuration from files
79
+ #
80
+ class FileConfig < Config
81
+
82
+ #
83
+ # Accepts a stream or a file to read configuration from
84
+ # Default is to read configuration from ./config/bot.yml
85
+ #
86
+ # If a stream is passed it is not closed from within the method
87
+ #
88
+ def initialize(fos = File.expand_path("config/bot.yml"))
89
+ stream = fos.is_a?(String) ? File.open(fos, "r") : fos
90
+
91
+ begin
92
+ config = YAML.load(stream.read)
93
+ config.symbolize_keys! if config
94
+ rescue Exception => err
95
+ puts err.message
96
+ puts "Unable to load configuration, aborting"
97
+ exit
98
+ ensure
99
+ stream.close if fos.is_a?(String)
100
+ end
101
+
102
+ super config.is_a?(Hash) ? config : {}
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,116 @@
1
+ module Jabbot
2
+ module Handlers
3
+ #
4
+ # Add a handler for this bot
5
+ #
6
+ def add_handler(type, handler)
7
+ handlers[type] << handler
8
+ handler
9
+ end
10
+
11
+ def dispatch(type, message)
12
+ handlers[type].each { |handler| handler.dispatch(message) }
13
+ end
14
+
15
+ def handlers
16
+ @handlers ||= {
17
+ :message => [],
18
+ :private => [],
19
+ :join => [],
20
+ :subject => [],
21
+ :leave => []
22
+ }
23
+ end
24
+
25
+ def handlers=(hash)
26
+ @handlers = hash
27
+ end
28
+ end
29
+
30
+ #
31
+ # A Handler object is an object which can handle a direct message, tweet or
32
+ # at reply.
33
+ #
34
+ class Handler
35
+ def initialize(pattern = nil, options = {}, &blk)
36
+ if pattern.is_a?(Hash)
37
+ options = pattern
38
+ pattern = nil
39
+ end
40
+
41
+ @options = options
42
+ @options[:from].collect! { |s| s.to_s } if @options[:from] && @options[:from].is_a?(Array)
43
+ @options[:from] = [@options[:from].to_s] if @options[:from] && [String, Symbol].include?(@options[:from].class)
44
+ @handler = nil
45
+ @handler = block_given? ? blk : nil
46
+ self.pattern = pattern
47
+ end
48
+
49
+ #
50
+ # Parse pattern string and set options
51
+ #
52
+ def pattern=(pattern)
53
+ return if pattern.nil? || pattern == ""
54
+
55
+ if pattern == :all
56
+ return
57
+ end
58
+
59
+ if pattern.is_a?(Regexp)
60
+ @options[:pattern] = pattern
61
+ return
62
+ end
63
+
64
+ words = pattern.split.collect { |s| s.strip } # Get all words in pattern
65
+ @options[:tokens] = words.inject([]) do |sum, token| # Find all tokens, ie :symbol :like :names
66
+ next sum unless token =~ /^:.*/ # Don't process regular words
67
+ sym = token.sub(":", "").to_sym # Turn token string into symbol, ie ":token" => :token
68
+ regex = @options[sym] || '[^\s]+' # Fetch regex if configured, else use any character but space matching
69
+ pattern.sub!(/(^|\s)#{token}(\s|$)/, '\1(' + regex.to_s + ')\2') # Make sure regex captures named switch
70
+ sum << sym
71
+ end
72
+
73
+ @options[:pattern] = /#{pattern}(\s.+)?/
74
+ end
75
+
76
+ #
77
+ # Determines if this handler is suited to handle an incoming message
78
+ #
79
+ def recognize?(message)
80
+ return false if @options[:pattern] && message.text !~ @options[:pattern] # Pattern check
81
+
82
+ users = @options[:from] ? @options[:from] : nil
83
+ return false if users && !users.include?(message.user) # Check allowed senders
84
+ true
85
+ end
86
+
87
+ #
88
+ # Process message to build params hash and pass message along with params of
89
+ # to +handle+
90
+ #
91
+ def dispatch(message)
92
+ return unless recognize?(message)
93
+ @params = {}
94
+
95
+ if @options[:pattern] && @options[:tokens]
96
+ matches = message.text.match(@options[:pattern])
97
+ @options[:tokens].each_with_index { |token, i| @params[token] = matches[i+1] }
98
+ @params[:text] = (matches[@options[:tokens].length+1] || "").strip
99
+ elsif @options[:pattern] && !@options[:tokens]
100
+ @params = message.text.match(@options[:pattern]).to_a[1..-1] || []
101
+ else
102
+ @params[:text] = message.text
103
+ end
104
+
105
+ return handle(message, @params)
106
+ end
107
+
108
+ #
109
+ # Handle a message. Calls the internal Proc with the message and the params
110
+ # hash as parameters.
111
+ #
112
+ def handle(message, params)
113
+ @handler.call(message, params) if @handler
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,85 @@
1
+ module Jabbot
2
+ @@prompt = false
3
+
4
+ def self.prompt=(p)
5
+ @@prompt = f
6
+ end
7
+
8
+ module Macros
9
+ def self.included(mod)
10
+ @@bot = nil
11
+ end
12
+
13
+ def configure(&blk)
14
+ bot.configure(&blk)
15
+ end
16
+
17
+ def message(pattern = nil, options = {}, &blk)
18
+ add_handler(:message, pattern, options, &blk)
19
+ end
20
+
21
+ def query(pattern = nil, options = {}, &blk)
22
+ add_handler(:private, pattern, options, &blk)
23
+ end
24
+ alias_method :private_message, :query
25
+
26
+ def join(options = {}, &blk)
27
+ add_handler(:join, /\Ajoin\Z/, options, &blk)
28
+ end
29
+
30
+ def leave(options = {}, &blk)
31
+ add_handler(:leave, /\Aleave\Z/, options, &blk)
32
+ end
33
+
34
+ def subject(pattern = nil, options = {}, &blk)
35
+ add_handler(:subject, pattern, options, &blk)
36
+ end
37
+ alias_method :topic, :subject
38
+
39
+ def client
40
+ bot.client
41
+ end
42
+
43
+ def close
44
+ bot.close
45
+ end
46
+ alias_method :quit, :close
47
+
48
+ def user
49
+ bot.user
50
+ end
51
+
52
+ def post(msg, to=nil)
53
+ if msg.is_a?(Hash) && msg.keys.size == 1
54
+ to = msg.values.first
55
+ msg = msg.keys.first
56
+ end
57
+ bot.send_message(msg, to)
58
+ end
59
+
60
+ def run?
61
+ !@@bot.nil?
62
+ end
63
+
64
+ private
65
+ def add_handler(type, pattern, options, &blk)
66
+ bot.add_handler(type, Jabbot::Handler.new(pattern, options, &blk))
67
+ end
68
+
69
+ def bot
70
+ return @@bot unless @@bot.nil?
71
+
72
+ begin
73
+ @@bot = Jabbot::Bot.new nil
74
+ rescue Exception
75
+ @@bot = Jabbot::Bot.new(Jabbot::Config.default)
76
+ end
77
+
78
+ @@bot
79
+ end
80
+
81
+ def self.bot=(bot)
82
+ @@bot = bot
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,7 @@
1
+ module Jabbot
2
+ Message = Struct.new(:user, :text, :time) do
3
+ def to_s
4
+ "#{user}: #{text}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,126 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
2
+ require 'fileutils'
3
+
4
+ class TestBot < Test::Unit::TestCase
5
+ should "not raise errors when initialized" do
6
+ assert_nothing_raised do
7
+ Jabbot::Bot.new Jabbot::Config.new
8
+ end
9
+ end
10
+
11
+ should "raise errors when initialized without config file" do
12
+ assert_raise SystemExit do
13
+ Jabbot::Bot.new
14
+ end
15
+ end
16
+
17
+ should "not raise error on initialize when config file exists" do
18
+ if File.exists?("config")
19
+ FileUtils.rm("config/bot.yml")
20
+ else
21
+ FileUtils.mkdir("config")
22
+ end
23
+
24
+ File.open("config/bot.yml", "w") { |f| f.puts "" }
25
+
26
+ assert_nothing_raised do
27
+ Jabbot::Bot.new
28
+ end
29
+
30
+ FileUtils.rm_rf("config")
31
+ end
32
+
33
+ should "provide configuration settings as methods" do
34
+ bot = Jabbot::Bot.new Jabbot::Config.new(:login => "jabbot")
35
+ assert_equal "jabbot", bot.login
36
+ end
37
+
38
+ should "return logger instance" do
39
+ bot = Jabbot::Bot.new(Jabbot::Config.default << Jabbot::Config.new)
40
+ assert bot.log.is_a?(Logger)
41
+ end
42
+
43
+ should "respect configured log level" do
44
+ bot = Jabbot::Bot.new(Jabbot::Config.new(:log_level => "info"))
45
+ assert_equal Logger::INFO, bot.log.level
46
+
47
+ bot = Jabbot::Bot.new(Jabbot::Config.new(:log_level => "warn"))
48
+ assert_equal Logger::WARN, bot.log.level
49
+ end
50
+ end
51
+
52
+ class TestBotMacros < Test::Unit::TestCase
53
+ should "provide configure macro" do
54
+ assert respond_to?(:configure)
55
+ end
56
+
57
+ should "yield configuration" do
58
+ Jabbot::Macros.bot = Jabbot::Bot.new Jabbot::Config.default
59
+
60
+ conf = nil
61
+ assert_nothing_raised { configure { |c| conf = c } }
62
+ assert conf.is_a?(Jabbot::Config)
63
+ end
64
+
65
+ should "add handler" do
66
+ Jabbot::Macros.bot = Jabbot::Bot.new Jabbot::Config.default
67
+
68
+ handler = add_handler(:message, ":command", :from => :cjno)
69
+ assert handler.is_a?(Jabbot::Handler), handler.class
70
+ end
71
+
72
+ should "provide client macro" do
73
+ assert respond_to?(:client)
74
+ end
75
+
76
+ should "provide user macro" do
77
+ assert respond_to?(:user)
78
+ end
79
+ end
80
+
81
+ class TestBotHandlers < Test::Unit::TestCase
82
+
83
+ should "include handlers" do
84
+ bot = Jabbot::Bot.new(Jabbot::Config.new)
85
+
86
+ assert_not_nil bot.handlers
87
+ assert_not_nil bot.handlers[:message]
88
+ assert_not_nil bot.handlers[:private]
89
+ assert_not_nil bot.handlers[:join]
90
+ assert_not_nil bot.handlers[:leave]
91
+ assert_not_nil bot.handlers[:subject]
92
+ end
93
+
94
+ should "add handler" do
95
+ bot = Jabbot::Bot.new(Jabbot::Config.new)
96
+ bot.add_handler :message, Jabbot::Handler.new
97
+ assert_equal 1, bot.handlers[:message].length
98
+
99
+ bot.add_handler :message, Jabbot::Handler.new
100
+ assert_equal 2, bot.handlers[:message].length
101
+
102
+ bot.add_handler :private, Jabbot::Handler.new
103
+ assert_equal 1, bot.handlers[:private].length
104
+
105
+ bot.add_handler :private, Jabbot::Handler.new
106
+ assert_equal 2, bot.handlers[:private].length
107
+
108
+ bot.add_handler :join, Jabbot::Handler.new
109
+ assert_equal 1, bot.handlers[:join].length
110
+
111
+ bot.add_handler :join, Jabbot::Handler.new
112
+ assert_equal 2, bot.handlers[:join].length
113
+
114
+ bot.add_handler :leave, Jabbot::Handler.new
115
+ assert_equal 1, bot.handlers[:leave].length
116
+
117
+ bot.add_handler :leave, Jabbot::Handler.new
118
+ assert_equal 2, bot.handlers[:leave].length
119
+
120
+ bot.add_handler :subject, Jabbot::Handler.new
121
+ assert_equal 1, bot.handlers[:subject].length
122
+
123
+ bot.add_handler :subject, Jabbot::Handler.new
124
+ assert_equal 2, bot.handlers[:subject].length
125
+ end
126
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
2
+ require 'stringio'
3
+
4
+ class TestConfig < Test::Unit::TestCase
5
+ should "default configuration be a hash" do
6
+ assert_not_nil Jabbot::Config::DEFAULT
7
+ assert Jabbot::Config::DEFAULT.is_a?(Hash)
8
+ end
9
+
10
+ should "initialize with no options" do
11
+ assert_hashes_equal({}, Jabbot::Config.new.settings)
12
+ end
13
+
14
+ should "return config from add" do
15
+ config = Jabbot::Config.new
16
+ assert_equal config, config.add(Jabbot::Config.new)
17
+ end
18
+
19
+ should "alias add to <<" do
20
+ config = Jabbot::Config.new
21
+ assert config.respond_to?(:<<)
22
+ assert config << Jabbot::Config.new
23
+ end
24
+
25
+ should "mirror method_missing as config getters" do
26
+ config = Jabbot::Config.default << Jabbot::Config.new
27
+ assert_equal Jabbot::Config::DEFAULT[:password], config.password
28
+ assert_equal Jabbot::Config::DEFAULT[:login], config.login
29
+ end
30
+
31
+ should "mirror missing methods as config setters" do
32
+ config = Jabbot::Config.default << Jabbot::Config.new
33
+ assert_equal Jabbot::Config::DEFAULT[:login], config.login
34
+
35
+ val = "jabbot"
36
+ config.login = val+'!'
37
+ assert_not_equal Jabbot::Config::DEFAULT[:login], config.login
38
+ assert_equal val+'!', config.login
39
+ end
40
+
41
+ should "not override default hash" do
42
+ config = Jabbot::Config.default
43
+ hash = Jabbot::Config::DEFAULT
44
+
45
+ config.login = "jabbot"
46
+ config.password = "secret"
47
+
48
+ assert_hashes_not_equal Jabbot::Config::DEFAULT, config.to_hash
49
+ assert_hashes_equal hash, Jabbot::Config::DEFAULT
50
+ end
51
+
52
+ should "return merged configuration from to_hash" do
53
+ config = Jabbot::Config.new
54
+ config.login = "jabbot"
55
+ config.password = "secret"
56
+
57
+ config2 = Jabbot::Config.new({})
58
+ config2.login = "not_jabbot2"
59
+ config << config2
60
+ options = config.to_hash
61
+
62
+ assert_equal "secret", options[:password]
63
+ assert_equal "not_jabbot2", options[:login]
64
+ end
65
+ end
66
+
67
+ class TestFileConfig < Test::Unit::TestCase
68
+ should "subclass config for file config" do
69
+ assert Jabbot::FileConfig.new(StringIO.new).is_a?(Jabbot::Config)
70
+ end
71
+
72
+ should "read settings from stream" do
73
+ config = Jabbot::FileConfig.new(StringIO.new <<-YAML)
74
+ login: jabbot
75
+ password: secret
76
+ YAML
77
+
78
+ assert_equal "jabbot", config.login
79
+ assert_equal "secret", config.password
80
+ end
81
+ end