scamp 1.2.0 → 2.0.0.pre

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