scamp 1.2.0 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/lib/scamp.rb CHANGED
@@ -1,31 +1,16 @@
1
1
  require 'eventmachine'
2
- require 'em-http-request'
3
- require 'yajl'
4
2
  require "logger"
5
3
 
6
4
  require "scamp/version"
7
- require 'scamp/connection'
8
- require 'scamp/rooms'
9
- require 'scamp/users'
10
5
  require 'scamp/matcher'
11
- require 'scamp/action'
12
- require 'scamp/messages'
6
+ require 'scamp/adapter'
7
+ require 'scamp/plugin'
13
8
 
14
9
  class Scamp
15
- include Connection
16
- include Rooms
17
- include Users
18
- include Messages
10
+ attr_accessor :adapters, :plugins, :matchers, :logger, :verbose, :first_match_only, :required_format, :strip_prefix
19
11
 
20
- attr_accessor :rooms, :user_cache, :room_cache, :matchers, :api_key, :subdomain,
21
- :logger, :verbose, :first_match_only, :ignore_self, :required_prefix,
22
- :rooms_to_join
23
-
24
- def initialize(options = {})
12
+ def initialize(options = {}, &block)
25
13
  options ||= {}
26
- raise ArgumentError, "You must pass an API key" unless options[:api_key]
27
- raise ArgumentError, "You must pass a subdomain" unless options[:subdomain]
28
-
29
14
  options.each do |k,v|
30
15
  s = "#{k}="
31
16
  if respond_to?(s)
@@ -34,31 +19,41 @@ class Scamp
34
19
  logger.warn "Scamp initialized with #{k.inspect} => #{v.inspect} but NO UNDERSTAND!"
35
20
  end
36
21
  end
37
-
38
- @rooms_to_join = []
39
- @rooms = {}
40
- @user_cache = {}
41
- @room_cache = {}
22
+
23
+ @strip_prefix ||= true
42
24
  @matchers ||= []
25
+ @adapters ||= {}
26
+ @plugins ||= []
27
+
28
+ yield self
43
29
  end
44
-
45
- def behaviour &block
46
- instance_eval &block
30
+
31
+ def adapter name, klass, opts={}
32
+ adapter = klass.new self, opts
33
+ sid = adapter.subscribe do |context, msg|
34
+ process_message(name, context, msg)
35
+ end
36
+ @adapters[name] = {:adapter => adapter, :sid => sid}
47
37
  end
48
-
49
- def connect!(room_list, &blk)
50
- logger.info "Starting up"
51
- connect(api_key, room_list, &blk)
38
+
39
+ def plugin klass, opts={}
40
+ plugins << klass.new(self, opts)
52
41
  end
53
-
54
- def command_list
55
- matchers.map{|m| [m.trigger, m.conditions] }
42
+
43
+ def connect!
44
+ EM.run do
45
+ @adapters.each do |name, data|
46
+ logger.info "Connecting to #{name} adapter"
47
+ data[:adapter].connect!
48
+ end
49
+ end
56
50
  end
57
51
 
58
52
  def logger
59
53
  unless @logger
60
54
  @logger = Logger.new(STDOUT)
61
55
  @logger.level = (verbose ? Logger::DEBUG : Logger::INFO)
56
+ @logger.info "Scamp using default logger as none was provided"
62
57
  end
63
58
  @logger
64
59
  end
@@ -73,18 +68,13 @@ class Scamp
73
68
  @first_match_only
74
69
  end
75
70
 
76
- private
77
-
78
71
  def match trigger, params={}, &block
79
- params ||= {}
80
- matchers << Matcher.new(self, {:trigger => trigger, :action => block, :conditions => params[:conditions], :required_prefix => required_prefix})
72
+ matchers << Matcher.new(self, {:trigger => trigger, :action => block, :on => params[:on], :conditions => params[:conditions]})
81
73
  end
82
-
83
- def process_message(msg)
84
- logger.debug "Received message #{msg.inspect}"
85
- return false if ignore_self && is_me?(msg[:user_id])
74
+
75
+ def process_message(channel, context, msg)
86
76
  matchers.each do |matcher|
87
- break if first_match_only & matcher.attempt(msg)
77
+ break if first_match_only & matcher.attempt(channel, context, msg)
88
78
  end
89
79
  end
90
80
  end
data/scamp.gemspec CHANGED
@@ -5,8 +5,8 @@ require "scamp/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "scamp"
7
7
  s.version = Scamp::VERSION
