module Plate require 'tilt' # This class contains everything you'll want to know about a site. It contains all data # about the site, including blog posts, content pages, static files, assets and anything else. class Site include Callbacks attr_accessor :assets, :build_destination, :cache_location, :destination, :layouts, :logger, :options, :pages, :posts, :source def initialize(source, destination, options = {}) # Setup source and destination for the site files self.source = source self.destination = destination # By default, the build goes into the destination folder. # Override this to output to a different folder by default self.build_destination = destination # Sanitize options self.options = Hash === options ? options.clone : {} self.options.symbolize_keys! clear end %w( assets layouts pages posts ).each do |group| class_eval "def #{group}; load!; @#{group}; end" end def all_files @all_files ||= self.assets + self.layouts + self.pages + self.posts.to_a end # All extensions that are registered, as strings. def asset_engine_extensions @asset_engine_extensions ||= self.registered_asset_engines.keys.collect { |e| ".#{e}" } end # Alphabetical list of all blog post categories used. def categories @categories ||= self.posts.collect(&:category).uniq.sort end # Clear out all data related to this site. Prepare for a reload, or first time load. def clear @loaded = false @tags_counts = nil @default_layout = nil self.assets = [] self.layouts = [] self.pages = [] self.posts = PostCollection.new end # The default blog post category def default_category options[:default_category] || 'Posts' end # The default layout for all pages that do not specifically name their own def default_layout return nil if self.layouts.size == 0 return @default_layout if @default_layout layout ||= self.layouts.reject { |l| !l.default? } layout = self.layouts if layout.size == 0 if Array === layout and layout.size > 0 layout = layout[0] end @default_layout = layout end # Find a page, asset or layout by source relative file path def find(search_path) self.all_files.find { |file| file == search_path } end # Find all pages and posts with this layout def find_by_layout(layout_name) result = [] result += self.pages.find_all { |page| page.layout == layout_name } result += self.posts.find_all { |post| post.layout == layout_name } result end # Find a specific layout by its file name. Any extensions are removed. def find_layout(layout_name) search_name = layout_name.to_s.downcase.strip.split('.')[0] matches = self.layouts.reject { |l| l.name != search_name } matches.empty? ? self.default_layout : matches[0] end def inspect "#<#{self.class}:0x#{object_id.to_s(16)} source=#{source.to_s.inspect}>" end # Load all data from the various source directories. def load! return if @loaded log("Loading site from source [#{source}]") run_callback :before_load self.load_pages! self.load_layouts! self.load_posts! run_callback :after_load @loaded = true end # Returns true if the site has been loaded from the source directories. def loaded? !!@loaded end # Write to the log if enable_logging is enabled def log(message, style = :indent) logger.send(:log, message, style) if logger and logger.respond_to?(:log) end def page_engine_extensions @page_engine_extensions ||= self.registered_page_engines.keys.collect { |e| ".#{e}" } end def relative_path(file_or_directory) file_or_directory.to_s.gsub(/^#{Regexp.quote(source)}(.*)$/, '\1') end def reload! clear load! end # Returns the asset engines that are available for use. def registered_asset_engines Plate.asset_engines end # Returns the engines available for use in page and layout formatting. def registered_page_engines Plate.template_engines end # All tags used on this site def tags @tags ||= self.posts.tag_list end def to_url(str) result = str.to_s.strip.downcase result = result.gsub(/[^-a-z0-9~\s\.:;+=_]/, '') result = result.gsub(/[\.:;=+-]+/, '') result = result.gsub(/[\s]/, '-') result end alias_method :sanitize_slug, :to_url # The base URL for this site. The url can be set using the config option named `:base_url`. # # The base URL will not have any trailing slashes. def url return '' unless self.options[:base_url] @url ||= self.options[:base_url].to_s.gsub(/(.*?)\/?$/, '\1') end protected # Load all layouts from layouts/ def load_layouts!(log = true) @layouts = [] Dir.glob(File.join(source, "layouts/**/*")).each do |file| # If this 'file' is a directory, just skip it. We only care about files. unless File.directory?(file) @layouts << Layout.new(self, file) end end log("#{@layouts.size} layouts loaded") if log @layouts end def load_pages!(log = true) @assets = [] @pages = [] # Load all pages, static pages and assets from content/ Dir.glob(File.join(source, "content/**/*")).each do |file| # If this 'file' is a directory, just skip it. We only care about files. unless File.directory?(file) # Check for assets that need to be compiled. Currently only looks to see if the file # ends in .coffee, .scss or .sass. if asset_engine_extensions.include?(File.extname(file)) @assets << Asset.new(self, file) else # Check for YAML meta header. If it starts with ---, then process it as a page intro = File.open(file) { |f| f.read(3) } # If file contents start with ---, then it is something we should process as a page. if intro == "---" @pages << Page.new(self, file) else @pages << StaticPage.new(self, file) end end end end log("#{@assets.size} assets loaded") if log log("#{@pages.size} pages and other files loaded") if log @pages end # Load blog posts from posts/ def load_posts!(log = true) @posts = PostCollection.new Dir.glob(File.join(source, "posts/**/*")).each do |file| # If this 'file' is a directory, just skip it. We only care about files. unless File.directory?(file) # Check for YAML meta header. If it starts with ---, then process it as a page intro = File.open(file) { |f| f.read(3) } # If file contents start with ---, then it is something we should process as a page. if intro == "---" @posts.add(Post.new(self, file)) end end end @posts.sort! log("#{@posts.size} posts loaded") if log @posts end end end