gherkin 7.0.4 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gherkin +34 -12
  3. data/lib/gherkin.rb +42 -0
  4. data/lib/gherkin/ast_builder.rb +260 -0
  5. data/lib/gherkin/ast_node.rb +30 -0
  6. data/lib/gherkin/dialect.rb +2 -12
  7. data/lib/gherkin/errors.rb +45 -0
  8. data/lib/gherkin/gherkin-languages.json +3520 -0
  9. data/lib/gherkin/gherkin_line.rb +97 -0
  10. data/lib/gherkin/parser.rb +3429 -0
  11. data/lib/gherkin/pickles/compiler.rb +233 -0
  12. data/lib/gherkin/stream/parser_message_stream.rb +93 -0
  13. data/lib/gherkin/stream/protobuf_message_stream.rb +21 -0
  14. data/lib/gherkin/stream/subprocess_message_stream.rb +26 -0
  15. data/lib/gherkin/token.rb +18 -0
  16. data/lib/gherkin/token_formatter_builder.rb +39 -0
  17. data/lib/gherkin/token_matcher.rb +169 -0
  18. data/lib/gherkin/token_scanner.rb +40 -0
  19. data/spec/gherkin/gherkin_spec.rb +36 -345
  20. data/spec/gherkin/stream/subprocess_message_stream_spec.rb +18 -0
  21. metadata +28 -53
  22. data/executables/gherkin-darwin-386 +0 -0
  23. data/executables/gherkin-darwin-amd64 +0 -0
  24. data/executables/gherkin-freebsd-386 +0 -0
  25. data/executables/gherkin-freebsd-amd64 +0 -0
  26. data/executables/gherkin-freebsd-arm +0 -0
  27. data/executables/gherkin-linux-386 +0 -0
  28. data/executables/gherkin-linux-amd64 +0 -0
  29. data/executables/gherkin-linux-arm +0 -0
  30. data/executables/gherkin-linux-mips +0 -0
  31. data/executables/gherkin-linux-mips64 +0 -0
  32. data/executables/gherkin-linux-mips64le +0 -0
  33. data/executables/gherkin-linux-mipsle +0 -0
  34. data/executables/gherkin-linux-s390x +0 -0
  35. data/executables/gherkin-netbsd-386 +0 -0
  36. data/executables/gherkin-netbsd-amd64 +0 -0
  37. data/executables/gherkin-netbsd-arm +0 -0
  38. data/executables/gherkin-openbsd-386 +0 -0
  39. data/executables/gherkin-openbsd-amd64 +0 -0
  40. data/executables/gherkin-windows-386.exe +0 -0
  41. data/executables/gherkin-windows-amd64.exe +0 -0
  42. data/lib/gherkin/exe_file_path.rb +0 -3
  43. data/lib/gherkin/gherkin.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4b01f45decc72c0d17d4c1ea68c653671414abfb36c887140ef8a060bc4f277
4
- data.tar.gz: dee6817d1308ddab1bb14fda79e730c27eaf09130646f3e7947a51943f9d043a
3
+ metadata.gz: 03d427e820bf0e7c744cbe67f7f9173ade3a528dd7a3f02c94ef6b261d54fe7b
4
+ data.tar.gz: 01d2c0fca9b02b4a0c6c480ab47c10178210814c86e7dd6f4ffcdde9540dcfda
5
5
  SHA512:
6
- metadata.gz: 1dba2967b674ce4ad491da5cb289ece8e2c59d1a30988b3d9664aa378e2e1c69981c600eb6edc7561ef3dbe20ae74de8b656b9eaefeadc994ee1cc4ae5fc44d4
7
- data.tar.gz: 633ea2dc1e7459abd5aab2d0271c072c4788f6e59628b0e1a4c175375de3cdf818a898d5e6b2d41422af27020794794a81d081324c582dbdcc9edc39439f88b7
6
+ metadata.gz: c638788e4c6926086d5271cf414665d3fdaebf464643283238ca6f86cb000d1650c69017c5bc11260c2770a093b0c9538ab2ab0df73e44b2d9ef1c7e4f208475
7
+ data.tar.gz: 4960487bbdde80f6d74df8e81fd4d3983a1fe2c998ea5d9842cfc8db9ab4607cf0c4909f5c89a2b1f19a0e8614eaabd0b673ed27d79488651da1caf51606cecb
@@ -4,12 +4,16 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"../lib"))
4
4
 
