lucid 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Jeff Nyman
1
+ Copyright (c) 2013 Jeff Nyman
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Lucid
2
2
 
3
- Lucid is a Test Description Language (TDL) execution engine, similar to tools like Cucumber, Spinach, or Turnip, but without a name based on food.
3
+ Lucid is a shiv around BDD tools. Currently the only such tool that Lucid intercepts calls to is Cucumber. The reason for this is that many of these BDD tools are highly opinionated in their structure. While most tools do have a way to force the structure to be your way, how this is done is not always consistent.
4
+
5
+ By way of example, Cucumber expects features to live in a directory called features and step definitions to live in a directory called step_definitions that is under the features directory. When running a feature, all step definitions are loaded unless you explicitly require just the ones you want. If you structure your project differently from how Cucumber expects, then you have to require files explicitly.
6
+
7
+ Lucid allows you to specify configuration options that are then passed to Cucumber.
4
8
 
5
9
  ## Installation
6
10
 
@@ -18,14 +22,12 @@ Or install it yourself as:
18
22
 
19
23
  ## Usage
20
24
 
21
- Instructions on usage will follow when I have an actual implementation.
25
+ Lucid can be configured project by project through the use of a lucid.yml file that lives in the root of your project. This file will contain configurable options. These options will be indicated by specific declarations.
22
26
 
23
27
  ## Contributing
24
28
 
25
- You can contribute to the development of Lucid.
26
-
27
- 1. Fork the project.
28
- 2. Create your feature branch (`git checkout -b my-new-feature`).
29
- 3. Commit your changes (`git commit -am 'Add some feature'`).
30
- 4. Push to the branch (`git push origin my-new-feature`).
31
- 5. Create new Pull Request.
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
data/bin/lucid CHANGED
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
+ app_lib = File.expand_path('../lib', File.dirname(__FILE__))
3
+ $LOAD_PATH.unshift(app_lib) unless $LOAD_PATH.include?(app_lib)
4
+
5
+ require 'lucid/app'
2
6
 
3
7
  begin
4
- require "lucid"
5
- rescue LoadError
6
- require_relative "../lib/lucid"
8
+ Lucid::App.new.run
9
+ rescue SystemExit
10
+ # noop
11
+ rescue Exception => e
12
+ STDERR.puts("#{e.message} (#{e.class})")
13
+ STDERR.puts(e.backtrace.join("\n"))
14
+ Kernel.exit(1)
7
15
  end
8
-
9
- app = Lucid::CLI.new(ARGV)
10
- app.start
data/lib/lucid.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "lucid/version"
2
- require "lucid/cli"
3
2
 
4
3
  module Lucid
5
4
  end
