lib/macros4cuke/templating/engine.rb in macros4cuke-0.2.20 vs lib/macros4cuke/templating/engine.rb in macros4cuke-0.2.21
- old
+ new
@@ -35,32 +35,63 @@
end
end # class
# Class used internally by the template engine.
-# Represents a named placeholder in a template, that is,
-# a name placed between <..> in the template.
-# At rendition, a placeholder is replaced by the text value that is associated with it.
-class Placeholder
+# Represents an end of line that must be rendered as such.
+class EOLine
+public
+ # Render an end of line.
+ # This method has the same signature as the {Engine#render} method.
+ # @return [String] An end of line marker. Its exact value is OS-dependent.
+ def render(aContextObject, theLocals)
+ return "\n"
+ end
+end # class
+
+
+# Base class used internally by the template engine.
+# The generalization of any element from a template that has one variable
+# whose actual value influences the rendition.
+class UnaryElement
# The name of the placeholder/variable.
attr_reader(:name)
# @param aVarName [String] The name of the placeholder from a template.
def initialize(aVarName)
@name = aVarName
end
+protected
+ # This method has the same signature as the {Engine#render} method.
+ # @return [Object] The actual value from the locals or context that is assigned to the variable.
+ def retrieve_value_from(aContextObject, theLocals)
+ actual_value = theLocals[name]
+ if actual_value.nil? && aContextObject.respond_to?(name.to_sym)
+ actual_value = aContextObject.send(name.to_sym)
+ end
+
+ return actual_value
+ end
+
+end # class
+
+
+
+# Class used internally by the template engine.
+# Represents a named placeholder in a template, that is,
+# a name placed between <..> in the template.
+# At rendition, a placeholder is replaced by the text value that is associated with it.
+class Placeholder < UnaryElement
+
public
# Render the placeholder given the passed arguments.
# This method has the same signature as the {Engine#render} method.
# @return [String] The text value assigned to the placeholder.
# Returns an empty string when no value is assigned to the placeholder.
def render(aContextObject, theLocals)
- actual_value = theLocals[name]
- if actual_value.nil? && aContextObject.respond_to?(name.to_sym)
- actual_value = aContextObject.send(name.to_sym)
- end
+ actual_value = retrieve_value_from(aContextObject, theLocals)
result = case actual_value
when NilClass
''
@@ -78,34 +109,93 @@
end
end # class
-# Class used internally by the template engine.
-# Represents an end of line that must be rendered as such.
-class EOLine
+# Base class used internally by the template engine.
+# Represents a section in a template, that is,
+# a set of template elements for which its rendition depends
+# on the value of a variable.
+class Section < UnaryElement
+ # The child elements of the section
+ attr_reader(:children)
+
+ # @param aVarName [String] The name of the placeholder from a template.
+ def initialize(aVarName)
+ super(aVarName)
+ @children = []
+ end
+
public
- # Render an end of line.
+ # Add a child element as member of the section
+ def add_child(aChild)
+ children << aChild
+ end
+
+protected
+ # Render the placeholder given the passed arguments.
# This method has the same signature as the {Engine#render} method.
- # @return [String] An end of line marker. Its exact value is OS-dependent.
+ # @return [String] The text value assigned to the placeholder.
+ # Returns an empty string when no value is assigned to the placeholder.
def render(aContextObject, theLocals)
- return "\n"
+ raise NotImplementedError, "Method Section::#{_method_} must be implemented in subclass(es)."
+ end
+
+end # class
+
+
+# Represents a section in a template, that is,
+# a set of template elements for which its rendition depends
+# on the (in)existence of an actual value bound to the variable name.
+class ConditionalSection < Section
+ # A boolean that indicates whether the rendition condition is the existence of a value for the variable (true)
+ # or its inexistence (false).
+ attr_reader(:existence)
+
+ # @param aVarName [String] The name of the placeholder from a template.
+ # @param renderWhenExisting [boolean] When true, render the children elements if a value exists for the variable.
+ def initialize(aVarName, renderWhenExisting = true)
+ super(aVarName)
+ @existence = renderWhenExisting
end
+
+public
+ # Render the placeholder given the passed arguments.
+ # This method has the same signature as the {Engine#render} method.
+ # @return [String] The text value assigned to the placeholder.
+ # Returns an empty string when no value is assigned to the placeholder.
+ def render(aContextObject, theLocals)
+ actual_value = retrieve_value_from(aContextObject, theLocals)
+ if (!actual_value.nil? && existence) || (actual_value.nil? && !existence)
+ # Let render the children
+ result = children.each_with_object('') do |a_child, sub_result|
+ sub_result << a_child.render(aContextObject, theLocals)
+ end
+ else
+ result = ''
+ end
+
+ return result
+ end
+
end # class
+SectionEndMarker = Struct.new(:name)
+
+
# A very simple implementation of a templating engine.
# Earlier versions of Macros4Cuke relied on the logic-less Mustache template engine.
# But it was decided afterwards to replace it by a very simple template engine.
# The reasons were the following:
# - Be closer to the usual Gherkin syntax (parameters of scenario outlines use chevrons <...>,
# while Mustache use !{{...}} delimiters),
# - Feature files are meant to be simple, so should the template engine be.
class Engine
- # The regular expression that matches any punctuation sign or delimiter that is forbidden between chevrons <...> template tags.
+ # The regular expression that matches a space, any punctuation sign or delimiter that is forbidden between chevrons <...> template tags.
DisallowedSigns = begin
- forbidden = '!"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~" # Used concatenation (+) to work around Ruby bug!
+ forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~" # Used concatenation (+) to work around Ruby bug!
all_escaped = []
forbidden.each_char() { |ch| all_escaped << Regexp.escape(ch) }
pattern = all_escaped.join("|")
Regexp.new(pattern)
end
@@ -238,14 +328,30 @@
end
# Parse the contents of a tag entry.
# @param aText [String] The text that is enclosed between chevrons.
def parse_tag(aText)
- # Disallow punctuation and delimiter signs in tags.
- matching = DisallowedSigns.match(aText)
+ # Recognize the first character
+ if aText =~ /^[\?\/]/
+ matching = DisallowedSigns.match(aText[1..-1])
+ else
+ # Disallow punctuation and delimiter signs in tags.
+ matching = DisallowedSigns.match(aText)
+ end
raise InvalidCharError.new(aText, matching[0]) if matching
+
+ SectionEndMarker
+ result = case aText[0, 1]
+ when '?'
+ ConditionalSection.new(aText[1..-1], true)
+
+ when '/'
+ SectionEndMarker.new(aText[1..-1])
+ else
+ Placeholder.new(aText)
+ end
- return Placeholder.new(aText)
+ return result
end
end # class
end # module
\ No newline at end of file