5
5
  require 'optparse'
6
6
  require 'json'
7
- require 'gherkin/gherkin'
7
+ require 'cucumber/messages'
8
+ require 'gherkin'
9
+ require 'gherkin/stream/subprocess_message_stream'
10
+ require 'gherkin/stream/protobuf_message_stream'
8
11
 
9
12
  options = {
10
13
  include_source: true,
11
14
  include_gherkin_document: true,
12
- include_pickles: true
15
+ include_pickles: true,
16
+ json: false
13
17
  }
14
18
 
15
19
  OptionParser.new do |opts|
@@ -22,14 +26,21 @@ OptionParser.new do |opts|
22
26
  opts.on("--[no-]pickles", "Don't print pickle messages") do |v|
23
27
  options[:include_pickles] = v
24
28
  end
29
+ opts.on("--json", "Print messages as JSON") do |v|
30
+ options[:json] = v
31
+ end
25
32
  end.parse!
26
33
 
27
- def include_messages(messages)
34
+ def process_messages(messages, options)
28
35
  messages.each do |message|
29
- json = message.class.encode_json(message)
30
- ob = JSON.parse(json)
31
- remove_empties(ob)
32
- puts JSON.generate(ob)
36
+ if options[:json]
37
+ json = message.class.encode_json(message)
38
+ ob = JSON.parse(json)
39
+ remove_empties(ob)
40
+ puts JSON.generate(ob)
41
+ else
42
+ message.write_delimited_to(STDOUT)
43
+ end
33
44
  end
34
45
  end
35
46
 
@@ -49,12 +60,23 @@ def remove_empties(ob)
49
60
  end
50
61
  end
51
62
 
52
- messages = ARGV.empty? ?
63
+ gherkin_executable = ENV['GHERKIN_EXECUTABLE']
64
+ if ARGV.empty?
65
+ # Read protobuf from STDIN
66
+ messages = Gherkin::Stream::ProtobufMessageStream.new(STDIN).messages
67
+ elsif gherkin_executable
53
68
  # Read protobuf from STDIN
