macros4cuke 0.2.20 → 0.2.21
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.
- data/CHANGELOG.md +10 -2
- data/lib/macros4cuke/constants.rb +1 -1
- data/lib/macros4cuke/templating/engine.rb +125 -19
- data/spec/macros4cuke/templating/section_spec.rb +113 -0
- metadata +3 -2
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
## 0.2.
|
1
|
+
## 0.2.21 / 2013-05-08
|
2
|
+
* [NEW] Added new class `Templating::UnaryElement`.
|
3
|
+
* [CHANGE] Made `Placeholder` class inherit from `UnaryElement`.
|
4
|
+
* [NEW] Added new class `Templating::Section`, a subclass of `UnaryElement`.
|
5
|
+
* [NEW] Added new class `Templating::ConditionalSection, a subclass of `Section`.
|
6
|
+
* [CHANGE] Method Engine#parse_tag modified in prevision of conditional tags.
|
7
|
+
* [NEW] File `section_spec.rb`: Added a RSpec to test the Conditional class.
|
8
|
+
|
9
|
+
## 0.2.20 / 2013-05-07
|
2
10
|
* [NEW] Added `examples` folder with a first example of an internationalized customisation of __Macros4Cuke__.
|
3
11
|
|
4
|
-
## 0.2.19 / 2013-05-
|
12
|
+
## 0.2.19 / 2013-05-07
|
5
13
|
* [CHANGE] Added validation of macro argument names in new `Engine::parse_tag` method.
|
6
14
|
* [CHANGE] InvalidCharError exception added.
|
7
15
|
* [CHANGE] File `engine_spec.rb`: Added one RSpec example for an invalid argument name.
|
@@ -37,10 +37,22 @@ end # class
|
|
37
37
|
|
38
38
|
|
39
39
|
# Class used internally by the template engine.
|
40
|
-
# Represents
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
# Represents an end of line that must be rendered as such.
|
41
|
+
class EOLine
|
42
|
+
public
|
43
|
+
# Render an end of line.
|
44
|
+
# This method has the same signature as the {Engine#render} method.
|
45
|
+
# @return [String] An end of line marker. Its exact value is OS-dependent.
|
46
|
+
def render(aContextObject, theLocals)
|
47
|
+
return "\n"
|
48
|
+
end
|
49
|
+
end # class
|
50
|
+
|
51
|
+
|
52
|
+
# Base class used internally by the template engine.
|
53
|
+
# The generalization of any element from a template that has one variable
|
54
|
+
# whose actual value influences the rendition.
|
55
|
+
class UnaryElement
|
44
56
|
# The name of the placeholder/variable.
|
45
57
|
attr_reader(:name)
|
46
58
|
|
@@ -49,16 +61,35 @@ class Placeholder
|
|
49
61
|
@name = aVarName
|
50
62
|
end
|
51
63
|
|
64
|
+
protected
|
65
|
+
# This method has the same signature as the {Engine#render} method.
|
66
|
+
# @return [Object] The actual value from the locals or context that is assigned to the variable.
|
67
|
+
def retrieve_value_from(aContextObject, theLocals)
|
68
|
+
actual_value = theLocals[name]
|
69
|
+
if actual_value.nil? && aContextObject.respond_to?(name.to_sym)
|
70
|
+
actual_value = aContextObject.send(name.to_sym)
|
71
|
+
end
|
72
|
+
|
73
|
+
return actual_value
|
74
|
+
end
|
75
|
+
|
76
|
+
end # class
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
# Class used internally by the template engine.
|
81
|
+
# Represents a named placeholder in a template, that is,
|
82
|
+
# a name placed between <..> in the template.
|
83
|
+
# At rendition, a placeholder is replaced by the text value that is associated with it.
|
84
|
+
class Placeholder < UnaryElement
|
85
|
+
|
52
86
|
public
|
53
87
|
# Render the placeholder given the passed arguments.
|
54
88
|
# This method has the same signature as the {Engine#render} method.
|
55
89
|
# @return [String] The text value assigned to the placeholder.
|
56
90
|
# Returns an empty string when no value is assigned to the placeholder.
|
57
91
|
def render(aContextObject, theLocals)
|
58
|
-
actual_value = theLocals
|
59
|
-
if actual_value.nil? && aContextObject.respond_to?(name.to_sym)
|
60
|
-
actual_value = aContextObject.send(name.to_sym)
|
61
|
-
end
|
92
|
+
actual_value = retrieve_value_from(aContextObject, theLocals)
|
62
93
|
|
63
94
|
result = case actual_value
|
64
95
|
when NilClass
|
@@ -80,19 +111,78 @@ public
|
|
80
111
|
end # class
|
81
112
|
|
82
113
|
|
83
|
-
#
|
84
|
-
# Represents
|
85
|
-
|
114
|
+
# Base class used internally by the template engine.
|
115
|
+
# Represents a section in a template, that is,
|
116
|
+
# a set of template elements for which its rendition depends
|
117
|
+
# on the value of a variable.
|
118
|
+
class Section < UnaryElement
|
119
|
+
# The child elements of the section
|
120
|
+
attr_reader(:children)
|
121
|
+
|
122
|
+
# @param aVarName [String] The name of the placeholder from a template.
|
123
|
+
def initialize(aVarName)
|
124
|
+
super(aVarName)
|
125
|
+
@children = []
|
126
|
+
end
|
127
|
+
|
86
128
|
public
|
87
|
-
#
|
129
|
+
# Add a child element as member of the section
|
130
|
+
def add_child(aChild)
|
131
|
+
children << aChild
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
# Render the placeholder given the passed arguments.
|
88
136
|
# This method has the same signature as the {Engine#render} method.
|
89
|
-
# @return [String]
|
137
|
+
# @return [String] The text value assigned to the placeholder.
|
138
|
+
# Returns an empty string when no value is assigned to the placeholder.
|
90
139
|
def render(aContextObject, theLocals)
|
91
|
-
|
140
|
+
raise NotImplementedError, "Method Section::#{_method_} must be implemented in subclass(es)."
|
141
|
+
end
|
142
|
+
|
143
|
+
end # class
|
144
|
+
|
145
|
+
|
146
|
+
# Represents a section in a template, that is,
|
147
|
+
# a set of template elements for which its rendition depends
|
148
|
+
# on the (in)existence of an actual value bound to the variable name.
|
149
|
+
class ConditionalSection < Section
|
150
|
+
# A boolean that indicates whether the rendition condition is the existence of a value for the variable (true)
|
151
|
+
# or its inexistence (false).
|
152
|
+
attr_reader(:existence)
|
153
|
+
|
154
|
+
# @param aVarName [String] The name of the placeholder from a template.
|
155
|
+
# @param renderWhenExisting [boolean] When true, render the children elements if a value exists for the variable.
|
156
|
+
def initialize(aVarName, renderWhenExisting = true)
|
157
|
+
super(aVarName)
|
158
|
+
@existence = renderWhenExisting
|
92
159
|
end
|
160
|
+
|
161
|
+
public
|
162
|
+
# Render the placeholder given the passed arguments.
|
163
|
+
# This method has the same signature as the {Engine#render} method.
|
164
|
+
# @return [String] The text value assigned to the placeholder.
|
165
|
+
# Returns an empty string when no value is assigned to the placeholder.
|
166
|
+
def render(aContextObject, theLocals)
|
167
|
+
actual_value = retrieve_value_from(aContextObject, theLocals)
|
168
|
+
if (!actual_value.nil? && existence) || (actual_value.nil? && !existence)
|
169
|
+
# Let render the children
|
170
|
+
result = children.each_with_object('') do |a_child, sub_result|
|
171
|
+
sub_result << a_child.render(aContextObject, theLocals)
|
172
|
+
end
|
173
|
+
else
|
174
|
+
result = ''
|
175
|
+
end
|
176
|
+
|
177
|
+
return result
|
178
|
+
end
|
179
|
+
|
93
180
|
end # class
|
94
181
|
|
95
182
|
|
183
|
+
SectionEndMarker = Struct.new(:name)
|
184
|
+
|
185
|
+
|
96
186
|
# A very simple implementation of a templating engine.
|
97
187
|
# Earlier versions of Macros4Cuke relied on the logic-less Mustache template engine.
|
98
188
|
# But it was decided afterwards to replace it by a very simple template engine.
|
@@ -101,9 +191,9 @@ end # class
|
|
101
191
|
# while Mustache use !{{...}} delimiters),
|
102
192
|
# - Feature files are meant to be simple, so should the template engine be.
|
103
193
|
class Engine
|
104
|
-
# The regular expression that matches any punctuation sign or delimiter that is forbidden between chevrons <...> template tags.
|
194
|
+
# The regular expression that matches a space, any punctuation sign or delimiter that is forbidden between chevrons <...> template tags.
|
105
195
|
DisallowedSigns = begin
|
106
|
-
forbidden = '!"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~" # Used concatenation (+) to work around Ruby bug!
|
196
|
+
forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~" # Used concatenation (+) to work around Ruby bug!
|
107
197
|
all_escaped = []
|
108
198
|
forbidden.each_char() { |ch| all_escaped << Regexp.escape(ch) }
|
109
199
|
pattern = all_escaped.join("|")
|
@@ -240,11 +330,27 @@ private
|
|
240
330
|
# Parse the contents of a tag entry.
|
241
331
|
# @param aText [String] The text that is enclosed between chevrons.
|
242
332
|
def parse_tag(aText)
|
243
|
-
#
|
244
|
-
|
333
|
+
# Recognize the first character
|
334
|
+
if aText =~ /^[\?\/]/
|
335
|
+
matching = DisallowedSigns.match(aText[1..-1])
|
336
|
+
else
|
337
|
+
# Disallow punctuation and delimiter signs in tags.
|
338
|
+
matching = DisallowedSigns.match(aText)
|
339
|
+
end
|
245
340
|
raise InvalidCharError.new(aText, matching[0]) if matching
|
341
|
+
|
342
|
+
SectionEndMarker
|
343
|
+
result = case aText[0, 1]
|
344
|
+
when '?'
|
345
|
+
ConditionalSection.new(aText[1..-1], true)
|
346
|
+
|
347
|
+
when '/'
|
348
|
+
SectionEndMarker.new(aText[1..-1])
|
349
|
+
else
|
350
|
+
Placeholder.new(aText)
|
351
|
+
end
|
246
352
|
|
247
|
-
return
|
353
|
+
return result
|
248
354
|
end
|
249
355
|
|
250
356
|
end # class
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# encoding: utf-8 -- You should see a paragraph character: §
|
2
|
+
# File: section_spec.rb
|
3
|
+
|
4
|
+
require_relative '../../spec_helper'
|
5
|
+
require_relative '../../../lib/macros4cuke/templating/engine' # Load the classes under test
|
6
|
+
|
7
|
+
module Macros4Cuke
|
8
|
+
|
9
|
+
module Templating # Open this namespace to get rid of module qualifier prefixes
|
10
|
+
|
11
|
+
|
12
|
+
describe ConditionalSection do
|
13
|
+
|
14
|
+
context "Creation and initialization" do
|
15
|
+
|
16
|
+
it "should be created with a variable name and a boolean" do
|
17
|
+
lambda { ConditionalSection.new('foobar', false) }.should_not raise_error
|
18
|
+
lambda { ConditionalSection.new('foobar', true) }.should_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should know the name of its variable" do
|
22
|
+
[false, true].each do |existence|
|
23
|
+
instance = ConditionalSection.new('foobar', existence)
|
24
|
+
instance.name.should == 'foobar'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should know whether the rendition on existence of actual value or not" do
|
29
|
+
[false, true].each do |existence|
|
30
|
+
instance = ConditionalSection.new('foobar', existence)
|
31
|
+
instance.existence.should == existence
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have no child at start" do
|
36
|
+
[false, true].each do |existence|
|
37
|
+
instance = ConditionalSection.new('foobar', existence)
|
38
|
+
instance.should have(0).children
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end # context
|
43
|
+
|
44
|
+
context "Provided services" do
|
45
|
+
# Return a list of possible child elements
|
46
|
+
let(:sample_children) do
|
47
|
+
[ StaticText.new("Hello "),
|
48
|
+
Placeholder.new("user"),
|
49
|
+
EOLine.new
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Default instantiation rule
|
54
|
+
subject { ConditionalSection.new('foobar', true) }
|
55
|
+
|
56
|
+
it "should add child element(s)" do
|
57
|
+
sample_children.each do |a_child|
|
58
|
+
subject.add_child(a_child)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Control that the addition worek as expected
|
62
|
+
subject.children.should == sample_children
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should render its children when conditions are met" do
|
67
|
+
# Adding the children
|
68
|
+
sample_children.each { |a_child| subject.add_child(a_child) }
|
69
|
+
|
70
|
+
# Case of an existing actual
|
71
|
+
locals = {'user' => "joe", "foobar" => 'exists' }
|
72
|
+
rendered_text = subject.render(Object.new, locals)
|
73
|
+
expected_text = "Hello joe\n"
|
74
|
+
rendered_text.should == expected_text
|
75
|
+
|
76
|
+
# Case of a conditional section that should be rendering when value is non-existing.
|
77
|
+
instance = ConditionalSection.new('foobar', false)
|
78
|
+
sample_children.each { |a_child| instance.add_child(a_child) }
|
79
|
+
|
80
|
+
# Case of a non-existing actual
|
81
|
+
locals = {'user' => "joe" }
|
82
|
+
rendered_text = instance.render(Object.new, locals)
|
83
|
+
rendered_text.should == expected_text
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should render noting when conditions are'nt met" do
|
87
|
+
# Adding the children
|
88
|
+
sample_children.each { |a_child| subject.add_child(a_child) }
|
89
|
+
|
90
|
+
# Case of a non-existing actual
|
91
|
+
locals = {'user' => "joe"}
|
92
|
+
rendered_text = subject.render(Object.new, locals)
|
93
|
+
rendered_text.should == ""
|
94
|
+
|
95
|
+
# Case of a conditional section that should be rendering when value is non-existing.
|
96
|
+
instance = ConditionalSection.new('foobar', false)
|
97
|
+
sample_children.each { |a_child| instance.add_child(a_child) }
|
98
|
+
|
99
|
+
# Case of a non-existing actual
|
100
|
+
locals = {'user' => "joe", "foobar" => 'exists' }
|
101
|
+
rendered_text = instance.render(Object.new, locals)
|
102
|
+
rendered_text.should == ""
|
103
|
+
end
|
104
|
+
|
105
|
+
end # context
|
106
|
+
|
107
|
+
end # describe
|
108
|
+
|
109
|
+
end # module
|
110
|
+
|
111
|
+
end # module
|
112
|
+
|
113
|
+
# End of file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: macros4cuke
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.21
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cucumber
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- spec/macros4cuke/macro-step-support_spec.rb
|
100
100
|
- spec/macros4cuke/macro-step_spec.rb
|
101
101
|
- spec/macros4cuke/templating/engine_spec.rb
|
102
|
+
- spec/macros4cuke/templating/section_spec.rb
|
102
103
|
homepage: https://github.com/famished-tiger/Macros4Cuke
|
103
104
|
licenses:
|
104
105
|
- MIT
|