Class: Tenjin::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/tenjin.rb

Overview

engine class for templates

Engine class supports the followings.

  • template caching

  • partial template

  • layout template

  • capturing (experimental)

ex. file ‘ex_list.rbhtml’

<ul>
<?rb for item in @items ?>
  <li>#{item}</li>
<?rb end ?>
</ul>

ex. file ‘ex_layout.rbhtml’

<html>
 <body>
  <h1>${@title}</li>
#{@_content}
<?rb import 'footer.rbhtml' ?>
 </body>
</html>

ex. file ‘main.rb’

require 'tenjin'
options = {:prefix=>'ex_', :postfix=>'.rbhtml', :layout=>'ex_layout.rbhtml'}
engine = Tenjin::Engine.new(options)
context = {:title=>'Tenjin Example', :items=>['foo', 'bar', 'baz']}
output = engine.render(:list, context)  # or 'ex_list.rbhtml'
print output

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Engine

initializer of Engine class.

options:

:prefix

prefix string for template name (ex. ‘template/’)

:postfix

postfix string for template name (ex. ‘.rbhtml’)

:layout

layout template name (default nil)

:path

array of directory name (default nil)

:cache

save converted ruby code into file or not (default true)

:path

list of directory (default nil)

:preprocess

flag to activate preprocessing (default nil)

:templateclass

template class object (default Tenjin::Template)



812
813
814
815
816
817
818
819
820
821
822
# File 'lib/tenjin.rb', line 812

def initialize(options={})
  @prefix  = options[:prefix]  || ''
  @postfix = options[:postfix] || ''
  @layout  = options[:layout]
  @cache   = options.fetch(:cache, true)
  @path    = options[:path]
  @preprocess = options.fetch(:preprocess, nil)
  @templateclass = options.fetch(:templateclass, Template)
  @init_opts_for_template = options
  @templates = {}   # filename->template
end

Instance Method Details

#cachename(filename) ⇒ Object



861
862
863
# File 'lib/tenjin.rb', line 861

def cachename(filename)
  return (filename + '.cache').untaint
end

#create_template(filename, _context = nil) ⇒ Object

create template object from file



866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
# File 'lib/tenjin.rb', line 866

def create_template(filename, _context=nil)
  template = @templateclass.new(nil, @init_opts_for_template)
  template.timestamp = Time.now()
  cache_filename = cachename(filename)
  _context = hook_context(Context.new) if _context.nil?
  if !@cache
    input = read_template_file(filename, _context)
    template.convert(input, filename)
  elsif !test(?f, cache_filename) || File.mtime(cache_filename) < File.mtime(filename)
    #$stderr.puts "*** debug: load original"
    input = read_template_file(filename, _context)
    template.convert(input, filename)
    store_cachefile(cache_filename, template)
  else
    #$stderr.puts "*** debug: load cache"
    template.filename = filename
    load_cachefile(cache_filename, template)
  end
  return template
end

#find_template_file(template_name) ⇒ Object

find template filename

Raises:

  • (Errno::ENOENT)


831
832
833
834
835
836
837
838
839
840
841
842
# File 'lib/tenjin.rb', line 831

def find_template_file(template_name)
  filename = to_filename(template_name)
  if @path
    for dir in @path
      filepath = "#{dir}#{File::SEPARATOR}#{filename}"
      return filepath if test(?f, filepath.untaint)
    end
  else
    return filename if test(?f, filename.dup.untaint)  # dup is required for frozen string
  end
  raise Errno::ENOENT.new("#{filename} (path=#{@path.inspect})")
end

#get_template(template_name, _context = nil) ⇒ Object

get template object



908
909
910
911
912
913
914
915
916
917
# File 'lib/tenjin.rb', line 908

def get_template(template_name, _context=nil)
  template = @templates[template_name]
  t = template
  unless t && t.timestamp && t.filename && t.timestamp >= File.mtime(t.filename)
    filename = find_template_file(template_name)
    template = create_template(filename, _context)  # _context is passed only for preprocessor
    register_template(template_name, template)
  end
  return template
end

#hook_context(context) ⇒ Object



945
946
947
948
949
950
951
952
953
954
# File 'lib/tenjin.rb', line 945

def hook_context(context)
  if !context
    context = Context.new
  elsif context.is_a?(Hash)
    context = Context.new(context)
  end
  context._engine = self
  context._layout = nil
  return context
end

#load_cachefile(cache_filename, template) ⇒ Object

load template from cache file



899
900
901
902
903
904
905
# File 'lib/tenjin.rb', line 899

def load_cachefile(cache_filename, template)
  s = File.read(cache_filename)
  if s.sub!(/\A\#\@ARGS (.*?)\r?\n/, '')
    template.args = $1.split(',')
  end
  template.script = s
end

#read_template_file(filename, _context) ⇒ Object

read template file and preprocess it



845
846
847
848
849
850
851
852
853
# File 'lib/tenjin.rb', line 845

def read_template_file(filename, _context)
  return File.read(filename) if !@preprocess
  _context ||= {}
  if _context.is_a?(Hash) || _context._engine.nil?
    _context = hook_context(_context)
  end
  preprocessor = Preprocessor.new(filename)
  return preprocessor.render(_context)
end

#register_template(template_name, template) ⇒ Object

register template object



856
857
858
859
# File 'lib/tenjin.rb', line 856

def register_template(template_name, template)
  #template.timestamp = Time.new unless template.timestamp
  @templates[template_name] = template
end

#render(template_name, context = Context.new, layout = true) ⇒ Object

get template object and evaluate it with context object. if argument ‘layout’ is true then default layout file (specified at initializer) is used as layout template, else if false then no layout template is used. if argument ‘layout’ is string, it is regarded as layout template name.



924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
# File 'lib/tenjin.rb', line 924

def render(template_name, context=Context.new, layout=true)
  #context = Context.new(context) if context.is_a?(Hash)
  context = hook_context(context)
  while true
    template = get_template(template_name, context)  # context is passed only for preprocessor
    _buf = context._buf
    output = template.render(context)
    context._buf = _buf
    unless context['_layout'].nil?
      layout = context['_layout']
      context['_layout'] = nil
    end
    layout = @layout if layout == true or layout.nil?
    break unless layout
    template_name = layout
    layout = false
    context['_content'] = output
  end
  return output
end

#store_cachefile(cache_filename, template) ⇒ Object

store template into cache file



888
889
890
891
892
893
894
895
896
# File 'lib/tenjin.rb', line 888

def store_cachefile(cache_filename, template)
  s = template.script
  s = "\#@ARGS #{template.args.join(',')}\n#{s}" if template.args
  File.open(cache_filename, 'w') do |f|
    f.flock(File::LOCK_EX)
    f.write(s)
    #f.lock(FIle::LOCK_UN)   # File#close() unlocks automatically
  end
end

#to_filename(template_name) ⇒ Object

convert short name into filename (ex. ‘:list’ => ‘template/list.rb.html’)



825
826
827
828
# File 'lib/tenjin.rb', line 825

def to_filename(template_name)
  name = template_name
  return name.is_a?(Symbol) ? "#{@prefix}#{name}#{@postfix}" : name
end