module Plate class Layout attr_accessor :site, :file, :meta, :content def initialize(site, file = nil, load_on_initialize = true) self.site = site self.file = file self.meta = {} self.content = "" load! if load_on_initialize and file? end # The name of the layound, without any path data def basename File.basename(self.file) end # Is this layout the default layout, by name. def default? self.name.downcase.strip.start_with? "default" end # The layout engine to use. Based off of the last file extension for this layout. def engine @engine ||= self.site.registered_page_engines[self.extension.gsub(/\./, '').to_sym] end # The last file extension of this layout. def extension File.extname(self.file) end # Does the file exist or not. def file? return false if self.file.nil? File.exists?(self.file) end # A unique ID for this layout. def id @id ||= Digest::MD5.hexdigest(relative_file) end def inspect "#<#{self.class}:0x#{object_id.to_s(16)} name=#{name.to_s.inspect}>" end def load! return if @loaded raise FileNotFound unless file? read_file! read_metadata! @loaded = true end # The name for a layout is just the lowercase, first part of the file name. def name return "" unless file? @name ||= self.basename.to_s.downcase.strip.split('.')[0] end # A parent layout for this current layout file. If no layout is specified for this # layout's parent, then nil is returned. If there is a parent layout for this layout, # any pages using it will be rendered using this layout first, then sent to the parent # for further rendering. def parent return @parent if @parent if self.meta[:layout] @parent = self.site.find_layout(self.meta[:layout]) else @parent = nil end @parent end def relative_file @relative_file ||= self.site.relative_path(self.file) end def reload! @template = nil @loaded = false @name = nil @engine = nil end # Render the given content against the current layout template. def render(content, page = nil, view = nil) if self.template view ||= View.new(self.site, page) result = self.template.render(view) { content } if self.parent result = self.parent.render(result, page, view) end view = nil result else content.respond_to?(:rendered_content) ? content.rendered_content : content.to_s end end # The render template to use for this layout. A template is only used if the # file extension for the layout is a valid layout extension from the current # site. def template return @template if @template if template? @template = self.engine.new() { self.content } else nil end end # Does this file have the ability to be used as a template? # # This currently only works if the layout is a .erb file. Otherwise anything that # calls this layout just returns the text it is given. def template? self.site.page_engine_extensions.include?(self.extension) end # Is this layout equal to another page being sent? def ==(other) other = other.relative_file if other.respond_to?(:relative_file) self.id == other or self.relative_file == other end protected # Read the file and store it in @content def read_file! self.content = file? ? File.read(self.file) : nil end # Reads all content from a layouts's meta data. At this time, the layout only supports # loading a parent layout. All other meta data is unused. # # Meta data is stored in YAML format within the head of a page after the -- declaration like so: # # --- # layout: default # # # Start of layout content def read_metadata! return unless self.content begin if matches = /^(---\n)(.*?)^\s*?$/m.match(self.content) if matches.size == 3 self.content = matches.post_match.strip self.meta = YAML.load(matches[2]) self.meta.symbolize_keys! end end rescue Exception => e self.meta = {} self.site.log(" ** Problem reading YAML for file #{relative_file} (#{e.message}). Meta data skipped") end end end end