cucumber 3.1.2 → 8.0.0

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.
Files changed (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1880 -1146
  3. data/CONTRIBUTING.md +220 -61
  4. data/README.md +143 -22
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +49 -53
  7. data/lib/autotest/discover.rb +3 -2
  8. data/lib/cucumber/cli/configuration.rb +32 -7
  9. data/lib/cucumber/cli/main.rb +16 -15
  10. data/lib/cucumber/cli/options.rb +111 -79
  11. data/lib/cucumber/cli/profile_loader.rb +45 -26
  12. data/lib/cucumber/cli/rerun_file.rb +1 -1
  13. data/lib/cucumber/configuration.rb +47 -31
  14. data/lib/cucumber/constantize.rb +3 -6
  15. data/lib/cucumber/deprecate.rb +32 -7
  16. data/lib/cucumber/errors.rb +5 -7
  17. data/lib/cucumber/events/envelope.rb +9 -0
  18. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  19. data/lib/cucumber/events/hook_test_step_created.rb +12 -0
  20. data/lib/cucumber/events/step_activated.rb +0 -5
  21. data/lib/cucumber/events/step_definition_registered.rb +0 -5
  22. data/lib/cucumber/events/test_case_created.rb +12 -0
  23. data/lib/cucumber/events/test_case_ready.rb +12 -0
  24. data/lib/cucumber/events/test_run_finished.rb +2 -1
  25. data/lib/cucumber/events/test_step_created.rb +12 -0
  26. data/lib/cucumber/events/undefined_parameter_type.rb +9 -0
  27. data/lib/cucumber/events.rb +15 -8
  28. data/lib/cucumber/file_specs.rb +8 -7
  29. data/lib/cucumber/filters/activate_steps.rb +6 -3
  30. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  31. data/lib/cucumber/filters/prepare_world.rb +5 -9
  32. data/lib/cucumber/filters/quit.rb +1 -3
  33. data/lib/cucumber/filters/tag_limits/verifier.rb +3 -7
  34. data/lib/cucumber/filters/tag_limits.rb +1 -3
  35. data/lib/cucumber/filters.rb +1 -0
  36. data/lib/cucumber/formatter/ansicolor.rb +74 -86
  37. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  38. data/lib/cucumber/formatter/backtrace_filter.rb +10 -7
  39. data/lib/cucumber/formatter/console.rb +76 -68
  40. data/lib/cucumber/formatter/console_counts.rb +4 -9
  41. data/lib/cucumber/formatter/console_issues.rb +12 -4
  42. data/lib/cucumber/formatter/duration.rb +1 -1
  43. data/lib/cucumber/formatter/duration_extractor.rb +4 -1
  44. data/lib/cucumber/formatter/errors.rb +7 -0
  45. data/lib/cucumber/formatter/fanout.rb +3 -1
  46. data/lib/cucumber/formatter/html.rb +11 -598
  47. data/lib/cucumber/formatter/http_io.rb +152 -0
  48. data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -2
  49. data/lib/cucumber/formatter/interceptor.rb +11 -30
  50. data/lib/cucumber/formatter/io.rb +57 -13
  51. data/lib/cucumber/formatter/json.rb +119 -124
  52. data/lib/cucumber/formatter/junit.rb +75 -55
  53. data/lib/cucumber/formatter/message.rb +23 -0
  54. data/lib/cucumber/formatter/message_builder.rb +256 -0
  55. data/lib/cucumber/formatter/pretty.rb +370 -153
  56. data/lib/cucumber/formatter/progress.rb +31 -32
  57. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  58. data/lib/cucumber/formatter/query/hook_by_test_step.rb +32 -0
  59. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  60. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  61. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  62. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +42 -0
  63. data/lib/cucumber/formatter/rerun.rb +24 -4
  64. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  65. data/lib/cucumber/formatter/steps.rb +8 -6
  66. data/lib/cucumber/formatter/summary.rb +17 -8
  67. data/lib/cucumber/formatter/unicode.rb +18 -20
  68. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  69. data/lib/cucumber/formatter/usage.rb +18 -15
  70. data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
  71. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +14 -18
  72. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  73. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  74. data/lib/cucumber/glue/dsl.rb +29 -15
  75. data/lib/cucumber/glue/hook.rb +37 -11
  76. data/lib/cucumber/glue/invoke_in_world.rb +17 -22
  77. data/lib/cucumber/glue/proto_world.rb +47 -53
  78. data/lib/cucumber/glue/registry_and_more.rb +62 -17
  79. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  80. data/lib/cucumber/glue/snippet.rb +23 -22
  81. data/lib/cucumber/glue/step_definition.rb +48 -23
  82. data/lib/cucumber/glue/world_factory.rb +1 -1
  83. data/lib/cucumber/hooks.rb +12 -11
  84. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -3
  85. data/lib/cucumber/multiline_argument/data_table.rb +143 -123
  86. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  87. data/lib/cucumber/multiline_argument.rb +4 -6
  88. data/lib/cucumber/platform.rb +5 -5
  89. data/lib/cucumber/rake/task.rb +34 -25
  90. data/lib/cucumber/rspec/disable_option_parser.rb +15 -11
  91. data/lib/cucumber/rspec/doubles.rb +3 -5
  92. data/lib/cucumber/running_test_case.rb +3 -53
  93. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  94. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  95. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  96. data/lib/cucumber/runtime/meta_message_builder.rb +106 -0
  97. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  98. data/lib/cucumber/runtime/support_code.rb +16 -15
  99. data/lib/cucumber/runtime/user_interface.rb +10 -19
  100. data/lib/cucumber/runtime.rb +78 -76
  101. data/lib/cucumber/step_definition_light.rb +4 -3
  102. data/lib/cucumber/step_definitions.rb +2 -2
  103. data/lib/cucumber/step_match.rb +17 -20
  104. data/lib/cucumber/step_match_search.rb +5 -3
  105. data/lib/cucumber/term/ansicolor.rb +72 -48
  106. data/lib/cucumber/term/banner.rb +57 -0
  107. data/lib/cucumber/version +1 -1
  108. data/lib/cucumber.rb +3 -2
  109. data/lib/simplecov_setup.rb +1 -1
  110. metadata +279 -81
  111. data/lib/cucumber/core_ext/string.rb +0 -11
  112. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  113. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  114. data/lib/cucumber/formatter/cucumber.css +0 -286
  115. data/lib/cucumber/formatter/cucumber.sass +0 -247
  116. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  117. data/lib/cucumber/formatter/html_builder.rb +0 -121
  118. data/lib/cucumber/formatter/inline-js.js +0 -30
  119. data/lib/cucumber/formatter/jquery-min.js +0 -154
  120. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  121. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  122. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  123. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  124. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  125. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,16 +1,19 @@
1
- # -*- coding: utf-8 -*-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'fileutils'
5
- require 'multi_json'
6
4
  require 'cucumber/configuration'
5
+ require 'cucumber/deprecate'
7
6
  require 'cucumber/load_path'
8
7
  require 'cucumber/formatter/duration'
9
8
  require 'cucumber/file_specs'
10
9
  require 'cucumber/filters'
11
10
  require 'cucumber/formatter/fanout'
12
11
  require 'cucumber/gherkin/i18n'
12
+ require 'cucumber/glue/registry_wrapper'
13
13
  require 'cucumber/step_match_search'
14
+ require 'cucumber/messages'
15
+ require 'cucumber/runtime/meta_message_builder'
16
+ require 'sys/uname'
14
17
 
15
18
  module Cucumber
16
19
  module FixRuby21Bug9285
@@ -19,21 +22,22 @@ module Cucumber
19
22
  end
20
23
  end
21
24
 
22
- class FileException < Exception
23
- attr :path
25
+ class FileException < RuntimeError
26
+ attr_reader :path
24
27
 
25
28
  def initialize(original_exception, path)
26
- super(original_exception)
27
29
  @path = path
30
+ super(original_exception)
28
31
  end
29
32
  end
30
33
 
31
34
  class FileNotFoundException < FileException
32
35
  end
33
36
 
34
- class FeatureFolderNotFoundException < Exception
37
+ class FeatureFolderNotFoundException < RuntimeError
35
38
  def initialize(path)
36
39
  @path = path
40
+ super
37
41
  end
38
42
 
39
43
  def message
@@ -54,7 +58,6 @@ module Cucumber
54
58
  def initialize(configuration = Configuration.default)
55
59
  @configuration = Configuration.new(configuration)
56
60
  @support_code = SupportCode.new(self, @configuration)
57
- @results = Formatter::LegacyApi::Results.new
58
61
  end
59
62
 
60
63
  # Allows you to take an existing runtime and change its configuration
@@ -63,17 +66,22 @@ module Cucumber
63
66
  @support_code.configure(@configuration)
64
67
  end
65
68
 
66
- require 'cucumber/wire/plugin'
67
69
  def run!
70
+ @configuration.notify :envelope, Cucumber::Messages::Envelope.new(
71
+ meta: MetaMessageBuilder.build_meta_message
72
+ )
73
+
68
74
  load_step_definitions
69
- install_wire_plugin
70
- fire_after_configuration_hook
75
+ fire_install_plugin_hook
76
+ fire_before_all_hook unless dry_run?
71
77
  # TODO: can we remove this state?
72
78
  self.visitor = report
73
79
 
74
80
  receiver = Test::Runner.new(@configuration.event_bus)
75
- compile features, receiver, filters
76
- @configuration.notify :test_run_finished
81
+ compile features, receiver, filters, @configuration.event_bus
82
+ @configuration.notify :test_run_finished, !failure?
83
+
84
+ fire_after_all_hook unless dry_run?
77
85
  end
78
86
 
79
87
  def features_paths
@@ -84,14 +92,6 @@ module Cucumber
84
92
  @configuration.dry_run?
85
93
  end
86
94
 
87
- def scenarios(status = nil)
88
- @results.scenarios(status)
89
- end
90
-
91
- def steps(status = nil)
92
- @results.steps(status)
93
- end
94
-
95
95
  def unmatched_step_definitions
96
96
  @support_code.unmatched_step_definitions
97
97
  end
@@ -107,14 +107,29 @@ module Cucumber
107
107
  # Returns Ast::DocString for +string_without_triple_quotes+.
108
108
  #
109
109
  def doc_string(string_without_triple_quotes, content_type = '', _line_offset = 0)
110
- location = Core::Ast::Location.of_caller
111
- Core::Ast::DocString.new(string_without_triple_quotes, content_type, location)
110
+ Core::Test::DocString.new(string_without_triple_quotes, content_type)
111
+ end
112
+
113
+ def failure?
114
+ if @configuration.wip?
115
+ summary_report.test_cases.total_passed > 0
116
+ else
117
+ !summary_report.ok?(@configuration.strict)
118
+ end
112
119
  end
113
120
 
114
121
  private
115
122
 
116
- def fire_after_configuration_hook #:nodoc
117
- @support_code.fire_hook(:after_configuration, @configuration)
123
+ def fire_install_plugin_hook # :nodoc:
124
+ @support_code.fire_hook(:install_plugin, @configuration, registry_wrapper)
125
+ end
126
+
127
+ def fire_before_all_hook # :nodoc:
128
+ @support_code.fire_hook(:before_all)
129
+ end
130
+
131
+ def fire_after_all_hook # :nodoc:
132
+ @support_code.fire_hook(:after_all)
118
133
  end
119
134
 
120
135
  require 'cucumber/core/gherkin/document'
@@ -135,22 +150,20 @@ module Cucumber
135
150
  end
136
151
 
137
152
  class NormalisedEncodingFile
138
- COMMENT_OR_EMPTY_LINE_PATTERN = /^\s*#|^\s*$/ #:nodoc:
139
- ENCODING_PATTERN = /^\s*#\s*encoding\s*:\s*([^\s]+)/ #:nodoc:
153
+ COMMENT_OR_EMPTY_LINE_PATTERN = /^\s*#|^\s*$/ # :nodoc:
154
+ ENCODING_PATTERN = /^\s*#\s*encoding\s*:\s*([^\s]+)/ # :nodoc:
140
155
 
141
156
  def self.read(path)
142
157
  new(path).read
143
158
  end
144
159
 
145
160
  def initialize(path)
146
- begin
147
- @file = File.new(path)
148
- set_encoding
149
- rescue Errno::EACCES => e
150
- raise FileNotFoundException.new(e, File.expand_path(path))
151
- rescue Errno::ENOENT
152
- raise FeatureFolderNotFoundException.new(path)
153
- end
161
+ @file = File.new(path)
162
+ set_encoding
163
+ rescue Errno::EACCES => e
164
+ raise FileNotFoundException.new(e, File.expand_path(path))
165
+ rescue Errno::ENOENT
166
+ raise FeatureFolderNotFoundException, path
154
167
  end
155
168
 
156
169
  def read
@@ -162,7 +175,7 @@ module Cucumber
162
175
  def set_encoding
163
176
  @file.each do |line|
164
177
  if ENCODING_PATTERN =~ line
165
- @file.set_encoding $1
178
+ @file.set_encoding Regexp.last_match(1)
166
179
  break
167
180
  end
168
181
  break unless COMMENT_OR_EMPTY_LINE_PATTERN =~ line
@@ -171,16 +184,17 @@ module Cucumber
171
184
  end
172
185
  end
173
186
 
174
- require 'cucumber/formatter/legacy_api/adapter'
175
- require 'cucumber/formatter/legacy_api/runtime_facade'
176
- require 'cucumber/formatter/legacy_api/results'
177
187
  require 'cucumber/formatter/ignore_missing_messages'
178
188
  require 'cucumber/formatter/fail_fast'
189
+ require 'cucumber/formatter/publish_banner_printer'
179
190
  require 'cucumber/core/report/summary'
191
+
180
192
  def report
181
193
  return @report if @report
194
+
182
195
  reports = [summary_report] + formatters
183
196
  reports << fail_fast_report if @configuration.fail_fast?
197
+ reports << publish_banner_printer unless @configuration.publish_quiet?
184
198
  @report ||= Formatter::Fanout.new(reports)
185
199
  end
186
200
 
@@ -192,52 +206,36 @@ module Cucumber
192
206
  @fail_fast_report ||= Formatter::FailFast.new(@configuration)
193
207
  end
194
208
 
209
+ def publish_banner_printer
210
+ @publish_banner_printer ||= Formatter::PublishBannerPrinter.new(@configuration)
211
+ end
212
+
195
213
  def formatters
196
214
  @formatters ||=
197
- @configuration.formatter_factories do |factory, formatter_options, path_or_io, options|
198
- create_formatter(factory, formatter_options, path_or_io, options)
215
+ @configuration.formatter_factories do |factory, formatter_options, path_or_io|
216
+ create_formatter(factory, formatter_options, path_or_io)
199
217
  end
200
218
  end
201
219
 
202
- def create_formatter(factory, formatter_options, path_or_io, cli_options)
203
- if !legacy_formatter?(factory)
204
- if accept_options?(factory)
205
- return factory.new(@configuration, formatter_options) if path_or_io.nil?
206
- return factory.new(@configuration.with_options(out_stream: path_or_io),
207
- formatter_options)
208
- else
209
- return factory.new(@configuration) if path_or_io.nil?
210
- return factory.new(@configuration.with_options(out_stream: path_or_io))
211
- end
220
+ def create_formatter(factory, formatter_options, path_or_io)
221
+ if accept_options?(factory)
222
+ return factory.new(@configuration, formatter_options) if path_or_io.nil?
223
+
224
+ factory.new(@configuration.with_options(out_stream: path_or_io),
225
+ formatter_options)
226
+ else
227
+ return factory.new(@configuration) if path_or_io.nil?
228
+
229
+ factory.new(@configuration.with_options(out_stream: path_or_io))
212
230
  end
213
- results = Formatter::LegacyApi::Results.new
214
- runtime_facade = Formatter::LegacyApi::RuntimeFacade.new(results, @support_code, @configuration)
215
- formatter = factory.new(runtime_facade, path_or_io, cli_options)
216
- Formatter::LegacyApi::Adapter.new(
217
- Formatter::IgnoreMissingMessages.new(formatter),
218
- results, @configuration
219
- )
220
231
  end
221
232
 
222
233
  def accept_options?(factory)
223
234
  factory.instance_method(:initialize).arity > 1
224
235
  end
225
236
 
226
- def legacy_formatter?(factory)
227
- factory.instance_method(:initialize).arity > 2
228
- end
229
-
230
- def failure?
231
- if @configuration.wip?
232
- summary_report.test_cases.total_passed > 0
233
- else
234
- !summary_report.ok?(@configuration.strict)
235
- end
236
- end
237
- public :failure?
238
-
239
237
  require 'cucumber/core/test/filters'
240
- def filters
238
+ def filters # rubocop:disable Metrics/AbcSize
241
239
  tag_expressions = @configuration.tag_expressions
242
240
  name_regexps = @configuration.name_regexps
243
241
  tag_limits = @configuration.tag_limits
@@ -250,14 +248,18 @@ module Cucumber
250
248
  # TODO: can we just use Glue::RegistryAndMore's step definitions directly?
251
249
  step_match_search = StepMatchSearch.new(@support_code.registry.method(:step_matches), @configuration)
252
250
  filters << Filters::ActivateSteps.new(step_match_search, @configuration)
253
- @configuration.filters.each do |filter|
254
- filters << filter
255
- end
251
+ @configuration.filters.each { |filter| filters << filter }
252
+
256
253
  unless configuration.dry_run?
257
254
  filters << Filters::ApplyAfterStepHooks.new(@support_code)
258
255
  filters << Filters::ApplyBeforeHooks.new(@support_code)
259
256
  filters << Filters::ApplyAfterHooks.new(@support_code)
260
257
  filters << Filters::ApplyAroundHooks.new(@support_code)
258
+ end
259
+
260
+ filters << Filters::BroadcastTestCaseReadyEvent.new(@configuration)
261
+
262
+ unless configuration.dry_run?
261
263
  filters << Filters::BroadcastTestRunStartedEvent.new(@configuration)
262
264
  filters << Filters::Quit.new
263
265
  filters << Filters::Retry.new(@configuration)
@@ -272,8 +274,8 @@ module Cucumber
272
274
  @support_code.load_files!(files)
273
275
  end
274
276
 
275
- def install_wire_plugin
276
- Cucumber::Wire::Plugin.new(@configuration).install if @configuration.all_files_to_load.any? { |f| f =~ %r{\.wire$} }
277
+ def registry_wrapper
278
+ Cucumber::Glue::RegistryWrapper.new(@support_code.registry)
277
279
  end
278
280
 
279
281
  def log
@@ -9,11 +9,12 @@ module Cucumber
9
9
  attr_reader :regexp_source, :location
10
10
 
11
11
  def initialize(regexp_source, location)
12
- @regexp_source, @location = regexp_source, location
12
+ @regexp_source = regexp_source
13
+ @location = location
13
14
  end
14
15
 
15
- def eql?(o)
16
- regexp_source == o.regexp_source && location == o.location
16
+ def eql?(other)
17
+ regexp_source == other.regexp_source && location == other.location
17
18
  end
18
19
 
19
20
  def hash
@@ -8,8 +8,8 @@ module Cucumber
8
8
  @support_code.load_files_from_paths(configuration.autoload_code_paths)
9
9
  end
10
10
 
11
- def to_json
12
- @support_code.step_definitions.map(&:to_hash).to_json
11
+ def to_json(obj = nil)
12
+ @support_code.step_definitions.map(&:to_hash).to_json(obj)
13
13
  end
14
14
  end
15
15
  end
@@ -4,12 +4,15 @@ require 'cucumber/multiline_argument'
4
4
 
5
5
  module Cucumber
6
6
  # Represents the match found between a Test Step and its activation
7
- class StepMatch #:nodoc:
7
+ class StepMatch # :nodoc:
8
8
  attr_reader :step_definition, :step_arguments
9
9
 
10
10
  def initialize(step_definition, step_name, step_arguments)
11
11
  raise "step_arguments can't be nil (but it can be an empty array)" if step_arguments.nil?
12
- @step_definition, @name_to_match, @step_arguments = step_definition, step_name, step_arguments
12
+
13
+ @step_definition = step_definition
14
+ @name_to_match = step_name
15
+ @step_arguments = step_arguments
13
16
  end
14
17
 
15
18
  def args
@@ -21,12 +24,12 @@ module Cucumber
21
24
 
22
25
  def activate(test_step)
23
26
  test_step.with_action(@step_definition.location) do
24
- invoke(MultilineArgument.from_core(test_step.source.last.multiline_arg))
27
+ invoke(MultilineArgument.from_core(test_step.multiline_arg))
25
28
  end
26
29
  end
27
30
 
28
31
  def invoke(multiline_arg)
29
- all_args = deep_clone_args
32
+ all_args = args
30
33
  multiline_arg.append_to(all_args)
31
34
  @step_definition.invoke(all_args)
32
35
  end
@@ -46,7 +49,7 @@ module Cucumber
46
49
  #
47
50
  # lambda { |param| "[#{param}]" }
48
51
  #
49
- def format_args(format = lambda { |a| a }, &proc)
52
+ def format_args(format = ->(a) { a }, &proc)
50
53
  replace_arguments(@name_to_match, @step_arguments, format, &proc)
51
54
  end
52
55
 
@@ -75,7 +78,7 @@ module Cucumber
75
78
 
76
79
  replacement = if block_given?
77
80
  yield(group.value)
78
- elsif Proc === format
81
+ elsif Proc == format.class
79
82
  format.call(group.value)
80
83
  else
81
84
  format % group.value
@@ -88,24 +91,18 @@ module Cucumber
88
91
  s
89
92
  end
90
93
 
91
- def inspect #:nodoc:
94
+ def inspect # :nodoc:
92
95
  "#<#{self.class}: #{location}>"
93
96
  end
94
-
95
- private
96
-
97
- def deep_clone_args
98
- Marshal.load( Marshal.dump( args ) )
99
- end
100
97
  end
101
98
 
102
99
  class SkippingStepMatch
103
100
  def activate(test_step)
104
- return test_step.with_action { raise Core::Test::Result::Skipped.new }
101
+ test_step.with_action { raise Core::Test::Result::Skipped }
105
102
  end
106
103
  end
107
104
 
108
- class NoStepMatch #:nodoc:
105
+ class NoStepMatch # :nodoc:
109
106
  attr_reader :step_definition, :name
110
107
 
111
108
  def initialize(step, name)
@@ -119,12 +116,12 @@ module Cucumber
119
116
 
120
117
  def location
121
118
  raise "No location for #{@step}" unless @step.location
119
+
122
120
  @step.location
123
121
  end
124
122
 
125
123
  def file_colon_line
126
- raise "No file:line for #{@step}" unless @step.file_colon_line
127
- @step.file_colon_line
124
+ location.to_s
128
125
  end
129
126
 
130
127
  def backtrace_line
@@ -132,7 +129,7 @@ module Cucumber
132
129
  end
133
130
 
134
131
  def text_length
135
- @step.text_length
132
+ @step.text.length
136
133
  end
137
134
 
138
135
  def step_arguments
@@ -141,7 +138,7 @@ module Cucumber
141
138
 
142
139
  def activate(test_step)
143
140
  # noop
144
- return test_step
141
+ test_step
145
142
  end
146
143
  end
147
144
 
@@ -151,7 +148,7 @@ module Cucumber
151
148
  end
152
149
 
153
150
  def activate(test_step)
154
- return test_step.with_action { raise @error }
151
+ test_step.with_action { raise @error }
155
152
  end
156
153
  end
157
154
  end
@@ -13,12 +13,14 @@ module Cucumber
13
13
 
14
14
  class AssertUnambiguousMatch
15
15
  def initialize(search, configuration)
16
- @search, @configuration = search, configuration
16
+ @search = search
17
+ @configuration = configuration
17
18
  end
18
19
 
19
20
  def call(step_name)
20
21
  result = @search.call(step_name)
21
22
  raise Cucumber::Ambiguous.new(step_name, result, @configuration.guess?) if result.length > 1
23
+
22
24
  result
23
25
  end
24
26
  end
@@ -34,7 +36,7 @@ module Cucumber
34
36
 
35
37
  private
36
38
 
37
- def best_matches(_step_name, step_matches) #:nodoc:
39
+ def best_matches(_step_name, step_matches) # :nodoc:
38
40
  no_groups = step_matches.select { |step_match| step_match.args.empty? }
39
41
  max_arg_length = step_matches.map { |step_match| step_match.args.length }.max
40
42
  top_groups = step_matches.select { |step_match| step_match.args.length == max_arg_length }
@@ -53,7 +55,7 @@ module Cucumber
53
55
 
54
56
  require 'delegate'
55
57
  class CachesStepMatch < SimpleDelegator
56
- def call(step_name) #:nodoc:
58
+ def call(step_name) # :nodoc:
57
59
  @match_cache ||= {}
58
60
 
59
61
  matches = @match_cache[step_name]
@@ -2,8 +2,28 @@
2
2
 
3
3
  module Cucumber
4
4
  module Term
5
- # The ANSIColor module can be used for namespacing and mixed into your own
6
- # classes.
5
+ # This module allows to colorize text using ANSI escape sequences.
6
+ #
7
+ # Include the module in your class and use its methods to colorize text.
8
+ #
9
+ # Example:
10
+ #
11
+ # require 'cucumber/term/ansicolor'
12
+ #
13
+ # class MyFormatter
14
+ # include Cucumber::Term::ANSIColor
15
+ #
16
+ # def initialize(config)
17
+ # $stdout.puts yellow("Initializing formatter")
18
+ # $stdout.puts green("Coloring is active \o/") if Cucumber::Term::ANSIColor.coloring?
19
+ # $stdout.puts grey("Feature path:") + blue(bold(config.feature_dirs))
20
+ # end
21
+ # end
22
+ #
23
+ # To see what colours and effects are available, just run this in your shell:
24
+ #
25
+ # ruby -e "require 'rubygems'; require 'cucumber/term/ansicolor'; puts Cucumber::Term::ANSIColor.attributes"
26
+ #
7
27
  module ANSIColor
8
28
  # :stopdoc:
9
29
  ATTRIBUTES = [
@@ -27,6 +47,7 @@ module Cucumber
27
47
  [:magenta, 35],
28
48
  [:cyan, 36],
29
49
  [:white, 37],
50
+ [:grey, 90],
30
51
  [:on_black, 40],
31
52
  [:on_red, 41],
32
53
  [:on_green, 42],
@@ -35,77 +56,80 @@ module Cucumber
35
56
  [:on_magenta, 45],
36
57
  [:on_cyan, 46],
37
58
  [:on_white, 47]
38
- ]
59
+ ].freeze
39
60
 