54
- Gherkin::ProtobufCucumberMessages.new(STDIN).messages
55
- :
56
- Gherkin::Gherkin.from_paths(
69
+ messages = Gherkin::Stream::SubprocessMessageStream.new(
70
+ gherkin_executable,
71
+ ARGV,
72
+ options[:include_source],
73
+ options[:include_gherkin_document],
74
+ options[:include_pickles]
75
+ ).messages
76
+ else
77
+ messages = Gherkin.from_paths(
57
78
  ARGV,
58
79
  options
59
80
  )
60
- include_messages(messages)
81
+ end
82
+ process_messages(messages, options)
@@ -0,0 +1,42 @@
1
+ require 'gherkin/stream/parser_message_stream'
2
+
3
+ module Gherkin
4
+ DEFAULT_OPTIONS = {
5
+ include_source: true,
6
+ include_gherkin_document: true,
7
+ include_pickles: true
8
+ }.freeze
9
+
10
+ def self.from_paths(paths, options={})
11
+ Stream::ParserMessageStream.new(
12
+ paths,
13
+ [],
14
+ options
15
+ ).messages
16
+ end
17
+
18
+ def self.from_sources(sources, options={})
19
+ Stream::ParserMessageStream.new(
20
+ [],
21
+ sources,
22
+ options
23
+ ).messages
24
+ end
25
+
26
+ def self.from_source(uri, data, options={})
27
+ from_sources([encode_source_message(uri, data)], options)
28
+ end
29
+
30
+ private
31
+
32
+ def self.encode_source_message(uri, data)
33
+ Cucumber::Messages::Source.new({
34
+ uri: uri,
35
+ data: data,
36
+ media: Cucumber::Messages::Media.new({
37
+ encoding: :UTF8,
38
+ content_type: 'text/x.cucumber.gherkin+plain'
39
+ })
40
+ })
41
+ end
42
+ end
@@ -0,0 +1,260 @@
1
+ require 'cucumber/messages'
2
+ require 'gherkin/ast_node'
3
+
4
+ module Gherkin
5
+ class AstBuilder
6
+ def initialize
7
+ reset
8
+ end
9
+
10
+ def reset
11
+ @stack = [AstNode.new(:None)]
12
+ @comments = []
13
+ end
14
+
15
+ def start_rule(rule_type)
16
+ @stack.push AstNode.new(rule_type)
17
+ end
18
+
19
+ def end_rule(rule_type)
20
+ node = @stack.pop
21
+ current_node.add(node.rule_type, transform_node(node))
22
+ end
23
+
24
+ def build(token)
25
+ if token.matched_type == :Comment
26
+ @comments.push(Cucumber::Messages::GherkinDocument::Comment.new(
27
+ location: get_location(token, 0),
28
+ text: token.matched_text
29
+ ))
30
+ else
31
+ current_node.add(token.matched_type, token)
32
+ end
33
+ end
34
+
35
+ def get_result
36
+ current_node.get_single(:GherkinDocument)
37
+ end
38
+
39
+ def current_node
40
+ @stack.last
41
+ end
42
+
43
+ def get_location(token, column)
44
+ column = column == 0 ? token.location[:column] : column
45
+ Cucumber::Messages::Location.new(
46
+ line: token.location[:line],
47
+ column: column
48
+ )
49
+ end
50
+
51
+ def get_tags(node)
52
+ tags = []
53
+ tags_node = node.get_single(:Tags)
54
+ return tags unless tags_node
55
+
56
+ tags_node.get_tokens(:TagLine).each do |token|
57
+ token.matched_items.each do |tag_item|
58
+ tags.push(Cucumber::Messages::GherkinDocument::Feature::Tag.new(
59
+ location: get_location(token, tag_item.column),
60
+ name: tag_item.text
61
+ ))
62
+ end
63
+ end
64
+
65
+ tags
66
+ end
67
+
68
+ def get_table_rows(node)
69
+ rows = node.get_tokens(:TableRow).map do |token|
70
+ Cucumber::Messages::GherkinDocument::Feature::TableRow.new(
71
+ location: get_location(token, 0),
72
+ cells: get_cells(token)
73
+ )
74
+ end
75
+ ensure_cell_count(rows)
76
+ rows
77
+ end
78
+
79
+ def ensure_cell_count(rows)
80
+ return if rows.empty?
81
+ cell_count = rows[0].cells.length
82
+ rows.each do |row|
83
+ if row.cells.length != cell_count
84
+ location = {line: row.location.line, column: row.location.column}
85
+ raise AstBuilderException.new("inconsistent cell count within the table", location)
86
+ end
87
+ end
88
+ end
89
+
90
+ def get_cells(table_row_token)
91
+ table_row_token.matched_items.map do |cell_item|
92
+ Cucumber::Messages::GherkinDocument::Feature::TableRow::TableCell.new(
93
+ location: get_location(table_row_token, cell_item.column),
94
+ value: cell_item.text
95
+ )
96
+ end
97
+ end
98
+
99
+ def get_description(node)
100
+ node.get_single(:Description)
101
+ end
102
+
103
+ def get_steps(node)
104
+ node.get_items(:Step)
105
+ end
106
+
107
+ def transform_node(node)
108
+ case node.rule_type
109
+ when :Step
110
+ step_line = node.get_token(:StepLine)
111
+ data_table = node.get_single(:DataTable)
112
+ doc_string = node.get_single(:DocString)
113
+
114
+ props = {
115
+ location: get_location(step_line, 0),
116
+ keyword: step_line.matched_keyword,
117
+ text: step_line.matched_text
118
+ }
119
+ props[:data_table] = data_table if data_table
120
+ props[:doc_string] = doc_string if doc_string
121
+
122
+ Cucumber::Messages::GherkinDocument::Feature::Step.new(props)
123
+ when :DocString
124
+ separator_token = node.get_tokens(:DocStringSeparator)[0]
125
+ content_type = separator_token.matched_text == '' ? nil : separator_token.matched_text
126
+ line_tokens = node.get_tokens(:Other)
127
+ content = line_tokens.map { |t| t.matched_text }.join("\n")
128
+
129
+ props = {
130
+ location: get_location(separator_token, 0),
131
+ content: content,
132
+ delimiter: separator_token.matched_keyword
133
+ }
134
+ props[:content_type] = content_type if content_type
135
+ Cucumber::Messages::GherkinDocument::Feature::Step::DocString.new(props)
136
+ when :DataTable
137
+ rows = get_table_rows(node)
138
+ Cucumber::Messages::GherkinDocument::Feature::Step::DataTable.new(
139
+ location: rows[0].location,
140
+ rows: rows,
141
+ )
142
+ when :Background
143
+ background_line = node.get_token(:BackgroundLine)
144
+ description = get_description(node)
145
+ steps = get_steps(node)
146
+
147
+ props = {
148
+ location: get_location(background_line, 0),
149
+ keyword: background_line.matched_keyword,
150
+ name: background_line.matched_text,
151
+ steps: steps
152
+ }
153
+ props[:description] = description if description
154
+ Cucumber::Messages::GherkinDocument::Feature::Background.new(props)
155
+ when :ScenarioDefinition
156
+ tags = get_tags(node)
157
+ scenario_node = node.get_single(:Scenario)
158
+ scenario_line = scenario_node.get_token(:ScenarioLine)
159
+ description = get_description(scenario_node)
160
+ steps = get_steps(scenario_node)
161
+ examples = scenario_node.get_items(:ExamplesDefinition)
162
+ props = {
163
+ tags: tags,
164
+ location: get_location(scenario_line, 0),
165
+ keyword: scenario_line.matched_keyword,
166
+ name: scenario_line.matched_text,
167
+ steps: steps,
168
+ examples: examples
169
+ }
170
+ props[:description] = description if description
171
+ Cucumber::Messages::GherkinDocument::Feature::Scenario.new(props)
172
+ when :ExamplesDefinition
173
+ tags = get_tags(node)
174
+ examples_node = node.get_single(:Examples)
175
+ examples_line = examples_node.get_token(:ExamplesLine)
176
+ description = get_description(examples_node)
177
+ rows = examples_node.get_single(:ExamplesTable)
178
+
179
+ table_header = rows.nil? ? nil : rows.first
180
+ table_body = rows.nil? ? nil : rows[1..-1]
181
+
182
+ props = {
183
+ tags: tags,
184
+ location: get_location(examples_line, 0),
185
+ keyword: examples_line.matched_keyword,
186
+ name: examples_line.matched_text,
187
+ }
188
+ props[:table_header] = table_header if table_header
189
+ props[:table_body] = table_body if table_body
190
+ props[:description] = description if description
191
+ Cucumber::Messages::GherkinDocument::Feature::Scenario::Examples.new(props)
192
+ when :ExamplesTable
193
+ get_table_rows(node)
194
+ when :Description
195
+ line_tokens = node.get_tokens(:Other)
196
+ # Trim trailing empty lines
197
+ last_non_empty = line_tokens.rindex { |token| !token.line.trimmed_line_text.empty? }
198
+ description = line_tokens[0..last_non_empty].map { |token| token.matched_text }.join("\n")
199
+ return description
200
+ when :Feature
201
+ header = node.get_single(:FeatureHeader)
202
+ return unless header
203
+ tags = get_tags(header)
204
+ feature_line = header.get_token(:FeatureLine)
205
+ return unless feature_line
206
+ children = []
207
+ background = node.get_single(:Background)
208
+ children.push(Cucumber::Messages::GherkinDocument::Feature::FeatureChild.new(background: background)) if background
209
+ node.get_items(:ScenarioDefinition).each do |scenario|
210
+ children.push(Cucumber::Messages::GherkinDocument::Feature::FeatureChild.new(scenario: scenario))
211
+ end
212
+ node.get_items(:Rule).each do |rule|
213
+ children.push(Cucumber::Messages::GherkinDocument::Feature::FeatureChild.new(rule: rule))
214
+ end
215
+ description = get_description(header)
216
+ language = feature_line.matched_gherkin_dialect
217
+
218
+ props = {
219
+ tags: tags,
220
+ location: get_location(feature_line, 0),
221
+ language: language,
222
+ keyword: feature_line.matched_keyword,
223
+ name: feature_line.matched_text,
224
+ children: children,
225
+ }
226
+ props[:description] = description if description
227
+
228
+ Cucumber::Messages::GherkinDocument::Feature.new(props)
229
+ when :Rule
230
+ header = node.get_single(:RuleHeader)
231
+ return unless header
232
+ rule_line = header.get_token(:RuleLine)
233
+ return unless rule_line
234
+ children = []
235
+ background = node.get_single(:Background)
236
+ children.push(Cucumber::Messages::GherkinDocument::Feature::FeatureChild::RuleChild.new(background: background)) if background
237
+ node.get_items(:ScenarioDefinition).each do |scenario|
238
+ children.push(Cucumber::Messages::GherkinDocument::Feature::FeatureChild::RuleChild.new(scenario: scenario))
239
+ end
240
+ description = get_description(header)
241
+
242
+ Cucumber::Messages::GherkinDocument::Feature::FeatureChild::Rule.new(
243
+ location: get_location(rule_line, 0),
244
+ keyword: rule_line.matched_keyword,
245
+ name: rule_line.matched_text,
246
+ description: description,
247
+ children: children,
248
+ )
249
+ when :GherkinDocument
250
+ feature = node.get_single(:Feature)
251
+ {
252
+ feature: feature,
253
+ comments: @comments
254
+ }
255
+ else
256
+ return node
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,30 @@
1
+ module Gherkin
2
+ class AstNode
3
+ attr_reader :rule_type
4
+
5
+ def initialize(rule_type)
6
+ @rule_type = rule_type
7
+ @_sub_items = Hash.new { |hash, key| hash[key] = [] } # returns [] for unknown key
8
+ end
9
+
10
+ def add(rule_type, obj)
11
+ @_sub_items[rule_type].push(obj)
12
+ end
13
+
14
+ def get_single(rule_type)
15
+ @_sub_items[rule_type].first
16
+ end
17
+
18
+ def get_items(rule_type)
19
+ @_sub_items[rule_type]
20
+ end
21
+
22
+ def get_token(token_type)
23
+ get_single(token_type)
24
+ end
25
+
26
+ def get_tokens(token_type)
27
+ @_sub_items[token_type]
28
+ end
29
+ end
30
+ end
@@ -1,18 +1,8 @@
1
1
  require 'json'
2
- require 'open3'
3
- require 'c21e/exe_file'
4
- require 'gherkin/exe_file_path'
5
2
 
6
3
  module Gherkin
7
- def self.dialects_json
8
- gherkin_executable = C21e::ExeFile.new(EXE_FILE_PATH).target_file
9
- data, = Open3.capture2e(gherkin_executable, '--dialects')
10
- data
11
- end
12
-
13
- private_class_method :dialects_json
14
-
15
- DIALECTS = JSON.parse(dialects_json)
4
+ DIALECT_FILE_PATH = File.expand_path("gherkin-languages.json", File.dirname(__FILE__))
5
+ DIALECTS = JSON.parse File.open(DIALECT_FILE_PATH, 'r:UTF-8').read
16
6
 
17
7
  class Dialect
18
8
  def self.for(name)
@@ -0,0 +1,45 @@
1
+ module Gherkin
2
+ class ParserError < StandardError; end
3
+
4
+ class ParserException < ParserError
5
+ attr_reader :location
6
+
7
+ def initialize(message, location)
8
+ @location = location
9
+ super("(#{location[:line]}:#{location[:column] || 0}): #{message}")
10
+ end
11
+ end
12
+
13
+ class NoSuchLanguageException < ParserException
14
+ def initialize(language, location)
15
+ super "Language not supported: #{language}", location
16
+ end
17
+ end
18
+
19
+ class AstBuilderException < ParserException; end
20
+
21
+ class CompositeParserException < ParserError
22
+ attr_reader :errors
23
+
24
+ def initialize(errors)
25
+ @errors = errors
26
+ super "Parser errors:\n" + errors.map(&:message).join("\n")
27
+ end
28
+ end
29
+
30
+ class UnexpectedTokenException < ParserException
31
+ def initialize(received_token, expected_token_types, state_comment)
32
+ message = "expected: #{expected_token_types.join(", ")}, got '#{received_token.token_value.strip}'"
33
+ column = received_token.location[:column]
34
+ location = (column.nil? || column.zero?) ? {line: received_token.location[:line], column: received_token.line.indent + 1} : received_token.location
35
+ super(message, location)
36
+ end
37
+ end
38
+
39
+ class UnexpectedEOFException < ParserException
40
+ def initialize(received_token, expected_token_types, state_comment)
41
+ message = "unexpected end of file, expected: #{expected_token_types.join(", ")}"
42
+ super(message, received_token.location)
43
+ end
44
+ end
45
+ end