cucumber-core 0.1.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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +20 -0
  8. data/README.md +9 -0
  9. data/Rakefile +24 -0
  10. data/cucumber-core.gemspec +32 -0
  11. data/lib/cucumber/core.rb +37 -0
  12. data/lib/cucumber/core/ast.rb +13 -0
  13. data/lib/cucumber/core/ast/background.rb +33 -0
  14. data/lib/cucumber/core/ast/comment.rb +17 -0
  15. data/lib/cucumber/core/ast/data_table.rb +326 -0
  16. data/lib/cucumber/core/ast/describes_itself.rb +16 -0
  17. data/lib/cucumber/core/ast/doc_string.rb +83 -0
  18. data/lib/cucumber/core/ast/empty_background.rb +12 -0
  19. data/lib/cucumber/core/ast/examples_table.rb +95 -0
  20. data/lib/cucumber/core/ast/feature.rb +62 -0
  21. data/lib/cucumber/core/ast/location.rb +140 -0
  22. data/lib/cucumber/core/ast/multiline_argument.rb +33 -0
  23. data/lib/cucumber/core/ast/names.rb +19 -0
  24. data/lib/cucumber/core/ast/outline_step.rb +51 -0
  25. data/lib/cucumber/core/ast/scenario.rb +43 -0
  26. data/lib/cucumber/core/ast/scenario_outline.rb +44 -0
  27. data/lib/cucumber/core/ast/step.rb +38 -0
  28. data/lib/cucumber/core/ast/tag.rb +14 -0
  29. data/lib/cucumber/core/compiler.rb +136 -0
  30. data/lib/cucumber/core/gherkin/ast_builder.rb +315 -0
  31. data/lib/cucumber/core/gherkin/document.rb +20 -0
  32. data/lib/cucumber/core/gherkin/parser.rb +45 -0
  33. data/lib/cucumber/core/gherkin/writer.rb +220 -0
  34. data/lib/cucumber/core/gherkin/writer/helpers.rb +178 -0
  35. data/lib/cucumber/core/platform.rb +30 -0
  36. data/lib/cucumber/core/test/case.rb +143 -0
  37. data/lib/cucumber/core/test/filters.rb +48 -0
  38. data/lib/cucumber/core/test/filters/tag_filter.rb +110 -0
  39. data/lib/cucumber/core/test/hook_compiler.rb +109 -0
  40. data/lib/cucumber/core/test/mapper.rb +56 -0
  41. data/lib/cucumber/core/test/mapping.rb +67 -0
  42. data/lib/cucumber/core/test/result.rb +191 -0
  43. data/lib/cucumber/core/test/runner.rb +149 -0
  44. data/lib/cucumber/core/test/step.rb +69 -0
  45. data/lib/cucumber/core/test/timer.rb +31 -0
  46. data/lib/cucumber/core/version.rb +9 -0
  47. data/lib/cucumber/initializer.rb +18 -0
  48. data/spec/capture_warnings.rb +68 -0
  49. data/spec/coverage.rb +10 -0
  50. data/spec/cucumber/core/ast/data_table_spec.rb +139 -0
  51. data/spec/cucumber/core/ast/doc_string_spec.rb +77 -0
  52. data/spec/cucumber/core/ast/examples_table_spec.rb +87 -0
  53. data/spec/cucumber/core/ast/location_spec.rb +105 -0
  54. data/spec/cucumber/core/ast/outline_step_spec.rb +77 -0
  55. data/spec/cucumber/core/ast/step_spec.rb +44 -0
  56. data/spec/cucumber/core/compiler_spec.rb +249 -0
  57. data/spec/cucumber/core/gherkin/parser_spec.rb +182 -0
  58. data/spec/cucumber/core/gherkin/writer_spec.rb +332 -0
  59. data/spec/cucumber/core/test/case_spec.rb +416 -0
  60. data/spec/cucumber/core/test/hook_compiler_spec.rb +78 -0
  61. data/spec/cucumber/core/test/mapper_spec.rb +68 -0
  62. data/spec/cucumber/core/test/mapping_spec.rb +103 -0
  63. data/spec/cucumber/core/test/result_spec.rb +178 -0
  64. data/spec/cucumber/core/test/runner_spec.rb +265 -0
  65. data/spec/cucumber/core/test/step_spec.rb +58 -0
  66. data/spec/cucumber/core/test/timer_spec.rb +13 -0
  67. data/spec/cucumber/core_spec.rb +419 -0
  68. data/spec/cucumber/initializer_spec.rb +49 -0
  69. metadata +221 -0
