Skip to content

Instantly share code, notes, and snippets.

@apeiros
Created January 28, 2010 20:07
Show Gist options
  • Save apeiros/289088 to your computer and use it in GitHub Desktop.
Save apeiros/289088 to your computer and use it in GitHub Desktop.
#--
# Copyright 2007 by Stefan Rusterholz.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'erb'
#--
# Copyright 2007 by Stefan Rusterholz.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
module SilverPlatter
# = Indexing
# Author: Stefan Rusterholz
# Contact: apeiros@gmx.net>
# Version: 0.1.0
# Revision: $Rev: 152 $
# Date: $Date: 2008-04-18 12:09:58 +0200 (Fri, 18 Apr 2008) $
#
# = About
# A helper class for ERB, allows constructs like the one in the Synopsis to
# enable simple use of variables/methods in templates.
#
# = Synopsis
# tmpl = Templater.new("Hello <%= name %>!")
# tmpl.result(self, :name => 'world') # => 'Hello World!'
#
class Templater
module VERSION
MAJOR = 0
MINOR = 1
TINY = 0
STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
end
# Option defaults
Opt = {
:safe_level => nil,
:trim_mode => '%<>',
:eoutvar => '_erbout'
}
# binding method
Binder = Object.instance_method(:binding)
# The template string
attr_reader :string
# Like Templater.new, but instead of a template string, the path to the file
# containing the template. Sets :filename.
def self.file(path, opt=nil)
new(File.read, (opt || {}).merge(:filename => path))
end
# ==== Arguments
# * string: The template string, it becomes frozen
# * opt: Option hash, keys:
# * :filename: The filename used for the evaluation (useful for error messages)
# * :safe_level: see ERB.new
# * :trim_mode: see ERB.new
# * :eoutvar: see ERB.new
def initialize(string, opt={})
opt, string = string, nil if string.kind_of?(Hash)
opt = Opt.merge(opt)
file = opt.delete(:filename)
@string = string.freeze
@erb = ERB.new(@string, *opt.values_at(:safe_level, :trim_mode, :eoutvar))
@erb.filename = file if file
end
# See Templater::Variables.new
# Returns the evaluated template. Default &on_error is the Templater::Raiser
# proc.
def result(delegate=nil, variables={}, on_error_name=nil, &on_error)
variables ||= {}
on_error ||= Raiser
variables = Variables.new(delegate, variables, on_error_name=nil, &on_error)
@erb.result(Binder.bind(variables).call)
rescue NameError => e
raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
end
def inspect # :nodoc:
sprintf "#<%s:0x%x string=%s>",
self.class,
object_id << 1,
@string.inspect
end
end
# = Indexing
# Author: Stefan Rusterholz
# Contact: apeiros@gmx.net>
# Version: 0.1.0
# Revision: $Rev: 151 $
# Date: $Date: 2008-04-18 12:06:55 +0200 (Fri, 18 Apr 2008) $
#
# = About
# Similar to OpenStruct, but slightly optimized for create once, use once and
# giving diagnostics on exceptions/missing keys.
# Used by SilverPlatter::Templater to resolve variables.
#
# = Synopsis
# tmpl = Variables.new(delegator, :variable => "content") { |exception|
# do_something_with(exception)
# }
#
class Variables
module VERSION
MAJOR = 0
MINOR = 1
TINY = 0
STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
end
# A proc for &on_error in SilverPlatter::Variables::new or SilverPlatter::Templater#result.
# Raises the error further on.
Raiser = proc { |e|
raise
}
# A proc for &on_error in SilverPlatter::Variables.new or SilverPlatter::Templater#result.
# Inserts <<error_class: error_message>> in the place where the error
# occurred.
Teller = proc { |e|
"<<#{e.class}: #{e}>>"
}
# An empty Hash
EmptyHash = {}.freeze
# Regex to match setter method names
SetterPattern = /=\z/.freeze
# === Arguments
# * delegate: All method calls and undefined variables are delegated to this object as method call.
# * variables: A hash with variables in it, keys must be Symbols.
# * on_error_name: Instead of a block you can pass the name of an existing handler, e.g. :Raiser or :Teller.
# * &on_error: The block is yielded in case of an exception with the exception as argument
#
def initialize(delegate=nil, variables={}, on_error_name=nil, &on_error)
@delegate = delegate
@table = (@delegate ? Hash.new { |h,k| @delegate.send(k) } : EmptyHash).merge(variables)
if !on_error && on_error_name then
@on_error = self.class.const_get(on_error_name)
else
@on_error = on_error || Raiser
end
end
# All keys this Variables instance provides, if the include_delegate argument is true and
# the object to delegate to responds to __keys__, then it will add the keys of the delegate.
def __keys__(include_delegate=true)
@table.keys + ((include_delegate && @delegate.respond_to?(:__keys__)) ? @delegate.__keys__ : [])
end
# See Object#respond_to?
def respond_to?(key)
@table.respond_to?(key) || (@delegate && @delegate.respond_to?(key)) || super
end
def method_missing(m, *args) # :nodoc:
argn = args.length
if argn.zero? && @table.has_key?(m) then
@table[m]
elsif argn == 1 && m.to_s =~ SetterPattern
@table[m] = args.first
elsif @delegate
@delegate.send(m, *args)
end
rescue => e
@on_error.call(e)
end
def inspect # :nodoc:
sprintf "#<%s:0x%08x @delegate=%s %s>",
self.class,
object_id,
@table.map { |k,v| "#{k}=#{v.inspect}" }.join(', '),
@delegate ? "#<%s:0x%08x ...>" % [@delegate.class, @delegate.object_id << 1] : "nil"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment