require 'html/template/internal'
module HTML
module Template
def Internal.register_functions_impl(registory, func_spec, &block)
if block && func_spec.kind_of?(Symbol)
registory[func_spec] = block
elsif func_spec.kind_of? Hash
unless func_spec.values.all?{|e| e.kind_of? Proc}
raise ArgumentError, "functions must be kind_of Proc"
end
registory.update(func_spec)
else
raise ArgumentError, "first argument must be symbol or hash contains functions"
end
end
class Pro
VERSION = "0.0.2"
include HTML::Template::Internal
INPUTS = [:filename, :filehandle, :arrayref, :scalarref, :source]
ASK_NAME_DEFAULT = 0
ASK_NAME_AS_IS = 1
ASK_NAME_LOWERCASE = 2
ASK_NAME_UPPERCASE = 4
ASK_NAME_MASK = ASK_NAME_AS_IS | ASK_NAME_LOWERCASE | ASK_NAME_UPPERCASE
@@func = {
# note that length,defined,sin,cos,log,tan,... are built-in
:sprintf => lambda { |*args| sprintf(*args) },
:substr => lambda { |str, *args| args.size == 2 ? str[*args] : str[args[0]..-1] },
:lc => lambda { |str| str.downcase },
:lcfirst => lambda { |str| str[0,1].downcase + str[1..-1] },
:uc => lambda { |str| str.upcase },
:ucfirst => lambda { |str| str.capitalize },
# :length => lambda { |str| str.length },
# :defined => lambda { |obj| obj.nil? },
# :abs => lambda { |num| num.abs },
# :hex => lambda { |obj| obj.to_s.hex },
# :oct => lambda { |obj| obj.to_s.oct },
:rand => lambda { |num| rand num },
:srand => lambda { |seed| srand seed },
}
def initialize(args={})
@options = default_options.merge(args)
if args.keys.count(&INPUTS.method(:include?)) != 1
raise ArgumentError, "HTML::Template::Pro.new called with multiple (or no) template sources specified!"
end
@params = @options[:param_map]
[:path, :associate, :filter].each do |opt|
unless @options[opt].kind_of? Array
@options[opt] = [ @options[opt] ]
end
end
@options[:expr_func] = @@func.merge(@options[:functions] || {})
initialize_tmpl_source args
if @scalarref and @options[:filter]
@scalarref = call_filters @scalarref
end
@filtered_template = {}
end
def param(args=nil, &block)
return @params.keys if args.nil?
if !(args.kind_of? Hash)
key = @options[:case_sensitive] ? args : args.downcase
if block
return @params[key] = block
else
return @params[key] || @params[args]
end
end
merge_params(args)
end
def clear_params
@params.clear
end
def output(options={})
@options[:associate].reverse.each do |assoc|
assoc.param.each do |key|
param(key => assoc.param(key)) unless @params[key]
end
end
if (options.include? :print_to)
exec_tmpl(options[:print_to])
else
output_string = String.new
exec_tmpl(output_string)
return output_string
end
end
def self.new_filehandle(file)
self.new(:filehandle => file)
end
def self.register_function(func_spec, &block)
HTML::Template::Internal.register_functions_impl(@@func, func_spec, &block)
end
def register_function(func_spec, &block)
HTML::Template::Internal.register_functions_impl(@options[:expr_func], func_spec, &block)
end
private
def default_options
return {
:param_map => {},
:filter => [],
:debug => 0,
:max_includes => 10,
:global_vars => false,
:no_includes => false,
:search_path_on_include => false,
:loop_context_vars => false,
:path_like_variable_scope => false,
:path => [],
:associate => [],
:case_sensitive => false,
:__strict_compatibility => true,
:strict => false,
:die_on_bad_params => false,
}
end
def initialize_tmpl_source(args)
if args.include? :filename
@filename = args[:filename]
@scalarref = nil
return
end
@filename = nil
if args.include? :source
source = args[:source]
@scalarref = case source
when IO then source.read
when Array then source.join('')
when String then source
else
if source.respond_to? :to_str
source.to_str
else
raise "unknown source type"
end
end
elsif args.include? :scalarref
@scalarref = args[:scalarref]
elsif args.include? :arrayref
@scalarref = args[:arrayref].join('')
elsif args.include? :filehandle
@scalarref = args[:filehandle].read
end
end
def merge_params(params)
unless @options[:case_sensitive]
params = lowercase_keys params
end
@params.update(params)
end
def lowercase_keys(orighash)
Hash[
orighash.map do |key, val|
case val
when Array then [key.downcase, val.map(&(method :lowercase_keys))]
when Proc then [key.downcase, val]
else [key.downcase, val]
end
end
]
end
def load_template(filepath)
File.open(filepath, 'r') do |file|
# filtered template is used in internal. we store it to `self'
# to prevent gc.
@filtered_template[filepath] = call_filters file.read
end
end
def call_filters(template)
@options[:filter].each do |filter|
format, sub = case filter
when Hash then filter.values_at(:format, :sub)
when Proc then ['scalar', filter]
else raise "bad value set for filter parameter - must be a Proc or a Hash object."
end
unless format and sub
raise "bad value set for filter parameter - hash must contain \"format\" key and \"sub\" key."
end
unless format == 'array' or format == 'scalar'
raise "bad value set for filter parameter - \"format\" must be either 'array' or 'scalar'"
end
unless sub.kind_of? Proc
raise "bad value set for filter parameter - \"sub\" must be a code ref"
end
if format == 'scalar'
template = sub.call(template)
else
template = sub.call(template.split("\n").map {|str| str + "\n"}).join('')
end
end
return template
end
end
# alias
Expr = Pro
end
end