bumps 0.0.1

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.
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+
3
+ module Bumps
4
+ class Configuration
5
+
6
+ def self.method_missing method, *args, &block
7
+ singleton.send method, *args, &block
8
+ end
9
+
10
+ def self.singleton
11
+ @singleton = Configuration.new unless defined? @singleton
12
+ @singleton
13
+ end
14
+
15
+ def initialize
16
+ @config = {
17
+ :output_stream => STDOUT,
18
+ :results_formatter => Cucumber::Formatter::Html
19
+ }
20
+ end
21
+
22
+ def method_missing method, *args
23
+ return @config[method] if @config.has_key?(method)
24
+ method.to_s.end_with?('=') ? @config[method.to_s.chop.to_sym] = args[0] : super(method, args)
25
+ end
26
+
27
+ def configure &block
28
+ instance_eval(&block)
29
+ end
30
+
31
+ def use_server server
32
+ @config[:pull_url] = URI.join(server, 'features/content').to_s
33
+ @config[:push_url] = URI.join(server, 'features/results').to_s
34
+ end
35
+
36
+ def push_to url
37
+ @config[:push_url] = url
38
+ end
39
+
40
+ def pull_from url
41
+ @config[:pull_url] = url
42
+ end
43
+
44
+ def format_results_with formatter_class
45
+ @config[:results_formatter] = formatter_class
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ module Bumps
2
+ class Feature
3
+
4
+ attr_accessor :name, :content
5
+
6
+ def initialize
7
+ @name = ''
8
+ @content = ''
9
+ end
10
+
11
+ def self.pull
12
+ Configuration.output_stream << "\nRetrieving features from #{Configuration.pull_url} ...\n"
13
+ begin
14
+ features = RemoteFeature.fetch(Configuration.pull_url)
15
+ rescue Exception => e
16
+ Configuration.output_stream << "\nCould not pull features: #{e}\n"
17
+ features = []
18
+ end
19
+
20
+ features.each do |feature|
21
+ feature.write_to Configuration.feature_directory
22
+ end
23
+ Configuration.output_stream << "Wrote #{features.size} features to #{Configuration.feature_directory}\n\n"
24
+ end
25
+
26
+ def write_to directory
27
+ write_content_to absolute_path_under(directory)
28
+ end
29
+
30
+ def write_content_to file_path
31
+ FileUtils.makedirs File.dirname(file_path)
32
+ File.open(file_path, 'w') {|f| f.write(content) }
33
+ end
34
+
35
+ def absolute_path_under directory
36
+ expanded_directory = File.expand_path directory
37
+ file_path = File.expand_path(File.join(directory, name))
38
+ unless file_path.start_with? expanded_directory
39
+ raise "Could not write feature to path #{file_path}, path is not below #{expanded_directory}"
40
+ end
41
+ file_path
42
+ end
43
+
44
+ def eql? match
45
+ self.instance_variables.each do |attr|
46
+ self_attr = self.instance_variable_get(attr)
47
+ match_attr = match.instance_variable_get(attr)
48
+ return false unless self_attr == match_attr
49
+ end
50
+
51
+ true
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ module Bumps::HookTasks
2
+
3
+ SetFeatureDirectoryTask = lambda do
4
+ # nasty? hell yeah. got any better ideas? need access to that protected method...
5
+ feature_directories = configuration.send :feature_dirs
6
+
7
+ error_message = 'More than one feature directory/file was specified. ' +
8
+ 'Please only specify a single feature directory when using bumps'
9
+ raise error_message if feature_directories.size > 1
10
+ Bumps::Configuration.feature_directory = feature_directories.first
11
+ end
12
+
13
+ PullFeaturesTask = lambda do
14
+ Bumps::Feature.pull
15
+ end
16
+
17
+ RegisterPushFormatterTask = lambda do
18
+ configuration.options[:formats]['Bumps::ResultsPushFormatter'] = Bumps::Configuration.output_stream
19
+ end
20
+
21
+ SetOutputStreamTask = lambda do
22
+ Bumps::Configuration.output_stream = @out_stream
23
+ end
24
+
25
+ end
@@ -0,0 +1,27 @@
1
+ module Bumps
2
+ class PreFeatureLoadHook
3
+ def self.register_on clazz
4
+ clazz.class_eval do
5
+
6
+ alias_method :original_load_plain_text_features, :load_plain_text_features
7
+
8
+ def bumps_load_plain_text_features
9
+ Bumps::PreFeatureLoadHook.tasks.each { |task| instance_eval(&task) }
10
+ original_load_plain_text_features
11
+ end
12
+
13
+ alias_method :load_plain_text_features, :bumps_load_plain_text_features
14
+
15
+ end
16
+ end
17
+
18
+ def self.tasks
19
+ [
20
+ HookTasks::SetFeatureDirectoryTask,
21
+ Bumps::HookTasks::SetOutputStreamTask,
22
+ HookTasks::PullFeaturesTask,
23
+ HookTasks::RegisterPushFormatterTask
24
+ ]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'nokogiri'
2
+
3
+ require 'open-uri'
4
+
5
+ module Bumps
6
+
7
+ class RemoteFeature
8
+
9
+ def self.fetch location
10
+ parse(open(location){|f| f.read})
11
+ end
12
+
13
+ def self.parse xml
14
+ document = Nokogiri::XML xml
15
+ document.search('feature').collect do |feature_element|
16
+ feature = Feature.new
17
+ feature.content = feature_element.text.strip
18
+ feature.name = feature_element.attribute('name').to_s
19
+ feature
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ require 'cucumber/formatter/html'
2
+ require 'net/http'
3
+
4
+ module Bumps
5
+ class ResultsPushFormatter < Cucumber::Ast::Visitor
6
+
7
+ def initialize(step_mother, io, options)
8
+ super step_mother
9
+ @step_mother = step_mother
10
+ @io = io
11
+ @options = options
12
+ end
13
+
14
+ def visit_features features
15
+ push results_of_running(features)
16
+ end
17
+
18
+ def push results
19
+ uri = URI.parse(Bumps::Configuration.push_url)
20
+ response, body = Net::HTTP.post_form uri, {:results => results}
21
+ if response.code_type == Net::HTTPOK
22
+ @io << "Successfully pushed results to #{Bumps::Configuration.push_url}\n\n"
23
+ else
24
+ @io << "Failed to push results to #{Bumps::Configuration.push_url} - HTTP #{response.code}: \n#{response.body}\n\n"
25
+ end
26
+ end
27
+
28
+ def results_of_running features
29
+ StringIO.open do |io|
30
+ formatter = Bumps::Configuration.results_formatter.new step_mother, io, options
31
+ formatter.visit_features features
32
+
33
+ io.string
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/bumps.rb'}"
9
+ puts "Loading bumps gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,78 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Bumps::Configuration do
4
+
5
+ before {@output_stream = mock('output stream').as_null_object}
6
+ subject {Bumps::Configuration.new}
7
+
8
+ it 'should provide access to the output stream' do
9
+ out_stream = mock 'out stream'
10
+ subject.output_stream = out_stream
11
+
12
+ subject.output_stream.should == out_stream
13
+ end
14
+
15
+ it 'should allow configuration using a block' do
16
+ subject.should_receive(:configuration_call)
17
+
18
+ subject.configure { configuration_call }
19
+ end
20
+
21
+ it 'should derive pull URL from server' do
22
+ subject.use_server 'http://server'
23
+
24
+ subject.pull_url.should == 'http://server/features/content'
25
+ end
26
+
27
+ it 'should derive push URL from server' do
28
+ subject.use_server 'http://server'
29
+
30
+ subject.push_url.should == 'http://server/features/results'
31
+ end
32
+
33
+ it 'should be able to handle a server URL with a trailing slash' do
34
+ subject.use_server 'http://server/'
35
+
36
+ subject.pull_url.should match(/^http:\/\/server\/[a-z]/)
37
+ end
38
+
39
+ it 'should allow a non-default push URL to be specified' do
40
+ subject.push_to 'http://url.com'
41
+
42
+ subject.push_url.should == 'http://url.com'
43
+ end
44
+
45
+ it 'should allow a non-default pull URL to be specified' do
46
+ subject.pull_from 'http://url.com'
47
+
48
+ subject.pull_url.should == 'http://url.com'
49
+ end
50
+
51
+ it 'should allow the feature directory to be set' do
52
+ subject.feature_directory = 'feature_directory'
53
+
54
+ subject.feature_directory.should == 'feature_directory'
55
+ end
56
+
57
+ it 'should allow results formatter to be specified' do
58
+ formatter_class = mock 'formatter class'
59
+
60
+ subject.format_results_with formatter_class
61
+
62
+ subject.results_formatter.should == formatter_class
63
+ end
64
+
65
+ it 'should default the results formatter to Cucumber HTML formatter' do
66
+ subject.results_formatter.should == Cucumber::Formatter::Html
67
+ end
68
+
69
+ it 'should allow access to configuration via class' do
70
+ singleton = mock 'singleton'
71
+ Bumps::Configuration.stub!(:singleton).and_return singleton
72
+
73
+ singleton.should_receive(:configuration_property=).with 'arg'
74
+
75
+ Bumps::Configuration.configuration_property = 'arg'
76
+ end
77
+
78
+ end
@@ -0,0 +1,119 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Bumps::Feature do
4
+ describe 'when pulling' do
5
+
6
+ before do
7
+ @output_stream = mock('output stream').as_null_object
8
+ Bumps::Configuration.stub!(:output_stream).and_return @output_stream
9
+ end
10
+
11
+ subject {Bumps::Feature}
12
+
13
+ it 'should write fetched features to the feature directory' do
14
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
15
+ Bumps::Configuration.stub!(:pull_url).and_return 'location'
16
+
17
+ features = 3.times.collect do |index|
18
+ feature = mock "feature #{index}"
19
+ feature.should_receive(:write_to).with 'feature_directory'
20
+ feature
21
+ end
22
+
23
+ Bumps::RemoteFeature.stub!(:fetch).with('location').and_return features
24
+
25
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
26
+
27
+ subject.pull
28
+ end
29
+
30
+ it 'should output an error message if the features could not be fetched' do
31
+ Bumps::Configuration.stub! :pull_url
32
+ Bumps::Configuration.stub! :feature_directory
33
+ Bumps::RemoteFeature.stub!(:fetch).and_raise "exception message"
34
+
35
+ @output_stream.should_receive(:<<).with "\nCould not pull features: exception message\n"
36
+
37
+ subject.pull
38
+ end
39
+
40
+ it 'should display which location the features are being retrieved from' do
41
+ Bumps::RemoteFeature.stub!(:fetch).and_return []
42
+ Bumps::Configuration.stub!(:pull_url).and_return 'pull_url'
43
+ Bumps::Configuration.stub! :feature_directory
44
+
45
+ @output_stream.should_receive(:<<).with "\nRetrieving features from pull_url ...\n"
46
+
47
+ subject.pull
48
+ end
49
+
50
+ it 'should display the total number of features retrieved and location they were written to' do
51
+ features = 3.times.collect{|index| mock("feature #{index}").as_null_object}
52
+ Bumps::RemoteFeature.stub!(:fetch).and_return features
53
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
54
+ Bumps::Configuration.stub! :pull_url
55
+
56
+ @output_stream.should_receive(:<<).with "Wrote 3 features to feature_directory\n\n"
57
+
58
+ subject.pull
59
+ end
60
+ end
61
+
62
+ describe 'when writing self to file' do
63
+
64
+ it 'should determine absolute path before writing contents' do
65
+ subject.stub(:absolute_path_under).with('directory').and_return 'path'
66
+
67
+ subject.should_receive(:write_content_to).with 'path'
68
+
69
+ subject.write_to 'directory'
70
+ end
71
+ end
72
+
73
+ describe 'when determining absolute feature file path' do
74
+
75
+ it 'should construct file name from expanded directory and feature name' do
76
+ subject.stub!(:name).and_return 'name'
77
+
78
+ subject.absolute_path_under('/a/b/c/..').should == '/a/b/name'
79
+ end
80
+
81
+ it 'should fail if given path does not resolve to one below the feature directory' do
82
+ subject.stub!(:name).and_return '../../etc/bashrc'
83
+ File.stub! :open # just in case
84
+
85
+ lambda {subject.absolute_path_under '/stuff/features'}.should raise_error 'Could not write feature to path /etc/bashrc, path is not below /stuff/features'
86
+ end
87
+ end
88
+
89
+ describe 'when writing content' do
90
+
91
+ it 'should overwrite existing files' do
92
+ FileUtils.stub! :makedirs
93
+
94
+ File.should_receive(:open).with anything, 'w'
95
+
96
+ subject.write_content_to ''
97
+ end
98
+
99
+ it 'should force the creation of directories in the feature name' do
100
+ File.stub! :open
101
+
102
+ FileUtils.should_receive(:makedirs).with 'features_dir/subdir'
103
+
104
+ subject.write_content_to 'features_dir/subdir/featurename.feature'
105
+ end
106
+
107
+ it 'should write content to file' do
108
+ FileUtils.stub! :makedirs
109
+ @file = mock 'file'
110
+ File.stub!(:open).and_yield @file
111
+ subject.stub!(:content).and_return 'content'
112
+
113
+ @file.should_receive(:write).with 'content'
114
+
115
+ subject.write_content_to ''
116
+ end
117
+ end
118
+
119
+ end