baldrick 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +29 -5
- data/README.rdoc +7 -5
- data/Rakefile +19 -18
- data/TODO.txt +40 -0
- data/features/follow_feed_orders.feature +9 -0
- data/features/follow_injour_orders.feature +9 -0
- data/features/resources/a_feed_servant +2 -0
- data/features/resources/common_configuration +10 -0
- data/features/resources/feed_server +30 -0
- data/features/resources/injour_servant +2 -0
- data/features/steps/feed_steps.rb +4 -0
- data/features/steps/helpers/command_output.rb +33 -0
- data/features/steps/helpers/scenario_process.rb +37 -0
- data/features/steps/injour_steps.rb +4 -0
- data/features/steps/order_steps.rb +3 -0
- data/features/steps/servant_steps.rb +5 -0
- data/features/support/env.rb +15 -0
- data/lib/baldrick.rb +1 -1
- data/lib/baldrick/listeners/xpath_locator.rb +1 -1
- data/spec/lib/baldrick/command_spec.rb +115 -0
- data/spec/lib/baldrick/listeners/feed_listener_spec.rb +43 -0
- data/spec/lib/baldrick/listeners/feed_orders_spec.rb +63 -0
- data/spec/lib/baldrick/listeners/injour_listener_spec.rb +82 -0
- data/spec/lib/baldrick/listeners/xpath_locator_spec.rb +43 -0
- data/spec/lib/baldrick/run_servant_spec.rb +14 -0
- data/spec/lib/baldrick/servant_spec.rb +45 -0
- data/spec/lib/baldrick/task_spec.rb +64 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +11 -0
- metadata +43 -23
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -2,17 +2,41 @@ History.txt
|
|
2
2
|
Manifest.txt
|
3
3
|
README.rdoc
|
4
4
|
Rakefile
|
5
|
+
TODO.txt
|
6
|
+
features/follow_feed_orders.feature
|
7
|
+
features/follow_injour_orders.feature
|
8
|
+
features/resources/a_feed_servant
|
9
|
+
features/resources/common_configuration
|
10
|
+
features/resources/feed_server
|
11
|
+
features/resources/injour_servant
|
12
|
+
features/steps/feed_steps.rb
|
13
|
+
features/steps/helpers/command_output.rb
|
14
|
+
features/steps/helpers/scenario_process.rb
|
15
|
+
features/steps/injour_steps.rb
|
16
|
+
features/steps/order_steps.rb
|
17
|
+
features/steps/servant_steps.rb
|
18
|
+
features/support/env.rb
|
5
19
|
lib/baldrick.rb
|
6
|
-
lib/baldrick_serve.rb
|
7
20
|
lib/baldrick/command.rb
|
8
|
-
lib/baldrick/run_servant.rb
|
9
|
-
lib/baldrick/servant.rb
|
10
|
-
lib/baldrick/task.rb
|
11
21
|
lib/baldrick/listeners/feed_listener.rb
|
12
22
|
lib/baldrick/listeners/feed_orders.rb
|
13
23
|
lib/baldrick/listeners/injour_listener.rb
|
14
24
|
lib/baldrick/listeners/xpath_locator.rb
|
25
|
+
lib/baldrick/run_servant.rb
|
26
|
+
lib/baldrick/servant.rb
|
27
|
+
lib/baldrick/task.rb
|
28
|
+
lib/baldrick_serve.rb
|
15
29
|
script/console
|
16
30
|
script/destroy
|
17
31
|
script/generate
|
18
|
-
|
32
|
+
spec/lib/baldrick/command_spec.rb
|
33
|
+
spec/lib/baldrick/listeners/feed_listener_spec.rb
|
34
|
+
spec/lib/baldrick/listeners/feed_orders_spec.rb
|
35
|
+
spec/lib/baldrick/listeners/injour_listener_spec.rb
|
36
|
+
spec/lib/baldrick/listeners/xpath_locator_spec.rb
|
37
|
+
spec/lib/baldrick/run_servant_spec.rb
|
38
|
+
spec/lib/baldrick/servant_spec.rb
|
39
|
+
spec/lib/baldrick/task_spec.rb
|
40
|
+
spec/spec.opts
|
41
|
+
spec/spec_helper.rb
|
42
|
+
tasks/rspec.rake
|
data/README.rdoc
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
= Baldrick
|
2
2
|
|
3
3
|
* http://github.com/brentsnook/baldrick
|
4
|
+
* http://groups.google.com/group/baldrick
|
5
|
+
* http://baldrick.lighthouseapp.com
|
4
6
|
|
5
|
-
|
7
|
+
== Description
|
8
|
+
|
9
|
+
A dogsbody. Does what you tell it. Use it to glue things together.
|
6
10
|
|
7
11
|
Baldrick recognises orders and then performs appropriate tasks.
|
8
12
|
Baldrick acts as the glue between the source of the order (for example a twitter Atom feed) and the task you would like performed (put the kettle on).
|
9
13
|
|
10
14
|
== Installation
|
11
15
|
|
12
|
-
<em>Note: the rubyforge project application is currently being considered. First gem should be up within a few days. Please build yourself in the meantime - 2009-02-23</em>
|
13
|
-
|
14
16
|
Make Baldrick cower 'neath *your* boot:
|
15
17
|
|
16
18
|
sudo gem install baldrick
|
@@ -25,7 +27,7 @@ Grab the code from github:
|
|
25
27
|
cd baldrick
|
26
28
|
rake install_gem
|
27
29
|
|
28
|
-
==
|
30
|
+
== Synopsis
|
29
31
|
|
30
32
|
You tell Baldrick:
|
31
33
|
|
@@ -70,7 +72,7 @@ Don't look at me, I just made it. How about these for suggestions?
|
|
70
72
|
* control a continuous integration build light
|
71
73
|
* control a nuclear power plant
|
72
74
|
|
73
|
-
|
75
|
+
We want to know what you use it for, please send us some love on the {google group}[http://groups.google.com/group/baldrick].
|
74
76
|
|
75
77
|
== License
|
76
78
|
|
data/Rakefile
CHANGED
@@ -1,30 +1,31 @@
|
|
1
|
-
%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
1
|
+
%w[rubygems rake rake/clean hoe fileutils newgem rubigen].each { |f| require f }
|
2
2
|
require File.dirname(__FILE__) + '/lib/baldrick'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
Hoe.spec 'baldrick' do
|
5
|
+
version = Baldrick::VERSION
|
6
|
+
developer 'Brent Snook', 'brent@fuglylogic.com'
|
7
|
+
self.readme_file = 'README.rdoc'
|
8
|
+
self.clean_globs |= %w[**/.DS_Store tmp *.log]
|
9
|
+
self.rsync_args = '-av --delete --ignore-errors' # is this needed?
|
10
|
+
|
11
|
+
self.extra_deps = [
|
12
12
|
['nokogiri','>= 1.1.1'],
|
13
13
|
]
|
14
|
-
|
14
|
+
|
15
|
+
self.extra_dev_deps = [
|
15
16
|
['newgem', ">= #{::Newgem::VERSION}"],
|
16
17
|
['rspec', '>= 1.1.12'],
|
17
|
-
['cucumber', '>= 0.1.16']
|
18
|
-
['injour', '>= 0.2.3'],
|
18
|
+
['cucumber', '>= 0.1.16']
|
19
19
|
]
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'cucumber/rake/task'
|
23
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
24
|
+
t.cucumber_opts = "features --format pretty"
|
25
25
|
end
|
26
26
|
|
27
27
|
require 'newgem/tasks' # load /tasks/*.rake
|
28
28
|
Dir['tasks/**/*.rake'].each { |t| load t }
|
29
29
|
|
30
|
-
task :default => [:spec, :features]
|
30
|
+
task :default => [:spec, :features]
|
31
|
+
|
data/TODO.txt
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
- add website - 2
|
3
|
+
- turnip logo
|
4
|
+
- how to include documentation from wiki? Just link?
|
5
|
+
|
6
|
+
- merge README and wiki page - 1
|
7
|
+
|
8
|
+
- create google group - 1
|
9
|
+
|
10
|
+
- test on various feeds - 1
|
11
|
+
|
12
|
+
- test on cruise.rb
|
13
|
+
- test on slashdot
|
14
|
+
- test on twitter
|
15
|
+
- test on thinkgeek
|
16
|
+
|
17
|
+
|
18
|
+
X
|
19
|
+
- set up rubyforge account
|
20
|
+
- publish gem to rubyforge
|
21
|
+
- test install from rubyforge - 2 '''
|
22
|
+
|
23
|
+
- create tag of first release
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
????
|
29
|
+
|
30
|
+
- should I package specs and features in my manifest?
|
31
|
+
- how is the gemspec used? Do I need one?
|
32
|
+
|
33
|
+
LATER
|
34
|
+
|
35
|
+
- add a spec around run_servant
|
36
|
+
- add ability to pass in feed listener xpath expressions for who, what, where, when during configuration
|
37
|
+
- fix bug: displaying multiple stack traces after error
|
38
|
+
- better way to log error with listener than puts
|
39
|
+
|
40
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Feature: Follow feed orders
|
2
|
+
In order to allow tasks to be triggered from RSS
|
3
|
+
As a person interested in a web feed
|
4
|
+
I want new RSS items to be followed as orders
|
5
|
+
|
6
|
+
Scenario: Feed item contains a new order
|
7
|
+
Given a servant is listening for orders from a feed
|
8
|
+
When a new item appears in the feed containing an order
|
9
|
+
Then the order is followed
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Feature: Follow injour orders
|
2
|
+
In order to trigger tasks by changing my status message
|
3
|
+
As an injour user
|
4
|
+
I want orders in my status message to be followed
|
5
|
+
|
6
|
+
Scenario: Status message contains a new order
|
7
|
+
Given a servant is listening for orders from injour
|
8
|
+
When an injour status is changed to contain an order
|
9
|
+
Then the order is followed
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# as with all servant files, don't give this file a .rb extension or cucumber will try to require it resulting in the server starting when you don't want it to
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../../lib/baldrick_serve'
|
4
|
+
require File.dirname(__FILE__) + '/../steps/helpers/command_output'
|
5
|
+
|
6
|
+
listen_every 0.5
|
7
|
+
|
8
|
+
on_hearing /follow an order/m do
|
9
|
+
CommandOutput.add 'Order followed'
|
10
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
|
3
|
+
server = WEBrick::HTTPServer.new :Port => 1726
|
4
|
+
|
5
|
+
server.mount_proc("/rss") do |request, response|
|
6
|
+
response.body = <<-RSS
|
7
|
+
<?xml version="1.0"?>
|
8
|
+
<rss version="2.0">
|
9
|
+
<channel>
|
10
|
+
<item>
|
11
|
+
<title>hey you, follow an order</title>
|
12
|
+
<link></link>
|
13
|
+
<description></description>
|
14
|
+
<pubDate>Tue, 20 Apr 2000 04:00:00 GMT</pubDate>
|
15
|
+
<guid></guid>
|
16
|
+
</item>
|
17
|
+
</channel>
|
18
|
+
</rss>
|
19
|
+
RSS
|
20
|
+
response['Content-Type'] = 'text/xml'
|
21
|
+
end
|
22
|
+
|
23
|
+
%w(INT TERM).each do |signal|
|
24
|
+
trap signal do
|
25
|
+
server.shutdown
|
26
|
+
exit!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
server.start
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class CommandOutput
|
2
|
+
|
3
|
+
OUTPUT_FILE = File.expand_path(File.dirname(__FILE__) + '/../../../tmp/command_output')
|
4
|
+
|
5
|
+
TIMEOUT = 10
|
6
|
+
|
7
|
+
def self.contents
|
8
|
+
wait_for {File.exists? OUTPUT_FILE and !file_contents.empty?}
|
9
|
+
file_contents
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.clear
|
13
|
+
FileUtils.rm OUTPUT_FILE, :force => true
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add output
|
17
|
+
File.open(OUTPUT_FILE, 'a') {|f| f.write(output) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.wait_for &condition
|
23
|
+
time_spent = 0
|
24
|
+
until yield or time_spent > TIMEOUT
|
25
|
+
sleep 0.5
|
26
|
+
time_spent += 0.5
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.file_contents
|
31
|
+
File.read OUTPUT_FILE
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ScenarioProcess
|
2
|
+
|
3
|
+
@commands = []
|
4
|
+
|
5
|
+
def self.run command, name
|
6
|
+
log = File.expand_path(File.dirname(__FILE__) + "/../../../tmp/#{name}.log")
|
7
|
+
FileUtils.rm log, :force => true
|
8
|
+
process = IO.popen "(#{command}) > #{log} 2>&1"
|
9
|
+
process.sync = true
|
10
|
+
wait_for log
|
11
|
+
|
12
|
+
@commands << command
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.kill_all
|
16
|
+
# this method of killing stuff sucks, feels error prone
|
17
|
+
@commands.each do |command|
|
18
|
+
`ps`.each do |process|
|
19
|
+
# the space at the front of the pattern is essential...
|
20
|
+
# otherwise this will only work intermittently; if the pid you want to kill
|
21
|
+
# is shorter than others in the process list then the group will not capture it
|
22
|
+
matches = process.match /\s*(\d*).*#{Regexp.escape(command)}/
|
23
|
+
`kill #{matches[1]}` if matches
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.wait_for file
|
31
|
+
until File.exists? file
|
32
|
+
sleep 0.2
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/baldrick.rb
CHANGED
@@ -0,0 +1,115 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick
|
4
|
+
|
5
|
+
describe Command do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@servant = mock('servant', :null_object => true)
|
9
|
+
Servant.stub!(:new).and_return @servant
|
10
|
+
@stdout = stub('stdout', :null_object => true)
|
11
|
+
|
12
|
+
# including Singleton marks new as private but I still want to instantiate it for testing
|
13
|
+
# nasty, I know
|
14
|
+
@command = Command.send(:new)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'when executing' do
|
18
|
+
|
19
|
+
it 'registers the injour listener type by default' do
|
20
|
+
@command.listener_class_for(:injour).should == Listeners::InjourListener
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'registers the feed listener type by default' do
|
24
|
+
@command.listener_class_for(:feed).should == Listeners::FeedListener
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'displays a startup message' do
|
28
|
+
@command.stub!(:should_serve?).and_return false
|
29
|
+
|
30
|
+
@stdout.should_receive(:<<).with /started/
|
31
|
+
|
32
|
+
@command.execute @stdout
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'tells the servant to serve repeatedly' do
|
36
|
+
@command.stub!(:should_serve?).and_return true, true, false
|
37
|
+
@command.stub!(:sleep)
|
38
|
+
|
39
|
+
@servant.should_receive(:serve).twice
|
40
|
+
|
41
|
+
@command.execute @stdout
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'waits for 2 seconds between service by default' do
|
45
|
+
@command.stub!(:should_serve?).and_return true, false
|
46
|
+
|
47
|
+
@command.should_receive(:sleep).with(2)
|
48
|
+
|
49
|
+
@command.execute @stdout
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'allows wait period between service to be specified' do
|
53
|
+
@command.stub!(:should_serve?).and_return true, false
|
54
|
+
@command.listen_every 10
|
55
|
+
|
56
|
+
@command.should_receive(:sleep).with(10)
|
57
|
+
|
58
|
+
@command.execute @stdout
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'configuration' do
|
63
|
+
|
64
|
+
it 'can be told of how to listen for orders of a particular type' do
|
65
|
+
@command.register_listener_type :type, (listener_class = mock('listener class'))
|
66
|
+
|
67
|
+
@command.listener_class_for(:type).should == listener_class
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'when told where to listen for orders' do
|
71
|
+
|
72
|
+
it 'adds a new listener of a recognised type to the servant' do
|
73
|
+
listener_class = stub('listener class', :new => (listener = mock('listener')))
|
74
|
+
@command.stub!(:listener_class_for).with(:recognised_type).and_return listener_class
|
75
|
+
|
76
|
+
@servant.should_receive(:add_listener).with listener
|
77
|
+
|
78
|
+
@command.listen_to :recognised_type
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'configures a new listener with given options' do
|
82
|
+
listener_class = stub 'listener class'
|
83
|
+
@command.stub!(:listener_class_for).with(:listener_type).and_return listener_class
|
84
|
+
@servant.stub! :add_listener
|
85
|
+
|
86
|
+
listener_class.should_receive(:new).with({:option => true})
|
87
|
+
|
88
|
+
@command.listen_to :listener_type, :option => true
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'fails if the listener type is not recognised' do
|
92
|
+
@command.stub!(:listener_class_for).with(:unrecognised_type).and_return nil
|
93
|
+
|
94
|
+
lambda{@command.listen_to :unrecognised_type}.should raise_error(RuntimeError, 'No order listener implementation found for unrecognised_type')
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'when told how to perform a task' do
|
100
|
+
|
101
|
+
%w{to on_hearing}.each do |to_alias|
|
102
|
+
|
103
|
+
it "adds a new task to the servant using the instructions given via '#{to_alias}'" do
|
104
|
+
procedure = lambda {}
|
105
|
+
Task.stub!(:new).with(/put the kettle on/, procedure).and_return(task = stub('job'))
|
106
|
+
|
107
|
+
@servant.should_receive(:add_task).with task
|
108
|
+
|
109
|
+
@command.send to_alias, /put the kettle on/, &procedure
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick::Listeners
|
4
|
+
|
5
|
+
describe FeedListener do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@listener = FeedListener.new :at => ''
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'allows RSS URL to be specified' do
|
12
|
+
@listener = FeedListener.new :at => 'http://billywitchdoctor.com/rss'
|
13
|
+
FeedOrders.stub!(:within).and_return []
|
14
|
+
|
15
|
+
@listener.should_receive(:open).with 'http://billywitchdoctor.com/rss'
|
16
|
+
|
17
|
+
@listener.orders
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'only return orders newer than that last stored' do
|
21
|
+
|
22
|
+
now = Time.now
|
23
|
+
old_news = {:what => 'shake has gone...', :when => now}
|
24
|
+
new_news = {:what => 'newsflash! the drizzle is coming', :when => now + 1}
|
25
|
+
stale_news = {:what => 'carl is incredibly lonely', :when => now - 1}
|
26
|
+
latest_news = {:what => 'the drizzle is the shizzle!', :when => now + 2}
|
27
|
+
|
28
|
+
@listener.stub! :open
|
29
|
+
FeedOrders.stub!(:within).and_return [old_news], [old_news, new_news, stale_news, latest_news]
|
30
|
+
|
31
|
+
@listener.orders
|
32
|
+
|
33
|
+
@listener.orders.should == [new_news, latest_news]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "handles orders with no 'when' specified" do
|
37
|
+
@listener.stub! :open
|
38
|
+
FeedOrders.stub!(:within).and_return [{:what => 'shake has gone...', :when => nil}]
|
39
|
+
|
40
|
+
@listener.orders
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick::Listeners
|
4
|
+
|
5
|
+
describe FeedOrders do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@item_locator = mock('item locator')
|
9
|
+
XPathLocator.stub!(:from_xml).and_return @item_locator
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'first searches items then elements for orders' do
|
13
|
+
@item_locator.should_receive(:find_nodes_matching).with('//item', '//entry').and_return []
|
14
|
+
|
15
|
+
FeedOrders.within ''
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'creates an order for every item found' do
|
19
|
+
node = stub 'node', :null_object => true
|
20
|
+
@item_locator.stub!(:find_nodes_matching).and_return [node, node, node]
|
21
|
+
|
22
|
+
FeedOrders.within('').size.should == 3
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'when creating an order' do
|
26
|
+
|
27
|
+
before :each do
|
28
|
+
@node = stub 'node', :null_object => true
|
29
|
+
@item_locator.stub!(:find_nodes_matching).and_return [@node]
|
30
|
+
@text_locator = stub('item locator', :null_object => true)
|
31
|
+
XPathLocator.stub!(:from_node).and_return @text_locator
|
32
|
+
|
33
|
+
@time = Time.now
|
34
|
+
Time.stub!(:parse).and_return @time
|
35
|
+
end
|
36
|
+
|
37
|
+
it "parses the publish time and use it as 'when'" do
|
38
|
+
Time.stub!(:parse).with(@time.to_s).and_return @time
|
39
|
+
@text_locator.stub!(:find_text_matching).with('published/text()', 'pubDate/text()', 'date/text()', 'updated/text()').and_return @time.to_s
|
40
|
+
|
41
|
+
FeedOrders.within('').first[:when].should == @time
|
42
|
+
end
|
43
|
+
|
44
|
+
it "uses the contents of the item as 'what'" do
|
45
|
+
@text_locator.stub!(:find_text_matching).and_return stub('text', :null_object => true)
|
46
|
+
@node.stub!(:to_s).and_return 'item node contents'
|
47
|
+
|
48
|
+
FeedOrders.within('').first[:what].should == 'item node contents'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "uses the author as the 'who'" do
|
52
|
+
@text_locator.stub!(:find_text_matching).with('author/name/text()', 'author/text()').and_return 'Alan Moore'
|
53
|
+
|
54
|
+
FeedOrders.within('').first[:who].should == 'Alan Moore'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "uses the link as the 'where'" do
|
58
|
+
@text_locator.stub!(:find_text_matching).with('link/text()', "link[@rel='alternate']/@href").and_return 'http://internet.com'
|
59
|
+
|
60
|
+
FeedOrders.within('').first[:where].should == 'http://internet.com'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick::Listeners
|
4
|
+
|
5
|
+
describe InjourListener do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@listener = InjourListener.new
|
9
|
+
Time.stub!(:parse)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'only return new orders' do
|
13
|
+
|
14
|
+
first_status = <<-FIRST_STATUS
|
15
|
+
=== nathan on dk3.mordhaus.net:43215 ===
|
16
|
+
* [05-Feb-2009 11:30 PM] i like chips
|
17
|
+
FIRST_STATUS
|
18
|
+
second_status = <<-SECOND_STATUS
|
19
|
+
=== nathan on dk3.mordhaus.net:43215 ===
|
20
|
+
* [05-Feb-2009 11:30 PM] i like chips
|
21
|
+
=== murderface on dk4.mordhaus.net:43215 ===
|
22
|
+
* [05-Feb-2009 11:31 PM] no kidding
|
23
|
+
SECOND_STATUS
|
24
|
+
|
25
|
+
@listener.stub!(:`).with('injour ls').and_return first_status, second_status
|
26
|
+
|
27
|
+
@listener.orders
|
28
|
+
|
29
|
+
@listener.orders.should == [
|
30
|
+
{
|
31
|
+
:who => 'murderface',
|
32
|
+
:what => 'no kidding',
|
33
|
+
:where => 'dk4.mordhaus.net:43215',
|
34
|
+
:when => nil
|
35
|
+
}]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'handles multiple orders from a single user' do
|
39
|
+
|
40
|
+
status = <<-STATUS
|
41
|
+
=== skwisgaar on dk5.mordhaus.net:43215 ===
|
42
|
+
* [05-Feb-2009 11:30 PM] stops copies me!
|
43
|
+
=== skwisgaar on dk5.mordhaus.net:43215 ===
|
44
|
+
* [05-Feb-2009 11:31 PM] I means its, stops copies me!
|
45
|
+
=== skwisgaar on dk5.mordhaus.net:43215 ===
|
46
|
+
* [05-Feb-2009 11:32 PM] STOPS COPIES ME!
|
47
|
+
STATUS
|
48
|
+
|
49
|
+
@listener.stub!(:`).with('injour ls').and_return status
|
50
|
+
|
51
|
+
@listener.orders.collect{|order| order[:what]}.should == ['stops copies me!', 'I means its, stops copies me!', 'STOPS COPIES ME!']
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'reading individual parts of an order' do
|
55
|
+
|
56
|
+
before :each do
|
57
|
+
status = <<-STATUS
|
58
|
+
=== twinkletits on dk6.mordhaus.net:43215 ===
|
59
|
+
* [05-Feb-2009 11:30 PM] who wants a banana sticker?
|
60
|
+
STATUS
|
61
|
+
@listener.stub!(:`).with('injour ls').and_return status
|
62
|
+
|
63
|
+
@order = @listener.orders.first
|
64
|
+
end
|
65
|
+
|
66
|
+
it "interprets injour user as 'who'" do
|
67
|
+
@order[:who].should == 'twinkletits'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "interprets status as 'what'" do
|
71
|
+
@order[:what].should == 'who wants a banana sticker?'
|
72
|
+
end
|
73
|
+
|
74
|
+
it "interprets injour address as 'where'" do
|
75
|
+
@order[:where].should == 'dk6.mordhaus.net:43215'
|
76
|
+
end
|
77
|
+
|
78
|
+
it "interprets parsed time as 'when'" do
|
79
|
+
@order[:when].should == Time.parse('05-Feb-2009 11:30 PM')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick::Listeners
|
4
|
+
|
5
|
+
describe XPathLocator do
|
6
|
+
|
7
|
+
describe 'when created' do
|
8
|
+
|
9
|
+
it 'allows a new locator to be created from an XML string' do
|
10
|
+
locator = XPathLocator.from_xml '<root><child/></root>'
|
11
|
+
|
12
|
+
locator.find_nodes_matching('//child').should_not be_empty
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows a new locator to be created from a previously returned node' do
|
16
|
+
node = XPathLocator.from_xml('<root><child><grandchild/></child></root>').find_nodes_matching('//child').first
|
17
|
+
locator = XPathLocator.from_node node
|
18
|
+
|
19
|
+
locator.find_nodes_matching('//grandchild').should_not be_empty
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'when finding nodes' do
|
24
|
+
|
25
|
+
it 'finds all nodes matching any of the given xpath expressions' do
|
26
|
+
locator = XPathLocator.from_xml '<root><match/></root>'
|
27
|
+
|
28
|
+
locator.find_nodes_matching('//nomatches', '//match').size.should == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'handles tags with a default namespace' do
|
32
|
+
locator = XPathLocator.from_xml '<root xmlns="http://namespace.com"><match/></root>'
|
33
|
+
|
34
|
+
locator.find_nodes_matching('//match').size.should == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'finds only the first text matching any of the given xpath expressions' do
|
38
|
+
locator = XPathLocator.from_xml '<root><match>first</match><match>second</match></root>'
|
39
|
+
|
40
|
+
locator.find_text_matching('//match').should == 'first'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick
|
4
|
+
|
5
|
+
describe RunServant do
|
6
|
+
|
7
|
+
describe 'when exiting' do
|
8
|
+
it 'rethrows any exceptions thrown in the script body'
|
9
|
+
it 'causes the command to execute'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'delegates all calls for missing methods to the command'
|
13
|
+
it 'causes an interrupt to stop execution of the command'
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick
|
4
|
+
|
5
|
+
describe Servant do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@servant = Servant.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'executes all orders from all listeners' do
|
12
|
+
2.times do |listener_number|
|
13
|
+
orders = [stub("order #{listener_number} a"), stub("order #{listener_number} b")]
|
14
|
+
listener = mock("listener #{listener_number}", :orders => orders)
|
15
|
+
@servant.add_listener listener
|
16
|
+
|
17
|
+
orders.each {|order| @servant.should_receive(:follow).with order}
|
18
|
+
end
|
19
|
+
|
20
|
+
@servant.serve
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'performs all tasks for an executed order' do
|
24
|
+
order = stub('order')
|
25
|
+
@servant.add_listener mock('listener', :orders => [order])
|
26
|
+
2.times do |task_number|
|
27
|
+
|
28
|
+
@servant.add_task(task = mock("task #{task_number}"))
|
29
|
+
|
30
|
+
task.should_receive(:run).with order
|
31
|
+
end
|
32
|
+
|
33
|
+
@servant.serve
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'recovers from a problem with a listener and continues' do
|
37
|
+
@servant.add_listener(first_listener = mock('first listener'))
|
38
|
+
@servant.add_listener(second_listener = mock('second listener'))
|
39
|
+
first_listener.stub!(:orders).and_raise Exception
|
40
|
+
|
41
|
+
second_listener.should_receive(:orders).and_return []
|
42
|
+
|
43
|
+
@servant.serve
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Baldrick
|
4
|
+
|
5
|
+
describe Task do
|
6
|
+
|
7
|
+
it 'checks the what value of the command when determining whether or not to run' do
|
8
|
+
@matcher = mock('matcher')
|
9
|
+
|
10
|
+
@matcher.should_receive(:match).with 'what value'
|
11
|
+
|
12
|
+
run :what => 'what value'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'runs if order matches' do
|
16
|
+
@matcher = stub('matcher', :match => [''])
|
17
|
+
@procedure = stub('procedure', :arity => 1)
|
18
|
+
|
19
|
+
@procedure.should_receive :call
|
20
|
+
|
21
|
+
run ''
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't run if order does not match" do
|
25
|
+
@matcher = stub('matcher', :match => nil)
|
26
|
+
@procedure = stub('procedure', :arity => 1)
|
27
|
+
|
28
|
+
@procedure.should_not_receive :call
|
29
|
+
|
30
|
+
run ''
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'only passes as many arguments as the procedure can handle' do
|
34
|
+
@matcher = stub('matcher', :match => ['', '1', '2', '3', '4'])
|
35
|
+
@procedure = stub('procedure', :arity => 3)
|
36
|
+
|
37
|
+
@procedure.should_receive(:call).with('1', '2', '3')
|
38
|
+
|
39
|
+
run ''
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't pass the matched string to the procedure" do
|
43
|
+
@matcher = stub('matcher', :match => ['matching string', '1', '2'])
|
44
|
+
@procedure = stub('procedure', :arity => 1)
|
45
|
+
|
46
|
+
@procedure.should_receive(:call).with('1')
|
47
|
+
|
48
|
+
run ''
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'passes all matching portions of the command and the command to the procedure' do
|
52
|
+
@matcher = stub('matcher', :match => ['', 'com', 'mand'])
|
53
|
+
@procedure = stub('procedure', :arity => 3)
|
54
|
+
|
55
|
+
@procedure.should_receive(:call).with('com', 'mand', {:what => 'command'})
|
56
|
+
|
57
|
+
run :what => 'command'
|
58
|
+
end
|
59
|
+
|
60
|
+
def run order
|
61
|
+
Task.new(@matcher, @procedure).run order
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: baldrick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brent Snook
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-14 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.2
|
33
|
+
version: 1.5.2
|
34
34
|
version:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: rspec
|
@@ -52,16 +52,6 @@ dependencies:
|
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 0.1.16
|
54
54
|
version:
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: injour
|
57
|
-
type: :development
|
58
|
-
version_requirement:
|
59
|
-
version_requirements: !ruby/object:Gem::Requirement
|
60
|
-
requirements:
|
61
|
-
- - ">="
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: 0.2.3
|
64
|
-
version:
|
65
55
|
- !ruby/object:Gem::Dependency
|
66
56
|
name: hoe
|
67
57
|
type: :development
|
@@ -70,9 +60,13 @@ dependencies:
|
|
70
60
|
requirements:
|
71
61
|
- - ">="
|
72
62
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
63
|
+
version: 2.3.3
|
74
64
|
version:
|
75
|
-
description:
|
65
|
+
description: |-
|
66
|
+
A dogsbody. Does what you tell it. Use it to glue things together.
|
67
|
+
|
68
|
+
Baldrick recognises orders and then performs appropriate tasks.
|
69
|
+
Baldrick acts as the glue between the source of the order (for example a twitter Atom feed) and the task you would like performed (put the kettle on).
|
76
70
|
email:
|
77
71
|
- brent@fuglylogic.com
|
78
72
|
executables: []
|
@@ -82,28 +76,54 @@ extensions: []
|
|
82
76
|
extra_rdoc_files:
|
83
77
|
- History.txt
|
84
78
|
- Manifest.txt
|
85
|
-
-
|
79
|
+
- TODO.txt
|
86
80
|
files:
|
87
81
|
- History.txt
|
88
82
|
- Manifest.txt
|
89
83
|
- README.rdoc
|
90
84
|
- Rakefile
|
85
|
+
- TODO.txt
|
86
|
+
- features/follow_feed_orders.feature
|
87
|
+
- features/follow_injour_orders.feature
|
88
|
+
- features/resources/a_feed_servant
|
89
|
+
- features/resources/common_configuration
|
90
|
+
- features/resources/feed_server
|
91
|
+
- features/resources/injour_servant
|
92
|
+
- features/steps/feed_steps.rb
|
93
|
+
- features/steps/helpers/command_output.rb
|
94
|
+
- features/steps/helpers/scenario_process.rb
|
95
|
+
- features/steps/injour_steps.rb
|
96
|
+
- features/steps/order_steps.rb
|
97
|
+
- features/steps/servant_steps.rb
|
98
|
+
- features/support/env.rb
|
91
99
|
- lib/baldrick.rb
|
92
|
-
- lib/baldrick_serve.rb
|
93
100
|
- lib/baldrick/command.rb
|
94
|
-
- lib/baldrick/run_servant.rb
|
95
|
-
- lib/baldrick/servant.rb
|
96
|
-
- lib/baldrick/task.rb
|
97
101
|
- lib/baldrick/listeners/feed_listener.rb
|
98
102
|
- lib/baldrick/listeners/feed_orders.rb
|
99
103
|
- lib/baldrick/listeners/injour_listener.rb
|
100
104
|
- lib/baldrick/listeners/xpath_locator.rb
|
105
|
+
- lib/baldrick/run_servant.rb
|
106
|
+
- lib/baldrick/servant.rb
|
107
|
+
- lib/baldrick/task.rb
|
108
|
+
- lib/baldrick_serve.rb
|
101
109
|
- script/console
|
102
110
|
- script/destroy
|
103
111
|
- script/generate
|
112
|
+
- spec/lib/baldrick/command_spec.rb
|
113
|
+
- spec/lib/baldrick/listeners/feed_listener_spec.rb
|
114
|
+
- spec/lib/baldrick/listeners/feed_orders_spec.rb
|
115
|
+
- spec/lib/baldrick/listeners/injour_listener_spec.rb
|
116
|
+
- spec/lib/baldrick/listeners/xpath_locator_spec.rb
|
117
|
+
- spec/lib/baldrick/run_servant_spec.rb
|
118
|
+
- spec/lib/baldrick/servant_spec.rb
|
119
|
+
- spec/lib/baldrick/task_spec.rb
|
120
|
+
- spec/spec.opts
|
121
|
+
- spec/spec_helper.rb
|
104
122
|
- tasks/rspec.rake
|
105
123
|
has_rdoc: true
|
106
124
|
homepage: http://github.com/brentsnook/baldrick
|
125
|
+
licenses: []
|
126
|
+
|
107
127
|
post_install_message:
|
108
128
|
rdoc_options:
|
109
129
|
- --main
|
@@ -125,9 +145,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
145
|
requirements: []
|
126
146
|
|
127
147
|
rubyforge_project: baldrick
|
128
|
-
rubygems_version: 1.3.
|
148
|
+
rubygems_version: 1.3.5
|
129
149
|
signing_key:
|
130
|
-
specification_version:
|
131
|
-
summary:
|
150
|
+
specification_version: 3
|
151
|
+
summary: A dogsbody
|
132
152
|
test_files: []
|
133
153
|
|