cucumber-gherkin 9.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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