8
- s.authors = ["Will Jessop"]
9
- s.email = ["will@willj.net"]
8
+ s.authors = ["Will Jessop", "Adam Holt"]
9
+ s.email = ["will@willj.net", "me@adamholt.co.uk"]
10
10
  s.homepage = "https://github.com/wjessop/Scamp"
11
11
  s.summary = %q{Eventmachine based Campfire bot framework}
12
12
  s.description = %q{Eventmachine based Campfire bot framework}
@@ -15,13 +15,12 @@ Gem::Specification.new do |s|
15
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
-
19
- s.add_dependency('eventmachine', '~> 1.0.0.rc.4')
20
- s.add_dependency('yajl-ruby', '~> 1.1')
21
- s.add_dependency('em-http-request', '~> 1.0')
22
18
 
23
- s.add_development_dependency "rake", "~> 0.9"
24
- s.add_development_dependency "rspec", "~> 2.6"
25
- s.add_development_dependency "mocha", "~> 0.10"
26
- s.add_development_dependency "webmock", "~> 1.7"
19
+ s.add_dependency 'eventmachine', '~> 1.0'
20
+
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "rspec", '~> 2.11'
23
+ s.add_development_dependency "mocha", '~> 0.12'
24
+ s.add_development_dependency "webmock", '~> 1.8'
25
+ s.add_development_dependency "simplecov"
27
26
  end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scamp::Adapter do