data/lib/lucid/app.rb ADDED
@@ -0,0 +1,108 @@
1
+ require "lucid/options"
2
+ require "lucid/parser"
3
+
4
+ module Lucid
5
+
6
+ PROJECT_OPTIONS = 'lucid.yml'
7
+
8
+ class App
9
+ def initialize(args=nil)
10
+ args ||= ARGV
11
+ @project_options = Lucid::PROJECT_OPTIONS
12
+ @options = Lucid::Options.parse(args)
13
+
14
+ parser = Lucid::Parser.new(@options)
15
+
16
+ @specs = parser.specs
17
+ #puts "[App.initialize] After calling parser for specs, I have: #{@specs.inspect}"
18
+ @tags = parser.tags
19
+
20
+ @message = "No specs were found matching the pattern '#{@options[:pattern]}'." and return unless @specs
21
+
22
+ @command = generate_command
23
+ end
24
+
25
+ def run
26
+ puts @message and return if @message
27
+ @options[:print] ? puts(@command) : system("#{@command}\n\n")
28
+ end
29
+
30
+ private
31
+
32
+ def generate_command
33
+ specs = []
34
+ steps = []
35
+
36
+ command = "#{@options[:command]} #{@options[:options]}"
37
+
38
+ if @specs.any?
39
+ @specs.each do |file|
40
+ file_parts = split_spec(file)
41
+ specs << construct_spec_file(file_parts[:path], file_parts[:name], file_parts[:full_name])
42
+ end
43
+ else
44
+ # If there are no spec files explicitly identified, then all of them
45
+ # will be run. However, if non-standard locations for features or step
46
+ # definitions are being used, that information has to be passed along.
47
+ specs << @options[:spec_path] if @options[:non_standard_spec_path]
48
+ ##steps << @options[:step_path] if @options[:non_standard_step_path]
49
+ end
50
+
51
+ need_root = nil
52
+
53
+ specs.each do |spec|
54
+ start_path = spec[0..spec.index("/") - 1] unless spec.index("/").nil?
55
+ start_path = spec if spec.index("/").nil?
56
+
57
+ if start_path != @options[:spec_path]
58
+ need_root = true
59
+ end
60
+ end
61
+
62
+ specs << @options[:spec_path] if @options[:non_standard_spec_path] unless need_root.nil?
63
+ steps << @options[:step_path] if @options[:non_standard_step_path]
64
+
65
+ requires = (@options[:requires] + steps).compact.uniq
66
+
67
+ command += " -r #{requires.join(' -r ')}" if requires.any?
68
+ command += " #{specs.join(' ')}" if specs.any?
69
+
70
+ return "#{command} #{@tags}".gsub(' ', ' ')
71
+ end
72
+
73
+ def split_spec(file)
74
+ name = File.basename(file, '.feature')
75
+
76
+ # The next bit gets rid of the file name and the actual path
77
+ # to the spec.
78
+ path = File.dirname(file).gsub(@options[:spec_path_regex], '')
79
+
80
+ return {:path => path, :name => name, :full_name => file}
81
+ end
82
+
83
+ def construct_spec_file(path, file, file_name)
84
+ #puts "[App.construct_spec_file] Path = #{path} || File = #{file} || File name = #{file_name}"
85
+
86
+ construct = ""
87
+
88
+ # If the file that is passed in matches the name of the specs path
89
+ # then the assumption is that the user specified the main specs path
90
+ # to be executed, such as with "lucid specs". If the information
91
+ # provided does not end with ".feature" then it is assumed that a
92
+ # full directory is being referenced.
93
+ if file == @options[:spec_path]
94
+ construct = "#{file}"
95
+ elsif file_name[-8, 8] == ".feature" #not file =~ /\.feature$/
96
+ construct = "#{file_name}".gsub('//', '/')
97
+ else
98
+ #construct = "#{@options[:spec_path]}/#{path}/#{file}.feature".gsub('//', '/')
99
+ construct = "#{file_name}".gsub('//', '/')
100
+ end
101
+
102
+ #puts "[App.construct_spec_file] Construct = #{construct}"
103
+
104
+ construct
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,148 @@
1
+ require "lucid/version"
2
+ require 'optparse'
3
+ require 'yaml'
4
+
5
+ module Lucid
6
+ class Options
7
+ def self.parse(args)
8
+ default_options = self.get_options(:default)
9
+ project_options = self.get_options(:project)
10
+ combine_options = default_options.merge(project_options)
11
+
12
+ option_parser = OptionParser.new do |opts|
13
+ opts.banner = ["Lucid: Test Description Language Execution Engine",
14
+ "Usage: lucid [options] PATTERN", ""].join("\n")
15
+
16
+ opts.separator ''
17
+
18
+ opts.on('-p', '--print', "Echo the Lucid command instead of executing it.") do
19
+ combine_options[:print] = true
20
+ end
21
+
22
+ opts.on('-o', '--options OPTIONS', "Options to pass to the tool.") do |options|
23
+ combine_options[:options] = options
24
+ end
25
+
26
+ opts.on('-t', '--tags TAGS', "Tags to include or exclude.") do |tags|
27
+ combine_options[:tags] = tags
28
+ end
29
+
30
+ opts.on('-v', '--version', "Display Lucid version information.") do
31
+ puts Lucid::VERSION
32
+ Kernel.exit(0)
33
+ end
34
+
35
+ opts.on('-h', '--help', "Display help on how to use Lucid.") do
36
+ puts opts
37
+ Kernel.exit(0)
38
+ end
39
+ end
40
+
41
+ option_parser.parse!(args)
42
+
43
+ # This statement is necessary to get the spec execution pattern from
44
+ # the command line. This will not be a switch and so it will be the
45
+ # only actual command line argument.
46
+ combine_options[:pattern] = args.first if args.any?
47
+
48
+ return self.establish(combine_options)
49
+ end
50
+
51
+ private
52
+
53
+ def self.get_options(type = :default)
54
+ if type == :project
55
+ project_options = {}
56
+
57
+ if File.exist?(Lucid::PROJECT_OPTIONS)
58
+ yaml_options = YAML.load_file(Lucid::PROJECT_OPTIONS)
59
+
60
+ ['command', 'options', 'spec_path', 'step_path', 'requires', 'shared', 'spec_path_regex'].each do |key|
61
+ begin
62
+ project_options[key.to_sym] = yaml_options[key] if yaml_options.has_key?(key)
63
+ rescue NoMethodError
64
+ # noop
65
+ end
66
+ end
67
+ end
68
+
69
+ project_options
70
+ else
71
+ {
72
+ :command => 'cucumber', # :cuke_command
73
+ :options => nil, # :cucumber
74
+ :spec_path => 'features', # :feature_path
75
+ :step_path => 'features/step_definitions',
76
+ :requires => [],
77
+ :shared => 'true', # value was 'shared'
78
+ :print => false,
79
+ :tags => nil,
80
+ :spec_path_regex => nil,
81
+ :step_path_regex => nil,
82
+ :pattern => nil
83
+ }
84
+ end
85
+
86
+ end
87
+
88
+ def self.establish(options)
89
+ defaults = self.get_options(:default)
90
+
91
+ current_set = options.dup # tmp_options
92
+
93
+ current_set[:spec_path] = current_set[:spec_path].gsub(/\\/, '/')
94
+ current_set[:spec_path] = current_set[:spec_path].sub(/\/$/, '')
95
+
96
+ current_set[:step_path] = current_set[:step_path].gsub(/\\/, '/')
97
+ current_set[:step_path] = current_set[:step_path].sub(/\/$/, '')
98
+
99
+ unless current_set[:pattern].nil?
100
+ current_set[:pattern] = current_set[:pattern].gsub(/\\/, '/')
101
+ current_set[:pattern] = current_set[:pattern].sub(/\/$/, '')
102
+ end
103
+
104
+ # Establish that the spec path and the step path are not the standard
105
+ # values that Cucumber would expect by default.
106
+ current_set[:non_standard_spec_path] = current_set[:spec_path] != defaults[:spec_path]
107
+ current_set[:non_standard_step_path] = current_set[:step_path] != defaults[:step_path]
108
+
109
+ # Create a regular expression from the spec path and the step path.
110
+ # This is done so that Lucid can look at the paths dynamically, mainly
111
+ # when dealing with shared steps.
112
+ current_set[:spec_path_regex] = Regexp.new(current_set[:spec_path].gsub('/', '\/')) unless current_set[:spec_path_regex]
113
+ current_set[:step_path_regex] = Regexp.new(current_set[:step_path].gsub('/', '\/')) unless current_set[:step_path_regex]
114
+
115
+ # If the values are set to nil, the following commands make sure to
116
+ # revert to the defaults.
117
+ current_set[:spec_path] ||= defaults[:spec_path]
118
+ current_set[:step_path] ||= defaults[:step_path]
119
+ current_set[:requires] ||= defaults[:requires]
120
+ current_set[:command] ||= defaults[:command]
121
+
122
+ # The shared setting has to be something that Lucid can use.
123
+ shared = current_set[:shared].nil? ? 'true' : current_set[:shared].to_s.strip
124
+ shared = 'true' if shared.strip == ''
125
+
126
+ # Here the shared value is established based on a few likely usage
127
+ # patterns for it. Note that if one of these usage patterns is not
128
+ # used, then shared will be kept at whatever setting it has.
129
+ if ['shared', 'yes', ''].include?(shared.downcase)
130
+ shared == true
131
+ elsif ['false', 'no'].include?(shared.downcase)
132
+ shared == false
133
+ end
134
+
135
+ current_set[:shared] = shared
136
+
137
+ # The pattern that was specified must be handled to make sure it is
138
+ # something that can be worked with.
139
+ unless current_set[:pattern].nil?
140
+ current_set[:pattern] = current_set[:pattern].strip
141
+ current_set[:pattern] = nil unless current_set[:pattern] && !current_set[:pattern].empty?
142
+ end
143
+
144
+ return current_set
145
+ end
146
+
147
+ end # class: Options
148
+ end # module: Lucid
@@ -0,0 +1,142 @@
1
+ module Lucid
2
+ class Parser
3
+
4
+ def initialize(options)
5
+ @options = options
6
+ end
7
+
8
+ def specs
9
+ #puts "[Parser.specs] Is @options[:pattern] nil? #{@options[:pattern].nil?}"
10
+ return [] if @options[:pattern].nil?
11
+
12
+ set_of_specs = gather_specs_by_glob
13
+
14
+ #puts "[Parser.specs] Were there any specs? #{set_of_specs.any?}"
15
+
16
+ return set_of_specs.any? ? set_of_specs : nil
17
+ end
18
+
19
+ def tags
20
+ tags = {
21
+ :or => [],
22
+ :and => [],
23
+ :not => []
24
+ }
25
+
26
+ return '' if @options[:tags].nil?
27
+
28
+ @options[:tags].split(',').each do |tag|
29
+ # Tags that are numeric can be ranged.
30
+ if tag =~ /^(~)?([0-9]+)-([0-9]+)$/
31
+ x = $2.to_i
32
+ y = $3.to_i
33
+ exclude = $1
34
+
35
+ # Make sure numeric tags are in numerical order.
36
+ if x > y
37
+ hold_x = x.dup
38
+ x = y.dup
39
+ y = hold_x.dup
40
+ end
41
+
42
+ (x..y).each do |capture|
43
+ if exclude
44
+ tags[:not] << "#{capture}"
45
+ else
46
+ tags[:or] << "#{capture}"
47
+ end
48
+ end
49
+ else
50
+ if tag =~ /^~(.+)/
51
+ tags[:not] << $1
52
+ elsif tag =~ /^\+(.+)/
53
+ tags[:and] << $1
54
+ else
55
+ tags[:or] << tag
56
+ end
57
+ end
58
+ end # each
59
+
60
+ [:and, :or, :not].each { |type| tags[type].uniq! }
61
+
62
+ intersection = tags[:or] & tags[:not]
63
+ tags[:or] -= intersection
64
+ tags[:not] -= intersection
65
+
66
+ intersection = tags[:and] & tags[:not]
67
+ tags[:and] -= intersection
68
+ tags[:not] -= intersection
69
+
70
+ tags[:or].each_with_index { |tag, i| tags[:or][i] = "@#{tag}" }
71
+ tags[:and].each_with_index { |tag, i| tags[:and][i] = "@#{tag}" }
72
+ tags[:not].each_with_index { |tag, i| tags[:not][i] = "~@#{tag}" }
73
+
74
+ tag_builder = ''
75
+ tag_builder += "-t #{tags[:or].join(',')} " if tags[:or].any?
76
+ tag_builder += "-t #{tags[:and].join(' -t ')} " if tags[:and].any?
77
+ tag_builder += "-t #{tags[:not].join(' -t ')}" if tags[:not].any?
78
+
79
+ tag_builder.gsub!('@@', '@')
80
+ tag_builder
81
+ end # method: tags
82
+
83
+ private
84
+
85
+ def gather_specs_by_glob
86
+ only = []
87
+ except = []
88
+ specs_to_include = []
89
+ specs_to_exclude = []
90
+
91
+ pattern = @options[:pattern].dup
92
+
93
+ #puts "[Parser.gather_specs_by_glob] The pattern is: #{pattern}"
94
+
95
+ # Determine if some specs were indicated to be excluded
96
+ # and mark those separately. This also handles when only
97
+ # specific specs are to be executed.
98
+ pattern.split(',').each do |f|
99
+ if f[0].chr == '~'
100
+ except << f[1..f.length]
101
+ else
102
+ only << f
103
+ end
104
+ end
105
+
106
+ # If there are exceptions, then all specs should be
107
+ # gathered by default. Unless, that is, the command
108
+ # indicates that only certain specs should be run.
109
+ pattern = '**/*' if except.any?
110
+ pattern = nil if only.any?
111
+
112
+ #puts "[Parser.gather_specs_by_glob] Is the pattern after only/except nil?: #{pattern.nil?}"
113
+ #puts "[Parser.gather_specs_by_glob] The @options[:spec_path] is: #{@options[:spec_path]}"
114
+
115
+ if only.any?
116
+ only.each do |f|
117
+ #puts "[Parser.gather_specs_by_glob] There is an only and it is: #{f}"
118
+
119
+ #specs_to_include += Dir.glob("#{@options[:spec_path]}/#{f}.feature")
120
+ specs_to_include += Dir.glob("#{f}")
121
+ end
122
+ else
123
+ #puts "[Parser.gather_specs_by_glob] There is no only so pattern is: #{pattern}"
124
+ specs_to_include += Dir.glob("#{@options[:spec_path]}/#{pattern}.feature")
125
+ end
126
+
127
+ puts "[Parser.gather_specs_by_glob] After checking only, specs_to_include is: #{specs_to_include}"
128
+
129
+ if except.any?
130
+ except.each do |f|
131
+ #puts "[Parser.gather_specs_by_glob] There is an except and it is: #{f}"
132
+ specs_to_exclude = Dir.glob("#{@options[:spec_path]}/#{f}.feature")
133
+ end
134
+ end
135
+
136
+ #puts "[Parser.gather_specs_by_glob] Returning #{specs_to_include - specs_to_exclude}"
137
+
138
+ (specs_to_include - specs_to_exclude).uniq
139
+ end
140
+
141
+ end # class: Parser
142
+ end # module: Lucid
data/lib/lucid/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lucid
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lucid.gemspec CHANGED
@@ -8,9 +8,12 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Lucid::VERSION
9
9
  gem.authors = ["Jeff Nyman"]
