diff --git a/History.txt b/History.txt
index 931c5a2..163113d 100644
--- a/History.txt
+++ b/History.txt
@@ -1,9 +1,10 @@
== 0.8.0 / 2008-
-* 1 major enhancement
+* 2 major enhancements
- Refactored the coderay and graphviz filters into helper methods
accessible from ERB
-* 1 minor enhancement
+ - Added support for partials
+* 2 minor enhancements
- Added a template file for creating Atom feeds
- The SITE configuration is available in pages and layouts as
the @config variable
diff --git a/lib/webby.rb b/lib/webby.rb
index 83d1df5..7ae6fec 100644
--- a/lib/webby.rb
+++ b/lib/webby.rb
@@ -96,7 +96,7 @@ def self.exclude
# modification time of the file is important.
#
def self.cairn
- @cairn ||= File.join(site.output_dir, '.cairn')
+ @cairn ||= ::File.join(site.output_dir, '.cairn')
end
# Returns the library path for Webby. If any arguments are given,
@@ -124,9 +124,9 @@ def self.path( *args )
# the _filename_ does not have to be equivalent to the directory.
#
def self.require_all_libs_relative_to( fname, dir = nil )
- dir ||= File.basename(fname, '.*')
- search_me = File.expand_path(
- File.join(File.dirname(fname), dir, '**', '*.rb'))
+ dir ||= ::File.basename(fname, '.*')
+ search_me = ::File.expand_path(
+ ::File.join(::File.dirname(fname), dir, '*.rb'))
Dir.glob(search_me).sort.each {|rb| require rb}
end
@@ -147,7 +147,7 @@ def try_require( lib )
false
end
-Webby.require_all_libs_relative_to __FILE__
+Webby.require_all_libs_relative_to(__FILE__)
end # unless defined?
diff --git a/lib/webby/auto_builder.rb b/lib/webby/auto_builder.rb
index eaa11b9..bead973 100644
--- a/lib/webby/auto_builder.rb
+++ b/lib/webby/auto_builder.rb
@@ -56,7 +56,7 @@ def update( *events )
@log.debug "changed #{evt.path}"
next unless test ?f, evt.path
next if evt.path =~ ::Webby.exclude
- Resource.new evt.path
+ Resources.new evt.path
end
@builder.run :load_files => false
diff --git a/lib/webby/builder.rb b/lib/webby/builder.rb
index f7b73ec..25e1e14 100644
--- a/lib/webby/builder.rb
+++ b/lib/webby/builder.rb
@@ -90,7 +90,7 @@ def run( opts = {} )
load_files if opts[:load_files]
loop_check
- Resource.pages.each do |page|
+ Resources.pages.each do |page|
next unless page.dirty? or opts[:rebuild]
@log.info "creating #{page.destination}"
@@ -99,7 +99,7 @@ def run( opts = {} )
FileUtils.mkdir_p ::File.dirname(page.destination)
# copy the resource to the output directory if it is static
- if page.is_static?
+ if page.instance_of? Resources::Static
FileUtils.cp page.path, page.destination
FileUtils.chmod 0644, page.destination
@@ -124,7 +124,7 @@ def load_files
::Find.find(layout_dir, content_dir) do |path|
next unless test ?f, path
next if path =~ ::Webby.exclude
- Resource.new path
+ Resources.new path
end
end
@@ -133,7 +133,7 @@ def load_files
# error if one is detected.
#
def loop_check
- layouts = Resource.layouts
+ layouts = Resources.layouts
layouts.each do |lyt|
stack = []
diff --git a/lib/webby/filters.rb b/lib/webby/filters.rb
index 90f0878..a16b869 100644
--- a/lib/webby/filters.rb
+++ b/lib/webby/filters.rb
@@ -90,4 +90,6 @@ def handle(filter, handler, *args)
end # module Filters
end # module Webby
+Webby.require_all_libs_relative_to(__FILE__)
+
# EOF
diff --git a/lib/webby/helpers.rb b/lib/webby/helpers.rb
index d56e822..103d0c8 100644
--- a/lib/webby/helpers.rb
+++ b/lib/webby/helpers.rb
@@ -27,4 +27,6 @@ def self.register( helper )
end # module Helper
end # module Webby
+Webby.require_all_libs_relative_to(__FILE__)
+
# EOF
diff --git a/lib/webby/helpers/url_helper.rb b/lib/webby/helpers/url_helper.rb
index e51d905..87a6768 100644
--- a/lib/webby/helpers/url_helper.rb
+++ b/lib/webby/helpers/url_helper.rb
@@ -39,7 +39,7 @@ def url_for( *args )
anchor = opts.delete(:anchor)
escape = opts.has_key?(:escape) ? opts.delte(:escape) : true
- url = Webby::Resource === obj ? obj.url : obj.to_s
+ url = Webby::Resources::Resource === obj ? obj.url : obj.to_s
url = escape_once(url) if escape
url << "#" << anchor if anchor
@@ -54,8 +54,8 @@ def url_for( *args )
# method for final URL creation; see the url_for method for
# documentation on those options.
#
- # The PagesDB#find method is used to locate the page; see the find method
- # for the available options.
+ # The Resources::DB#find method is used to locate the page; see the find
+ # method for the available options.
#
# ==== Examples
#
@@ -96,7 +96,7 @@ def link_to( name, *args )
attrs = opts.delete(:attrs)
url = case url
- when String, Webby::Resource
+ when String, Webby::Resources::Resource
self.url_for(url, opts)
when :back
'javascript:history.back()'
@@ -210,7 +210,7 @@ def _find_page( args )
link_opts = opts.delete(:url) || {}
link_opts[:attrs] = opts.delete(:attrs)
- if Webby::Resource === name
+ if Webby::Resources::Resource === name
p, name = name, nil
elsif opts.empty? && name
p = @pages.find(Webby.site.find_by.to_sym => name)
diff --git a/lib/webby/renderer.rb b/lib/webby/renderer.rb
index 912d749..efe32c7 100644
--- a/lib/webby/renderer.rb
+++ b/lib/webby/renderer.rb
@@ -5,10 +5,6 @@
unless defined? ::Webby::Renderer
require 'erb'
-try_require 'bluecloth'
-try_require 'redcloth'
-try_require 'haml'
-try_require 'sass'
module Webby
@@ -27,7 +23,10 @@ module Webby
class Renderer
include ERB::Util
- class Error < StandardError; end # :nodoc:
+ # :stopdoc:
+ class Error < StandardError; end
+ @@stack = []
+ # :startdoc:
# call-seq:
# Renderer.write( page )
@@ -43,7 +42,7 @@ def self.write( page )
::File.open(page.destination, 'w') do |fd|
fd.write renderer.layout_page
end
- break unless renderer.__send__(:next_page)
+ break unless renderer.__send__(:_next_page)
}
end
@@ -55,13 +54,13 @@ def self.write( page )
# render the filtered page into the desired layout.
#
def initialize( page )
- unless page.is_page?
+ unless page.instance_of? Resources::Page
raise ArgumentError,
"only page resources can be rendered '#{page.path}'"
end
@page = page
- @pages = Resource.pages
+ @pages = Resources.pages
@content = nil
@config = ::Webby.site
@@ -77,19 +76,25 @@ def initialize( page )
# page's meta-data.
#
def layout_page
- layouts = Resource.layouts
+ layouts = Resources.layouts
obj = @page
str = @page.render(self)
+ @@stack << @page.path
loop do
lyt = layouts.find :filename => obj.layout
break if lyt.nil?
- @content, str = str, ::Webby::File.read(lyt.path)
- str = Filters.process(self, lyt, str)
+ @content, str = str, ::Webby::Resources::File.read(lyt.path)
+ str = _track_rendering(lyt.path) {
+ Filters.process(self, lyt, str)
+ }
@content, obj = nil, lyt
end
+ @@stack.pop if @page.path == @@stack.last
+ raise Error, "rendering stack corrupted" unless @@stack.empty?
+
str
rescue => err
@log.error "while rendering page '#{@page.path}'"
@@ -103,7 +108,28 @@ def layout_page
# determined from the page's meta-data.
#
def render_page
- Filters.process(self, @page, ::Webby::File.read(@page.path))
+ _track_rendering(@page.path) {
+ Filters.process(self, @page, ::Webby::Resources::File.read(@page.path))
+ }
+ end
+
+ # call-seq:
+ # partial( name ) => string
+ #
+ # Finds the partial identified by _name_ and returns the contents after
+ # rendering. Rendering if performed by filtering the contents of the
+ # partial using the filters identified in the meta-data of the partial.
+ #
+ def partial( name, opts = {} )
+ fn = '_' + name
+ part = Resources.partials.find(
+ :filename => fn, :in_directory => @page.dir ) rescue nil
+ part ||= Resources.partials.find(:filename => fn)
+ raise Error, "could not find partial '#{name}'" if part.nil?
+
+ _track_rendering(part.path) {
+ Filters.process(self, part, ::Webby::Resources::File.read(part.path))
+ }
end
# call-seq:
@@ -141,13 +167,13 @@ def get_binding
private
# call-seq:
- # next_page => true or false
+ # _next_page => true or false
#
# Returns +true+ if there is a next page to render. Returns +false+ if
# there is no next page or if pagination has not been configured for the
# current page.
#
- def next_page
+ def _next_page
return false unless defined? @pager and @pager
# go to the next page; break out if there is no next page
@@ -161,9 +187,39 @@ def next_page
true
end
+ # call-seq:
+ # _track_rendering( path ) {block}
+ #
+ # Keep track of the page rendering for the given _path_. The _block_ is
+ # where the the page will be rendered.
+ #
+ # This method keeps a stack of the current pages being rendeered. It looks
+ # for duplicates in the stack -- an indication of a rendering loop. When a
+ # rendering loop is detected, an error is raised.
+ #
+ # This method returns whatever is returned from the _block_.
+ #
+ def _track_rendering( path )
+ loop_error = @@stack.include? path
+ @@stack << path
+
+ if loop_error
+ msg = "rendering loop detected for '#{path}'\n"
+ msg << " current rendering stack\n\t"
+ msg << @@stack.join("\n\t")
+ raise Error, msg
+ end
+
+ yield
+ ensure
+ @@stack.pop if path == @@stack.last
+ end
+
end # class Renderer
end # module Webby
+Webby.require_all_libs_relative_to(__FILE__, 'stelan')
+
end # unless defined?
# EOF
diff --git a/lib/webby/resource.rb b/lib/webby/resource.rb
deleted file mode 100644
index 8030125..0000000
--- a/lib/webby/resource.rb
+++ /dev/null
@@ -1,293 +0,0 @@
-# $Id$
-
-module Webby
-
-# A Webby::Resource is any file that can be found in the content directory
-# or in the layout directory. This class contains information about the
-# resources available to Webby. This information includes the resource
-# type (static, page, layout), if the resource is dirty (it needs to be
-# rendered), the output location of the rendered resource, etc.
-#
-# A resource is a "layout" if the resource is found in the layout
-# directory. Static and page resources are found in the content directory.
-#
-# A resource is considered static only if it *does not* contain a YAML
-# meta-data header at the top of the file. These resources will be copied
-# as-is from the content directory to the output directory.
-#
-# If a resouce does have meta-data, then it will be processed (i.e.
-# rendered/filtered) by Webby, and the rendered results will be written to
-# the output directory.
-#
-class Resource
-
- instance_methods.each do |m|
- undef_method(m) unless m =~ %r/\A__/ ||
- m == 'class'
- end
-
- class << self
- # Returns the pages hash object.
- def pages
- @pages ||= PagesDB.new
- end
-
- # Returns the layouts hash object.
- def layouts
- @layouts ||= PagesDB.new
- end
-
- # Clear the contents of the +layouts+ and the +pages+ hash objects.
- def clear
- self.pages.clear
- self.layouts.clear
- end
- end # class << self
-
- # The full path to the resource file
- attr_reader :path
-
- # The directory of the resource excluding the content directory
- attr_reader :dir
-
- # The resource filename excluding path and extension
- attr_reader :filename
-
- # Extesion of the resource file
- attr_reader :ext
-
- # Resource file modification time
- attr_reader :mtime
-
- # Resource page number (if needed)
- attr_reader :number
-
- # call-seq:
- # Resource.new( filename ) => resource
- #
- # Creates a new resource object given the _filename_.
- #
- def initialize( fn )
- @path = fn.sub(%r/\A(?:\.\/|\/)/o, '').freeze
- @dir = Webby::File.dirname(@path)
- @filename = Webby::File.basename(@path)
- @ext = Webby::File.extname(@path)
- @mtime = ::File.mtime @path
-
- @number = nil
- @rendering = false
-
- # deal with the meta-data
- @mdata = ::Webby::File.meta_data(@path)
- @have_mdata = !@mdata.nil?
-
- @mdata ||= {}
- @mdata = ::Webby.site.page_defaults.merge(@mdata) if is_page?
- @mdata.sanitize!
-
- self.class.pages << self if is_page? or is_static?
- self.class.layouts << self if is_layout?
- end
-
- # call-seq:
- # equal?( other ) => true or false
- #
- # Returns +true+ if the path of this resource is equivalent to the path of
- # the _other_ resource. Returns +false+ if this is not the case.
- #
- def equal?( other )
- return false unless self.class == other.class
- @path == other.path
- end
- alias :== :equal?
- alias :eql? :equal?
-
- # call-seq:
- # resource <=> other => -1, 0, +1, or nil
- #
- # Resource comparison operates on the full path of the resource objects
- # and uses the standard String comparison operator. Returns +nil+ if
- # _other_ is not a Resource instance.
- #
- def <=>( other )
- return unless self.class == other.class
- @path <=> other.path
- end
-
- # call-seq:
- # extension => string
- #
- # Returns the extension that will be appended to the output destination
- # filename. The extension is determined by looking at the following:
- #
- # * this resource's meta-data for an 'extension' property
- # * the meta-data of this resource's layout for an 'extension' property
- # * the extension of this resource file
- #
- def extension
- return @mdata['extension'] if @mdata.has_key? 'extension'
-
- if @mdata.has_key? 'layout'
- lyt = self.class.layouts.find :filename => @mdata['layout']
- ext = lyt ? lyt.extension : nil
- return ext if ext
- end
-
- return (is_layout? ? nil : @ext)
- end
-
- # call-seq:
- # destination => string
- #
- # Returns the path in the output directory where the results of rendering
- # this resource should be stored. This path is used to determine if the
- # resource is dirty and in need of rendering.
- #
- # The destination for any resource can be overridden by explicitly setting
- # the 'destination' property in the resource's meta-data.
- #
- def destination
- return @dest if defined? @dest and @dest
- return @dest = ::Webby.cairn if is_layout?
-
- @dest = if @mdata.has_key? 'destination' then @mdata['destination']
- else ::File.join(dir, filename) end
-
- @dest = ::File.join(::Webby.site.output_dir, @dest)
- @dest << @number.to_s if @number
-
- ext = extension
- unless ext.nil? or ext.empty?
- @dest << '.'
- @dest << ext
- end
- @dest
- end
-
- # call-seq
- # url => string or nil
- #
- # Returns a string suitable for use as a URL linking to this page. Nil
- # is returned for layouts.
- #
- def url
- return nil if is_layout?
- return @url if defined? @url and @url
-
- @url = destination.sub(::Webby.site.output_dir, '')
- @url = File.dirname(@url) if filename == 'index'
- @url
- end
-
- # call-seq:
- # resource.number = Integer
- #
- # Sets the page number for the current resource to the given integer. This
- # number is used to modify the output destination for resources that
- # require pagination.
- #
- def number=( num )
- @number = num
- @dest = nil
- end
-
- # call-seq:
- # render => string
- #
- # Creates a new Webby::Renderer instance and uses that instance to render
- # the resource contents using the configured filter(s). The filter(s) to
- # use is defined in the resource's meta-data as the 'filter' key.
- #
- # Note, this only renders this resource. The returned string does not
- # include any layout rendering.
- #
- def render( renderer = nil )
- raise Error, "page '#@path' is in a rendering loop" if @rendering
-
- @rendering = true
- renderer ||= Renderer.new(self)
- content = renderer.render_page
- @rendering = false
-
- return content
-
- rescue
- @rendering = false
- raise
- end
-
- # call-seq:
- # is_layout? => true or false
- #
- # Returns +true+ if this resource is a layout.
- #
- def is_layout?
- @is_layout ||=
- !(%r/\A(?:\.\/|\/)?#{::Webby.site.layout_dir}\//o =~ @path).nil?
- end
-
- # call-seq:
- # is_static? => true or false
- #
- # Returns +true+ if this resource is a static file.
- #
- def is_static?
- !@have_mdata
- end
-
- # call-seq:
- # is_page? => true or false
- #
- # Returns +true+ if this resource is a page suitable for rendering.
- #
- def is_page?
- @have_mdata and !is_layout?
- end
-
- # call-seq:
- # dirty? => true or false
- #
- # Returns +true+ if this resource is newer than its corresponding output
- # product. The resource needs to be rendered (if a page or layout) or
- # copied (if a static file) to the output directory.
- #
- def dirty?
- return @mdata['dirty'] if @mdata.has_key? 'dirty'
-
- # if the destination file does not exist, then we are dirty
- return true unless test ?e, destination
-
- # if this file's mtime is larger than the destination file's
- # mtime, then we are dirty
- dirty = @mtime > ::File.mtime(destination)
- return dirty if is_static? or dirty
-
- # check to see if the layout is dirty, and it it is then we
- # are dirty, too
- if @mdata.has_key? 'layout'
- lyt = self.class.layouts.find :filename => @mdata['layout']
- unless lyt.nil?
- return true if lyt.dirty?
- end
- end
-
- # if we got here, then we are not dirty
- false
- end
-
- # call-seq:
- # method_missing( symbol [, *args, &block] ) => result
- #
- # Invoked by Ruby when a message is sent to the resource that it cannot
- # handle. The default behavior is to convert _symbol_ to a string and
- # search for that string in the resource's meta-data. If found, the
- # meta-data item is returned; otherwise, +nil+ is returned.
- #
- def method_missing( name, *a, &b )
- @mdata[name.to_s]
- end
-
-end # class Resource
-end # module Webby
-
-# EOF
diff --git a/lib/webby/resources.rb b/lib/webby/resources.rb
new file mode 100644
index 0000000..a624477
--- /dev/null
+++ b/lib/webby/resources.rb
@@ -0,0 +1,82 @@
+# $Id$
+
+module Webby::Resources
+
+ class << self
+ # Returns the pages hash object.
+ #
+ def pages
+ @pages ||= ::Webby::Resources::DB.new
+ end
+
+ # Returns the layouts hash object.
+ #
+ def layouts
+ @layouts ||= ::Webby::Resources::DB.new
+ end
+
+ # Returns the partials hash object.
+ #
+ def partials
+ @partials ||= ::Webby::Resources::DB.new
+ end
+
+ # Clear the contents of the +layouts+, +pages+ and +partials+ hash
+ # objects.
+ #
+ def clear
+ self.pages.clear
+ self.layouts.clear
+ self.partials.clear
+ end
+
+ # call-seq:
+ # Resources.new( filename )
+ #
+ #
+ def new( fn )
+ # normalize the path
+ fn = self.path(fn)
+
+ # see if we are dealing with a layout
+ if %r/\A#{::Webby.site.layout_dir}\//o =~ fn
+ r = ::Webby::Resources::Layout.new(fn)
+ self.layouts << r
+ return r
+ end
+
+ # see if we are dealing with a partial
+ filename = ::Webby::Resources::File.basename(fn)
+ if %r/\A_/o =~ filename
+ r = ::Webby::Resources::Partial.new(fn)
+ self.partials << r
+ return r
+ end
+
+ # see if we are dealing with a static resource
+ meta = ::Webby::Resources::File.meta_data(fn)
+ if meta.nil?
+ r = ::Webby::Resources::Static.new(fn)
+ self.pages << r
+ return r
+ end
+
+ # this is a renderable page
+ r = ::Webby::Resources::Page.new(fn)
+ self.pages << r
+ return r
+ end
+
+ # Returns a normalized path for the given filename.
+ #
+ def path( filename )
+ filename.sub(%r/\A(?:\.\/|\/)/o, '').freeze
+ end
+
+ end # class << self
+
+end # module Webby::Resources
+
+Webby.require_all_libs_relative_to(__FILE__)
+
+# EOF
diff --git a/lib/webby/pages_db.rb b/lib/webby/resources/db.rb
similarity index 96%
rename from lib/webby/pages_db.rb
rename to lib/webby/resources/db.rb
index 5af1dbc..ebd0fc2 100644
--- a/lib/webby/pages_db.rb
+++ b/lib/webby/resources/db.rb
@@ -1,18 +1,18 @@
# $Id$
-module Webby
+module Webby::Resources
# A rudimentary "database" for holding resource objects and finding them.
# The database is held in a Ruby hash keyed by the directories in the
# content folder.
#
-class PagesDB
+class DB
# call-seq:
- # PagesDB.new
+ # DB.new
#
- # Create a new pages database object. This is used to store resources and
- # to find them by their attributes.
+ # Create a new resources database object. This is used to store resources
+ # and to find them by their attributes.
#
def initialize
@db = Hash.new {|h,k| h[k] = []}
@@ -211,7 +211,7 @@ def children( page, opts = {} )
ary
end
-end # class PagesDB
+end # class DB
end # module Webby
# EOF
diff --git a/lib/webby/file.rb b/lib/webby/resources/file.rb
similarity index 80%
rename from lib/webby/file.rb
rename to lib/webby/resources/file.rb
index 112dcdf..55b6cf0 100644
--- a/lib/webby/file.rb
+++ b/lib/webby/resources/file.rb
@@ -2,12 +2,12 @@
require 'yaml'
-module Webby
+module Webby::Resources
-# The Webby::File class is identical to the core Ruby file class except for
-# YAML meta-data stored at the top of the file. This meta-data is made
-# available through the meta_data
and meta_data=
-# functions.
+# The Webby::Resources::File class is identical to the core Ruby file class
+# except for YAML meta-data stored at the top of the file. This meta-data
+# is made available through the meta_data
and
+# meta_data=
functions.
#
# The meta-data data must be found between two YAML block separators "---",
# each on their own line.
@@ -30,7 +30,7 @@ class File < ::File
class << self
# call-seq:
- # Webby::File.read( name [, length [, offset]]) => string
+ # File.read( name [, length [, offset]]) => string
#
# Opens the file, optionally seeks to the given _offset_, then returns
# _length_ bytes (defaulting to the rest of the file). +read+ ensures
@@ -44,7 +44,7 @@ def read( name, *args )
end
# call-seq:
- # Webby::File.readlines( name, sep_string = $/ ) => array
+ # File.readlines( name, sep_string = $/ ) => array
#
# Reads the entire file specified by _name_ as individual lines, and
# returns those lines in an array. Lines are separated by _sep_string_.
@@ -58,7 +58,7 @@ def readlines( name, sep = $/ )
end
# call-seq:
- # Webby::File.meta_data( name ) => object or nil
+ # File.meta_data( name ) => object or nil
#
# Reads the meta-data from the file specified by _name_. +meta_data+
# ensures the files is closed before returning.
@@ -71,7 +71,7 @@ def meta_data( name )
end
# call-seq:
- # Webby::File.dirname( filename ) => dir_name
+ # File.dirname( filename ) => dir_name
#
# Returns all components of the _filename_ except the last one. The
# filename must be formed using forward slashes ("/") regardless of the
@@ -82,7 +82,7 @@ def dirname( fn )
end
# call-seq:
- # Webby::File.basename( filename ) => base_name
+ # File.basename( filename ) => base_name
#
# Returns the last component of the _filename_, which must be formed
# using forward slashes ("/"regardless of the separator used on the
@@ -93,7 +93,7 @@ def basename( fn )
end
# call-seq:
- # Webby::File.extname( filename ) => ext_name
+ # File.extname( filename ) => ext_name
#
# Returns the extension (the portion of file name in path after the
# period). This method excludes the period from the extension name.
@@ -104,16 +104,16 @@ def extname( fn )
end
# call-seq:
- # Webby::File.new( filename, mode = "r" ) => file
- # Webby::File.new( filename [, mode [, perm]] ) => file
+ # File.new( filename, mode = "r" ) => file
+ # File.new( filename [, mode [, perm]] ) => file
#
# Opens the file named by _filename_ according to _mode_ (default is 'r')
- # and returns a new +Webby::File+ object. See the description of class
- # +IO+ for a description of _mode_. The file _mode_ may optionally be
- # specified as a +Fixnum+ by or-ing together the flags (+O_RDONLY+ etc,
- # again described under +IO+). Optional permission bits may be given in
- # _perm_. These _mode_ and permission bits are platform dependent; on Unix
- # systems, see +open(2)+ for details.
+ # and returns a new +Webby::Resources::File+ object. See the description
+ # of class +IO+ for a description of _mode_. The file _mode_ may
+ # optionally be specified as a +Fixnum+ by or-ing together the flags
+ # (+O_RDONLY+ etc, again described under +IO+). Optional permission bits
+ # may be given in _perm_. These _mode_ and permission bits are platform
+ # dependent; on Unix systems, see +open(2)+ for details.
#
# f = File.new("testfile", "r")
# f = File.new("newfile", "w+")
@@ -218,6 +218,6 @@ def end_of_meta_data
end
end # class File
-end # module Webby
+end # module Webby::Resources
# EOF
diff --git a/lib/webby/resources/layout.rb b/lib/webby/resources/layout.rb
new file mode 100644
index 0000000..b142a28
--- /dev/null
+++ b/lib/webby/resources/layout.rb
@@ -0,0 +1,65 @@
+# $Id$
+
+require Webby.libpath(*%w[webby resources resource])
+
+module Webby::Resources
+
+# A Layout is any file that is found in the layout folder of the webiste
+# directory. Layouts container the common elements of all the pages in a
+# website, and pages from the content folder are rendered into the layout.
+#
+class Layout < Resource
+
+ # call-seq:
+ # Layout.new( path )
+ #
+ # Creates a new Layout object given the full path to the layout file.
+ #
+ def initialize( fn )
+ super
+
+ @mdata = ::Webby::Resources::File.meta_data(@path)
+ @mdata ||= {}
+ @mdata.sanitize!
+ end
+
+ # call-seq:
+ # destination => string
+ #
+ # The output file destination for the layout. This is the ".cairn" file in
+ # the output folder. It is used to determine if the layout is newer than
+ # the build products.
+ #
+ def destination
+ ::Webby.cairn
+ end
+
+ # call-seq:
+ # extension => string or nil
+ #
+ # Returns the extension to be applied to output files rendered by the
+ # layotut. This will either be a string or +nil+ if the layout does not
+ # specify an extension to use.
+ #
+ def extension
+ return @mdata['extension'] if @mdata.has_key? 'extension'
+
+ if @mdata.has_key? 'layout'
+ lyt = ::Webby::Resources.layouts.find :filename => @mdata['layout']
+ ext = lyt ? lyt.extension : nil
+ end
+ end
+
+ # call-seq:
+ # url => nil
+ #
+ # Layouts do not have a URL. This method will alwasy return +nil+.
+ #
+ def url
+ nil
+ end
+
+end # class Layout
+end # module Webby::Resources
+
+# EOF
diff --git a/lib/webby/resources/page.rb b/lib/webby/resources/page.rb
new file mode 100644
index 0000000..8d9d172
--- /dev/null
+++ b/lib/webby/resources/page.rb
@@ -0,0 +1,109 @@
+# $Id$
+
+require Webby.libpath(*%w[webby resources resource])
+
+module Webby::Resources
+
+# A Page is a file in the content folder that contains YAML meta-data at
+# the top of the file. Pages are processed by the Webby rendering engine
+# and then inserted into the desired layout. The string resulting from
+# processing and layout is then written to the output directory.
+#
+class Page < Resource
+
+ # Resource page number (if needed)
+ attr_reader :number
+
+ # call-seq:
+ # Resource.new( path )
+ #
+ # Creates a new page object from the full path to the page file.
+ #
+ def initialize( fn )
+ super
+ @number = nil
+
+ @mdata = ::Webby::Resources::File.meta_data(@path)
+ @mdata ||= {}
+ @mdata = ::Webby.site.page_defaults.merge(@mdata)
+ @mdata.sanitize!
+ end
+
+ # call-seq:
+ # render => string
+ #
+ # Creates a new Webby::Renderer instance and uses that instance to render
+ # the page contents using the configured filter(s). The filter(s) to
+ # use is defined in the page's meta-data as the 'filter' key.
+ #
+ # Note, this only renders this page. The returned string does not include
+ # any layout rendering.
+ #
+ def render( renderer = nil )
+ renderer ||= ::Webby::Renderer.new(self)
+ renderer.render_page
+ end
+
+ # call-seq:
+ # page.number = Integer
+ #
+ # Sets the page number for the current resource to the given integer. This
+ # number is used to modify the output destination for resources that
+ # require pagination.
+ #
+ def number=( num )
+ @number = num
+ @dest = nil
+ end
+
+ # call-seq:
+ # destination => string
+ #
+ # Returns the path in the output directory where the rendered page should
+ # be stored. This path is used to determine if the page is dirty and in
+ # need of rendering.
+ #
+ # The destination for a page can be overridden by explicitly setting
+ # the 'destination' property in the page's meta-data.
+ #
+ def destination
+ return @dest if defined? @dest and @dest
+
+ @dest = if @mdata.has_key? 'destination' then @mdata['destination']
+ else ::File.join(dir, filename) end
+
+ @dest = ::File.join(::Webby.site.output_dir, @dest)
+ @dest << @number.to_s if @number
+
+ ext = extension
+ unless ext.nil? or ext.empty?
+ @dest << '.' << ext
+ end
+ @dest
+ end
+
+ # call-seq:
+ # extension => string
+ #
+ # Returns the extension that will be appended to the output destination
+ # filename. The extension is determined by looking at the following:
+ #
+ # * this page's meta-data for an 'extension' property
+ # * the meta-data of this page's layout for an 'extension' property
+ # * the extension of this page file
+ #
+ def extension
+ return @mdata['extension'] if @mdata.has_key? 'extension'
+
+ if @mdata.has_key? 'layout'
+ lyt = ::Webby::Resources.layouts.find :filename => @mdata['layout']
+ ext = lyt ? lyt.extension : nil
+ return ext if ext
+ end
+ @ext
+ end
+
+end # class Page
+end # module Webby::Resources
+
+# EOF
diff --git a/lib/webby/resources/partial.rb b/lib/webby/resources/partial.rb
new file mode 100644
index 0000000..a68bb5b
--- /dev/null
+++ b/lib/webby/resources/partial.rb
@@ -0,0 +1,81 @@
+# $Id$
+
+require Webby.libpath(*%w[webby resources resource])
+
+module Webby::Resources
+
+# A Partial is a file in the content folder whose filename starts with an
+# underscore "_" character. Partials contain text that can be included into
+# other pages. Partials are not standalone pages, and they will never
+# correspond directly to an output file.
+#
+# Partials can contain YAML meta-data at the top of the file. This
+# information is only used to determin the filters to apply to the
+# partial. If there is no meta-data, then the partial text is used "as is"
+# without any processing by the Webby rendering engine.
+#
+class Partial < Resource
+
+ # call-seq:
+ # Partial.new( path )
+ #
+ # Creates a new Partial object given the full path to the partial file.
+ # Partial filenames start with an underscore (this is an enforced
+ # convention).
+ #
+ def initialize( fn )
+ super
+
+ @mdata = ::Webby::Resources::File.meta_data(@path)
+ @mdata ||= {}
+ @mdata.sanitize!
+ end
+
+ # call-seq:
+ # dirty? => true or false
+ #
+ # Returns +true+ if this resource is newer than its corresponding output
+ # product. The resource needs to be rendered (if a page or layout) or
+ # copied (if a static file) to the output directory.
+ #
+ def dirty?
+ return @mdata['dirty'] if @mdata.has_key? 'dirty'
+
+ # if the destination file does not exist, then we are dirty
+ return true unless test(?e, destination)
+
+ # if this file's mtime is larger than the destination file's
+ # mtime, then we are dirty
+ dirty = @mtime > ::File.mtime(destination)
+ return dirty if dirty
+
+ # if we got here, then we are not dirty
+ false
+ end
+
+ # call-seq:
+ # destination => string
+ #
+ # The output file destination for the partial. This is the ".cairn" file in
+ # the output folder. It is used to determine if the partial is newer than
+ # the build products.
+ #
+ def destination
+ ::Webby.cairn
+ end
+
+ alias :extension :ext
+
+ # call-seq:
+ # url => nil
+ #
+ # Partials do not have a URL. This method will alwasy return +nil+.
+ #
+ def url
+ nil
+ end
+
+end # class Partial
+end # module Webby::Resources
+
+# EOF
diff --git a/lib/webby/resources/resource.rb b/lib/webby/resources/resource.rb
new file mode 100644
index 0000000..90d55d5
--- /dev/null
+++ b/lib/webby/resources/resource.rb
@@ -0,0 +1,145 @@
+# $Id$
+
+unless defined? Webby::Resources::Resource
+
+module Webby::Resources
+
+# A Webby::Resource is any file that can be found in the content directory
+# or in the layout directory. This class contains information about the
+# resources available to Webby.
+#
+class Resource
+
+ instance_methods.each do |m|
+ undef_method(m) unless m =~ %r/\A__|\?$/ ||
+ m == 'class'
+ end
+
+ # The full path to the resource file
+ attr_reader :path
+
+ # The directory of the resource excluding the content directory
+ attr_reader :dir
+
+ # The resource filename excluding path and extension
+ attr_reader :filename
+
+ # Extesion of the resource file
+ attr_reader :ext
+
+ # Resource file modification time
+ attr_reader :mtime
+
+ # call-seq:
+ # Resource.new( filename ) => resource
+ #
+ # Creates a new resource object given the _filename_.
+ #
+ def initialize( fn )
+ @path = fn
+ @dir = ::Webby::Resources::File.dirname(@path)
+ @filename = ::Webby::Resources::File.basename(@path)
+ @ext = ::Webby::Resources::File.extname(@path)
+ @mtime = ::File.mtime @path
+
+ @mdata = @@mdata ||= {}
+ end
+
+ # call-seq:
+ # equal?( other ) => true or false
+ #
+ # Returns +true+ if the path of this resource is equivalent to the path of
+ # the _other_ resource. Returns +false+ if this is not the case.
+ #
+ def equal?( other )
+ return false unless other.kind_of? ::Webby::Resources::Resource
+ @path == other.path
+ end
+ alias :== :equal?
+ alias :eql? :equal?
+
+ # call-seq:
+ # resource <=> other => -1, 0, +1, or nil
+ #
+ # Resource comparison operates on the full path of the resource objects
+ # and uses the standard String comparison operator. Returns +nil+ if
+ # _other_ is not a Resource instance.
+ #
+ def <=>( other )
+ return unless other.kind_of? ::Webby::Resources::Resource
+ @path <=> other.path
+ end
+
+ # call-seq:
+ # method_missing( symbol [, *args, &block] ) => result
+ #
+ # Invoked by Ruby when a message is sent to the resource that it cannot
+ # handle. The default behavior is to convert _symbol_ to a string and
+ # search for that string in the resource's meta-data. If found, the
+ # meta-data item is returned; otherwise, +nil+ is returned.
+ #
+ def method_missing( name, *a, &b )
+ @mdata[name.to_s]
+ end
+
+ # call-seq:
+ # dirty? => true or false
+ #
+ # Returns +true+ if this resource is newer than its corresponding output
+ # product. The resource needs to be rendered (if a page or layout) or
+ # copied (if a static file) to the output directory.
+ #
+ def dirty?
+ return @mdata['dirty'] if @mdata.has_key? 'dirty'
+
+ # if the destination file does not exist, then we are dirty
+ return true unless test(?e, destination)
+
+ # if this file's mtime is larger than the destination file's
+ # mtime, then we are dirty
+ dirty = @mtime > ::File.mtime(destination)
+ return dirty if dirty
+
+ # check to see if the layout is dirty, and if it is then we
+ # are dirty, too
+ if @mdata.has_key? 'layout'
+ lyt = ::Webby::Resources.layouts.find :filename => @mdata['layout']
+ unless lyt.nil?
+ return true if lyt.dirty?
+ end
+ end
+
+ # if we got here, then we are not dirty
+ false
+ end
+
+ # call-seq
+ # url => string or nil
+ #
+ # Returns a string suitable for use as a URL linking to this page. Nil
+ # is returned for layouts.
+ #
+ def url
+ return @url if defined? @url and @url
+
+ @url = destination.sub(::Webby.site.output_dir, '')
+ @url = File.dirname(@url) if filename == 'index'
+ @url
+ end
+
+ # :stopdoc:
+ def destination
+ raise NotImplementedError
+ end
+
+ def extension
+ raise NotImplementedError
+ end
+ # :startdoc:
+
+end # class Resource
+end # module Webby::Resources
+
+end # unless defined?
+
+# EOF
diff --git a/lib/webby/resources/static.rb b/lib/webby/resources/static.rb
new file mode 100644
index 0000000..272d517
--- /dev/null
+++ b/lib/webby/resources/static.rb
@@ -0,0 +1,54 @@
+# $Id$
+
+require Webby.libpath(*%w[webby resources resource])
+
+module Webby::Resources
+
+# A Static resource is any file in the content folder that does not
+# contain YAML meta-data at the top of the file and does not start with
+# and underscore "_" character (those are partials). Static resources will
+# be copied as-is from the content directory to the output directory.
+#
+class Static < Resource
+
+ # call-seq:
+ # render => string
+ #
+ # Returns the contents of the file.
+ #
+ def render
+ ::File.read(path)
+ end
+
+ # call-seq:
+ # dirty? => true or false
+ #
+ # Returns +true+ if this static file is newer than its corresponding output
+ # product. The static file needs to be copied to the output directory.
+ #
+ def dirty?
+ return true unless test(?e, destination)
+ @mtime > ::File.mtime(destination)
+ end
+
+ # call-seq:
+ # destination => string
+ #
+ # Returns the path in the output directory where the static file should
+ # be copied. This path is used to determine if the static file is dirty
+ # and in need of copying to the output file.
+ #
+ def destination
+ return @dest if defined? @dest and @dest
+
+ @dest = ::File.join(::Webby.site.output_dir, dir, filename)
+ @dest << '.' << @ext if @ext and !@ext.empty?
+ @dest
+ end
+
+ alias :extension :ext
+
+end # class Layout
+end # module Webby::Resources
+
+# EOF
diff --git a/lib/webby/webby_task.rb b/lib/webby/webby_task.rb
index a2b33ae..2a555a0 100644
--- a/lib/webby/webby_task.rb
+++ b/lib/webby/webby_task.rb
@@ -86,8 +86,8 @@ def define_create_tasks
raise "Usage: rake #{t.name} path" unless ARGV.length == 2
page = t.application.top_level_tasks.pop
- name = ::Webby::File.basename(page)
- ext = ::Webby::File.extname(page)
+ name = ::Webby::Resources::File.basename(page)
+ ext = ::Webby::Resources::File.extname(page)
dir = ::File.dirname(page)
dir = '' if dir == '.'
diff --git a/spec/webby/file_spec.rb b/spec/webby/resources/file_spec.rb
similarity index 86%
rename from spec/webby/file_spec.rb
rename to spec/webby/resources/file_spec.rb
index e1fe491..387fdd1 100644
--- a/spec/webby/file_spec.rb
+++ b/spec/webby/resources/file_spec.rb
@@ -1,9 +1,9 @@
# $Id$
-require ::File.join(::File.dirname(__FILE__), %w[.. spec_helper])
+require ::File.join(::File.dirname(__FILE__), %w[.. .. spec_helper])
# ---------------------------------------------------------------------------
-describe Webby::File do
+describe Webby::Resources::File do
FN = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', 'lorem_ipsum.txt'))
FN_YAML = FN.gsub %r/\.txt\z/, '_yaml.txt'
@@ -35,7 +35,7 @@
it 'should return nil for meta-data on regular files' do
begin
- fd = Webby::File.new FN, 'r'
+ fd = Webby::Resources::File.new FN, 'r'
fd.meta_data.should be_nil
fd.readlines.should == LINES
@@ -45,12 +45,12 @@
end
it 'should add meta-data to the top of a file' do
- Webby::File.open(FN,'a+') do |fd|
+ Webby::Resources::File.open(FN,'a+') do |fd|
fd.meta_data.should be_nil
fd.meta_data = %w(one two three)
end
- Webby::File.open(FN,'r') do |fd|
+ Webby::Resources::File.open(FN,'r') do |fd|
fd.meta_data.should == %w(one two three)
end
@@ -68,12 +68,12 @@
end
it 'should remove the meta-data when set to nil' do
- Webby::File.open(FN_YAML,'a+') do |fd|
+ Webby::Resources::File.open(FN_YAML,'a+') do |fd|
fd.meta_data.should == %w(one two three)
fd.meta_data = nil
end
- Webby::File.open(FN_YAML,'r') do |fd|
+ Webby::Resources::File.open(FN_YAML,'r') do |fd|
fd.meta_data.should be_nil
end
@@ -84,7 +84,7 @@
it 'should skip the meta-data when reading from the file' do
begin
- fd = Webby::File.new FN_YAML, 'r'
+ fd = Webby::Resources::File.new FN_YAML, 'r'
fd.meta_data.should == %w(one two three)
fd.getc.should == ?L; fd.seek 0
@@ -100,6 +100,6 @@
fd.close
end
end
-end # describe Webby::File
+end # describe Webby::Resources::File
# EOF