# Cheap-n-cheerful HTML page template system. You create a 
# template containing:
#
# * variable names between percent signs (<tt>%fred%</tt>)
# * blocks of repeating stuff:
#
#     START:key
#       ... stuff
#     END:key
#
# You feed the code a hash. For simple variables, the values
# are resolved directly from the hash. For blocks, the hash entry
# corresponding to +key+ will be an array of hashes. The block will
# be generated once for each entry. Blocks can be nested arbitrarily
# deeply.
#
# The template may also contain
#
#   IF:key
#     ... stuff
#   ENDIF:key
#
# _stuff_ will only be included in the output if the corresponding
# key is set in the value hash.
#
# Usage:  Given a set of templates <tt>T1, T2,</tt> etc
#
#            values = { "name" => "Dave", state => "TX" }
#
#            t = TemplatePage.new(T1, T2, T3)
#            File.open(name, "w") {|f| t.write_html_on(f, values)}
#         or
#            res = ''
#            t.write_html_on(res, values)
#
#
# Requires:: Ruby 1.6.5 or later

##
# Ruby versions prior to 1.6.5 seem to have problems with the recursive
# pattern matches we use. Eventually we might support them, but
# for now we'll add a check 

if RUBY_VERSION < "1.6.5"
  raise "template.rb (and hence RDoc) requires Ruby 1.6.5 or later"
end

class TemplatePage

  
  # +templates+ is an array of strings containing the templates.
  # We start at the first, and substitute in subsequent ones
  # where the string <tt>!INCLUDE!</tt> occurs. For example,
  # we could have the overall page template containing
  #
  #   <html><body>
  #     <h1>Master</h1>
  #     !INCLUDE!
  #   </bost></html>
  #
  # and substitute subpages in to it by passing [master, sub_page].
  # This gives us a cheap way of framing pages

  def initialize(*templates)
    result = "!INCLUDE!"
    templates.each do |content|
      result.sub!(/!INCLUDE!/, content)
    end
    @template = result
  end

  # Render the templates into HTML, storing the result on +op+ 
  # using the method <tt><<</tt>. The <tt>value_hash</tt> contains
  # key/value pairs used to drive the substitution (as described above)

  def write_html_on(op, value_hash)
    op << substitute_into(@template, value_hash)
  end


  # Substitute a set of key/value pairs into the given template. 
  # Keys with scalar values have them substituted directly into
  # the page. Those with array values invoke <tt>substitute_array</tt>
  # (below), which examples a block of the template once for each 
  # row in the array.
  #
  # This reoutine also copes with the <tt>IF:</tt>_key_ directive,
  # removing chunks of the template if the corresponding key
  # does not appear in the hash

  def substitute_into(template, values)
    values.each do |key, value|
      if value.kind_of? Array
        substitute_array(template, key, value)
      else
        template.gsub!(/%#{key}%/, value)
      end
    end
    1 while template.gsub!(/IF:(\w+)(.*?)ENDIF:\1/m) {
      key = $1
      if values[key]
        $2
      else
        ''
      end
    }
    # Generate a cross reference if a reference is given,
    # otherwise just fill in the name part
    template.gsub!(/HREF:(.*?):(.*?):/) {
      ref = values[$1]
      name = values[$2]
      if ref
	"<a href=\"#{ref}\">#{name}</a>"
      else
	name
      end
    }
		   
    template
  end

  # Called with an array containing hashes of key/value pairs,
  # we replace the START:_key_/END:_key_ block once
  # for each row in the array, expanding the values it
  # contains recursively

  def substitute_array(template, key, array)
    template.gsub!(/START:#{key}\n?(.*?)END:#{key}/m) {
      line = $1
      array.collect do |values|
        substitute_into(line.dup, values)
      end .join("")
    }
  end

end
