cucumber-gherkin 9.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.
@@ -0,0 +1,188 @@
1
+ module Gherkin
2
+ module Pickles
3
+ class Compiler
4
+ def initialize(id_generator)
5
+ @id_generator = id_generator
6
+ end
7
+
8
+ def compile(gherkin_document, source)
9
+ pickles = []
10
+
11
+ return pickles unless gherkin_document.feature
12
+ feature = gherkin_document.feature
13
+ language = feature.language
14
+ tags = feature.tags
15
+
16
+ compile_feature(pickles, language, tags, feature, source)
17
+ pickles
18
+ end
19
+
20
+ private
21
+
22
+ def compile_feature(pickles, language, tags, feature, source)
23
+ feature_background_steps = []
24
+ feature.children.each do |child|
25
+ if child.background
26
+ feature_background_steps.concat(child.background.steps)
27
+ elsif child.rule
28
+ compile_rule(pickles, language, tags, feature_background_steps, child.rule, source)
29
+ else
30
+ scenario = child.scenario
31
+ if scenario.examples.empty?
32
+ compile_scenario(tags, feature_background_steps, scenario, language, pickles, source)
33
+ else
34
+ compile_scenario_outline(tags, feature_background_steps, scenario, language, pickles, source)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def compile_rule(pickles, language, tags, feature_background_steps, rule, source)
41
+ rule_background_steps = feature_background_steps.dup
42
+ rule.children.each do |child|
43
+ if child.background
44
+ rule_background_steps.concat(child.background.steps)
45
+ else
46
+ scenario = child.scenario
47
+ if scenario.examples.empty?
48
+ compile_scenario(tags, rule_background_steps, scenario, language, pickles, source)
49
+ else
50
+ compile_scenario_outline(tags, rule_background_steps, scenario, language, pickles, source)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def compile_scenario(feature_tags, background_steps, scenario, language, pickles, source)
57
+ steps = scenario.steps.empty? ? [] : [].concat(pickle_steps(background_steps))
58
+
59
+ tags = [].concat(feature_tags).concat(scenario.tags)
60
+
61
+ scenario.steps.each do |step|
62
+ steps.push(pickle_step(step))
63
+ end
64
+
65
+ pickle = Cucumber::Messages::Pickle.new(
66
+ uri: source.uri,
67
+ id: @id_generator.new_id,
68
+ tags: pickle_tags(tags),
69
+ name: scenario.name,
70
+ language: language,
71
+ ast_node_ids: [scenario.id],
72
+ steps: steps
73
+ )
74
+ pickles.push(pickle)
75
+ end
76
+
77
+ def compile_scenario_outline(feature_tags, background_steps, scenario, language, pickles, source)
78
+ scenario.examples.reject { |examples| examples.table_header.nil? }.each do |examples|
79
+ variable_cells = examples.table_header.cells
80
+ examples.table_body.each do |values_row|
81
+ value_cells = values_row.cells
82
+ steps = scenario.steps.empty? ? [] : [].concat(pickle_steps(background_steps))
83
+ tags = [].concat(feature_tags).concat(scenario.tags).concat(examples.tags)
84
+
85
+ scenario.steps.each do |scenario_outline_step|
86
+ step_props = pickle_step_props(scenario_outline_step, variable_cells, values_row)
87
+ steps.push(Cucumber::Messages::Pickle::PickleStep.new(step_props))
88
+ end
89
+
90
+ pickle = Cucumber::Messages::Pickle.new(
91
+ uri: source.uri,
92
+ id: @id_generator.new_id,
93
+ name: interpolate(scenario.name, variable_cells, value_cells),
94
+ language: language,
95
+ steps: steps,
96
+ tags: pickle_tags(tags),
97
+ ast_node_ids: [
98
+ scenario.id,
99
+ values_row.id
100
+ ],
101
+ )
102
+ pickles.push(pickle)
103
+
104
+ end
105
+ end
106
+ end
107
+
108
+ def interpolate(name, variable_cells, value_cells)
109
+ variable_cells.each_with_index do |variable_cell, n|
110
+ value_cell = value_cells[n]
111
+ name = name.gsub('<' + variable_cell.value + '>', value_cell.value)
112
+ end
113
+ name
114
+ end
115
+
116
+ def pickle_steps(steps)
117
+ steps.map do |step|
118
+ pickle_step(step)
119
+ end
120
+ end
121
+
122
+ def pickle_step(step)
123
+ Cucumber::Messages::Pickle::PickleStep.new(pickle_step_props(step, [], nil))
124
+ end
125
+
126
+ def pickle_step_props(step, variable_cells, values_row)
127
+ value_cells = values_row ? values_row.cells : []
128
+ props = {
129
+ id: @id_generator.new_id,
130
+ ast_node_ids: [step.id],
131
+ text: interpolate(step.text, variable_cells, value_cells),
132
+ }
133
+ if values_row
134
+ props[:ast_node_ids].push(values_row.id)
135
+ end
136
+
137
+ if step.data_table
138
+ data_table = Cucumber::Messages::PickleStepArgument.new(
139
+ data_table: pickle_data_table(step.data_table, variable_cells, value_cells)
140
+ )
141
+ props[:argument] = data_table
142
+ end
143
+ if step.doc_string
144
+ doc_string = Cucumber::Messages::PickleStepArgument.new(
145
+ doc_string: pickle_doc_string(step.doc_string, variable_cells, value_cells)
146
+ )
147
+ props[:argument] = doc_string
148
+ end
149
+ props
150
+ end
151
+
152
+ def pickle_data_table(data_table, variable_cells, value_cells)
153
+ Cucumber::Messages::PickleStepArgument::PickleTable.new(
154
+ rows: data_table.rows.map do |row|
155
+ Cucumber::Messages::PickleStepArgument::PickleTable::PickleTableRow.new(
156
+ cells: row.cells.map do |cell|
157
+ Cucumber::Messages::PickleStepArgument::PickleTable::PickleTableRow::PickleTableCell.new(
158
+ value: interpolate(cell.value, variable_cells, value_cells)
159
+ )
160
+ end
161
+ )
162
+ end
163
+ )
164
+ end
165
+
166
+ def pickle_doc_string(doc_string, variable_cells, value_cells)
167
+ props = {
168
+ content: interpolate(doc_string.content, variable_cells, value_cells)
169
+ }
170
+ if doc_string.media_type
171
+ props[:media_type] = interpolate(doc_string.media_type, variable_cells, value_cells)
172
+ end
173
+ Cucumber::Messages::PickleStepArgument::PickleDocString.new(props)
174
+ end
175
+
176
+ def pickle_tags(tags)
177
+ tags.map {|tag| pickle_tag(tag)}
178
+ end
179
+
180
+ def pickle_tag(tag)
181
+ Cucumber::Messages::Pickle::PickleTag.new(
182
+ name: tag.name,
183
+ ast_node_id: tag.id
184
+ )
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,61 @@
1
+ module Gherkin
2
+ class Query
3
+ def initialize
4
+ @ast_node_locations = {}
5
+ end
6
+
7
+ def update(message)
8
+ update_feature(message.gherkin_document.feature) if message.gherkin_document
9
+ end
10
+
11
+ def location(ast_node_id)
12
+ return @ast_node_locations[ast_node_id] if @ast_node_locations.has_key?(ast_node_id)
13
+ raise AstNodeNotLocatedException, "No location found for #{ast_node_id} }. Known: #{@ast_node_locations.keys}"
14
+ end
15
+
16
+ private
17
+
18
+ def update_feature(feature)
19
+ store_nodes_location(feature.tags)
20
+
21
+ feature.children.each do |child|
22
+ update_rule(child.rule) if child.rule
23
+ update_background(child.background) if child.background
24
+ update_scenario(child.scenario) if child.scenario
25
+ end
26
+ end
27
+
28
+ def update_rule(rule)
29
+ rule.children.each do |child|
30
+ update_background(child.background) if child.background
31
+ update_scenario(child.scenario) if child.scenario
32
+ end
33
+ end
34
+
35
+ def update_background(background)
36
+ update_steps(background.steps)
37
+ end
38
+
39
+ def update_scenario(scenario)
40
+ store_node_location(scenario)
41
+ store_nodes_location(scenario.tags)
42
+ update_steps(scenario.steps)
43
+ scenario.examples.each do |examples|
44
+ store_nodes_location(examples.tags)
45
+ store_nodes_location(examples.table_body)
46
+ end
47
+ end
48
+
49
+ def update_steps(steps)
50
+ store_nodes_location(steps)
51
+ end
52
+
53
+ def store_nodes_location(nodes)
54
+ nodes.each { |node| store_node_location(node) }
55
+ end
56
+
57
+ def store_node_location(node)
58
+ @ast_node_locations[node.id] = node.location
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,96 @@
1
+ require 'cucumber/messages'
2
+ require 'gherkin/parser'
3
+ require 'gherkin/token_matcher'
4
+ require 'gherkin/pickles/compiler'
5
+
6
+ module Gherkin
7
+ module Stream
8
+ class ParserMessageStream
9
+ def initialize(paths, sources, options)
10
+ @paths = paths
11
+ @sources = sources
12
+ @options = options
13
+
14
+ id_generator = options[:id_generator] || Cucumber::Messages::IdGenerator::UUID.new
15
+ @parser = Parser.new(AstBuilder.new(id_generator))
16
+ @compiler = Pickles::Compiler.new(id_generator)
17
+ end
18
+
19
+ def messages
20
+ enumerated = false
21
+ Enumerator.new do |y|
22
+ raise DoubleIterationException, "Messages have already been enumerated" if enumerated
23
+ enumerated = true
24
+
25
+ sources.each do |source|
26
+ y.yield(Cucumber::Messages::Envelope.new(source: source)) if @options[:include_source]
27
+ begin
28
+ gherkin_document = nil
29
+
30
+ if @options[:include_gherkin_document]
31
+ gherkin_document = build_gherkin_document(source)
32
+ y.yield(Cucumber::Messages::Envelope.new(gherkin_document: gherkin_document))
33
+ end
34
+ if @options[:include_pickles]
35
+ gherkin_document ||= build_gherkin_document(source)
36
+ pickles = @compiler.compile(gherkin_document, source)
37
+ pickles.each do |pickle|
38
+ y.yield(Cucumber::Messages::Envelope.new(pickle: pickle))
39
+ end
40
+ end
41
+ rescue CompositeParserException => err
42
+ yield_error_attachments(y, err.errors, source.uri)
43
+ rescue ParserException => err
44
+ yield_error_attachments(y, [err], source.uri)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def yield_error_attachments(y, errors, uri)
53
+ errors.each do |err|
54
+ attachment = Cucumber::Messages::Attachment.new(
55
+ source: Cucumber::Messages::SourceReference.new(
56
+ uri: uri,
57
+ location: Cucumber::Messages::Location.new(
58
+ line: err.location[:line],
59
+ column: err.location[:column]
60
+ )
61
+ ),
62
+ text: err.message
63
+ )
64
+ y.yield(Cucumber::Messages::Envelope.new(attachment: attachment))
65
+ end
66
+ end
67
+
68
+ def sources
69
+ Enumerator.new do |y|
70
+ @paths.each do |path|
71
+ source = Cucumber::Messages::Source.new(
72
+ uri: path,
73
+ data: File.open(path, 'r:UTF-8', &:read),
74
+ media_type: 'text/x.cucumber.gherkin+plain'
75
+ )
76
+ y.yield(source)
77
+ end
78
+ @sources.each do |source|
79
+ y.yield(source)
80
+ end
81
+ end
82
+ end
83
+
84
+ def build_gherkin_document(source)
85
+ if @options[:default_dialect]
86
+ token_matcher = TokenMatcher.new(@options[:default_dialect])
87
+ gd = @parser.parse(source.data, token_matcher)
88
+ else
89
+ gd = @parser.parse(source.data)
90
+ end
91
+ gd[:uri] = source.uri
92
+ Cucumber::Messages::GherkinDocument.new(gd)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,26 @@
1
+ require 'open3'
2
+ require 'cucumber/messages'
3
+
4
+ module Gherkin
5
+ module Stream
6
+ class SubprocessMessageStream
7
+ def initialize(gherkin_executable, paths, print_source, print_ast, print_pickles)
8
+ @gherkin_executable, @paths, @print_source, @print_ast, @print_pickles = gherkin_executable, paths, print_source, print_ast, print_pickles
9
+ end
10
+
11
+ def messages
12
+ args = [@gherkin_executable]
13
+ args.push('--no-source') unless @print_source
14
+ args.push('--no-ast') unless @print_ast
15
+ args.push('--no-pickles') unless @print_pickles
16
+ args = args.concat(@paths)
17
+ stdin, stdout, stderr, wait_thr = Open3.popen3(*args)
18
+ if(stdout.eof?)
19
+ error = stderr.read
20
+ raise error
21
+ end
22
+ Cucumber::Messages::BinaryToMessageEnumerator.new(stdout)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module Gherkin
2
+ class Token < Struct.new(:line, :location)
3
+ attr_accessor :matched_type, :matched_text, :matched_keyword, :matched_indent,
4
+ :matched_items, :matched_gherkin_dialect
5
+
6
+ def eof?
7
+ line.nil?
8
+ end
9
+
10
+ def detach
11
+ # TODO: detach line - is this needed?
12
+ end
13
+
14
+ def token_value
15
+ eof? ? "EOF" : line.get_line_text(-1)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ module Gherkin
2
+ class TokenFormatterBuilder
3
+ def initialize
4
+ reset
5
+ end
6
+
7
+ def reset
8
+ @tokens_text = ""
9
+ end
10
+
11
+ def build(token)
12
+ @tokens_text << "#{format_token(token)}\n"
13
+ end
14
+
15
+ def start_rule(rule_type)
16
+ end
17
+
18
+ def end_rule(rule_type)
19
+ end
20
+
21
+ def get_result
22
+ @tokens_text
23
+ end
24
+
25
+ private
26
+ def format_token(token)
27
+ return "EOF" if token.eof?
28
+
29
+ sprintf "(%s:%s)%s:%s/%s/%s",
30
+ token.location[:line],
31
+ token.location[:column],
32
+ token.matched_type,
33
+ token.matched_keyword,
34
+ token.matched_text,
35
+ Array(token.matched_items).map { |i| "#{i.column}:#{i.text}"}.join(',')
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,169 @@
1
+ require 'gherkin/dialect'
2
+ require 'gherkin/errors'
3
+
4
+ module Gherkin
5
+ class TokenMatcher
6
+ LANGUAGE_PATTERN = /^\s*#\s*language\s*:\s*([a-zA-Z\-_]+)\s*$/
7
+
8
+ def initialize(dialect_name = 'en')
9
+ @default_dialect_name = dialect_name
10
+ change_dialect(dialect_name, nil)
11
+ reset
12
+ end
13
+
14
+ def reset
15
+ change_dialect(@default_dialect_name, nil) unless @dialect_name == @default_dialect_name
16
+ @active_doc_string_separator = nil
17
+ @indent_to_remove = 0
18
+ end
19
+
20
+ def match_TagLine(token)
21
+ return false unless token.line.start_with?('@')
22
+
23
+ set_token_matched(token, :TagLine, nil, nil, nil, token.line.tags)
24
+ true
25
+ end
26
+
27
+ def match_FeatureLine(token)
28
+ match_title_line(token, :FeatureLine, @dialect.feature_keywords)
29
+ end
30
+
31
+ def match_RuleLine(token)
32
+ match_title_line(token, :RuleLine, @dialect.rule_keywords)
33
+ end
34
+
35
+ def match_ScenarioLine(token)
36
+ match_title_line(token, :ScenarioLine, @dialect.scenario_keywords) ||
37
+ match_title_line(token, :ScenarioLine, @dialect.scenario_outline_keywords)
38
+ end
39
+
40
+ def match_BackgroundLine(token)
41
+ match_title_line(token, :BackgroundLine, @dialect.background_keywords)
42
+ end
43
+
44
+ def match_ExamplesLine(token)
45
+ match_title_line(token, :ExamplesLine, @dialect.examples_keywords)
46
+ end
47
+
48
+ def match_TableRow(token)
49
+ return false unless token.line.start_with?('|')
50
+ # TODO: indent
51
+ set_token_matched(token, :TableRow, nil, nil, nil, token.line.table_cells)
52
+ true
53
+ end
54
+
55
+ def match_Empty(token)
56
+ return false unless token.line.empty?
57
+ set_token_matched(token, :Empty, nil, nil, 0)
58
+ true
59
+ end
60
+
61
+ def match_Comment(token)
62
+ return false unless token.line.start_with?('#')
63
+ text = token.line.get_line_text(0) #take the entire line, including leading space
64
+ set_token_matched(token, :Comment, text, nil, 0)
65
+ true
66
+ end
67
+
68
+ def match_Language(token)
69
+ return false unless token.line.trimmed_line_text =~ LANGUAGE_PATTERN
70
+
71
+ dialect_name = $1
72
+ set_token_matched(token, :Language, dialect_name)
73
+
74
+ change_dialect(dialect_name, token.location)
75
+
76
+ true
77
+ end
78
+
79
+ def match_DocStringSeparator(token)
80
+ if @active_doc_string_separator.nil?
81
+ # open
82
+ _match_DocStringSeparator(token, '"""', true) ||
83
+ _match_DocStringSeparator(token, '```', true)
84
+ else
85
+ # close
86
+ _match_DocStringSeparator(token, @active_doc_string_separator, false)
87
+ end
88
+ end
89
+
90
+ def _match_DocStringSeparator(token, separator, is_open)
91
+ return false unless token.line.start_with?(separator)
92
+
93
+ media_type = nil
94
+ if is_open
95
+ media_type = token.line.get_rest_trimmed(separator.length)
96
+ @active_doc_string_separator = separator
97
+ @indent_to_remove = token.line.indent
98
+ else
99
+ @active_doc_string_separator = nil
100
+ @indent_to_remove = 0
101
+ end
102
+
103
+ set_token_matched(token, :DocStringSeparator, media_type, separator)
104
+ true
105
+ end
106
+
107
+ def match_EOF(token)
108
+ return false unless token.eof?
109
+ set_token_matched(token, :EOF)
110
+ true
111
+ end
112
+
113
+ def match_Other(token)
114
+ text = token.line.get_line_text(@indent_to_remove) # take the entire line, except removing DocString indents
115
+ set_token_matched(token, :Other, unescape_docstring(text), nil, 0)
116
+ true
117
+ end
118
+
119
+ def match_StepLine(token)
120
+ keywords = @dialect.given_keywords +
121
+ @dialect.when_keywords +
122
+ @dialect.then_keywords +
123
+ @dialect.and_keywords +
124
+ @dialect.but_keywords
125
+
126
+ keyword = keywords.detect { |k| token.line.start_with?(k) }
127
+
128
+ return false unless keyword
129
+
130
+ title = token.line.get_rest_trimmed(keyword.length)
131
+ set_token_matched(token, :StepLine, title, keyword)
132
+ return true
133
+ end
134
+
135
+ private
136
+
137
+ def change_dialect(dialect_name, location)
138
+ dialect = Dialect.for(dialect_name)
139
+ raise NoSuchLanguageException.new(dialect_name, location) if dialect.nil?
140
+
141
+ @dialect_name = dialect_name
142
+ @dialect = dialect
143
+ end
144
+
145
+ def match_title_line(token, token_type, keywords)
146
+ keyword = keywords.detect { |k| token.line.start_with_title_keyword?(k) }
147
+
148
+ return false unless keyword
149
+
150
+ title = token.line.get_rest_trimmed(keyword.length + ':'.length)
151
+ set_token_matched(token, token_type, title, keyword)
152
+ true
153
+ end
154
+
155
+ def set_token_matched(token, matched_type, text=nil, keyword=nil, indent=nil, items=[])
156
+ token.matched_type = matched_type
157
+ token.matched_text = text && text.chomp
158
+ token.matched_keyword = keyword
159
+ token.matched_indent = indent || (token.line && token.line.indent) || 0
160
+ token.matched_items = items
161
+ token.location[:column] = token.matched_indent + 1
162
+ token.matched_gherkin_dialect = @dialect_name
163
+ end
164
+
165
+ def unescape_docstring(text)
166
+ @active_doc_string_separator ? text.gsub("\\\"\\\"\\\"", "\"\"\"") : text
167
+ end
168
+ end
169
+ end