File: ast_builder.rb

package info (click to toggle)
ruby-cucumber-core 1.5.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 580 kB
  • sloc: ruby: 5,763; makefile: 2
file content (388 lines) | stat: -rw-r--r-- 10,320 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
require 'cucumber/core/ast'
require 'cucumber/core/platform'

module Cucumber
  module Core
    module Gherkin
      # Builds an AST of a feature by listening to events from the
      # Gherkin parser.
      class AstBuilder

        def initialize(uri)
          @uri = uri
        end

        def feature(attributes)
          DocumentBuilder.new(file, attributes).feature
        end

        private

        def file
          @uri
        end

        class Builder
          attr_reader :file, :attributes, :comments, :line
          private     :file, :attributes, :comments, :line

          def initialize(file, attributes)
            @file = file
            @attributes = rubify_keys(attributes.dup)
            @comments = []
            @line = @attributes[:location][:line]
          end

          def handle_comments(comments)
            remaining_comments = []
            comments.each do |comment|
              if line > comment.location.line
                @comments << comment
              else
                remaining_comments << comment
              end
            end
            children.each { |child| remaining_comments = child.handle_comments(remaining_comments) }
            remaining_comments
          end

          private

          def keyword
            attributes[:keyword]
          end

          def name
            attributes[:name]
          end

          def description
            attributes[:description] ||= ""
          end

          def tags
            attributes[:tags].map do |tag|
              Ast::Tag.new(
                Ast::Location.new(file, tag[:location][:line]),
                tag[:name])
            end
          end

          def location
            Ast::Location.new(file, attributes[:location][:line])
          end

          def children
            []
          end

          def rubify_keys(hash)
            hash.keys.each do |key|
              if key.downcase != key
                hash[underscore(key).to_sym] = hash.delete(key)
              end
            end
            return hash
          end

          def underscore(string)
            string.to_s.gsub(/::/, '/').
              gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
              gsub(/([a-z\d])([A-Z])/,'\1_\2').
              tr("-", "_").
              downcase
          end
        end

        class DocumentBuilder < Builder
          def initialize(file, attributes)
            @file = file
            @attributes = rubify_keys(attributes.dup)
          end

          def feature
            return Ast::NullFeature.new unless attributes[:feature]
            feature_builder = FeatureBuilder.new(file, attributes[:feature])
            feature_builder.handle_comments(all_comments)
            feature_builder.result
          end

          def all_comments
            attributes[:comments].map do |comment|
              Ast::Comment.new(
                Ast::Location.new(file, comment[:location][:line]),
                comment[:text]
              )
            end
          end
        end

        class FeatureBuilder < Builder
          attr_reader :language, :feature_element_builders

          def initialize(*)
            super
            @language = Ast::LanguageDelegator.new(attributes[:language], ::Gherkin::Dialect.for(attributes[:language]))
            @feature_element_builders = attributes[:children].map do |child|
              case child[:type]
              when :Background
                BackgroundBuilder.new(file, child)
              when :Scenario
                ScenarioBuilder.new(file, child)
              else 
                ScenarioOutlineBuilder.new(file, child)
              end
            end
          end

          def result
            Ast::Feature.new(
              language,
              location,
              comments,
              tags,
              keyword,
              name,
              description,
              feature_elements
            )
          end

          private

          def feature_elements
            feature_element_builders.map { |builder| builder.result(language) }
          end

          def children
            feature_element_builders
          end
        end

        class BackgroundBuilder < Builder
          attr_reader :step_builders

          def initialize(*)
            super
            @step_builders = attributes[:steps].map { |step| StepBuilder.new(file, step) }
          end

          def result(language)
            Ast::Background.new(
              location,
              comments,
              keyword,
              name,
              description,
              steps(language)
            )
          end

          def steps(language)
            step_builders.map { |builder| builder.result(language) }
          end

          def children
            step_builders
          end
        end

        class ScenarioBuilder < Builder
          attr_reader :step_builders

          def initialize(*)
            super
            @step_builders = attributes[:steps].map { |step| StepBuilder.new(file, step) }
          end

          def result(language)
            Ast::Scenario.new(
              location,
              comments,
              tags,
              keyword,
              name,
              description,
              steps(language)
            )
          end

          def steps(language)
            step_builders.map { |builder| builder.result(language) }
          end

          def children
            step_builders
          end
        end

        class StepBuilder < Builder
          attr_reader :multiline_argument_builder

          def initialize(*)
            super
            @multiline_argument_builder = attributes[:argument] ? argument_builder(attributes[:argument]) : nil
          end

          def result(language)
            Ast::Step.new(
              language,
              location,
              comments,
              keyword,
              attributes[:text],
              multiline_argument
            )
          end

          def multiline_argument
            return Ast::EmptyMultilineArgument.new unless multiline_argument_builder
            multiline_argument_builder.result
          end

          def children
            return [] unless multiline_argument_builder
            [multiline_argument_builder]
          end

          private

          def argument_builder(attributes)
            attributes[:type] == :DataTable ? DataTableBuilder.new(file, attributes) : DocStringBuilder.new(file, attributes) 
          end
        end

        class OutlineStepBuilder < StepBuilder
          def result(language)
            Ast::OutlineStep.new(
              language,
              location,
              comments,
              keyword,
              attributes[:text],
              multiline_argument
            )
          end
        end

        class ScenarioOutlineBuilder < Builder
          attr_reader :step_builders, :example_builders

          def initialize(*)
            super
            @step_builders = attributes[:steps].map { |step| OutlineStepBuilder.new(file, step) }
            @example_builders = attributes[:examples].map { |example| ExamplesTableBuilder.new(file, example) }
          end

          def result(language)
            Ast::ScenarioOutline.new(
              location,
              comments,
              tags,
              keyword,
              name,
              description,
              steps(language),
              examples(language)
            )
          end

          def steps(language)
            step_builders.map { |builder| builder.result(language) }
          end

          def examples(language)
            example_builders.map { |builder| builder.result(language) }
          end

          def children
            step_builders + example_builders
          end
        end

        class ExamplesTableBuilder < Builder
          attr_reader :header_builder, :example_rows_builders

          def initialize(*)
            super
            @header_builder = HeaderBuilder.new(file, attributes[:table_header])
            @example_rows_builders = attributes[:table_body].map do |row_attributes|
              ExampleRowBuilder.new(file, row_attributes)
            end
          end

          def result(language)
            Ast::Examples.new(
              location,
              comments,
              tags,
              keyword,
              name,
              description,
              header,
              example_rows(language)
            )
          end

          private

          def header
            @header = header_builder.result
          end

          def example_rows(language)
            example_rows_builders.each.with_index.map { |builder, index| builder.result(language, header, index) }
          end

          class HeaderBuilder < Builder
            def result
              cells = attributes[:cells].map { |c| c[:value] }
              Ast::ExamplesTable::Header.new(cells, location, comments)
            end
          end

          def children
            [header_builder] + example_rows_builders
          end

          class ExampleRowBuilder < Builder
            def result(language, header, index)
              cells = attributes[:cells].map { |c| c[:value] }
              header.build_row(cells, index + 1, location, language, comments)
            end
          end
        end

        class DataTableBuilder < Builder
          def result
            Ast::DataTable.new(
              rows,
              location
            )
          end

          def rows
            attributes[:rows] = attributes[:rows].map { |r| r[:cells].map { |c| c[:value] } }
          end
        end

        class DocStringBuilder < Builder
          def result
            Ast::DocString.new(
              attributes[:content],
              attributes[:content_type],
              doc_string_location
            )
          end

          def doc_string_location
            start_line = attributes[:location][:line]
            end_line = start_line + attributes[:content].each_line.to_a.length + 1
            Ast::Location.new(file, start_line..end_line)
          end
        end

      end
    end
  end
end