macros4cuke 0.2.20 → 0.2.21

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,7 +1,15 @@
1
- ## 0.2.20 / 2013-05-06
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-06
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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Macros4Cuke # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.2.20'
6
+ Version = '0.2.21'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Macros for Cucumber"
@@ -37,10 +37,22 @@ end # class
37
37
 
38
38
 
39
39
  # Class used internally by the template engine.
40
- # Represents a named placeholder in a template, that is,
41
- # a name placed between <..> in the template.
42
- # At rendition, a placeholder is replaced by the text value that is associated with it.
43
- class Placeholder
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[name]
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
- # Class used internally by the template engine.
84
- # Represents an end of line that must be rendered as such.
85
- class EOLine
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
- # Render an end of line.
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] An end of line marker. Its exact value is OS-dependent.
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
- return "\n"
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
- # Disallow punctuation and delimiter signs in tags.
244
- matching = DisallowedSigns.match(aText)
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 Placeholder.new(aText)
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.20
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-07 00:00:00.000000000 Z
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