cellophane 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.cellophane.yaml +1 -0
- data/.cellophane.yaml.sample +7 -0
- data/.document +4 -0
- data/LICENSE.txt +20 -0
- data/README.textile +269 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/bin/cellophane +14 -0
- data/features/feature_paths.feature +29 -0
- data/features/glob_pattern.feature +173 -0
- data/features/project_options.feature +29 -0
- data/features/regular_expression_pattern.feature +94 -0
- data/features/step_definitions.feature +47 -0
- data/features/support/cellophane_methods.rb +29 -0
- data/features/support/cellophane_steps.rb +43 -0
- data/features/support/env.rb +17 -0
- data/features/tags.feature +55 -0
- data/lib/cellophane/main.rb +82 -0
- data/lib/cellophane/options.rb +145 -0
- data/lib/cellophane/parser.rb +137 -0
- data/spec/cellophane_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +87 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
Feature: Step Definitions
|
2
|
+
|
3
|
+
@1
|
4
|
+
Scenario: Requiring existing step definition
|
5
|
+
|
6
|
+
Given a project directory with the following structure
|
7
|
+
| type | path |
|
8
|
+
| directory | features |
|
9
|
+
| directory | features/step_definitions |
|
10
|
+
| file | features/one.feature |
|
11
|
+
| file | features/two.feature |
|
12
|
+
| file | features/step_definitions/one_steps.rb |
|
13
|
+
When Cellophane is called with "one"
|
14
|
+
Then the command should include "features/one.feature"
|
15
|
+
And the command should include "-r features/step_definitions/one_steps.rb"
|
16
|
+
|
17
|
+
@2
|
18
|
+
Scenario: Not requiring step definition that doesn't exist
|
19
|
+
|
20
|
+
Given a project directory with the following structure
|
21
|
+
| type | path |
|
22
|
+
| directory | features |
|
23
|
+
| directory | features/step_definitions |
|
24
|
+
| file | features/one.feature |
|
25
|
+
| file | features/two.feature |
|
26
|
+
| file | features/step_definitions/one_steps.rb |
|
27
|
+
When Cellophane is called with "two"
|
28
|
+
Then the command should include "features/two.feature"
|
29
|
+
And the command should not include "-r features/step_definitions/two_steps.rb"
|
30
|
+
|
31
|
+
@3
|
32
|
+
Scenario: Step definitions nested in feature subdirectories
|
33
|
+
|
34
|
+
Given a project directory with the following structure
|
35
|
+
| type | path |
|
36
|
+
| directory | features |
|
37
|
+
| directory | features/admin |
|
38
|
+
| directory | features/admin/step_definitions |
|
39
|
+
| file | features/admin/one.feature |
|
40
|
+
| file | features/admin/two.feature |
|
41
|
+
| file | features/admin/step_definitions/one_steps.rb |
|
42
|
+
And a project options file with the following options
|
43
|
+
| option |
|
44
|
+
| step_path: {nested_in: step_definitions} |
|
45
|
+
When Cellophane is called with "admin/one"
|
46
|
+
Then the command should include "features/admin/one.feature"
|
47
|
+
And the command should include "-r features/admin/step_definitions/one_steps.rb"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CellophaneMethods
|
2
|
+
def call_cellophane(args = [])
|
3
|
+
cellophane = Cellophane::Main.new(args)
|
4
|
+
@command = cellophane.command
|
5
|
+
@message = cellophane.message
|
6
|
+
end
|
7
|
+
|
8
|
+
def output_command
|
9
|
+
puts "\n\n#{@command}\n\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
def output_message
|
13
|
+
puts "\n\n#{@message}\n\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
def save_initial_dir
|
17
|
+
@initial_dir = Dir.pwd
|
18
|
+
end
|
19
|
+
|
20
|
+
def restore_initial_dir
|
21
|
+
Dir.chdir(@initial_dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_project_dir_removed
|
25
|
+
FileUtils.remove_dir(@project_dir, true) if @project_dir && File.exist?(@project_dir)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
World(CellophaneMethods)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
Given /^I debug$/ do
|
2
|
+
require 'ruby-debug'
|
3
|
+
debugger
|
4
|
+
puts "\n\ndebugging\n\n"
|
5
|
+
end
|
6
|
+
|
7
|
+
Given /^Cellophane is called with "([^"]*)"$/ do |args|
|
8
|
+
call_cellophane(args.split(' '))
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^the (command|message) should include "([^"]+)"$/ do |what, expected|
|
12
|
+
(what == 'command' ? @command : @message).should =~ /#{expected}/
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^the (command|message) should not include "([^"]+)"$/ do |what, expected|
|
16
|
+
(what == 'command' ? @command : @message).should_not =~ /#{expected}/
|
17
|
+
end
|
18
|
+
|
19
|
+
Given /^a project directory with the following structure$/ do |structure|
|
20
|
+
@project_dir = './test_project'
|
21
|
+
# just in case the last run failed to exit cleanly, delete the test_project directory
|
22
|
+
# if it exists
|
23
|
+
FileUtils.remove_dir(@project_dir, true) if File.exist?(@project_dir)
|
24
|
+
FileUtils.mkdir(@project_dir)
|
25
|
+
Dir.chdir(@project_dir)
|
26
|
+
|
27
|
+
structure.hashes.each do |entry|
|
28
|
+
if entry[:type] == 'directory'
|
29
|
+
FileUtils.mkdir_p(entry[:path])
|
30
|
+
else
|
31
|
+
File.open(entry[:path], 'w') {|f| f.write("# #{entry[:path]}") }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Given /^a project options file with the following options$/ do |lines|
|
37
|
+
options = lines.hashes.collect { |line| line[:option] }
|
38
|
+
|
39
|
+
contents = options.join("\n")
|
40
|
+
|
41
|
+
# already in the project dir
|
42
|
+
File.open(Cellophane::PROJECT_OPTIONS_FILE, 'w') { |f| f.write(contents) }
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'cellophane/main'
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
Before do
|
6
|
+
save_initial_dir
|
7
|
+
ensure_project_dir_removed
|
8
|
+
end
|
9
|
+
|
10
|
+
Before('@debug') do
|
11
|
+
require 'ruby-debug'
|
12
|
+
end
|
13
|
+
|
14
|
+
After do
|
15
|
+
restore_initial_dir
|
16
|
+
ensure_project_dir_removed
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Feature: Tags
|
2
|
+
|
3
|
+
@1
|
4
|
+
Scenario: OR tag
|
5
|
+
|
6
|
+
When Cellophane is called with "-t one,two"
|
7
|
+
Then the command should include "-t @one,@two"
|
8
|
+
|
9
|
+
@2
|
10
|
+
Scenario: NOT tag
|
11
|
+
|
12
|
+
When Cellophane is called with "-t one,~two"
|
13
|
+
Then the command should include "-t @one -t ~@two"
|
14
|
+
|
15
|
+
@3
|
16
|
+
Scenario: AND tag
|
17
|
+
|
18
|
+
When Cellophane is called with "-t one,+two"
|
19
|
+
Then the command should include "-t @one -t @two"
|
20
|
+
|
21
|
+
@4
|
22
|
+
Scenario: Mixed tags in a logical order
|
23
|
+
|
24
|
+
When Cellophane is called with "-t one,two,~three,+four"
|
25
|
+
Then the command should include "-t @one,@two -t @four -t ~@three"
|
26
|
+
|
27
|
+
@5
|
28
|
+
Scenario: Mixed tags not in a logical order
|
29
|
+
|
30
|
+
When Cellophane is called with "-t +four,one,~three,two"
|
31
|
+
Then the command should include "-t @one,@two -t @four -t ~@three"
|
32
|
+
|
33
|
+
@6
|
34
|
+
Scenario: Numeric OR tag ranges
|
35
|
+
|
36
|
+
When Cellophane is called with "-t 1-3"
|
37
|
+
Then the command should include "-t @1,@2,@3"
|
38
|
+
|
39
|
+
@7
|
40
|
+
Scenario: Numeric NOT tag ranges
|
41
|
+
|
42
|
+
When Cellophane is called with "-t ~1-3"
|
43
|
+
Then the command should include "-t ~@1 -t ~@2 -t ~@3"
|
44
|
+
|
45
|
+
@8
|
46
|
+
Scenario: Numeric OR tag range with a NOT in the OR range
|
47
|
+
|
48
|
+
When Cellophane is called with "-t 1-3,~2"
|
49
|
+
Then the command should include "-t @1,@3"
|
50
|
+
|
51
|
+
@9
|
52
|
+
Scenario: Numeric OR tag range with a NOT out of the OR range
|
53
|
+
|
54
|
+
When Cellophane is called with "-t 1-3,~slow"
|
55
|
+
Then the command should include "-t @1,@2,@3 -t ~@slow"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'cellophane/parser'
|
3
|
+
require 'cellophane/options'
|
4
|
+
|
5
|
+
module Cellophane
|
6
|
+
|
7
|
+
PROJECT_OPTIONS_FILE = '.cellophane.yaml'
|
8
|
+
|
9
|
+
class Main
|
10
|
+
attr_reader :command, :message, :project_options_file
|
11
|
+
|
12
|
+
def initialize(args = nil)
|
13
|
+
args ||= ARGV
|
14
|
+
@project_options_file = Cellophane::PROJECT_OPTIONS_FILE
|
15
|
+
@options = Cellophane::Options.parse(args)
|
16
|
+
|
17
|
+
@message = 'Invalid regular expression provided.' and return if @options[:regexp] && @options[:pattern].nil?
|
18
|
+
|
19
|
+
parser = Cellophane::Parser.new(@options)
|
20
|
+
@features = parser.features
|
21
|
+
|
22
|
+
@message = 'No features matching PATTERN were found.' and return unless @features
|
23
|
+
|
24
|
+
@tags = parser.tags
|
25
|
+
|
26
|
+
@command = generate_command
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
puts @message and return if @message
|
31
|
+
@options[:print] ? puts(@command) : system("#{@command}\n\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def generate_command
|
37
|
+
cuke_cmd = "cucumber #{@options[:cucumber]}"
|
38
|
+
|
39
|
+
features = []
|
40
|
+
steps = []
|
41
|
+
|
42
|
+
if @features.any?
|
43
|
+
@features.each do |file|
|
44
|
+
file_parts = split_feature(file)
|
45
|
+
features << construct_feature_file(file_parts[:path], file_parts[:name])
|
46
|
+
steps << construct_step_file(file_parts[:path], file_parts[:name])
|
47
|
+
end
|
48
|
+
|
49
|
+
else
|
50
|
+
# if there are no features explicitly identified, then cucumber will run all. However,
|
51
|
+
# if we are using non-standard locations for features or step definitions, we must tell
|
52
|
+
# cucumber accordingly
|
53
|
+
features << @options[:feature_path] if @options[:non_standard_feature_path]
|
54
|
+
steps << @options[:step_path] if @options[:non_standard_step_path]
|
55
|
+
end
|
56
|
+
|
57
|
+
requires = (@options[:requires] + steps).compact.uniq
|
58
|
+
cuke_cmd += " -r #{requires.join(' -r ')}" if requires.any?
|
59
|
+
cuke_cmd += " #{features.join(' ')}" if features.any?
|
60
|
+
return "#{cuke_cmd} #{@tags}".gsub(' ', ' ')
|
61
|
+
end
|
62
|
+
|
63
|
+
def construct_feature_file(path, file)
|
64
|
+
"#{@options[:feature_path]}/#{path}/#{file}.feature".gsub('//', '/')
|
65
|
+
end
|
66
|
+
|
67
|
+
def construct_step_file(path, file)
|
68
|
+
step_path = @options[:step_path].is_a?(Hash) ? "#{@options[:feature_path]}/#{path}/#{@options[:step_path][:nested_in]}" : "#{@options[:step_path]}/#{path}"
|
69
|
+
step_file = "#{step_path}/#{file}_steps.rb".gsub('//', '/')
|
70
|
+
return File.exist?(step_file) ? step_file : nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def split_feature(file)
|
74
|
+
name = File.basename(file, '.feature')
|
75
|
+
# now get rid of the file_name and the feature_path
|
76
|
+
path = File.dirname(file).gsub(@options[:feature_path_regexp], '')
|
77
|
+
return {:path => path, :name => name}
|
78
|
+
end
|
79
|
+
|
80
|
+
end # class Main
|
81
|
+
end # module Cellophane
|
82
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Cellophane
|
5
|
+
class Options
|
6
|
+
def self.parse(args)
|
7
|
+
default_options = self.get_options(:default)
|
8
|
+
project_options = self.get_options(:project)
|
9
|
+
merged_options = default_options.merge(project_options)
|
10
|
+
|
11
|
+
option_parser = OptionParser.new do |opts|
|
12
|
+
# Set a banner, displayed at the top of the help screen.
|
13
|
+
# TODO add example usage including ~feature, patterns, and ~tag
|
14
|
+
opts.banner = "Usage: cellophane [options] PATTERN"
|
15
|
+
|
16
|
+
opts.on('-r', '--regexp', 'PATTERN is a regular expression. Default is false.') do
|
17
|
+
merged_options[:regexp] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('-t', '--tags TAGS', 'Tags to include/exclude.') do |tags|
|
21
|
+
merged_options[:tags] = tags
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('-c', '--cucumber OPTIONS', 'Options to pass to cucumber.') do |cucumber|
|
25
|
+
merged_options[:cucumber] = cucumber
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('-p', '--print', 'Echo the command instead of calling cucumber.') do
|
29
|
+
merged_options[:print] = true
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-d', '--debug', 'Require ruby-debug.') do
|
33
|
+
require 'rubygems'
|
34
|
+
require 'ruby-debug'
|
35
|
+
end
|
36
|
+
|
37
|
+
# This displays the help screen, all programs are assumed to have this option.
|
38
|
+
opts.on( '-h', '--help', 'Display this screen.' ) do
|
39
|
+
puts opts
|
40
|
+
exit(0)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
option_parser.parse!(args)
|
45
|
+
|
46
|
+
# get the pattern from the command line (no switch)
|
47
|
+
merged_options[:pattern] = args.first if args.any?
|
48
|
+
|
49
|
+
return self.normalize_options(merged_options)
|
50
|
+
end # self.parse
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.get_options(type = :default)
|
55
|
+
if type == :project
|
56
|
+
project_options = {}
|
57
|
+
|
58
|
+
# load is used here due to require not requiring a file if
|
59
|
+
# it has already been required. This is mainly for testing
|
60
|
+
# purposes (multiple features needing to have different
|
61
|
+
# options for validation), but it shouldn't make a difference
|
62
|
+
# for run time.
|
63
|
+
#load project_options_file if File.exist?(project_options_file)
|
64
|
+
|
65
|
+
if File.exist?(Cellophane::PROJECT_OPTIONS_FILE)
|
66
|
+
yaml_options = YAML.load_file(Cellophane::PROJECT_OPTIONS_FILE)
|
67
|
+
|
68
|
+
['cucumber', 'feature_path', 'feature_path_regexp', 'step_path', 'requires'].each do |key|
|
69
|
+
project_options[key.to_sym] = yaml_options[key] if yaml_options.has_key?(key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
project_options
|
74
|
+
else
|
75
|
+
{
|
76
|
+
:pattern => nil,
|
77
|
+
:regexp => false,
|
78
|
+
:print => false,
|
79
|
+
:cucumber => nil,
|
80
|
+
:tags => nil,
|
81
|
+
:feature_path => 'features',
|
82
|
+
:feature_path_regexp => nil,
|
83
|
+
:step_path => 'features/step_definitions',
|
84
|
+
:requires => []
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end # get_options
|
88
|
+
|
89
|
+
def self.normalize_options(options)
|
90
|
+
defaults = self.get_options(:default)
|
91
|
+
|
92
|
+
# ran into freezing problems in Ruby 1.9.2 otherwise
|
93
|
+
tmp_options = options.dup
|
94
|
+
|
95
|
+
# normalize the paths for features and steps
|
96
|
+
# had originally used the gsub! and sub!, but hit freezing problems with Ruby 1.9.2
|
97
|
+
|
98
|
+
# globs don't work with backslashes (if on windows)
|
99
|
+
tmp_options[:feature_path] = tmp_options[:feature_path].gsub(/\\/, '/')
|
100
|
+
# strip trailing slash
|
101
|
+
tmp_options[:feature_path] = tmp_options[:feature_path].sub(/\/$/, '')
|
102
|
+
|
103
|
+
if tmp_options[:step_path].is_a?(Hash)
|
104
|
+
# if the step path is configured in YAML, it will be a string key, not a symbol
|
105
|
+
key = tmp_options[:step_path].has_key?('nested_in') ? 'nested_in' : :nested_in
|
106
|
+
if tmp_options[:step_path].has_key?(key)
|
107
|
+
tmp_options[:step_path][:nested_in] = tmp_options[:step_path][key].gsub(/\\/, '/')
|
108
|
+
tmp_options[:step_path][:nested_in] = tmp_options[:step_path][key].sub(/\/$/, '')
|
109
|
+
end
|
110
|
+
else
|
111
|
+
tmp_options[:step_path] = tmp_options[:step_path].gsub(/\\/, '/')
|
112
|
+
tmp_options[:step_path] = tmp_options[:step_path].sub(/\/$/, '')
|
113
|
+
end
|
114
|
+
|
115
|
+
# need to know this later
|
116
|
+
tmp_options[:non_standard_feature_path] = tmp_options[:feature_path] != defaults[:feature_path]
|
117
|
+
tmp_options[:non_standard_step_path] = tmp_options[:step_path] != defaults[:step_path]
|
118
|
+
|
119
|
+
# make a regexp out of the features path if there isn't one already. we need to escape slashes so the
|
120
|
+
# regexp can be made
|
121
|
+
tmp_options[:feature_path_regexp] = Regexp.new(tmp_options[:feature_path].gsub('/', '\/')) unless tmp_options[:feature_path_regexp]
|
122
|
+
|
123
|
+
# just in case someone sets necessary values to nil, let's go back to defaults
|
124
|
+
tmp_options[:regexp] ||= defaults[:regexp]
|
125
|
+
tmp_options[:feature_path] ||= defaults[:feature_path]
|
126
|
+
tmp_options[:step_path] ||= defaults[:step_path]
|
127
|
+
tmp_options[:requires] ||= defaults[:requires]
|
128
|
+
|
129
|
+
# do what needs to be done on the pattern
|
130
|
+
unless tmp_options[:pattern].nil?
|
131
|
+
tmp_options[:pattern] = tmp_options[:pattern].strip
|
132
|
+
tmp_options[:pattern] = nil unless tmp_options[:pattern] && !tmp_options[:pattern].empty?
|
133
|
+
|
134
|
+
begin
|
135
|
+
tmp_options[:pattern] = Regexp.new(tmp_options[:pattern]) if tmp_options[:regexp]
|
136
|
+
rescue
|
137
|
+
# if the regexp fails for some reason
|
138
|
+
tmp_options[:pattern] = nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
return tmp_options
|
143
|
+
end # normalize_options
|
144
|
+
end # class Options
|
145
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Cellophane
|
2
|
+
class Parser
|
3
|
+
def initialize(options)
|
4
|
+
@options = options
|
5
|
+
end
|
6
|
+
|
7
|
+
def features
|
8
|
+
# if no pattern is specified, let cucumber run 'em all
|
9
|
+
return [] if @options[:pattern].nil?
|
10
|
+
collected_features = @options[:regexp] ? collect_features_by_regexp : collect_features_by_glob
|
11
|
+
return collected_features.any? ? collected_features : nil
|
12
|
+
end # features
|
13
|
+
|
14
|
+
def tags
|
15
|
+
tags = {
|
16
|
+
:or => [],
|
17
|
+
:and => [],
|
18
|
+
:not => []
|
19
|
+
}
|
20
|
+
|
21
|
+
return '' if @options[:tags].nil?
|
22
|
+
|
23
|
+
@options[:tags].split(',').each do |t|
|
24
|
+
# if tags are numeric, let's support ranges !!!
|
25
|
+
if t =~ /^(~)?([0-9]+)-([0-9]+)$/
|
26
|
+
x = $2.to_i
|
27
|
+
y = $3.to_i
|
28
|
+
exclude = $1
|
29
|
+
|
30
|
+
# in case the user put them in the wrong order ... doh!
|
31
|
+
if x > y
|
32
|
+
z = x.dup
|
33
|
+
x = y.dup
|
34
|
+
y = z.dup
|
35
|
+
end
|
36
|
+
|
37
|
+
(x..y).each do |i|
|
38
|
+
if exclude
|
39
|
+
tags[:not] << "#{i}"
|
40
|
+
else
|
41
|
+
tags[:or] << "#{i}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
if t =~ /^~(.+)/
|
46
|
+
tags[:not] << $1
|
47
|
+
elsif t =~ /^\+(.+)/
|
48
|
+
tags[:and] << $1
|
49
|
+
else
|
50
|
+
tags[:or] << t
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end # each
|
54
|
+
|
55
|
+
[:and, :or, :not].each { |type| tags[type].uniq! }
|
56
|
+
|
57
|
+
# if there are AND/OR tags, remove any NOT tags so we avoid
|
58
|
+
# duplicating the tag when passing to cucumber...so instead of
|
59
|
+
# cucumber -t @1,@2,@3 -t ~@2
|
60
|
+
# we'd like to see
|
61
|
+
# cucumber -t @1,@3
|
62
|
+
|
63
|
+
intersection = tags[:or] & tags[:not]
|
64
|
+
tags[:or] -= intersection
|
65
|
+
tags[:not] -= intersection
|
66
|
+
|
67
|
+
intersection = tags[:and] & tags[:not]
|
68
|
+
tags[:and] -= intersection
|
69
|
+
tags[:not] -= intersection
|
70
|
+
|
71
|
+
# now add @ and ~ as appropriate
|
72
|
+
tags[:or].each_with_index { |tag, i| tags[:or][i] = "@#{tag}" }
|
73
|
+
tags[:and].each_with_index { |tag, i| tags[:and][i] = "@#{tag}" }
|
74
|
+
tags[:not].each_with_index { |tag, i| tags[:not][i] = "~@#{tag}" }
|
75
|
+
|
76
|
+
tags_fragment = ''
|
77
|
+
tags_fragment += "-t #{tags[:or].join(',')} " if tags[:or].any?
|
78
|
+
tags_fragment += "-t #{tags[:and].join(' -t ')} " if tags[:and].any?
|
79
|
+
tags_fragment += "-t #{tags[:not].join(' -t ')}" if tags[:not].any?
|
80
|
+
|
81
|
+
# if the user passes in tags with @ already in it
|
82
|
+
tags_fragment.gsub('@@', '@')
|
83
|
+
end # def self.parse_tags
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def collect_features_by_regexp
|
88
|
+
features = []
|
89
|
+
|
90
|
+
# start by globbing all feature files
|
91
|
+
Dir.glob("#{@options[:feature_path]}/**/*.feature").each do |feature_file|
|
92
|
+
# keep the ones that match the regexp
|
93
|
+
features << feature_file if @options[:pattern].match(feature_file)
|
94
|
+
end
|
95
|
+
|
96
|
+
features.uniq
|
97
|
+
end # collect_features_by_regexp
|
98
|
+
|
99
|
+
def collect_features_by_glob
|
100
|
+
only = []
|
101
|
+
except = []
|
102
|
+
features_to_include = []
|
103
|
+
features_to_exclude = []
|
104
|
+
pattern = @options[:pattern].dup
|
105
|
+
|
106
|
+
# want to run certain ones and/or exclude certain ones
|
107
|
+
pattern.split(',').each do |f|
|
108
|
+
if f[0].chr == '~'
|
109
|
+
except << f[1..f.length]
|
110
|
+
else
|
111
|
+
only << f
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# if we have an exception, we want to get all features by default
|
116
|
+
pattern = '**/*' if except.any?
|
117
|
+
# unless we specifically say we want only certain ones
|
118
|
+
pattern = nil if only.any?
|
119
|
+
|
120
|
+
if only.any?
|
121
|
+
only.each do |f|
|
122
|
+
features_to_include += Dir.glob("#{@options[:feature_path]}/#{f}.feature")
|
123
|
+
end
|
124
|
+
else
|
125
|
+
features_to_include += Dir.glob("#{@options[:feature_path]}/#{pattern}.feature")
|
126
|
+
end
|
127
|
+
|
128
|
+
if except.any?
|
129
|
+
except.each do |f|
|
130
|
+
features_to_exclude = Dir.glob("#{@options[:feature_path]}/#{f}.feature")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
(features_to_include - features_to_exclude).uniq
|
135
|
+
end # collect_features_by_glob
|
136
|
+
end # class Parser
|
137
|
+
end # module Cellophane
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'cellophane'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cellophane
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Phillip Koebbe
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-09 00:00:00 -06:00
|
18
|
+
default_executable: cellophane
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Cellophane is a thin wrapper around Cucumber, making it easier to be creative when running features.
|
22
|
+
email: phillip@livingdoor.net
|
23
|
+
executables:
|
24
|
+
- cellophane
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- LICENSE.txt
|
29
|
+
- README.textile
|
30
|
+
files:
|
31
|
+
- .cellophane.yaml
|
32
|
+
- .cellophane.yaml.sample
|
33
|
+
- .document
|
34
|
+
- LICENSE.txt
|
35
|
+
- README.textile
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- bin/cellophane
|
39
|
+
- features/feature_paths.feature
|
40
|
+
- features/glob_pattern.feature
|
41
|
+
- features/project_options.feature
|
42
|
+
- features/regular_expression_pattern.feature
|
43
|
+
- features/step_definitions.feature
|
44
|
+
- features/support/cellophane_methods.rb
|
45
|
+
- features/support/cellophane_steps.rb
|
46
|
+
- features/support/env.rb
|
47
|
+
- features/tags.feature
|
48
|
+
- lib/cellophane/main.rb
|
49
|
+
- lib/cellophane/options.rb
|
50
|
+
- lib/cellophane/parser.rb
|
51
|
+
- spec/cellophane_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/phillipkoebbe/cellophane
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: A thin wrapper around Cucumber.
|
85
|
+
test_files:
|
86
|
+
- spec/cellophane_spec.rb
|
87
|
+
- spec/spec_helper.rb
|