# Cheap-n-cheerful HTML page template system. You create a # template containing: # # * variable names between percent signs (%fred%) # * 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 T1, T2, 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) # # class TemplatePage ########## # A context holds a stack of key/value pairs (like a symbol # table). When asked to resolve a key, it first searches the top of # the stack, then the next level, and so on until it finds a match # (or runs out of entries) class Context def initialize @stack = [] end def push(hash) @stack.push(hash) end def pop @stack.pop end # Find a scalar value, throwing an exception if not found. This # method is used when substituting the %xxx% constructs def find_scalar(key) @stack.reverse_each do |level| if val = level[key] return val unless val.kind_of? Array end end raise "Template error: can't find variable '#{key}'" end # Lookup any key in the stack of hashes def lookup(key) @stack.reverse_each do |level| val = level[key] return val if val end nil end end ######### # Simple class to read lines out of a string class LineReader # we're initialized with an array of lines def initialize(lines) @lines = lines end # read the next line def read @lines.shift end # Return a list of lines up to the line that matches # a pattern. That last line is discarded. def read_up_to(pattern) res = [] while line = read if pattern.match(line) return LineReader.new(res) else res << line end end raise "Missing end tag in template: #{pattern.source}" end # Return a copy of ourselves that can be modified without # affecting us def dup LineReader.new(@lines.dup) end end # +templates+ is an array of strings containing the templates. # We start at the first, and substitute in subsequent ones # where the string !INCLUDE! occurs. For example, # we could have the overall page template containing # #
#