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 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