lib/mustache.rb



require 'mustache/enumerable'
require 'mustache/template'
require 'mustache/context'
require 'mustache/settings'
require 'mustache/utils'

# Mustache is the base class from which your Mustache subclasses
# should inherit (though it can be used on its own).
#
# The typical Mustache workflow is as follows:
#
# * Create a Mustache subclass: class Stats < Mustache
# * Create a template: stats.mustache
# * Instantiate an instance: view = Stats.new
# * Render that instance: view.render
#
# You can skip the instantiation by calling `Stats.render` directly.
#
# While Mustache will do its best to load and render a template for
# you, this process is completely customizable using a few options.
#
# All settings can be overriden at the class level.
#
# For example, going with the above example, we can use
# `Stats.template_path = "/usr/local/templates"` to specify the path
# Mustache uses to find templates.
#
# Here are the available options:
#
# * template_path
#
# The `template_path` setting determines the path Mustache uses when
# looking for a template. By default it is "."
# Setting it to /usr/local/templates, for example, means (given all
# other settings are default) a Mustache subclass `Stats` will try to
# load /usr/local/templates/stats.mustache
#
# * template_extension
#
# The `template_extension` is the extension Mustache uses when looking
# for template files. By default it is "mustache"
#
# * template_file
#
# You can tell Mustache exactly which template to use with this
# setting. It can be a relative or absolute path.
#
# * template
#
# Sometimes you want Mustache to render a string, not a file. In those
# cases you may set the `template` setting. For example:
#
#   >> Mustache.render("Hello {{planet}}", :planet => "World!")
#   => "Hello World!"
#
# The `template` setting is also available on instances.
#
#   view = Mustache.new
#   view.template = "Hi, {{person}}!"
#   view[:person] = 'Mom'
#   view.render # => Hi, mom!
#
# * view_namespace
#
# To make life easy on those developing Mustache plugins for web frameworks or
# other libraries, Mustache will attempt to load view classes (i.e. Mustache
# subclasses) using the `view_class` class method. The `view_namespace` tells
# Mustache under which constant view classes live. By default it is `Object`.
#
# * view_path
#
# Similar to `template_path`, the `view_path` option tells Mustache where to look
# for files containing view classes when using the `view_class` method.
#
class Mustache

  # Initialize a new Mustache instance.
  #
  # @param [Hash] options An options hash
  # @option options [String] template_path 
  # @option options [String] template_extension
  # @option options [String] template_file
  # @option options [String] template
  # @option options [String] view_namespace 
  # @option options [String] view_path
  def initialize(options = {})
    @options = options
    
    initialize_settings
  end

  # Instantiates an instance of this class and calls `render` with
  # the passed args.
  #
  # @return A rendered String version of a template.
  def self.render(*args)
    new.render(*args)
  end

  # Parses our fancy pants template file and returns normal file with
  # all special {{tags}} and {{#sections}}replaced{{/sections}}.
  #
  # @example Render view
  #   @view.render("Hi {{thing}}!", :thing => :world)
  #
  # @example Set view template and then render
  #   View.template = "Hi {{thing}}!"
  #   @view = View.new
  #   @view.render(:thing => :world)
  #
  # @param [String,Hash] data A String template or a Hash context.
  #                           If a Hash is given, we'll try to figure
  #                           out the template from the class.
  # @param [Hash] ctx A Hash context if `data` is a String template.
  # @return [String] Returns a rendered version of a template.
  def render(data = template, ctx = {})
    case data
    when Hash
      ctx = data
    when Symbol
      self.template_name = data
    end

    tpl = case data
    when Hash
      templateify(template)
    when Symbol
      templateify(template)
    else
      templateify(data)
    end

    return tpl.render(context) if ctx == {}

    begin
      context.push(ctx)
      tpl.render(context)
    ensure
      context.pop
    end
  end

  # Context accessors.
  #
  # @example Context accessors
  #   view = Mustache.new
  #   view[:name] = "Jon"
  #   view.template = "Hi, {{name}}!"
  #   view.render # => "Hi, Jon!"
  def [](key)
    context[key.to_sym]
  end

  def []=(key, value)
    context[key.to_sym] = value
  end

  # A helper method which gives access to the context at a given time.
  # Kind of a hack for now, but useful when you're in an iterating section
  # and want access to the hash currently being iterated over.
  def context
    @context ||= Context.new(self)
  end

  # Given a file name and an optional context, attempts to load and
  # render the file as a template.
  def self.render_file(name, context = {})
    render(partial(name), context)
  end

  # Given a file name and an optional context, attempts to load and
  # render the file as a template.
  def render_file(name, context = {})
    self.class.render_file(name, context)
  end

  # Given a name, attempts to read a file and return the contents as a
  # string. The file is not rendered, so it might contain
  # {{mustaches}}.
  #
  # Call `render` if you need to process it.
  def self.partial(name)
    self.new.partial(name)
  end

  # Override this in your subclass if you want to do fun things like
  # reading templates from a database. It will be rendered by the
  # context, so all you need to do is return a string.
  def partial(name)
    path = "#{template_path}/#{name}.#{template_extension}"

    begin
      File.read(path)
    rescue
      raise if raise_on_context_miss?
      ""
    end
  end

  # Override this to provide custom escaping.
  # By default it uses `CGI.escapeHTML`.
  #
  # @example Overriding #escape
  #   class PersonView < Mustache
  #     def escape(value)
  #       my_html_escape_method(value.to_s)
  #     end
  #   end
  #
  # @param [Object] value Value to escape.
  # @return [String] Escaped content.
  def escape(value)
    self.escapeHTML(value.to_s)
  end
  
  # Override this to provide custom escaping.
  #
  # @example Overriding #escapeHTML
  #   class PersonView < Mustache
  #     def escapeHTML(str)
  #       my_html_escape_method(str)
  #     end
  #   end
  #
  # @deprecated Use {#escape} instead.
  #
  #   Note that {#escape} can receive any kind of object.
  #   If your override logic is expecting a string, you will
  #   have to call to_s on it yourself.
  # @param [String] str String to escape.
  # @return [String] Escaped HTML.
  def escapeHTML(str)
    CGI.escapeHTML(str)
  end

  # Has this instance or its class already compiled a template?
  def compiled?
    (@template && @template.is_a?(Template)) || self.class.compiled?
  end


  private


  # When given a symbol or string representing a class, will try to produce an
  # appropriate view class.
  #
  # @example
  #   Mustache.view_namespace = Hurl::Views
  #   Mustache.view_class(:Partial) # => Hurl::Views::Partial
  def self.view_class(name)
    name = classify(name.to_s)

    # Emptiness begets emptiness.
    return Mustache if name.to_s.empty?

    name = "#{view_namespace}::#{name}"
    const = rescued_const_get(name)

    return const if const

    const_from_file(name)
  end

  def self.rescued_const_get name
    const_get(name, true) || Mustache
  rescue NameError
    nil
  end

  def self.const_from_file name
    file_name = underscore(name)
    file_path = "#{view_path}/#{file_name}.rb"

    return Mustache unless File.exist?(file_path)

    require file_path.chomp('.rb')
    rescued_const_get(name)
  end

  # Has this template already been compiled? Compilation is somewhat
  # expensive so it may be useful to check this before attempting it.
  def self.compiled?
    @template.is_a? Template
  end


  # template_partial => TemplatePartial
  # template/partial => Template::Partial
  def self.classify(underscored)
    Mustache::Utils::String.new(underscored).classify
  end

  # TemplatePartial => template_partial
  # Template::Partial => template/partial
  # Takes a string but defaults to using the current class' name.
  def self.underscore(classified = name)
    classified = superclass.name if classified.to_s.empty?

    Mustache::Utils::String.new(classified).underscore(view_namespace)
  end

  # @param [Template,String] obj      Turns `obj` into a template
  # @param [Hash]            options  Options for template creation
  def self.templateify(obj, options = {})
    obj.is_a?(Template) ? obj : Template.new(obj, options)
  end

  def templateify(obj)
    opts = {:partial_resolver => self.method(:partial)}
    opts.merge!(@options) if @options.is_a?(Hash)
    self.class.templateify(obj, opts)
  end

  # Return the value of the configuration setting on the superclass, or return
  # the default.
  #
  # @param [Symbol] attr_name Name of the attribute. It should match
  #                           the instance variable.
  # @param [Object] default Default value to use if the superclass does
  #                         not respond.
  #
  # @return Inherited or default configuration setting.
  def self.inheritable_config_for(attr_name, default)
    superclass.respond_to?(attr_name) ? superclass.send(attr_name) : default
  end
end