# Radiant ERB (Embedded Ruby) behavior
# ====================================
#
# Provides ERB support for Radiant content. For more about Radiant see
# "http://radiantcms.org/". Install this file inside "app/behaviors/"
# and restart your server if using FastCGI or an equivalent.
#
#
# History
# -------
#
# 2006-07-16 (ADH): Created.

require 'erb'

class ErbBehavior < Behavior::Base
  register "ERB"

  description %{
    Pages using this behavior can include ERB (embedded Ruby) in a manner
    very similar to .rhtml templates in more generic Ruby applications. The
    majority of instance variables usually accessible, gathered from the
    underlying ActionController instance, are collected and made available
    to the ERB parser in a manner similar to that used by Rails itself for
    the ActionView renderer.

    Variables gathered from the controller are copies held locally and are
    not written back to the controller if they are updated. Modifying them
    is harmless, though usually rather pointless.

    Layouts or body parts (for pages without layouts) are first run through
    Radiant so that snippet etc. substitutions take place. If you have set
    a filter for part of the page be careful that it does not damage ERB -
    for example, a Textile filter would squash ERB by escaping the angle
    brackets so you'd end up with ERB literally quoted in the page.
  }

  # Radiant pages using the ERB behavior expect something close to
  # ActionView behavior, so include ERB::Util for things like url_encode().

  include ERB::Util

  # ActionView magic; define an empty module within ourselves, which we will
  # use to evaluate a Ruby source code equivalent of the ERB template that is
  # generated by the ERB module, munged to include our instance variables by
  # wrapping the ERB-generated code in an outer function declaration with
  # various local variable definitions.

  module ERBBehaviorCompiledTemplates
  end

  include ERBBehaviorCompiledTemplates

  # Override the default rendering method.

  def render_page
    lazy_initialize_parser_and_context()

    # Ensure selected instance variables are stored.

    erbbehavior_collect_instance_variables()

    # Use the same parser routine that the superclass uses; if we have
    # a layout, parse it; if we have no layout, parse the page's 'body'
    # part; capture the output.

    if layout = @page.layout
      output = parse_object(layout)
    else
      part = @page.part(:body)
      if part
        output = parse_object(part)
      else
        output = ''
      end
    end

    # Get a new ERB instance and use an undocumented method, as used by
    # ActionView, to get at the Ruby source equivalent of the template.

    src = ERB.new(output).src

    # We now add code to set the instance variables inside that source.
    # To do this, we wrap the source in a function that takes the hash of
    # variables then self-initialises based on that hash; this is straight
    # out of ActionView too, pretty much.

    locals_code = ""
    locals_keys = @variables.keys
    locals_keys.each do |key|
      locals_code << "#{key} = local_variables[:#{key}] if local_variables.has_key?(:#{key})\n"
    end

    code = "def erbbehavior_render(local_variables)\n" +
             "#{locals_code}\n"+
             "#{src}\n" +
           "end\n"

    # Evaluate the code within the temporary internal class, thus defining
    # the temporary rendering method.

    ERBBehaviorCompiledTemplates.module_eval(code)

    # Call that method to return the final result.

    return erbbehavior_render(@variables)
  end

  # We can't cache pages that might change on every fetch.

  def cache_page?
    false
  end

  # Add all of our instance variables to the hash, except for any
  # items from a protected list.

  def erbbehavior_collect_instance_variables
    @variables            = {}
    @@protected_variables = [ '@variables', '@variables_added' ]

    instance_variables.each do |var|
      next if @@protected_variables.include?(var)
      @variables[var[1..-1]] = instance_variable_get(var)
    end
  end
end