40
61
  ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
41
62
  # :startdoc:
42
63
 
43
- # Returns true, if the coloring function of this module
44
- # is switched on, false otherwise.
45
- def self.coloring?
46
- @coloring
47
- end
48
-
49
- # Turns the coloring on or off globally, so you can easily do
50
- # this for example:
51
- # Cucumber::Term::ANSIColor::coloring = STDOUT.isatty
52
- def self.coloring=(val)
53
- @coloring = val
54
- end
55
- self.coloring = true
56
-
57
- ATTRIBUTES.each do |c, v|
58
- eval %Q{
59
- def #{c}(string = nil)
60
- result = String.new
61
- result << "\e[#{v}m" if Cucumber::Term::ANSIColor.coloring?
62
- if block_given?
63
- result << yield
64
- elsif string
65
- result << string
66
- elsif respond_to?(:to_str)
67
- result << to_str
68
- else
69
- return result #only switch on
70
- end
71
- result << "\e[0m" if Cucumber::Term::ANSIColor.coloring?
72
- result
73
- end
74
- }
75
- end
76
-
77
64
  # Regular expression that is used to scan for ANSI-sequences while
78
65
  # uncoloring strings.
79
66
  COLORED_REGEXP = /\e\[(?:[34][0-7]|[0-9])?m/
80
67
 
81
- def self.included(klass)
82
- if klass == String
68
+ @coloring = true
69
+
70
+ class << self
71
+ # Turns the coloring on or off globally, so you can easily do
72
+ # this for example:
73
+ # Cucumber::Term::ANSIColor::coloring = $stdout.isatty
74
+ attr_accessor :coloring
75
+
76
+ # Returns true, if the coloring function of this module
77
+ # is switched on, false otherwise.
78
+ alias coloring? :coloring
79
+
80
+ def included(klass)
81
+ return unless klass == String
82
+
83
83
  ATTRIBUTES.delete(:clear)
84
84
  ATTRIBUTE_NAMES.delete(:clear)
85
85
  end
86
86
  end
87
87
 
88
- # Returns an uncolored version of the string, that is all
88
+ ATTRIBUTES.each do |color_name, color_code|
89
+ define_method(color_name) do |text = nil, &block|
90
+ if block
91
+ colorize(block.call, color_code)
92
+ elsif text
93
+ colorize(text, color_code)
94
+ elsif respond_to?(:to_str)
95
+ colorize(to_str, color_code)
96
+ else
97
+ colorize(nil, color_code) # switch coloration on
98
+ end
99
+ end
100
+ end
101
+
102
+ # Returns an uncolored version of the string
89
103
  # ANSI-sequences are stripped from the string.
90
- def uncolored(string = nil) # :yields:
104
+ def uncolored(text = nil)
91
105
  if block_given?
92
- yield.gsub(COLORED_REGEXP, '')
93
- elsif string
94
- string.gsub(COLORED_REGEXP, '')
106
+ uncolorize(yield)
107
+ elsif text
108
+ uncolorize(text)
95
109
  elsif respond_to?(:to_str)
96
- to_str.gsub(COLORED_REGEXP, '')
110
+ uncolorize(to_str)
97
111
  else
98
112
  ''
99
113
  end
100
114
  end
101
115
 
102
- module_function
103
-
104
116
  # Returns an array of all Cucumber::Term::ANSIColor attributes as symbols.
105
117
  def attributes
106
118
  ATTRIBUTE_NAMES
107
119
  end
108
- extend self
120
+
121
+ private
122
+
123
+ def colorize(text, color_code)
124
+ return String.new(text || '') unless Cucumber::Term::ANSIColor.coloring?
125
+ return "\e[#{color_code}m" unless text
126
+
127
+ "\e[#{color_code}m#{text}\e[0m"
128
+ end
129
+
130
+ def uncolorize(string)
131
+ string.gsub(COLORED_REGEXP, '')
132
+ end
109
133
  end
110
134
  end
111
135
  end
@@ -0,0 +1,57 @@
1
+ require 'cucumber/term/ansicolor'
2
+
3
+ module Cucumber
4
+ module Term
5
+ module Banner
6
+ def display_banner(lines, io, border_modifiers = nil)
7
+ BannerMaker.new.display_banner(lines, io, border_modifiers || %i[green bold])
8
+ end
9
+
10
+ class BannerMaker
11
+ include Term::ANSIColor
12
+
13
+ def display_banner(lines, io, border_modifiers)
14
+ lines = lines.split("\n") if lines.is_a? String
15
+ longest_line_length = lines.map { |line| line_length(line) }.max
16
+
17
+ io.puts apply_modifiers("┌#{'─' * (longest_line_length + 2)}┐", border_modifiers)
18
+ lines.map do |line|
19
+ padding = ' ' * (longest_line_length - line_length(line))
20
+ io.puts "#{apply_modifiers('│', border_modifiers)} #{display_line(line)}#{padding} #{apply_modifiers('│', border_modifiers)}"
21
+ end
22
+ io.puts apply_modifiers("└#{'─' * (longest_line_length + 2)}┘", border_modifiers)
23
+ end
24
+
25
+ private
26
+
27
+ def display_line(line)
28
+ line.is_a?(Array) ? line.map { |span| display_span(span) }.join : line
29
+ end
30
+
31
+ def display_span(span)
32
+ return apply_modifiers(span.shift, span) if span.is_a?(Array)
33
+
34
+ span
35
+ end
36
+
37
+ def apply_modifiers(str, modifiers)
38
+ display = str
39
+ modifiers.each { |modifier| display = send(modifier, display) }
40
+ display
41
+ end
42
+
43
+ def line_length(line)
44
+ if line.is_a?(Array)
45
+ line.map { |span| span_length(span) }.sum
46
+ else
47
+ line.length
48
+ end
49
+ end
50
+
51
+ def span_length(span)
52
+ span.is_a?(Array) ? span[0].length : span.length
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end