4
+ let(:bot) { stub }
5
+ describe "#new" do
6
+ it "creates a new adapter" do
7
+ adapter = Scamp::Adapter.new(bot)
8
+ adapter.should_not be_nil
9
+ end
10
+
11
+ it "accepts a hash of options" do
12
+ adapter = Scamp::Adapter.new(bot, :hello => "world")
13
+ adapter.should_not be_nil
14
+ end
15
+ end
16
+
17
+ describe ".matches_required_format?" do
18
+ let(:adapter) { Scamp::Adapter.new(bot) }
19
+ let(:bot) { stub(:required_format => nil) }
20
+
21
+ describe "no requirement" do
22
+ it "matches required format" do
23
+ adapter.matches_required_format?("Hello").should be_true
24
+ end
25
+ end
26
+
27
+ describe "string requirement" do
28
+ let(:bot) { stub(:required_format => "Bot: ") }
29
+
30
+ it "matches msg with correct prefix" do
31
+ adapter.matches_required_format?("Bot: Hello").should be_true
32
+ end
33
+
34
+ it "does not match msg with incorrect prefix" do
35
+ adapter.matches_required_format?("Not: Hello").should be_false
36
+ end
37
+ end
38
+
39
+ describe "regex requirement" do
40
+ let(:bot) { stub(:required_format => /^Bot: /) }
41
+
42
+ it "matches msg with correct prefix" do
43
+ adapter.matches_required_format?("Bot: Hello").should be_true
44
+ end
45
+
46
+ it "does not match msg with incorrect prefix" do
47
+ adapter.matches_required_format?("Not: Hello").should be_false
48
+ end
49
+ end
50
+
51
+ describe "other requirement" do
52
+ let(:bot) { stub(:required_format => Object.new) }
53
+
54
+ it "raises an ArgumentError" do
55
+ expect {
56
+ adapter.matches_required_format?("Bot: Hello").should be_true
57
+ }.to raise_error(ArgumentError)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe ".strip_prefix" do
63
+ describe "with strip_prefix enabled" do
64
+ describe "with a string require_format" do
65
+ let(:adapter) { Scamp::Adapter.new(bot) }
66
+ let(:bot) { stub(:required_format => "Bot: ", :strip_prefix => true) }
67
+
68
+ it "should strip the required_format if it is a string" do
69
+ adapter.strip_prefix("Bot: Hello").should == "Hello"
70
+ end
71
+
72
+ it "should not strip an incorrect prefix" do
73
+ adapter.strip_prefix("Not: Hello").should == "Not: Hello"
74
+ end
75
+ end
76
+
77
+ describe "with a Regex require_format" do
78
+ let(:adapter) { Scamp::Adapter.new(bot) }
79
+ let(:bot) { stub(:required_format => /^Bot: /, :strip_prefix => true) }
80
+
81
+ it "should strip the required_format if it is a string" do
82
+ adapter.strip_prefix("Bot: Hello").should == "Bot: Hello"
83
+ end
84
+
85
+ it "should not strip an incorrect prefix" do
86
+ adapter.strip_prefix("Not: Hello").should == "Not: Hello"
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "with strip_prefix disabled" do
92
+ let(:adapter) { Scamp::Adapter.new(bot) }
93
+ let(:bot) { stub(:required_format => "Bot: ", :strip_prefix => false) }
94
+
95
+ it "should strip the required_format if it is a string" do
96
+ adapter.strip_prefix("Bot: Hello").should == "Bot: Hello"
97
+ end
98
+
99
+ it "should not strip an incorrect prefix" do
100
+ adapter.strip_prefix("Not: Hello").should == "Not: Hello"
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "channels" do
106
+ let(:adapter) { Scamp::Adapter.new(bot) }
107
+
108
+ describe ".push" do
109
+ it "should push through the correct value" do
110
+ EM.run_block do
111
+ value = nil
112
+ adapter.subscribe do |msg|
113
+ value = msg
114
+ end
115
+ adapter.push "Hello World"
116
+
117
+ value.should == "Hello World"
118
+ end
119
+ end
120
+ end
121
+
122
+ describe ".subscribe" do
123
+ it "allows subscriptions" do
124
+ EM.run_block do
125
+ subscribed = false
126
+ adapter.subscribe do |msg|
127
+ subscribed = true
128
+ end
129
+ adapter.push true
130
+
131
+ subscribed.should be_true
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ describe ".connect!" do
138
+ let(:adapter) { Scamp::Adapter.new(bot) }
139
+ it "should raise an implementation error" do
140
+ expect {
141
+ adapter.connect!
142
+ }.to raise_error(NotImplementedError)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scamp::Matcher do
4
+ describe "#new" do
5
+ let(:bot) { stub(adapters: {one: "two"}) }
6
+
7
+ it "requires a trigger" do
8
+ expect {
9
+ Scamp::Matcher.new(bot, action: ->(ctx, msg){})
10
+ }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it "requires a action" do
14
+ expect {
15
+ Scamp::Matcher.new(bot, trigger: "Hello")
16
+ }.to raise_error(ArgumentError)
17
+ end
18
+ end
19
+
20
+ describe ".attempt" do
21
+ context "channels" do
22
+ let(:msg) { Scamp::Message.new stub, body: "hello" }
23
+
24
+ context "listened" do
25
+ it "runs the action when" do
26
+ result = false
27
+ matcher = Scamp::Matcher.new stub,
28
+ on: [:one],
29
+ trigger: "hello",
30
+ action: ->(ctx, msg){result = true}
31
+ matcher.attempt(:one, stub, msg)
32
+ result.should be_true
33
+ end
34
+ end
35
+
36
+ context "unlistened" do
37
+ it "does not run the action" do
38
+ result = false
39
+ matcher = Scamp::Matcher.new stub,
40
+ on: [:one],
41
+ trigger: "hello",
42
+ action: ->(ctx, msg){result = true}
43
+ matcher.attempt(:two, stub, msg)
44
+ result.should be_false
45
+ end
46
+ end
47
+ end
48
+
49
+ context "triggers" do
50
+ context "matching" do
51
+ it "runs the action" do
52
+ result = false
53
+ matcher = Scamp::Matcher.new stub,
54
+ on: [:one],
55
+ trigger: "hello",
56
+ action: ->(ctx, msg){result = true}
57
+ msg = Scamp::Message.new stub, body: "hello"
58
+
59
+ matcher.attempt(:one, stub, msg)
60
+ result.should be_true
61
+ end
62
+ end
63
+
64
+ context "non matching" do
65
+ it "runs the action" do
66
+ result = false
67
+ matcher = Scamp::Matcher.new stub,
68
+ on: [:one],
69
+ trigger: "hello",
70
+ action: ->(ctx, msg){result = true}
71
+ msg = Scamp::Message.new stub, body: "goodbye"
72
+
73
+ matcher.attempt(:one, stub, msg)
74
+ result.should be_false
75
+ end
76
+ end
77
+ end
78
+
79
+ context "validations" do
80
+ let(:message_class) {
81
+ class MyMessage < Scamp::Message
82
+ def valid?(conditions)
83
+ conditions[:hello] == "world"
84
+ end
85
+ end
86
+ MyMessage
87
+ }
88
+
89
+ context "valid message" do
90
+ it "runs the action" do
91
+ result = false
92
+ matcher = Scamp::Matcher.new stub,
93
+ on: [:one],
94
+ trigger: "hello",
95
+ action: ->(ctx, msg){result = true},
96
+ conditions: {hello: "world"}
97
+
98
+ msg = message_class.new stub, body: "hello"
99
+
100
+ matcher.attempt(:one, stub, msg)
101
+ result.should be_true
102
+ end
103
+ end
104
+
105
+ context "invalid message" do
106
+ it "runs the action" do
107
+ result = false
108
+ matcher = Scamp::Matcher.new stub,
109
+ on: [:one],
110
+ trigger: "hello",
111
+ action: ->(ctx, msg){result = true},
112
+ conditions: {hello: "goodbye"}
113
+
114
+ msg = message_class.new stub, body: "hello"
115
+
116
+ matcher.attempt(:one, stub, msg)
117
+ result.should be_false
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scamp::Matches do
4
+ let(:msg) { "Name: Scamp, URL: https://github.com/wjessop/Scamp/" }
5
+ let(:match) { msg.match(/^Name: (?<name>\w+), URL: (?<url>.*)/) }
6
+ let(:matches) { Scamp::Matches.new(match) }
7
+
8
+ it "exposes the name from the capture" do
9
+ matches.name.should eql("Scamp")
10
+ matches[0].should eql("Scamp")
11
+ end
12
+
13
+ it "exposes the url from the capture" do
14
+ matches.url.should eql("https://github.com/wjessop/Scamp/")
15
+ matches[1].should eql("https://github.com/wjessop/Scamp/")
16
+ end
17
+
18
+ describe '.each' do
19
+ it "iterates over each match" do
20
+ results = []
21
+ matches.each do |match|
22
+ results << match
23
+ end
24
+
25
+ results.should include("Scamp")
26
+ results.should include("https://github.com/wjessop/Scamp/")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scamp::Message do
4
+ describe "#new" do
5
+ it "should store and provide access to metadata" do
6
+ msg = Scamp::Message.new("foo", body: "hello", foo: "foo", bar: "bar")
7
+ msg.foo.should eql("foo")
8
+ msg.bar.should eql("bar")
9
+ end
10
+
11
+ it "should maintain metadata integrity when multiple instances exist" do
12
+ msg = Scamp::Message.new("foo", body: "hello", foo: "foo")
13
+ msg2 = Scamp::Message.new("foo", body: "hello", foo: "ouchie")
14
+ msg.foo.should eql("foo")
15
+ msg.foo.should eql("foo")
16
+ end
17
+
18
+ it "should isolate data access message instances created by different adapters" do
19
+ msg = Scamp::Message.new("foo", body: "hello", foo: "foo")
20
+ msg2 = Scamp::Message.new("foo2", body: "hello", bar: "bar")
21
+
22
+ msg.should respond_to(:foo)
23
+ msg.should_not respond_to(:bar)
24
+
25
+ msg2.should respond_to(:bar)
26
+ msg2.should_not respond_to(:foo)
27
+ end
28
+ end
29
+
30
+ describe ".valid?" do
31
+ it "returns true by default" do
32
+ msg = Scamp::Message.new('foo', body: "hello")
33
+ msg.valid?(nil).should be_true
34
+ end
35
+ end
36
+
37
+ describe ".matches?" do
38
+ let(:msg) { Scamp::Message.new('foo', body: "hello") }
39
+
40
+ it "matches a string trigger" do
41
+ msg.matches?("hello").should be_true
42
+ end
43
+
44
+ it "matches a regex trigger" do
45
+ msg.matches?(/^hello$/).should be_true
46
+ end
47
+
48
+ it "does not match an invalid matcher" do
49
+ msg.matches?("goodbye").should be_false
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scamp::Plugin do
4
+ describe "#matchers" do
5
+ it "is empty by default" do
6
+ Scamp::Plugin.matchers.should be_empty
7
+ end
8
+
9
+ it "should add a new matcher" do
10
+ expect {
11
+ Scamp::Plugin.match "Hello", :method
12
+ }.to change { Scamp::Plugin.matchers.size }.by(1)
13
+ end
14
+
15
+ describe ".new" do
16
+ let(:bot) { mock }
17
+ let(:plugin) {
18
+ class TestPlugin < Scamp::Plugin
19
+ match "Hello", :name
20
+
21
+ def name(context, msg)
22
+ end
23
+ end
24
+ TestPlugin
25
+ }
26
+
27
+ it "attaches matchers to the bot" do
28
+ bot.expects(:match).with("Hello", {:on => nil}).once
29
+ plugin.new(bot)
30
+ end
31
+ end
32
+ end
33
+ end