lib/macros4cuke/templating/engine.rb in macros4cuke-0.3.42 vs lib/macros4cuke/templating/engine.rb in macros4cuke-0.4.00
- old
+ new
@@ -37,10 +37,38 @@
end
end # class
# Class used internally by the template engine.
+# Represents a comment from a template.
+# A static text is a text that is reproduced verbatim
+# when rendering a template.
+class Comment
+ # The comment as extracted from the original template.
+ attr_reader(:source)
+
+
+ # @param aSourceText [String] A piece of text extracted
+ # from the template that must be rendered verbatim.
+ def initialize(aSourceText)
+ @source = aSourceText
+ end
+
+ public
+
+ # Render the comment.
+ # Comments are rendered as empty text. This is necessary because
+ # Cucumber::RbSupport::RbWorld#steps complains when it sees a comment.
+ # This method has the same signature as the {Engine#render} method.
+ # @return [String] Empty string ("as is")
+ def render(aContextObject, theLocals)
+ return ''
+ end
+end # class
+
+
+# Class used internally by the template engine.
# 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.
@@ -158,11 +186,11 @@
# 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)
msg = "Method Section.#{__method__} must be implemented in subclass."
- raise NotImplementedError, msg
+ fail(NotImplementedError, msg)
end
end # class
@@ -237,10 +265,13 @@
end
# The original text of the template is kept here.
attr_reader(:source)
+ # The internal representation of the template text
+ attr_reader(:representation)
+
# Builds an Engine and compiles the given template text into
# an internal representation.
# @param aSourceTemplate [String] The template source text.
# It may contain zero or tags enclosed between chevrons <...>.
def initialize(aSourceTemplate)
@@ -258,15 +289,23 @@
# tag/placeholder name => actual value.
# @return [String] The rendition of the template given
# the passed argument values.
def render(aContextObject = Object.new, theLocals)
return '' if @representation.empty?
-
+
+ prev = nil
result = @representation.each_with_object('') do |element, subResult|
- subResult << element.render(aContextObject, theLocals)
+ # Output compaction rules:
+ # -In case of consecutive eol's only one is rendered.
+ # -In case of comment followed by one eol, both aren't rendered
+ unless element.is_a?(EOLine) &&
+ (prev.is_a?(EOLine) || prev.is_a?(Comment))
+ subResult << element.render(aContextObject, theLocals)
+ end
+ prev = element
end
-
+
return result
end
# Retrieve all placeholder names that appear in the template.
@@ -293,28 +332,32 @@
return @variables
end
# Class method. Parse the given line text into a raw representation.
# @return [Array] Couples of the form:
- # [:static, text] or [:dynamic, tag text]
+ # [:static, text], [:comment, text] or [:dynamic, tag text]
def self.parse(aTextLine)
scanner = StringScanner.new(aTextLine)
result = []
-
- until scanner.eos?
- # Scan tag at current position...
- tag_literal = scanner.scan(/<(?:[^\\<>]|\\.)*>/)
- unless tag_literal.nil?
- result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
+
+ if scanner.check(/\s*#/) # Detect comment line
+ result << [:comment, aTextLine]
+ else
+ until scanner.eos?
+ # Scan tag at current position...
+ tag_literal = scanner.scan(/<(?:[^\\<>]|\\.)*>/)
+ unless tag_literal.nil?
+ result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
+ end
+
+ # ... or scan plain text at current position
+ literal = scanner.scan(/(?:[^\\<>]|\\.)+/)
+ result << [:static, literal] unless literal.nil?
+ identify_parse_error(aTextLine) if tag_literal.nil? && literal.nil?
end
-
- # ... or scan plain text at current position
- text_literal = scanner.scan(/(?:[^\\<>]|\\.)+/)
- result << [:static, text_literal] unless text_literal.nil?
- identify_parse_error(aTextLine) if tag_literal.nil? && text_literal.nil?
end
-
+
return result
end
private
@@ -334,15 +377,15 @@
case ch
when '<' then unbalance += 1
when '>' then unbalance -= 1
end
- raise StandardError, "Nested opening chevron '<'." if unbalance > 1
- raise StandardError, "Missing opening chevron '<'." if unbalance < 0
+ fail(StandardError, "Nested opening chevron '<'.") if unbalance > 1
+ fail(StandardError, "Missing opening chevron '<'.") if unbalance < 0
end
- raise StandardError, "Missing closing chevron '>'." if unbalance == 1
+ fail(StandardError, "Missing closing chevron '>'.") if unbalance == 1
end
# Create the internal representation of the given template.
def compile(aSourceTemplate)
@@ -353,11 +396,11 @@
raw_lines = input_lines.map do |line|
line_items = self.class.parse(line)
line_items.each do |(kind, text)|
# A tag text cannot be empty nor blank
if (kind == :dynamic) && text.strip.empty?
- raise EmptyArgumentError.new(line.strip)
+ fail(EmptyArgumentError.new(line.strip))
end
end
line_items
end
@@ -388,20 +431,20 @@
end
else
false
end
end
- if line_to_squeeze && ! section_item.nil?
+ if line_to_squeeze && !section_item.nil?
line_rep = [section_item]
else
line_rep_ending(line_rep)
end
return line_rep
end
-
+
# Apply rule: if last item in line is an end of section marker,
# then place eoline before that item.
# Otherwise, end the line with a eoline marker.
def line_rep_ending(theLineRep)
if theLineRep.last.is_a?(SectionEndMarker)
@@ -416,30 +459,32 @@
# @param aCouple [Array] a two-element array of the form: [kind, text]
# Where kind must be one of :static, :dynamic
def compile_couple(aCouple)
(kind, text) = aCouple
-
+
result = case kind
when :static then StaticText.new(text)
+ when :comment then Comment.new(text)
when :dynamic then parse_tag(text)
end
return result
end
-
+
+
# Parse the contents of a tag entry.
# @param aText [String] The text that is enclosed between chevrons.
def parse_tag(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
+ fail(InvalidCharError.new(aText, matching[0])) if matching
result = case aText[0, 1]
when '?'
ConditionalSection.new(aText[1..-1], true)
@@ -475,11 +520,11 @@
end
end
unless open_sections.empty?
error_message = "Unterminated section #{open_sections.last}."
- raise StandardError, error_message
+ fail(StandardError, error_message)
end
return compiled
end
@@ -488,14 +533,14 @@
def validate_section_end(marker, sections)
msg_prefix = "End of section</#{marker.name}> "
if sections.empty?
msg = 'found while no corresponding section is open.'
- raise StandardError, msg_prefix + msg
+ fail(StandardError, msg_prefix + msg)
end
if marker.name != sections.last.name
msg = "doesn't match current section '#{sections.last.name}'."
- raise StandardError, msg_prefix + msg
+ fail(StandardError, msg_prefix + msg)
end
end
end # class