Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit c4aed8c7b7c17bbfffffcc97a01c761ab8fe0559 0 parents
Phillip Koebbe authored Phillip Koebbe committed
1  .gitignore
@@ -0,0 +1 @@
+.DS_Store
15 bin/cellophane
@@ -0,0 +1,15 @@
+#! /usr/bin/env ruby
+
+$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
+require 'cellophane/main'
+
+begin
+ Cellophane::Main.new.run
+rescue Cellophane::CellophaneException => e
+ STDERR.puts("#{e.message} (#{e.class})")
+ Kernel.exit(1)
+rescue Exception => e
+ STDERR.puts("#{e.message} (#{e.class})")
+ STDERR.puts(e.backtrace.join("\n"))
+ Kernel.exit(1)
+end
117 lib/cellophane/configuration.rb
@@ -0,0 +1,117 @@
+require 'optparse'
+require "#{ENV['HOME']}/.cellophane.rb" if File.exists?("#{ENV['HOME']}/.cellophane.rb")
+require '.cellophane.rb' if File.exist?('.cellophane.rb')
+
+module Cellophane
+ class Configuration
+ def options
+ merged_options = get_options(:default).merge(get_options(:user)).merge(get_options(:project))
+
+ option_parser = OptionParser.new do |opts|
+ # Set a banner, displayed at the top of the help screen.
+ # TODO add example usage including ~feature, patterns, and ~tag
+ opts.banner = "Usage: cellophane [options] PATTERN"
+
+ opts.on('-r', '--regexp', 'PATTERN is a regular expression. Default is false.') do
+ merged_options[:regexp] = true
+ end
+
+ opts.on('-t', '--tags TAGS', 'Tags to include/exclude.') do |tags|
+ merged_options[:tags] = tags
+ end
+
+ opts.on('-c', '--cucumber OPTIONS', 'Options to pass to cucumber.') do |cucumber|
+ merged_options[:cucumber] = cucumber
+ end
+
+ opts.on('-d', '--dry-run', 'Echo the command instead of calling cucumber.') do
+ merged_options[:dry_run] = true
+ end
+
+ # This displays the help screen, all programs are assumed to have this option.
+ opts.on( '-h', '--help', 'Display this screen.' ) do
+ puts opts
+ exit
+ end
+ end
+
+ # Parse the command-line. Remember there are two forms
+ # of the parse method. The 'parse' method simply parses
+ # ARGV, while the 'parse!' method parses ARGV and removes
+ # any options found there, as well as any parameters for
+ # the options. What's left is the list of files to resize.
+ option_parser.parse!
+
+ # get the pattern from the command line (no switch)
+ merged_options[:pattern] = ARGV.at(0) if ARGV.size > 0
+
+ return normalize_options(merged_options)
+ end # self.options
+
+ private
+
+ def get_options(type = :default)
+ case type
+ when :user
+ self.respond_to?(:user_options) ? self.user_options : {}
+ when :project
+ self.respond_to?(:project_options) ? self.project_options : {}
+ else
+ {
+ :pattern => nil,
+ :regexp => false,
+ :dry_run => false,
+ :cucumber => nil,
+ :tags => nil,
+ :feature_path => 'features',
+ :feature_path_regexp => nil,
+ :step_path => 'features/step_definitions',
+ :requires => []
+ }
+ end # case type
+ end # get_options
+
+ def normalize_options(options)
+ paths = [:feature_path]
+
+ # options[:steps] might look like {:nested => 'dirname'} to indicate the steps
+ # are nested under the feature directory (see documentation)
+ paths << :step_path unless options[:step_path].is_a?(Hash)
+
+ # normalize the paths for features and steps
+ paths.each do |path|
+ # globs don't work with backslashes (if on windows)
+ options[path].gsub!(/\\/, '/')
+
+ # strip trailing slash
+ options[path].sub!(/\/$/, '')
+ end
+
+ # make a regexp out of the features path if there isn't one already. we need to escape slashes so the
+ # regexp can be made
+ options[:feature_path_regexp] = Regexp.new(options[:feature_path].gsub('/', '\/')) unless options[:feature_path_regexp]
+
+ # just in case someone sets necessary values to nil, let's go back to defaults
+ defaults = get_options(:default)
+ options[:regexp] ||= defaults[:regexp]
+ options[:feature_path] ||= defaults[:feature_path]
+ options[:step_path] ||= defaults[:step_path]
+ options[:requires] ||= defaults[:requires]
+
+ # do what needs to be done on the pattern
+ unless options[:pattern].nil?
+ options[:pattern].strip!
+ options[:pattern] = nil if options[:pattern].empty?
+
+ begin
+ options[:pattern] = Regexp.new(options[:pattern]) if options[:regexp]
+ rescue
+ # if the regexp fails for some reason
+ options[:pattern] = nil
+ end
+ end
+
+ return options
+ end # normalize_options
+ end # class Configuration
+end
65 lib/cellophane/main.rb
@@ -0,0 +1,65 @@
+require 'fileutils'
+require 'cellophane/parser'
+require 'cellophane/configuration'
+
+module Cellophane
+ class CellophaneException < Exception
+ end
+
+ class Main
+ def initialize
+ @options = Cellophane::Configuration.new.options
+
+ raise CellophaneException.new('Improper regular expression provided.') if @options[:regexp] && @options[:pattern].nil?
+
+ parser = Cellophane::Parser.new(@options)
+ @features = parser.features
+
+ raise CellophaneException.new('No features matching pattern were found.') unless @features
+
+ @tags = parser.tags
+ end
+
+ def run
+ feature_files = []
+ step_files = []
+ @features.each do |file|
+ file_parts = split_feature(file)
+ feature_files << construct_feature_file(file_parts[:path], file_parts[:name])
+ step_files << construct_step_file(file_parts[:path], file_parts[:name])
+ end
+
+ cuke_cmd = "cucumber #{@options[:cucumber]}"
+
+ requires = (@options[:requires] + step_files).compact.uniq
+ cuke_cmd += " -r #{requires.join(' -r ')}" if requires.any?
+
+ execute "#{cuke_cmd} #{feature_files.join(' ')} #{@tags}".gsub(' ', ' ')
+ end
+
+ private
+
+ def construct_feature_file(path, file)
+ "#{@options[:feature_path]}/#{path}/#{file}.feature".gsub('//', '/')
+ end
+
+ def construct_step_file(path, file)
+ step_path = @options[:step_path].is_a?(Hash) ? "#{options[:feature_path]}/#{path}/#{@options[:step_path][:nested_in]}" : "#{@options[:step_path]}/#{path}"
+ step_file = "#{step_path}/#{file}_steps.rb".gsub('//', '/')
+ return File.exist?(step_file) ? step_file : nil
+ end
+
+ def execute(cmd)
+ @options[:dry_run] ? puts(cmd) : system("#{cmd}\n\n")
+ end
+
+ def split_feature(file)
+ name = File.basename(file, '.feature')
+ # now get rid of the file_name and the feature_path
+ path = File.dirname(file).gsub(@options[:feature_path_regexp], '')
+ return {:path => path, :name => name}
+ end
+
+ end # class Main
+end # module Cellophane
+
116 lib/cellophane/parser.rb
@@ -0,0 +1,116 @@
+module Cellophane
+ class Parser
+ def initialize(options)
+ @options = options
+ end
+
+ def features
+ # if no pattern is specified, let cucumber run 'em all
+ return [] if @options[:pattern].nil?
+ collected_features = @options[:regexp] ? collect_features_by_regexp : collect_features_by_glob
+ return collected_features.any? ? collected_features : nil
+ end # features
+
+ def tags
+ only = []
+ except = []
+ tag_pattern = @options[:tags]
+
+ tags = ''
+
+ unless tag_pattern.nil?
+ # going to either run certain ones or exclude certain ones
+ tag_pattern.split(',').each do |t|
+ # if tags are numeric, let's support ranges !!!
+ if t =~ /^(~)?([0-9]+)-([0-9]+)$/
+ x = $2.to_i
+ y = $3.to_i
+ exclude = $1
+
+ # in case the user put them in the wrong order ... doh!
+ if x > y
+ z = x.dup
+ x = y.dup
+ y = z.dup
+ end
+
+ (x..y).each do |i|
+ if exclude
+ except << "~@#{i}"
+ else
+ only << "@#{i}"
+ end
+ end
+ else
+ if t[0].chr == '~'
+ except << "~@#{t[1..t.length]}"
+ else
+ only << "@#{t}"
+ end
+ end
+ end
+
+ only.uniq!
+ except.uniq!
+
+ tags += "-t #{only.join(',')} " if only.any?
+ tags += "-t #{except.join(' -t ')}" if except.any?
+ end
+
+ # if the user passes in tags with @ already in it
+ tags.gsub('@@', '@')
+ end # def self.parse_tags
+
+ private
+
+ def collect_features_by_regexp
+ features = []
+
+ # start by globbing all feature files
+ Dir.glob("#{@options[:feature_path]}/**/*.feature").each do |feature_file|
+ # keep the ones that match the regexp
+ features << feature_file if @options[:pattern].match(feature_file)
+ end
+
+ features.uniq
+ end # collect_features_by_regexp
+
+ def collect_features_by_glob
+ only = []
+ except = []
+ features_to_include = []
+ features_to_exclude = []
+ pattern = @options[:pattern].dup
+
+ # want to run certain ones and/or exclude certain ones
+ pattern.split(',').each do |f|
+ if f[0].chr == '~'
+ except << f[1..f.length]
+ else
+ only << f
+ end
+ end
+
+ # if we have an exception, we want to get all features by default
+ pattern = '**/*' if except.any?
+ # unless we specifically say we want only certain ones
+ pattern = nil if only.any?
+
+ if only.any?
+ only.each do |f|
+ features_to_include += Dir.glob("#{@options[:feature_path]}/#{f}.feature")
+ end
+ else
+ features_to_include += Dir.glob("#{@options[:feature_path]}/#{pattern}.feature")
+ end
+
+ if except.any?
+ except.each do |f|
+ features_to_exclude = Dir.glob("#{@options[:feature_path]}/#{f}.feature")
+ end
+ end
+
+ (features_to_include - features_to_exclude).uniq
+ end # collect_features_by_glob
+ end # class Parser
+end # module Cellophane
Please sign in to comment.
Something went wrong with that request. Please try again.