@@ -0,0 +1,20 @@
1
+ require 'cucumber/initializer'
2
+
3
+ module Cucumber
4
+ module Core
5
+ module Gherkin
6
+ class Document
7
+ include Cucumber.initializer(:uri, :body)
8
+ attr_reader :uri, :body
9
+
10
+ def to_s
11
+ body
12
+ end
13
+
14
+ def ==(other)
15
+ to_s == other.to_s
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ require 'cucumber/core/gherkin/ast_builder'
2
+ require 'gherkin/parser/parser'
3
+
4
+ module Cucumber
5
+ module Core
6
+ module Gherkin
7
+ ParseError = Class.new(StandardError)
8
+
9
+ class Parser
10
+ include Cucumber.initializer(:receiver)
11
+
12
+ def document(document)
13
+ builder = AstBuilder.new(document.uri)
14
+ parser = ::Gherkin::Parser::Parser.new(builder, true, "root", false)
15
+
16
+ begin
17
+ parser.parse(document.body, document.uri, 0)
18
+ builder.language = parser.i18n_language
19
+ receiver.feature builder.result
20
+ rescue *PARSER_ERRORS => e
21
+ raise Core::Gherkin::ParseError.new("#{document.uri}: #{e.message}")
22
+ end
23
+ end
24
+
25
+ def done
26
+ receiver.done
27
+ self
28
+ end
29
+
30
+ private
31
+
32
+ PARSER_ERRORS = if Cucumber::JRUBY
33
+ [
34
+ ::Java::GherkinLexer::LexingError
35
+ ]
36
+ else
37
+ [
38
+ ::Gherkin::Lexer::LexingError,
39
+ ::Gherkin::Parser::ParseError,
40
+ ]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,220 @@
1
+ require 'cucumber/core/gherkin/writer/helpers'
2
+ require 'cucumber/core/gherkin/document'
3
+
4
+ module Cucumber
5
+ module Core
6
+ module Gherkin
7
+
8
+ module Writer
9
+ NEW_LINE = ''
10
+ def gherkin(uri = nil, &source)
11
+ uri ||= 'features/test.feature'
12
+ builder = Gherkin.new(uri, &source)
13
+ builder.build
14
+ end
15
+
16
+ class Gherkin
17
+ def initialize(uri, &source)
18
+ @uri, @source = uri, source
19
+ end
20
+
21
+ def comment(line)
22
+ comment_lines << "# #{line}"
23
+ end
24
+
25
+ def comment_lines
26
+ @comment_lines ||= []
27
+ end
28
+
29
+ def feature(*args, &source)
30
+ @feature = Feature.new(comment_lines, *args).tap do |builder|
31
+ builder.instance_exec(&source) if source
32
+ end
33
+ self
34
+ end
35
+
36
+ def build
37
+ instance_exec(&@source)
38
+ Document.new(@uri, @feature.build.join("\n"))
39
+ end
40
+ end
41
+
42
+ class Feature
43
+ include HasElements
44
+ include HasOptionsInitializer
45
+ include HasDescription
46
+ include Indentation.level(0)
47
+
48
+ default_keyword 'Feature'
49
+
50
+ elements :background, :scenario, :scenario_outline
51
+
52
+ def build(source = [])
53
+ elements.inject(source + statements) { |acc, el| el.build(acc) + [NEW_LINE] }
54
+ end
55
+
56
+ private
57
+ def language
58
+ options[:language]
59
+ end
60
+
61
+ def statements
62
+ prepare_statements language_statement,
63
+ comments_statement,
64
+ tag_statement,
65
+ name_statement,
66
+ description_statement,
67
+ NEW_LINE
68
+ end
69
+
70
+ def language_statement
71
+ "# language: #{language}" if language
72
+ end
73
+ end
74
+
75
+ class Background
76
+ include HasElements
77
+ include HasOptionsInitializer
78
+ include HasDescription
79
+ include Indentation.level 2
80
+
81
+ default_keyword 'Background'
82
+
83
+ elements :step
84
+
85
+ private
86
+ def statements
87
+ prepare_statements comments_statement, tag_statement, name_statement, description_statement
88
+ end
89
+ end
90
+
91
+ class Scenario
92
+ include HasElements
93
+ include HasOptionsInitializer
94
+ include HasDescription
95
+ include Indentation.level 2
96
+
97
+ default_keyword 'Scenario'
98
+
99
+ elements :step
100
+
101
+ private
102
+ def statements
103
+ prepare_statements comments_statement,
104
+ tag_statement,
105
+ name_statement,
106
+ description_statement
107
+ end
108
+ end
109
+
110
+ class ScenarioOutline
111
+ include HasElements
112
+ include HasOptionsInitializer
113
+ include HasDescription
114
+ include Indentation.level 2
115
+
116
+ default_keyword 'Scenario Outline'
117
+
118
+ elements :step, :examples
119
+
120
+ private
121
+ def statements
122
+ prepare_statements comments_statement, tag_statement, name_statement, description_statement
123
+ end
124
+ end
125
+
126
+ class Step
127
+ include HasElements
128
+ include HasOptionsInitializer
129
+ include Indentation.level 4
130
+
131
+ default_keyword 'Given'
132
+
133
+ elements :table
134
+
135
+ def doc_string(string, content_type='')
136
+ elements << DocString.new(string, content_type)
137
+ end
138
+
139
+ private
140
+ def statements
141
+ prepare_statements comments_statement, name_statement
142
+ end
143
+
144
+ def name_statement
145
+ "#{keyword} #{name}"
146
+ end
147
+ end
148
+
149
+ class Table
150
+ include Indentation.level(6)
151
+ include HasRows
152
+
153
+ def initialize(*)
154
+ end
155
+
156
+ def build(source)
157
+ source + statements
158
+ end
159
+
160
+ private
161
+ def statements
162
+ row_statements
163
+ end
164
+ end
165
+
166
+ class DocString
167
+ include Indentation.level(6)
168
+
169
+ attr_reader :strings, :content_type
170
+ private :strings, :content_type
171
+
172
+ def initialize(string, content_type)
173
+ @strings = string.split("\n").map(&:strip)
174
+ @content_type = content_type
175
+ end
176
+
177
+ def build(source)
178
+ source + statements
179
+ end
180
+
181
+ private
182
+ def statements
183
+ prepare_statements doc_string_statement
184
+ end
185
+
186
+ def doc_string_statement
187
+ [
188
+ %["""#{content_type}],
189
+ strings,
190
+ '"""'
191
+ ]
192
+ end
193
+ end
194
+
195
+ class Examples
196
+ include HasOptionsInitializer
197
+ include HasRows
198
+ include HasDescription
199
+ include Indentation.level(4)
200
+
201
+ default_keyword 'Examples'
202
+
203
+ def build(source)
204
+ source + statements
205
+ end
206
+
207
+ private
208
+ def statements
209
+ prepare_statements NEW_LINE,
210
+ comments_statement,
211
+ tag_statement,
212
+ name_statement,
213
+ description_statement,
214
+ row_statements(2)
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,178 @@
1
+ module Cucumber
2
+ module Core
3
+ module Gherkin
4
+ module Writer
5
+
6
+ module HasOptionsInitializer
7
+ def self.included(base)
8
+ base.extend HasDefaultKeyword
9
+ end
10
+
11
+ attr_reader :name, :options
12
+ private :name, :options
13
+
14
+ def initialize(*args)
15
+ @comments = args.shift if args.first.is_a?(Array)
16
+ @comments ||= []
17
+ @options = args.pop if args.last.is_a?(Hash)
18
+ @options ||= {}
19
+ @name = args.first
20
+ end
21
+
22
+ private
23
+
24
+ def comments_statement
25
+ @comments
26
+ end
27
+
28
+ def keyword
29
+ options.fetch(:keyword) { self.class.keyword }
30
+ end
31
+
32
+ def name_statement
33
+ "#{keyword}: #{name}".strip
34
+ end
35
+
36
+ def tag_statement
37
+ tags
38
+ end
39
+
40
+ def tags
41
+ options[:tags]
42
+ end
43
+
44
+ module HasDefaultKeyword
45
+ def default_keyword(keyword)
46
+ @keyword = keyword
47
+ end
48
+
49
+ def keyword
50
+ @keyword
51
+ end
52
+ end
53
+ end
54
+
55
+ module AcceptsComments
56
+ def comment(line)
57
+ comment_lines << "# #{line}"
58
+ end
59
+
60
+ def comment_lines
61
+ @comment_lines ||= []
62
+ end
63
+
64
+ def slurp_comments
65
+ # TODO: I can't think of another way to handle this?
66
+ # When we use the comments, we need to reset the collection
67
+ # for the next element...
68
+ slurped_comments = comment_lines.dup
69
+ @comment_lines = nil
70
+ slurped_comments
71
+ end
72
+ end
73
+
74
+ module HasElements
75
+ include AcceptsComments
76
+
77
+ def self.included(base)
78
+ base.extend HasElementBuilders
79
+ end
80
+
81
+ def build(source = [])
82
+ elements.inject(source + statements) { |acc, el| el.build(acc) }
83
+ end
84
+
85
+ private
86
+ def elements
87
+ @elements ||= []
88
+ end
89
+
90
+ module HasElementBuilders
91
+ def elements(*names)
92
+ names.each { |name| element(name) }
93
+ end
94
+
95
+ private
96
+ def element(name)
97
+ define_method name do |*args, &source|
98
+ factory_name = String(name).split("_").map(&:capitalize).join
99
+ factory = Writer.const_get(factory_name)
100
+ factory.new(slurp_comments, *args).tap do |builder|
101
+ builder.instance_exec(&source) if source
102
+ elements << builder
103
+ end
104
+ self
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ module Indentation
111
+ def self.level(number)
112
+ Module.new do
113
+ define_method :indent do |string, amount=nil|
114
+ amount ||= number
115
+ return string if string.nil? || string.empty?
116
+ (' ' * amount) + string
117
+ end
118
+
119
+ define_method :indent_level do
120
+ number
121
+ end
122
+
123
+ define_method :prepare_statements do |*statements|
124
+ statements.flatten.compact.map { |s| indent(s) }
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ module HasDescription
131
+ private
132
+ def description
133
+ options.fetch(:description) { '' }.split("\n").map(&:strip)
134
+ end
135
+
136
+ def description_statement
137
+ description.map { |s| indent(s,2) } unless description.empty?
138
+ end
139
+ end
140
+
141
+ module HasRows
142
+ def row(*cells)
143
+ rows << cells
144
+ end
145
+
146
+ def rows
147
+ @rows ||= []
148
+ end
149
+
150
+ private
151
+
152
+ def row_statements(indent=nil)
153
+ rows.map { |row| indent(table_row(row), indent) }
154
+ end
155
+
156
+ def table_row(row)
157
+ padded = pad(row)
158
+ "| #{padded.join(' | ')} |"
159
+ end
160
+
161
+ def pad(row)
162
+ row.map.with_index { |text, position| justify_cell(text, position) }
163
+ end
164
+
165
+ def column_length(column)
166
+ lengths = rows.transpose.map { |r| r.map(&:length).max }
167
+ lengths[column]
168
+ end
169
+
170
+ def justify_cell(cell, position)
171
+ cell.ljust(column_length(position))
172
+ end
173
+ end
174
+ end
175
+
176
+ end
177
+ end
178
+ end