cuporter 0.3.10 → 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ # use a set of defaults like this to support primarily command line usage.
2
+ ---
3
+ defaults:
4
+ report: tree
5
+ format: html
6
+ link_assets: true
7
+ use_copied_public_assets: true
8
+
@@ -0,0 +1,79 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Config
4
+
5
+ class OptionSet
6
+
7
+ attr_reader :options
8
+
9
+ def initialize(file_config = {})
10
+ # CLI options replace any found in the file
11
+ cli_options_over_file_options = file_config.merge(Cuporter::Config::CLI::Options.options)
12
+
13
+ # defaults will be used for anything not so far specified in the file
14
+ # or CLI
15
+ @options = post_process(Cuporter::Config::DEFAULTS.merge(cli_options_over_file_options))
16
+ end
17
+
18
+ def [](key)
19
+ @options[key]
20
+ end
21
+
22
+ def output_file(format)
23
+ if (file = @options[:output_file].find {|f| f =~ ext_for(format) })
24
+ File.open(file, "w")
25
+ end
26
+ end
27
+
28
+ def dump
29
+ col_1_max = @options.keys.max_by {|i| i.to_s.length }.to_s.size
30
+ @options.each do |key, value|
31
+ puts ":#{key.to_s.ljust(col_1_max, ' ')} => #{value.inspect}"
32
+ end
33
+ end
34
+
35
+ def dry_run?
36
+ @options[:dry_run]
37
+ end
38
+
39
+ private
40
+
41
+ def post_process(options)
42
+ options[:input_file_pattern] = options.delete(:input_file) || "#{options.delete(:input_dir)}/**/*.feature"
43
+ options[:root_dir] = options[:input_file_pattern].split(File::SEPARATOR).first
44
+ options[:filter_args] = Cuporter::Config::CLI::FilterArgsBuilder.new(options.delete(:tags)).args
45
+ options[:output_file].each_with_index do |file_path, i|
46
+ options[:output_file][i] = full_path(options[:output_home], file_path.dup )
47
+ end
48
+
49
+ unless options[:output_file].find {|f| f =~ /\.html$/ }
50
+ options[:copy_public_assets] = options[:use_copied_public_assets] = false
51
+ end
52
+
53
+ unless options[:output_file].empty?
54
+ options[:log_dir] = File.dirname(options[:output_file].first)
55
+ end
56
+ options
57
+ end
58
+
59
+ def full_path(root_dir, file_path)
60
+ path = root_dir.empty? ? file_path : File.join(root_dir, file_path)
61
+ expanded_path = File.expand_path(path)
62
+ path_nodes = expanded_path.split(File::SEPARATOR)
63
+ file = path_nodes.pop
64
+ FileUtils.makedirs(path_nodes.join(File::SEPARATOR))
65
+ expanded_path
66
+ end
67
+
68
+ def ext_for(format)
69
+ case format
70
+ when 'text', 'pretty'
71
+ /\.txt$/
72
+ else
73
+ /\.#{format}$/
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Config
4
+ module YamlFile
5
+ class OptionSetCollection
6
+
7
+ def self.path
8
+ Cuporter::Config::CLI::Options[:config_file] || "config/cuporter.yml"
9
+ end
10
+
11
+ def self.config_file
12
+ return [] unless File.exists?(path)
13
+
14
+ require 'yaml'
15
+ yaml = YAML.load_file(path)
16
+ if yaml["option_sets"]
17
+ yaml["option_sets"].map do |option_set|
18
+ cast(option_set["options"])
19
+ end
20
+ else
21
+ yaml["defaults"].empty? ? [] : [cast(yaml["defaults"])]
22
+ end
23
+ end
24
+
25
+ def self.cast(option_set)
26
+ pairs = {}
27
+ option_set.each do |key, value|
28
+ pairs[key.to_sym] = case key
29
+ when /^(tags|output_file|format)$/i
30
+ value.is_a?(Array) ? value : [value]
31
+ else
32
+ value
33
+ end
34
+ end
35
+
36
+ return pairs
37
+ end
38
+
39
+ end # class
40
+ end # File
41
+ end # Config
42
+ end
data/lib/cuporter.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  # Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
2
2
  $LOAD_PATH.unshift( File.expand_path("#{File.dirname(__FILE__)}"))
