# $Id: builder.rb 5 2007-08-22 04:25:49Z tim_pease $
require 'find'
require 'fileutils'
require 'erb'
module Webby
# The Builder class performs the work of scanning the content folder,
# creating Resource objects, and converting / copying the contents to the
# output folder as needed.
#
class Builder
class << self
# call-seq:
# Builder.run( :rebuild => false )
#
# Create a new instance of the Builder class and invoke the run method.
# If the :rebuild
option is given as +true+, then all pages
# will be recreated / copied.
#
def run( opts = {} )
self.new.run opts
end
# call-seq:
# Builder.create( page, :from => template )
#
# This mehod is used to create a new _page_ in the content folder based
# on the specified template. _page_ is the relative path to the new page
# from the content/
folder. The _template_ is the name of
# the template to use from the templates/
folder.
#
def create( page, opts = {} )
tmpl = opts[:from]
raise Error, "template not given" unless tmpl
raise Error, "#{page} already exists" if test ?e, page
puts "creating #{page}"
FileUtils.mkdir_p File.dirname(page)
str = ERB.new(::File.read(tmpl), nil, '-').result
::File.open(page, 'w') {|fd| fd.write str}
return nil
end
end # class << self
# call-seq:
# run( :rebuild => false )
#
# Runs the Webby builder by loading in the layout files from the
# layouts/
folder and the content from the
# contents/
folder. Content is analyzed, and those that need
# to be copied or compiled (filtered using ERB, Texttile, Markdown, etc.)
# are handled. The results are placed in the output/
folder.
#
# If the :rebuild
flag is set to +true+, then all content is
# copied and/or compiled to the output folder.
#
# A content file can mark itself as dirty by setting the +dirty+ flag to
# +true+ in the meta-data of the file. This will cause the contenet to
# always be compiled when the builder is run. Conversely, setting the
# dirty flag to +false+ will cause the content to never be compiled or
# copied to the output folder.
#
# A content file needs to be built if the age of the file is less then the
# age of the output product -- i.e. the content file has been modified
# more recently than the output file.
#
def run( opts = {} )
Resource.clear
unless test(?d, output_dir)
puts "creating #{output_dir}"
FileUtils.mkdir output_dir
end
load_layouts
load_content
Resource.pages.each do |page|
next unless page.dirty? or opts[:rebuild]
puts "creating #{page.destination}"
# make sure the directory exists
FileUtils.mkdir_p ::File.dirname(page.destination)
# copy the resource to the output directory if it is static
if page.is_static?
FileUtils.cp page.path, page.destination
# otherwise, layout the resource and write the results to
# the output directory
else
::File.open(page.destination, 'w') do |fd|
fd.write Renderer.new(page).layout_page
end
end
end
# touch the output directory so we know when the
# website was last generated
FileUtils.touch output_dir
return nil
end
private
# Scan the layouts/
folder and create a new Resource object
# for each file found there.
#
def load_layouts
excl = Regexp.new exclude.join('|')
::Find.find(layout_dir) do |path|
next unless test ?f, path
next if path =~ excl
Resource.new path
end
layouts = Resource.layouts
# look for loops in the layout references -- i.e. a layout
# eventually refers back to itself
layouts.each do |lyt|
stack = []
while lyt
if stack.include? lyt.filename
stack << lyt.filename
raise Error,
"loop detected in layout references: #{stack.join(' > ')}"
end
stack << lyt.filename
lyt = layouts.find_by_name lyt.layout
end # while
end # each
end
# Scan the content/
folder and create a new Resource object
# for each file found there.
#
def load_content
excl = Regexp.new exclude.join('|')
Find.find(content_dir) do |path|
next unless test ?f, path
next if path =~ excl
Resource.new path, ::Webby.page_defaults
end
end
%w(output_dir layout_dir content_dir exclude).each do |key|
self.class_eval <<-CODE
def #{key}( ) ::Webby.config['#{key}'] end
CODE
end
end # class Builder
end # module Webby
# EOF