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