cuporter 0.3.10 → 0.3.12

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,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