10
10
  gem.email = ["jeffnyman@gmail.com"]
11
- gem.description = %q{Test Description Language Execution Engine}
12
- gem.summary = %q{Test Description Language Execution Engine}
11
+ gem.license = "MIT"
12
+ gem.description = %q{Execution Wrapper for Cucumber}
13
+ gem.summary = %q{Execution Wrapper for Cucumber}
13
14
  gem.homepage = "https://github.com/jnyman/lucid"
15
+
16
+ gem.required_ruby_version = '>= 1.9.2'
14
17
 
15
18
  gem.files = `git ls-files`.split($/)
16
19
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
data/lucid.yml ADDED
@@ -0,0 +1,8 @@
1
+ # Lucid Projects File
2
+ command: bundle exec cucumber
3
+ options: -p lucid
4
+ spec_path: specs
5
+ step_path: specs/steps
6
+ requires:
7
+ - specs/lucid
8
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lucid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,9 +9,9 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-19 00:00:00.000000000 Z
12
+ date: 2013-02-26 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: Test Description Language Execution Engine
14
+ description: Execution Wrapper for Cucumber
15
15
  email:
16
16
  - jeffnyman@gmail.com
17
17
  executables:
@@ -26,11 +26,15 @@ files:
26
26
  - Rakefile
27
27
  - bin/lucid
