baldrick 0.0.1 → 0.0.2
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.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
|
|