macros4cuke 0.4.08 → 0.4.09
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.rubocop.yml +3 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +4 -0
- data/README.md +5 -3
- data/features/{1_Basics → 1_the_basics}/README.md +0 -0
- data/features/{1_Basics → 1_the_basics}/demo01.feature +5 -5
- data/features/{2_Macros_with_arguments → 2_macros_with_arguments}/README.md +0 -0
- data/features/{2_Macros_with_arguments → 2_macros_with_arguments}/demo02.feature +10 -10
- data/features/{2_Macros_with_arguments → 2_macros_with_arguments}/demo03.feature +5 -5
- data/features/{3_Macros_with_table_arguments → 3_macros_with_table_arguments}/README.md +0 -0
- data/features/{3_Macros_with_table_arguments → 3_macros_with_table_arguments}/demo04.feature +5 -5
- data/features/{3_Macros_with_table_arguments → 3_macros_with_table_arguments}/demo05.feature +4 -4
- data/features/{4_Conditional_steps → 4_conditional_steps}/demo06.feature +18 -16
- data/features/{5_Goodies → 5_goodies}/demo07.feature +6 -5
- data/features/{5_Goodies → 5_goodies}/demo08.feature +0 -0
- data/features/step_definitions/demo_steps.rb +5 -4
- data/lib/macro_steps.rb +6 -6
- data/lib/macros4cuke/coll-walker-factory.rb +12 -12
- data/lib/macros4cuke/constants.rb +4 -4
- data/lib/macros4cuke/formatter/all-notifications.rb +8 -8
- data/lib/macros4cuke/formatter/to-gherkin.rb +2 -2
- data/lib/macros4cuke/formatter/to-null.rb +10 -10
- data/lib/macros4cuke/formatting-service.rb +74 -74
- data/lib/macros4cuke/macro-collection.rb +13 -13
- data/lib/macros4cuke/macro-step-support.rb +19 -12
- data/lib/macros4cuke/macro-step.rb +48 -48
- data/lib/macros4cuke/templating/engine.rb +79 -79
- data/lib/macros4cuke/templating/placeholder.rb +52 -52
- data/lib/macros4cuke/templating/section.rb +116 -116
- data/lib/macros4cuke/templating/template-element.rb +88 -88
- data/lib/macros4cuke/templating/unary-element.rb +37 -37
- data/lib/macros4cuke.rb +1 -1
- data/spec/macros4cuke/coll-walker-factory_spec.rb +269 -269
- data/spec/macros4cuke/formatter/to-gherkin_spec.rb +5 -5
- data/spec/macros4cuke/formatter/to-null_spec.rb +62 -62
- data/spec/macros4cuke/formatter/to-trace_spec.rb +8 -8
- data/spec/macros4cuke/formatting-service_spec.rb +7 -7
- data/spec/macros4cuke/macro-collection_spec.rb +3 -3
- data/spec/macros4cuke/macro-step-support_spec.rb +4 -4
- data/spec/macros4cuke/macro-step_spec.rb +8 -8
- data/spec/macros4cuke/templating/comment_spec.rb +45 -0
- data/spec/macros4cuke/templating/engine_spec.rb +23 -23
- data/spec/macros4cuke/templating/eo-line_spec.rb +39 -0
- data/spec/macros4cuke/templating/section_spec.rb +1 -1
- data/spec/macros4cuke/templating/static_text_spec.rb +45 -0
- data/spec/macros4cuke/use-sample-collection.rb +7 -7
- data/spec/spec_helper.rb +3 -3
- metadata +31 -14
@@ -13,19 +13,19 @@ module Macros4Cuke # Module used as a namespace
|
|
13
13
|
|
14
14
|
|
15
15
|
# Module containing all classes implementing the simple template engine
|
16
|
-
# used internally in Macros4Cuke.
|
16
|
+
# used internally in Macros4Cuke.
|
17
17
|
module Templating
|
18
18
|
|
19
|
-
# Class used internally by the template engine.
|
20
|
-
# Represents a static piece of text from a template.
|
21
|
-
# A static text is a text that is reproduced verbatim
|
19
|
+
# Class used internally by the template engine.
|
20
|
+
# Represents a static piece of text from a template.
|
21
|
+
# A static text is a text that is reproduced verbatim
|
22
22
|
# when rendering a template.
|
23
23
|
class StaticText
|
24
24
|
# The static text extracted from the original template.
|
25
25
|
attr_reader(:source)
|
26
|
-
|
27
26
|
|
28
|
-
|
27
|
+
|
28
|
+
# @param aSourceText [String] A piece of text extracted
|
29
29
|
# from the template that must be rendered verbatim.
|
30
30
|
def initialize(aSourceText)
|
31
31
|
@source = aSourceText
|
@@ -42,16 +42,16 @@ class StaticText
|
|
42
42
|
end # class
|
43
43
|
|
44
44
|
|
45
|
-
# Class used internally by the template engine.
|
46
|
-
# Represents a comment from a template.
|
47
|
-
# A static text is a text that is reproduced verbatim
|
45
|
+
# Class used internally by the template engine.
|
46
|
+
# Represents a comment from a template.
|
47
|
+
# A static text is a text that is reproduced verbatim
|
48
48
|
# when rendering a template.
|
49
49
|
class Comment
|
50
50
|
# The comment as extracted from the original template.
|
51
51
|
attr_reader(:source)
|
52
|
-
|
53
52
|
|
54
|
-
|
53
|
+
|
54
|
+
# @param aSourceText [String] A piece of text extracted
|
55
55
|
# from the template that must be rendered verbatim.
|
56
56
|
def initialize(aSourceText)
|
57
57
|
@source = aSourceText
|
@@ -70,12 +70,12 @@ class Comment
|
|
70
70
|
end # class
|
71
71
|
|
72
72
|
|
73
|
-
# Class used internally by the template engine.
|
73
|
+
# Class used internally by the template engine.
|
74
74
|
# Represents an end of line that must be rendered as such.
|
75
75
|
class EOLine
|
76
76
|
|
77
77
|
public
|
78
|
-
|
78
|
+
|
79
79
|
# Render an end of line.
|
80
80
|
# This method has the same signature as the {Engine#render} method.
|
81
81
|
# @return [String] An end of line marker. Its exact value is OS-dependent.
|
@@ -86,24 +86,24 @@ end # class
|
|
86
86
|
|
87
87
|
|
88
88
|
|
89
|
-
# A very simple implementation of a templating engine.
|
90
|
-
# Earlier versions of Macros4Cuke relied on the logic-less
|
91
|
-
# Mustache template engine.
|
92
|
-
# But it was decided afterwards to replace it by a very simple
|
93
|
-
# template engine.
|
94
|
-
# The reasons were the following:
|
95
|
-
# - Be closer to the usual Gherkin syntax (parameters of scenario outlines
|
96
|
-
# use chevrons <...>,
|
97
|
-
# while Mustache use !{{...}} delimiters),
|
98
|
-
# - Feature files are meant to be simple, so should the template engine be.
|
89
|
+
# A very simple implementation of a templating engine.
|
90
|
+
# Earlier versions of Macros4Cuke relied on the logic-less
|
91
|
+
# Mustache template engine.
|
92
|
+
# But it was decided afterwards to replace it by a very simple
|
93
|
+
# template engine.
|
94
|
+
# The reasons were the following:
|
95
|
+
# - Be closer to the usual Gherkin syntax (parameters of scenario outlines
|
96
|
+
# use chevrons <...>,
|
97
|
+
# while Mustache use !{{...}} delimiters),
|
98
|
+
# - Feature files are meant to be simple, so should the template engine be.
|
99
99
|
class Engine
|
100
|
-
# The regular expression that matches a space,
|
100
|
+
# The regular expression that matches a space,
|
101
101
|
# any punctuation sign or delimiter that is forbidden
|
102
102
|
# between chevrons <...> template tags.
|
103
|
-
DisallowedSigns = begin
|
103
|
+
DisallowedSigns = begin
|
104
104
|
# Use concatenation (+) to work around Ruby bug!
|
105
|
-
forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~"
|
106
|
-
all_escaped = []
|
105
|
+
forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~"
|
106
|
+
all_escaped = []
|
107
107
|
forbidden.each_char { |ch| all_escaped << Regexp.escape(ch) }
|
108
108
|
pattern = all_escaped.join('|')
|
109
109
|
Regexp.new(pattern)
|
@@ -111,13 +111,13 @@ class Engine
|
|
111
111
|
|
112
112
|
# The original text of the template is kept here.
|
113
113
|
attr_reader(:source)
|
114
|
-
|
114
|
+
|
115
115
|
# The internal representation of the template text
|
116
116
|
attr_reader(:representation)
|
117
|
-
|
117
|
+
|
118
118
|
# Builds an Engine and compiles the given template text into
|
119
119
|
# an internal representation.
|
120
|
-
# @param aSourceTemplate [String] The template source text.
|
120
|
+
# @param aSourceTemplate [String] The template source text.
|
121
121
|
# It may contain zero or tags enclosed between chevrons <...>.
|
122
122
|
def initialize(aSourceTemplate)
|
123
123
|
@source = aSourceTemplate
|
@@ -126,14 +126,14 @@ class Engine
|
|
126
126
|
|
127
127
|
public
|
128
128
|
|
129
|
-
# Render the template within the given scope object and
|
129
|
+
# Render the template within the given scope object and
|
130
130
|
# with the locals specified.
|
131
131
|
# The method mimicks the signature of the Tilt::Template#render method.
|
132
132
|
# @param aContextObject [anything] context object to get actual values
|
133
133
|
# (when not present in the locals Hash).
|
134
134
|
# @param theLocals [Hash] Contains one or more pairs of the form:
|
135
135
|
# tag/placeholder name => actual value.
|
136
|
-
# @return [String] The rendition of the template given
|
136
|
+
# @return [String] The rendition of the template given
|
137
137
|
# the passed argument values.
|
138
138
|
def render(aContextObject = Object.new, theLocals)
|
139
139
|
return '' if @representation.empty?
|
@@ -143,7 +143,7 @@ class Engine
|
|
143
143
|
# Output compaction rules:
|
144
144
|
# -In case of consecutive eol's only one is rendered.
|
145
145
|
# -In case of comment followed by one eol, both aren't rendered
|
146
|
-
unless element.is_a?(EOLine) &&
|
146
|
+
unless element.is_a?(EOLine) &&
|
147
147
|
(prev.is_a?(EOLine) || prev.is_a?(Comment))
|
148
148
|
subResult << element.render(aContextObject, theLocals)
|
149
149
|
end
|
@@ -153,7 +153,7 @@ class Engine
|
|
153
153
|
return result
|
154
154
|
end
|
155
155
|
|
156
|
-
|
156
|
+
|
157
157
|
# Retrieve all placeholder names that appear in the template.
|
158
158
|
# @return [Array] The list of placeholder names.
|
159
159
|
def variables()
|
@@ -161,20 +161,20 @@ class Engine
|
|
161
161
|
@variables ||= begin
|
162
162
|
vars = @representation.each_with_object([]) do |element, subResult|
|
163
163
|
case element
|
164
|
-
when Placeholder
|
164
|
+
when Placeholder
|
165
165
|
subResult << element.name
|
166
|
-
|
166
|
+
|
167
167
|
when Section
|
168
168
|
subResult.concat(element.variables)
|
169
|
-
|
169
|
+
|
170
170
|
else
|
171
171
|
# Do nothing
|
172
172
|
end
|
173
173
|
end
|
174
|
-
|
174
|
+
|
175
175
|
vars.flatten.uniq
|
176
176
|
end
|
177
|
-
|
177
|
+
|
178
178
|
return @variables
|
179
179
|
end
|
180
180
|
|
@@ -192,12 +192,12 @@ class Engine
|
|
192
192
|
# Scan tag at current position...
|
193
193
|
tag_literal = scanner.scan(/<(?:[^\\<>]|\\.)*>/)
|
194
194
|
unless tag_literal.nil?
|
195
|
-
result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
|
195
|
+
result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
|
196
196
|
end
|
197
|
-
|
197
|
+
|
198
198
|
# ... or scan plain text at current position
|
199
199
|
literal = scanner.scan(/(?:[^\\<>]|\\.)+/)
|
200
|
-
result << [:static, literal] unless literal.nil?
|
200
|
+
result << [:static, literal] unless literal.nil?
|
201
201
|
identify_parse_error(aTextLine) if tag_literal.nil? && literal.nil?
|
202
202
|
end
|
203
203
|
end
|
@@ -209,7 +209,7 @@ class Engine
|
|
209
209
|
|
210
210
|
# Called when the given text line could not be parsed.
|
211
211
|
# Raises an exception with the syntax issue identified.
|
212
|
-
# @param aTextLine [String] A text line from the template.
|
212
|
+
# @param aTextLine [String] A text line from the template.
|
213
213
|
def self.identify_parse_error(aTextLine)
|
214
214
|
# Unsuccessful scanning: we typically have improperly balanced chevrons.
|
215
215
|
# We will analyze the opening and closing chevrons...
|
@@ -221,25 +221,25 @@ class Engine
|
|
221
221
|
|
222
222
|
no_escaped.each_char do |ch|
|
223
223
|
case ch
|
224
|
-
when '<' then unbalance += 1
|
225
|
-
when '>' then unbalance -= 1
|
224
|
+
when '<' then unbalance += 1
|
225
|
+
when '>' then unbalance -= 1
|
226
226
|
end
|
227
|
-
|
227
|
+
|
228
228
|
fail(StandardError, "Nested opening chevron '<'.") if unbalance > 1
|
229
229
|
fail(StandardError, "Missing opening chevron '<'.") if unbalance < 0
|
230
230
|
end
|
231
|
-
|
232
|
-
fail(StandardError, "Missing closing chevron '>'.") if unbalance == 1
|
231
|
+
|
232
|
+
fail(StandardError, "Missing closing chevron '>'.") if unbalance == 1
|
233
233
|
end
|
234
|
-
|
235
|
-
|
234
|
+
|
235
|
+
|
236
236
|
# Create the internal representation of the given template.
|
237
237
|
def compile(aSourceTemplate)
|
238
238
|
# Split the input text into lines.
|
239
239
|
input_lines = aSourceTemplate.split(/\r\n?|\n/)
|
240
|
-
|
240
|
+
|
241
241
|
# Parse the input text into raw data.
|
242
|
-
raw_lines = input_lines.map do |line|
|
242
|
+
raw_lines = input_lines.map do |line|
|
243
243
|
line_items = self.class.parse(line)
|
244
244
|
line_items.each do |(kind, text)|
|
245
245
|
# A tag text cannot be empty nor blank
|
@@ -247,27 +247,27 @@ class Engine
|
|
247
247
|
fail(EmptyArgumentError.new(line.strip))
|
248
248
|
end
|
249
249
|
end
|
250
|
-
|
250
|
+
|
251
251
|
line_items
|
252
252
|
end
|
253
|
-
|
253
|
+
|
254
254
|
compiled_lines = raw_lines.map { |line| compile_line(line) }
|
255
255
|
return compile_sections(compiled_lines.flatten)
|
256
256
|
end
|
257
|
-
|
258
|
-
# Convert the array of raw entries (per line)
|
257
|
+
|
258
|
+
# Convert the array of raw entries (per line)
|
259
259
|
# into full-fledged template elements.
|
260
260
|
def compile_line(aRawLine)
|
261
261
|
line_rep = aRawLine.map { |couple| compile_couple(couple) }
|
262
|
-
|
263
|
-
# Apply the rule: when a line just consist of spaces
|
262
|
+
|
263
|
+
# Apply the rule: when a line just consist of spaces
|
264
264
|
# and a section element, then remove all the spaces from that line.
|
265
265
|
section_item = nil
|
266
266
|
line_to_squeeze = line_rep.all? do |item|
|
267
267
|
case item
|
268
268
|
when StaticText
|
269
269
|
item.source =~ /\s+/
|
270
|
-
|
270
|
+
|
271
271
|
when Section, SectionEndMarker
|
272
272
|
if section_item.nil?
|
273
273
|
section_item = item
|
@@ -284,14 +284,14 @@ class Engine
|
|
284
284
|
else
|
285
285
|
line_rep_ending(line_rep)
|
286
286
|
end
|
287
|
-
|
287
|
+
|
288
288
|
return line_rep
|
289
289
|
end
|
290
290
|
|
291
291
|
|
292
|
-
# Apply rule: if last item in line is an end of section marker,
|
293
|
-
# then place eoline before that item.
|
294
|
-
# Otherwise, end the line with a eoline marker.
|
292
|
+
# Apply rule: if last item in line is an end of section marker,
|
293
|
+
# then place eoline before that item.
|
294
|
+
# Otherwise, end the line with a eoline marker.
|
295
295
|
def line_rep_ending(theLineRep)
|
296
296
|
if theLineRep.last.is_a?(SectionEndMarker)
|
297
297
|
section_end = theLineRep.pop
|
@@ -299,10 +299,10 @@ class Engine
|
|
299
299
|
theLineRep << section_end
|
300
300
|
else
|
301
301
|
theLineRep << EOLine.new
|
302
|
-
end
|
302
|
+
end
|
303
303
|
end
|
304
304
|
|
305
|
-
|
305
|
+
|
306
306
|
# @param aCouple [Array] a two-element array of the form: [kind, text]
|
307
307
|
# Where kind must be one of :static, :dynamic
|
308
308
|
def compile_couple(aCouple)
|
@@ -329,11 +329,11 @@ class Engine
|
|
329
329
|
matching = DisallowedSigns.match(aText)
|
330
330
|
end
|
331
331
|
fail(InvalidCharError.new(aText, matching[0])) if matching
|
332
|
-
|
332
|
+
|
333
333
|
result = case aText[0, 1]
|
334
334
|
when '?'
|
335
335
|
ConditionalSection.new(aText[1..-1], true)
|
336
|
-
|
336
|
+
|
337
337
|
when '/'
|
338
338
|
SectionEndMarker.new(aText[1..-1])
|
339
339
|
else
|
@@ -342,43 +342,43 @@ class Engine
|
|
342
342
|
|
343
343
|
return result
|
344
344
|
end
|
345
|
-
|
345
|
+
|
346
346
|
# Transform a flat sequence of elements into a hierarchy of sections.
|
347
347
|
# @param flat_sequence [Array] a linear list of elements (including sections)
|
348
348
|
def compile_sections(flat_sequence)
|
349
349
|
open_sections = [] # The list of nested open sections
|
350
|
-
|
350
|
+
|
351
351
|
compiled = flat_sequence.each_with_object([]) do |element, subResult|
|
352
352
|
case element
|
353
353
|
when Section
|
354
354
|
open_sections << element
|
355
|
-
|
356
|
-
when SectionEndMarker
|
355
|
+
|
356
|
+
when SectionEndMarker
|
357
357
|
validate_section_end(element, open_sections)
|
358
358
|
subResult << open_sections.pop
|
359
|
-
|
359
|
+
|
360
360
|
else
|
361
361
|
if open_sections.empty?
|
362
362
|
subResult << element
|
363
363
|
else
|
364
364
|
open_sections.last.add_child(element)
|
365
365
|
end
|
366
|
-
end
|
366
|
+
end
|
367
367
|
end
|
368
|
-
|
368
|
+
|
369
369
|
unless open_sections.empty?
|
370
370
|
error_message = "Unterminated section #{open_sections.last}."
|
371
371
|
fail(StandardError, error_message)
|
372
372
|
end
|
373
|
-
|
373
|
+
|
374
374
|
return compiled
|
375
375
|
end
|
376
|
-
|
377
|
-
# Validate the given end of section marker taking into account
|
376
|
+
|
377
|
+
# Validate the given end of section marker taking into account
|
378
378
|
# the open sections.
|
379
379
|
def validate_section_end(marker, sections)
|
380
380
|
msg_prefix = "End of section</#{marker.name}> "
|
381
|
-
|
381
|
+
|
382
382
|
if sections.empty?
|
383
383
|
msg = 'found while no corresponding section is open.'
|
384
384
|
fail(StandardError, msg_prefix + msg)
|
@@ -388,7 +388,7 @@ class Engine
|
|
388
388
|
fail(StandardError, msg_prefix + msg)
|
389
389
|
end
|
390
390
|
end
|
391
|
-
|
391
|
+
|
392
392
|
end # class
|
393
393
|
|
394
394
|
end # module
|
@@ -1,52 +1,52 @@
|
|
1
|
-
# File: placeholder.rb
|
2
|
-
# Purpose: Implementation of the Placeholder class.
|
3
|
-
|
4
|
-
require_relative 'unary-element' # Load the superclass
|
5
|
-
|
6
|
-
|
7
|
-
module Macros4Cuke # Module used as a namespace
|
8
|
-
|
9
|
-
# Module containing all classes implementing the simple template engine
|
10
|
-
# used internally in Macros4Cuke.
|
11
|
-
module Templating
|
12
|
-
|
13
|
-
# Class used internally by the template engine.
|
14
|
-
# Represents a named placeholder in a template, that is,
|
15
|
-
# a name placed between <..> in the template.
|
16
|
-
# At rendition, a placeholder is replaced by the text value
|
17
|
-
# that is associated with it.
|
18
|
-
class Placeholder < UnaryElement
|
19
|
-
|
20
|
-
public
|
21
|
-
|
22
|
-
# Render the placeholder given the passed arguments.
|
23
|
-
# This method has the same signature as the {Engine#render} method.
|
24
|
-
# @return [String] The text value assigned to the placeholder.
|
25
|
-
# Returns an empty string when no value is assigned to the placeholder.
|
26
|
-
def render(aContextObject, theLocals)
|
27
|
-
actual_value = retrieve_value_from(aContextObject, theLocals)
|
28
|
-
|
29
|
-
result = case actual_value
|
30
|
-
when NilClass
|
31
|
-
''
|
32
|
-
|
33
|
-
when Array
|
34
|
-
# TODO: Move away from hard-coded separator.
|
35
|
-
actual_value.join('<br/>')
|
36
|
-
|
37
|
-
when String
|
38
|
-
actual_value
|
39
|
-
else
|
40
|
-
actual_value.to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
return result
|
44
|
-
end
|
45
|
-
|
46
|
-
end # class
|
47
|
-
|
48
|
-
end # module
|
49
|
-
|
50
|
-
end # module
|
51
|
-
|
52
|
-
# End of file
|
1
|
+
# File: placeholder.rb
|
2
|
+
# Purpose: Implementation of the Placeholder class.
|
3
|
+
|
4
|
+
require_relative 'unary-element' # Load the superclass
|
5
|
+
|
6
|
+
|
7
|
+
module Macros4Cuke # Module used as a namespace
|
8
|
+
|
9
|
+
# Module containing all classes implementing the simple template engine
|
10
|
+
# used internally in Macros4Cuke.
|
11
|
+
module Templating
|
12
|
+
|
13
|
+
# Class used internally by the template engine.
|
14
|
+
# Represents a named placeholder in a template, that is,
|
15
|
+
# a name placed between <..> in the template.
|
16
|
+
# At rendition, a placeholder is replaced by the text value
|
17
|
+
# that is associated with it.
|
18
|
+
class Placeholder < UnaryElement
|
19
|
+
|
20
|
+
public
|
21
|
+
|
22
|
+
# Render the placeholder given the passed arguments.
|
23
|
+
# This method has the same signature as the {Engine#render} method.
|
24
|
+
# @return [String] The text value assigned to the placeholder.
|
25
|
+
# Returns an empty string when no value is assigned to the placeholder.
|
26
|
+
def render(aContextObject, theLocals)
|
27
|
+
actual_value = retrieve_value_from(aContextObject, theLocals)
|
28
|
+
|
29
|
+
result = case actual_value
|
30
|
+
when NilClass
|
31
|
+
''
|
32
|
+
|
33
|
+
when Array
|
34
|
+
# TODO: Move away from hard-coded separator.
|
35
|
+
actual_value.join('<br/>')
|
36
|
+
|
37
|
+
when String
|
38
|
+
actual_value
|
39
|
+
else
|
40
|
+
actual_value.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
return result
|
44
|
+
end
|
45
|
+
|
46
|
+
end # class
|
47
|
+
|
48
|
+
end # module
|
49
|
+
|
50
|
+
end # module
|
51
|
+
|
52
|
+
# End of file
|