module Gumdrop class Renderer include Util::SiteAccess SPECIAL_OPTS= %w(layout force_partial) MUNGABLE_RE= Regexp.new(%Q<(href|data|src)([\s]*)=([\s]*)('|"|"|"|')?\\/([\\/]?)>, 'i') attr_reader :context, :cache def initialize site.active_renderer= self @context, @content, @opts= nil, nil, nil @stack= [] @cache= {} end def draw(content, opts={}) event_block :render_item do |data| data[:content]= content log.debug " rendering: #{ content.source_filename } (#{ content.uri })" if content.binary? or content.missing? log.warn "Missing content body for: #{ content.uri }" nil else opts[:calling_page]= @context unless opts.has_key? :calling_page _in_context(content, opts) do data[:context]= @context data[:output]= _render_content! @cache[content.source_path]= data[:output] if @context.cache data[:output] end end end end private def _render_content! output= @content.body _render_pipeline(@content.source_filename) do |template_class| output= _render_text(output, template_class) end output= _render_layouts output _relativize_uris output end def _render_text(text, template_class, sub_content="") log.debug " #{ template_class.to_s }" template= template_class.new(@content.source_path) { text } template.render(@context, content:sub_content) { sub_content } end def _render_layouts(text) _layout_pipeline do |layout_class| text= _render_layout text, layout_class end text end def _render_layout(text, layout) log.debug " layout: #{layout.source_filename}" _render_pipeline(layout.source_filename) do |layout_class| text = _render_text(layout.body, layout_class, text) end text end # NOTE: Currently, the render pipeline ends when Renderer.for # returns nil for an ext. Should it continue on until all the # possible file ext templates are looked up? def _render_pipeline(path) filename_parts= path.split('.') begin ext= filename_parts.pop template_class= Renderer.for(ext) yield template_class unless template_class.nil? end while !template_class.nil? #and filename_parts.size end def _layout_pipeline layout = _layout_for_content unless layout.nil? yield layout # Nested Layouts! sub_layout= _sub_layout_for(layout) while !sub_layout.nil? yield sub_layout sub_layout= _sub_layout_for(sub_layout) end end end def _relativize_uris(text) return text unless _relativize? path_to_root= _path_to_root text.force_encoding("UTF-8") if text.respond_to? :force_encoding text.gsub MUNGABLE_RE do |match| if $5 == '/' "#{ $1 }#{ $2 }=#{ $3 }#{ $4 }/" else "#{ $1 }#{ $2 }=#{ $3 }#{ $4 }#{ path_to_root }" end end end def _relativize? return false if !site.config.relative_paths return false if @context.force_absolute return false if @content.partial? return true if site.config.relative_paths_exts == :all site.config.relative_paths_exts.include?(@content.ext) end def _layout_for_content case when @opts[:inline_render] then nil when (@content.params.has_key?(:layout) and !@content.params.layout) then nil # when (@content.partial? and !@opts[:layout] and !@content.params.layout) then nil else layout= @opts[:layout] || @content.params.layout || @context.get(:layout) site.layouts.first layout end end def _sub_layout_for(layout) sub_layout_name= @context.get :layout return nil if sub_layout_name.nil? sub_layout= site.layouts.first sub_layout_name return nil if sub_layout.nil? return nil if sub_layout.uri == layout.uri sub_layout end def _default_layout if site.config.layout_exts.include? @content.ext site.config.default_layout else nil end end def _path_to_root '../' * @content.level end def _in_context(content, opts) _new_context(content, opts) output= yield _revert_context output end def _new_context(content, opts) @stack.push({ content: @content, context: @context, opts: @opts }.to_hash_object) @context= RenderContext.new content, self, @context safe_opts= opts.reject { |o| SPECIAL_OPTS.include? o.to_s } @context.set safe_opts @content= content @opts= opts if @stack.size == 1 @context.set :layout, _default_layout end end def _revert_context prev= @stack.pop case @opts[:hoist] when :all, true _hoist_data(prev.context) when Array _hoist_data(prev.context, @opts[:hoist]) end @context= prev.context @content= prev.content @opts= prev.opts end def _hoist_data(to_context, keys=nil) keys ||= @context.state.keys safe_keys= keys.reject {|k| SPECIAL_OPTS.include? k.to_s } safe_keys.each do |key| to_context.set key, @context.state[key] end end def _previous @stack.last end class << self # Returns the `Tilt::Template` for the given `ext` or nil def for(ext) Tilt[ext] rescue LoadError # stupid tilt and redcarpet, they don't play well together! nil end end end class RenderContext include Util::SiteAccess include Util::ViewHelpers attr_reader :content, :state def initialize(content, renderer, parent=nil) @content= content @renderer= renderer @parent= parent @state= {} end def render(path=nil, opts={}) content= site.resolve path, opts raise StandardError, "Content or Partial cannot be found at: #{path} (#{opts})" if content.nil? opts[:force_partial]= true opts[:calling_page]= self return @renderer.draw content, opts if opts[:cache] == false if @renderer.cache.has_key? content.source_path @renderer.cache[content.source_path] else output= @renderer.draw content, opts @renderer.cache[content.source_path]= output if opts[:cache] output end end def get(key) _get_from_state key.to_sym end def set(key, value=nil) if key.is_a? Hash key.each do |k,v| @state[k.to_s.to_sym]= v end else @state[key.to_s.to_sym]= value end end def page @content_page ||= begin parent= self while !parent.nil? and !parent.calling_page.nil? do parent= parent.calling_page end parent end end def content_for(key, &block) keyname= "_content_#{key}" if block_given? content= capture &block @state[keyname]= content #block nil else if @state.has_key?(keyname) # @state[keyname].call @state[keyname] else nil end end end def capture(&block) erbout = eval('_erbout', block.binding) rescue nil unless erbout.nil? erbout_length = erbout.length block.call content = erbout[erbout_length..-1] erbout[erbout_length..-1] = '' else content= block.call end content end def content_for?(key) keyname= "_content_#{key}" @state.has_key?(keyname) end def method_missing(sym, *args, &block) if sym.to_s.ends_with? '=' key= sym.to_s.chop set key, args[0] else get(sym) end end private def _get_from_state(key) if @state.has_key? key @state[key] else _get_from_parent key end end def _get_from_parent(key) if @parent.nil? or !@parent.has_key?(key) _get_from_content key else @parent.get key end end def _get_from_content(key) return nil if @content.nil? return @content.send(key.to_sym) if @content.respond_to?(key.to_sym) return @content.params[key] if @content.params.has_key?(key) nil end end end