28
28
  - lib/lucid.rb
29
- - lib/lucid/cli.rb
29
+ - lib/lucid/app.rb
30
+ - lib/lucid/options.rb
31
+ - lib/lucid/parser.rb
30
32
  - lib/lucid/version.rb
31
33
  - lucid.gemspec
34
+ - lucid.yml
32
35
  homepage: https://github.com/jnyman/lucid
33
- licenses: []
36
+ licenses:
37
+ - MIT
34
38
  post_install_message:
35
39
  rdoc_options: []
36
40
  require_paths:
@@ -40,7 +44,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
40
44
  requirements:
41
45
  - - ! '>='
42
46
  - !ruby/object:Gem::Version
43
- version: '0'
47
+ version: 1.9.2
44
48
  required_rubygems_version: !ruby/object:Gem::Requirement
45
49
  none: false
46
50
  requirements:
@@ -52,5 +56,6 @@ rubyforge_project:
52
56
  rubygems_version: 1.8.24
53
57
  signing_key:
54
58
  specification_version: 3
55
- summary: Test Description Language Execution Engine
59
+ summary: Execution Wrapper for Cucumber
56
60
  test_files: []
61
+ has_rdoc:
data/lib/lucid/cli.rb DELETED
@@ -1,13 +0,0 @@
1
- module Lucid
2
- class CLI
3
-
4
- def initialize(args = ARV)
5
- @args = args
6
- end
7
-
8
- def start
9
-
10
- end
11
-
12
- end # class CLI
13
- end # module Lucid