3
- $LOAD_PATH.unshift( File.expand_path("#{File.dirname(__FILE__)}/.."))
4
3
  require 'cuporter/extensions/string'
4
+ require 'cuporter/logging'
5
5
  require 'cuporter/node'
6
6
  require 'cuporter/formatters/text'
7
7
  require 'cuporter/formatters/csv'
8
8
  require 'cuporter/filter'
9
9
  require 'cuporter/feature_parser'
10
- require 'cuporter/tag_nodes_parser'
11
- require 'cuporter/node_parser'
12
10
  require 'cuporter/document'
13
11
  require 'cuporter/document/assets'
14
12
  require 'cuporter/document/html_document'
@@ -1,16 +1,11 @@
1
- # Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ require 'cuporter/feature_parser/language'
3
+ require 'cuporter/feature_parser/parser_base'
4
+ require 'cuporter/feature_parser/tag_nodes_parser'
5
+ require 'cuporter/feature_parser/node_parser'
2
6
 
3
7
  module Cuporter
4
- class FeatureParser
5
- FEATURE_LINE = /^\s*(Feature:[^#]*)/u
6
- TAG_LINE = /^\s*(@\w.+)/u
7
- SCENARIO_LINE = /^\s*(Scenario:[^#]*)$/u
8
- SCENARIO_OUTLINE_LINE = /^\s*(Scenario Outline:[^#]*)$/u
9
- SCENARIO_SET_LINE = /^\s*(Scenarios:[^#]*)$/u
10
- EXAMPLE_SET_LINE = /^\s*(Examples:[^#]*)$/u
11
- EXAMPLE_LINE = /^\s*(\|.*\|)\s*$/u
12
- PY_STRING_LINE = /^\s*"""\s*$/u
13
-
8
+ module FeatureParser
14
9
  # adds a node to the doc for each cucumber '@' tag, populated with features and
15
10
  # scenarios
16
11
  def self.tag_nodes(file, report, filter, root_dir)
@@ -25,64 +20,5 @@ module Cuporter
25
20
  parser.root = root_dir
26
21
  parser.parse_feature
27
22
  end
28
- attr_writer :root
29
-
30
- def initialize(file)
31
- @file = file
32
- @current_tags = []
33
- @lines = File.read(@file).split(/\n/)
34
- end
35
-
36
- def file_relative_path
37
- @file_relative_path ||= @file.sub(/^.*#{@root}\//,"#{@root}/")
38
- end
39
-
40
- def parse_feature
41
- @open_comment_block = false
42
-
43
- @lines.each do |line|
44
- next if @open_comment_block && line !~ PY_STRING_LINE
45
-
46
- case line
47
- when PY_STRING_LINE
48
- # toggle, to declare the multiline comment 'heredoc' open or closed
49
- @open_comment_block = !@open_comment_block
50
- when TAG_LINE
51
- # may be more than one tag line
52
- @current_tags |= clean_cuke_line($1).split(/\s+/)
53
- when FEATURE_LINE
54
- @feature = new_feature_node(clean_cuke_line($1), file_relative_path)
55
- @current_tags = []
56
- when SCENARIO_LINE
57
- # How do we know when we have read all the lines from a "Scenario Outline:"?
58
- # One way is when we encounter a "Scenario:"
59
- close_scenario_outline
60
-
61
- handle_scenario_line(clean_cuke_line($1))
62
- @current_tags = []
63
- when SCENARIO_OUTLINE_LINE
64
- # ... another is when we hit a subsequent "Scenario Outline:"
65
- close_scenario_outline
66
-
67
- @scenario_outline = new_scenario_outline_node(clean_cuke_line($1))
68
- @current_tags = []
69
- when EXAMPLE_SET_LINE, SCENARIO_SET_LINE
70
- handle_example_set_line if @example_set
71
-
72
- @example_set = new_example_set_node(clean_cuke_line($1))
73
- @current_tags = []
74
- when @example_set && EXAMPLE_LINE
75
- new_example_line(clean_cuke_line($1))
76
- end
77
- end
78
-
79
- # EOF is the final way that we know we are finished with a "Scenario Outline"
80
- close_scenario_outline
81
- return @feature
82
- end
83
-
84
- def clean_cuke_line(sub_expression)
85
- sub_expression.strip.escape_apostrophe
86
- end
87
23
  end
88
24
  end
@@ -0,0 +1,41 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ begin
3
+ require 'gherkin/i18n'
4
+ rescue LoadError => ex
5
+ $gherkin_warning = %Q{Warning: Rubygems could not find Gherkin. Cuporter will use an old gherkin (2.3.3) language file.
6
+ Make sure the 'gherkin' gem is installed by checking your rvm config, your bundler config, or gem list.
7
+
8
+ "#{ex.class}: #{ex.message}"
9
+ "#{ex.backtrace.join("\n\t\t")}"
10
+ }
11
+ require 'cuporter/feature_parser/old_gherkin_yaml/i18n'
12
+ end
13
+
14
+ module Cuporter
15
+ module FeatureParser
16
+ class Language
17
+
18
+ attr_reader :feature_line, :scenario_line, :scenario_outline_line, :examples_line
19
+
20
+ def initialize(line_1)
21
+ @iso_code = 'en'
22
+ if (line_1.to_s =~ LANGUAGE_LINE)
23
+ @iso_code = $1
24
+ warn($gherkin_warning) if $gherkin_warning
25
+ end
26
+ @feature_line = pattern_for('feature')
27
+ @scenario_line = pattern_for('scenario')
28
+ @scenario_outline_line = pattern_for('scenario_outline')
29
+ @examples_line = pattern_for('examples')
30
+ end
31
+
32
+ private
33
+
34
+ def pattern_for(keyword)
35
+ /^\s*((#{Gherkin::I18n::LANGUAGES[@iso_code][keyword]}):[^#]*)/u
36
+ end
37
+
38
+ LANGUAGE_LINE = /^\s*#\s*language:\s*([\w-]+)/u
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+
3
+ module Cuporter
4
+ module FeatureParser
5
+ class NodeParser < ParserBase
6
+
7
+ # ++sub_expression++ is the paren group in the regex, dereferenced with $1 in the caller
8
+ def new_feature_node(sub_expression, file)
9
+ f = Node.new_node(:Feature, @doc, :cuke_name => sub_expression, :tags => @current_tags, :file_path => file)
10
+ f.filter = @filter
11
+ f
12
+ end
13
+
14
+ def handle_scenario_line(sub_expression)
15
+ if @filter.pass?(@current_tags | @feature.tags)
16
+ @feature.add_child(Node.new_node(:Scenario, @doc, :cuke_name => sub_expression, :tags => @current_tags))
17
+ end
18
+ end
19
+
20
+ def new_scenario_outline_node(sub_expression)
21
+ so = Node.new_node(:ScenarioOutline, @doc, :cuke_name => sub_expression, :tags => @current_tags)
22
+ so.filter = @filter
23
+ so
24
+ end
25
+
26
+ def handle_example_set_line
27
+ if @filter.pass?(@feature.tags | @scenario_outline.tags | @example_set.tags)
28
+ @scenario_outline.add_child @example_set
29
+ end
30
+ end
31
+
32
+ def new_example_set_node(sub_expression)
33
+ es = Node.new_node(:Examples, @doc, :cuke_name => sub_expression, :tags => @current_tags)
34
+ es.filter = @filter
35
+ es
36
+ end
37
+
38
+ def new_example_line(sub_expression)
39
+ example_type = :ExampleHeader
40
+ # if the example set has a child already, then it must be the header
41
+ example_type = :Example if @example_set.has_children?
42
+ @example_set.add_child(Node.new_node(example_type, @doc, :cuke_name => sub_expression))
43
+ end
44
+
45
+ def close_scenario_outline
46
+ if @scenario_outline
47
+ if @example_set
48
+ handle_example_set_line
49
+ @example_set = nil
50
+ end
51
+ @feature.add_child(@scenario_outline) if @scenario_outline.has_children?
52
+ @scenario_outline = nil
53
+ end
54
+ end
55
+
56
+ def initialize(file, doc, filter)
57
+ super(file)
58
+ @filter = filter
59
+ @doc = doc
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ require 'yaml'
2
+
3
+ module Gherkin
4
+ class I18n
5
+
6
+ LANGUAGES = YAML.load_file(File.dirname(__FILE__) + '/i18n.yml')
7
+
8
+ end
9
+ end
@@ -0,0 +1,84 @@
1
+ # Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
2
+
3
+ module Cuporter
4
+ module FeatureParser
5
+ class ParserBase
6
+ TAG_LINE = /^\s*(@\w.+)/u
7
+ EXAMPLE_LINE = /^\s*(\|.*\|)\s*$/u
8
+ PY_STRING_LINE = /^\s*"""\s*$/u
9
+
10
+ def initialize(file)
11
+ @file = file
12
+ @current_tags = []
13
+ @lines = File.read(@file).split(/\n/)
14
+ @lang = Language.new(@lines.first)
15
+ end
16
+ attr_reader :lang
17
+ attr_writer :root
18
+
19
+ def file_relative_path
20
+ @file_relative_path ||= @file.sub(/^.*#{@root}\//,"#{@root}/")
21
+ end
22
+
23
+ def parse_feature
24
+ begin
25
+ handle_lines
26
+ rescue Exception => ex
27
+ Cuporter.log_error(ex, "Error parsing file", "at line #{@line_no}:", @file,
28
+ %Q{\n\tIf this file can be run by Cucumber with no Gherkin lexing or parsing errors,
29
+ please submit a bug ticket @ github including: 1) this feature file or its contents, and 2) this stack trace.
30
+ })
31
+ end
32
+ return @feature
33
+ end
34
+
35
+ def handle_lines
36
+ @open_comment_block = false
37
+
38
+ @lines.each_with_index do |line, i|
39
+ @line_no = i + 1
40
+ next if @open_comment_block && line !~ PY_STRING_LINE
41
+
42
+ case line
43
+ when PY_STRING_LINE
44
+ # toggle, to declare the multiline comment 'heredoc' open or closed
45
+ @open_comment_block = !@open_comment_block
46
+ when TAG_LINE
47
+ # may be more than one tag line
48
+ @current_tags |= clean_cuke_line($1).split(/\s+/)
49
+ when @lang.feature_line
50
+ @feature = new_feature_node(clean_cuke_line($1), file_relative_path)
51
+ @current_tags = []
52
+ when @lang.scenario_line
53
+ # How do we know when we have read all the lines from a "Scenario Outline:"?
54
+ # One way is when we encounter a "Scenario:"
55
+ close_scenario_outline
56
+
57
+ handle_scenario_line(clean_cuke_line($1))
58
+ @current_tags = []
59
+ when @lang.scenario_outline_line
60
+ # ... another is when we hit a subsequent "Scenario Outline:"
61
+ close_scenario_outline
62
+
63
+ @scenario_outline = new_scenario_outline_node(clean_cuke_line($1))
64
+ @current_tags = []
65
+ when @lang.examples_line
66
+ handle_example_set_line if @example_set
67
+
68
+ @example_set = new_example_set_node(clean_cuke_line($1))
69
+ @current_tags = []
70
+ when @example_set && EXAMPLE_LINE
71
+ new_example_line(clean_cuke_line($1))
72
+ end
73
+ end
74
+
75
+ # EOF is the final way that we know we are finished with a "Scenario Outline"
76
+ close_scenario_outline
77
+ end
78
+
79
+ def clean_cuke_line(sub_expression)
80
+ sub_expression.strip.escape_apostrophe
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,96 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+
3
+ module Cuporter
4
+ module FeatureParser
5
+ class TagNodesParser < ParserBase
6
+
7
+ # ++sub_expression++ is the paren group in the regex, dereferenced with $1 in the caller
8
+ def new_feature_node(sub_expression, file)
9
+ {:cuke_name => sub_expression, :tags => @current_tags, :file_path => file}
10
+ end
11
+
12
+ def handle_scenario_line(sub_expression)
13
+ if @filter.pass?(@feature[:tags] | @current_tags)
14
+ s = {:cuke_name => sub_expression, :tags => @current_tags}
15
+
16
+ (@feature[:tags] | s[:tags]).each do |tag|
17
+ next unless @filter.pass?([tag])
18
+ add_scenario(tag, @feature, s)
19
+ end
20
+ end
21
+ end
22
+
23
+ def new_scenario_outline_node(sub_expression)
24
+ {:cuke_name => sub_expression, :tags => @current_tags}
25
+ end
26
+
27
+ def handle_example_set_line
28
+ end
29
+
30
+ def new_example_set_node(sub_expression)
31
+ {:cuke_name => sub_expression.to_s.strip, :tags => @current_tags}
32
+ end
33
+
34
+ def new_example_line(sub_expression)
35
+ context_tags = (@feature[:tags] | @scenario_outline[:tags] | @example_set[:tags])
36
+ if @filter.pass?(context_tags)
37
+ e = {:cuke_name => sub_expression}
38
+
39
+ context_tags.each do |tag|
40
+ next unless @filter.pass?([tag])
41
+ add_example(tag, @feature, @scenario_outline, @example_set, e)
42
+ end
43
+ end
44
+ end
45
+
46
+ def close_scenario_outline
47
+ if @scenario_outline
48
+ if @example_set
49
+ @example_set = nil
50
+ end
51
+ @scenario_outline = nil
52
+ end
53
+ end
54
+ def add_scenario(tag, feature, scenario)
55
+ unless ( t = @report.tag_node(tag))
56
+ t = @report.add_child(Node.new_node(:tag, @doc, 'cuke_name' => tag))
57
+ end
58
+ unless ( f = t.feature_node(feature) )
59
+ f = t.add_child(Node.new_node(:Feature, @doc, feature))
60
+ end
61
+ f.add_child(Node.new_node(:Scenario, @doc, scenario))
62
+ end
63
+
64
+ def add_example(tag, feature, scenario_outline, example_set, example)
65
+ unless ( t = @report.tag_node(tag))
66
+ t = @report.add_child(Node.new_node(:Tag, @doc, 'cuke_name' => tag))
67
+ end
68
+ unless ( f = t.feature_node(feature) )
69
+ f = t.add_child(Node.new_node(:Feature, @doc, feature))
70
+ end
71
+ unless ( so = f.scenario_outline_node(scenario_outline) )
72
+ so = f.add_child(Node.new_node(:ScenarioOutline, @doc, scenario_outline))
73
+ end
74
+
75
+ # The first Example is an ExampleHeader, which does not get counted or
76
+ # numbered. If the ExampleSet is new, it has no children, and therefore
77
+ # this is the first and should be an ExampleHeader.
78
+ example_type = :Example
79
+ unless ( es = so.example_set_node(example_set) )
80
+ es = so.add_child(Node.new_node(:Examples, @doc, example_set))
81
+ example_type = :ExampleHeader
82
+ end
83
+ es.add_child(Node.new_node(example_type, @doc, example))
84
+ end
85
+
86
+
87
+ def initialize(file, report, filter)
88
+ super(file)
89
+ @filter = filter
90
+ @report = report
91
+ @doc = report.document
92
+ end
93
+
94
+ end
95
+ end
96
+ end