lib/webgen/website.rb in webgen-0.4.7 vs lib/webgen/website.rb in webgen-0.5.0
- old
+ new
@@ -1,334 +1,212 @@
-#
-#--
-#
-# $Id: website.rb 601 2007-02-14 21:20:44Z thomas $
-#
-# webgen: template based static website generator
-# Copyright (C) 2004 Thomas Leitner
-#
-# This program is free software; you can redistribute it and/or modify it under the terms of the GNU
-# General Public License as published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
-# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with this program; if not,
-# write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-#++
-#
+# Standard lib requires
+require 'logger'
+require 'set'
-require 'pathname'
-require 'yaml'
-require 'fileutils'
-require 'webgen/config'
-require 'webgen/plugin'
+# Requirements for Website
+require 'webgen/loggable'
+require 'webgen/logger'
+require 'webgen/configuration'
+require 'webgen/websiteaccess'
+require 'webgen/blackboard'
+require 'webgen/cache'
+require 'webgen/tree'
-module Webgen
+# Files for autoloading
+require 'webgen/source'
+require 'webgen/output'
+require 'webgen/sourcehandler'
+require 'webgen/contentprocessor'
+# Load other needed files
+require 'webgen/path'
+require 'webgen/node'
+require 'webgen/page'
- # Base class for directories which have a README file with information stored in YAML format.
- # Should not be used directly, use its child classes!
- class DirectoryInfo
- # The unique name.
- attr_reader :name
+# The Webgen namespace houses all classes/modules used by webgen.
+module Webgen
- # Contains additional information, like a description or the creator.
- attr_reader :infos
-
- # Returns a new object for the given +name+.
- def initialize( name )
- @name = name
- raise ArgumentError.new( "'#{name}' is not a directory!" ) if !File.directory?( path )
- @infos = YAML::load( File.read( File.join( path, 'README' ) ) )
- raise ArgumentError.new( "'#{name}/README' does not contain key-value pairs in YAML format!" ) unless @infos.kind_of?( Hash )
+ # Returns the data directory for webgen.
+ def self.data_dir
+ unless defined?(@@data_dir)
+ require 'rbconfig'
+ @@data_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'webgen'))
+ @@data_dir = File.expand_path(File.join(Config::CONFIG["datadir"], "webgen")) if !File.exists?(@@data_dir)
+ raise "Could not find webgen data directory! This is a bug, report it please!" unless File.directory?(@@data_dir)
end
-
- # The absolute directory path. Requires that child classes have defined a constant +BASE_PATH+.
- def path
- File.expand_path( File.join( self.class::BASE_PATH, name ) )
- end
-
- # The files under the directory.
- def files
- Dir.glob( File.join( path, '**', '*' ), File::FNM_CASEFOLD )
- end
-
- # Copies the files returned by +#files+ into the directory +dest+, preserving the directory
- # hierarchy.
- def copy_to( dest )
- files.collect do |file|
- destpath = File.join( dest, File.dirname( file ).sub( /^#{path}/, '' ) )
- FileUtils.mkdir_p( File.dirname( destpath ) )
- if File.directory?( file )
- FileUtils.mkdir_p( File.join( destpath, File.basename( file ) ) )
- else
- FileUtils.cp( file, destpath )
- end
- File.join( destpath, File.basename( file ) )
- end
- end
-
- # Returns all available entries.
- def self.entries
- unless defined?( @entries )
- @entries = {}
- Dir.glob( File.join( self::BASE_PATH, '*' ), File::FNM_CASEFOLD ).each do |f|
- next unless File.directory?( f )
- name = File.basename( f );
- @entries[name] = self.new( name )
- end
- end
- @entries
- end
-
+ @@data_dir
end
- # A Web site template is a collection of files which provide a starting point for a Web site.
- # These files provide stubs for the content and should not contain any style information.
- class WebSiteTemplate < DirectoryInfo
+ # Represents a webgen website and is used to render it.
+ class Website
- # Base path for the templates.
- BASE_PATH = File.join( Webgen.data_dir, 'website_templates' )
+ # Raised when the configuration file of the website is invalid.
+ class ConfigFileInvalid < RuntimeError; end
- end
+ include Loggable
+ # The website configuration. Can only be used after #init has been called (which is
+ # automatically done in #render).
+ attr_reader :config
- # A Web site style provides style information for a Web site. This means it contains, at least, a
- # template file and a CSS file.
- class WebSiteStyle < DirectoryInfo
+ # The blackboard used for inter-object communication. Can only be used after #init has been
+ # called.
+ attr_reader :blackboard
- # Base path for the styles.
- BASE_PATH = File.join( Webgen.data_dir, 'website_styles' )
+ # A cache to store information that should be available between runs. Can only be used after
+ # #init has been called.
+ attr_reader :cache
- # See DirectoryInfo#files
- def files
- super.select {|f| f != File.join( path, 'README' )}
- end
+ # The internal data structure used to store information about individual nodes.
+ attr_reader :tree
- end
+ # The logger used for logging. If set to +nil+, logging is disabled.
+ attr_accessor :logger
+ # The website directory.
+ attr_reader :directory
- # A gallery style provides style information for gallery pages. It should contains the files
- # +gallery_main.template+, +gallery_gallery.template+ and +gallery_image.template+ and an optional
- # readme file.
- class GalleryStyle < DirectoryInfo
-
- # Base path for the styles.
- BASE_PATH = File.join( Webgen.data_dir, 'gallery_styles' )
-
- # See DirectoryInfo#files
- def files
- super.select {|f| f != File.join( path, 'README' )} - plugin_files
+ # Create a new webgen website for the website in the directory +dir+. You can provide a
+ # block (has to take the configuration object as parameter) for adjusting the configuration
+ # values during the initialization.
+ def initialize(dir, logger=Webgen::Logger.new($stdout, false), &block)
+ @blackboard = nil
+ @cache = nil
+ @logger = logger
+ @config_block = block
+ @directory = dir
end
- def plugin_files
- plugin_files = []
- @infos['plugin files'].each do |pfile|
- plugin_files += Dir.glob( File.join( path, pfile ), File::FNM_CASEFOLD )
- end if @infos['plugin files']
- plugin_files
+ # Define a service +service_name+ provided by the instance of +klass+. The parameter +method+
+ # needs to define the method which should be invoked when the service is invoked. Can only be
+ # used after #init has been called.
+ def autoload_service(service_name, klass, method = service_name)
+ blackboard.add_service(service_name) {|*args| cache.instance(klass).send(method, *args)}
end
- end
+ # Initialize the configuration, blackboard and cache objects and load the default configuration
+ # as well as website specific extension files. An already existing configuration/blackboard is
+ # deleted!
+ def init
+ execute_in_env do
+ @blackboard = Blackboard.new
+ @config = Configuration.new
- # A sipttra style provides a template and other files for styling sipttra files. It should contain
- # at least a +sipttra.template+.
- class SipttraStyle < DirectoryInfo
+ load 'webgen/default_config.rb'
+ Dir.glob(File.join(@directory, 'ext', '**/init.rb')) {|f| load(f)}
+ read_config_file
- # Base path for the styles.
- BASE_PATH = File.join( Webgen.data_dir, 'sipttra_styles' )
-
- # See DirectoryInfo#files
- def files
- super.select {|f| f != File.join( path, 'README' )}
+ @config_block.call(@config) if @config_block
+ restore_tree_and_cache
+ end
+ self
end
- end
+ # Render the website (after calling #init if the website is not already initialized).
+ def render
+ execute_in_env do
+ init unless @config
+ puts "Starting webgen..."
+ shm = SourceHandler::Main.new
+ shm.render(@tree)
+ save_tree_and_cache
+ puts "Finished"
- # A WebSite object represents a webgen website directory and is used for manipulating it.
- class WebSite
-
- # The website directory.
- attr_reader :directory
-
- # The logger used for the website
- attr_reader :logger
-
- # The plugin manager used for this website.
- attr_reader :manager
-
- # Creates a new WebSite object for the given +directory+ and loads its plugins. If the
- # +plugin_config+ parameter is given, it is used to resolve the values for plugin parameters.
- # Otherwise, a ConfigurationFile instance is used as plugin configuration.
- def initialize( directory = Dir.pwd, plugin_config = nil )
- @directory = File.expand_path( directory )
- @logger = Webgen::Logger.new
-
- wrapper_mod = Module.new
- wrapper_mod.module_eval { include DEFAULT_WRAPPER_MODULE }
- @loader = PluginLoader.new( wrapper_mod )
- @loader.load_from_dir( File.join( @directory, Webgen::PLUGIN_DIR ) )
-
- @manager = PluginManager.new( [DEFAULT_PLUGIN_LOADER, @loader], DEFAULT_PLUGIN_LOADER.plugin_classes + @loader.plugin_classes )
- @manager.logger = @logger
- set_plugin_config( plugin_config )
- end
-
- # Returns a modified value for Configuration:srcDir, Configuration:outDir and Configuration:websiteDir.
- def param_for_plugin( plugin_name, param )
- case [plugin_name, param]
- when ['Core/Configuration', 'srcDir'] then @srcDir
- when ['Core/Configuration', 'outDir'] then @outDir
- when ['Core/Configuration', 'websiteDir'] then @directory
- else
- (@plugin_config ? @plugin_config.param_for_plugin( plugin_name, param ) : PluginParamValueNotFound)
+ if @logger && @logger.log_output.length > 0
+ puts "\nLog messages:"
+ puts @logger.log_output
+ end
end
end
- # Initializes all plugins and renders the website.
- def render( files = [] )
- @logger.level = @manager.param_for_plugin( 'Core/Configuration', 'loggerLevel' )
- @manager.init
+ # Clean the website directory from all generated output files (including the cache file). If
+ # +del_outdir+ is +true+, then the base output directory is also deleted. When a delete
+ # operation fails, the error is silently ignored and the clean operation continues.
+ #
+ # Note: Uses the configured output instance for the operations!
+ def clean(del_outdir = false)
+ init
+ execute_in_env do
+ output = @blackboard.invoke(:output_instance)
+ @tree.node_access[:alcn].each do |name, node|
+ next if node.is_fragment? || node['no_output'] || node.path == '/' || node == @tree.dummy_root
+ output.delete(node.path) rescue nil
+ end
- @logger.info( 'WebSite#render' ) { "Starting rendering of website <#{directory}>..." }
- @logger.info( 'WebSite#render' ) { "Using webgen data directory at <#{Webgen.data_dir}>!" }
- if files.empty?
- @manager['Core/FileHandler'].render_site
- else
- @manager['Core/FileHandler'].render_files( files )
- end
- @logger.info( 'WebSite#render' ) { "Rendering of website <#{directory}> finished" }
- end
+ if @config['website.cache'].first == :file
+ FileUtils.rm(File.join(@directory, @config['website.cache'].last)) rescue nil
+ end
- # Loads the configuration file from the +directory+.
- def self.load_config_file( directory = Dir.pwd )
- begin
- ConfigurationFile.new( File.join( directory, 'config.yaml' ) )
- rescue ConfigurationFileInvalid => e
- nil
+ if del_outdir
+ output.delete('/') rescue nil
+ end
end
end
- # Create a website in the +directory+, using the template +template_name+ and the style +style_name+.
- def self.create_website( directory, template_name = 'default', style_name = 'default' )
- template = WebSiteTemplate.entries[template_name]
- style = WebSiteStyle.entries[style_name]
- raise ArgumentError.new( "Invalid website template '#{template}'" ) if template.nil?
- raise ArgumentError.new( "Invalid website style '#{style}'" ) if style.nil?
-
- raise ArgumentError.new( "Directory <#{directory}> does already exist!") if File.exists?( directory )
- FileUtils.mkdir( directory )
- return template.copy_to( directory ) + style.copy_to( File.join( directory, Webgen::SRC_DIR) )
+ # The provided block is executed within a proper environment sothat any object can access the
+ # Website object.
+ def execute_in_env
+ set_back = Thread.current[:webgen_website].nil?
+ Thread.current[:webgen_website] = self
+ yield
+ ensure
+ Thread.current[:webgen_website] = nil if set_back
end
- # Copies the style files for +style+ to the source directory of the website +directory+
- # overwritting exisiting files.
- def self.use_website_style( directory, style_name )
- style = WebSiteStyle.entries[style_name]
- raise ArgumentError.new( "Invalid website style '#{style_name}'" ) if style.nil?
- src_dir = File.join( directory, Webgen::SRC_DIR )
- raise ArgumentError.new( "Directory <#{src_dir}> does not exist!") unless File.exists?( src_dir )
- return style.copy_to( src_dir )
- end
-
- # Copies the gallery style files for +style+ to the source directory of the website +directory+
- # overwritting exisiting files.
- def self.use_gallery_style( directory, style_name )
- style = GalleryStyle.entries[style_name]
- raise ArgumentError.new( "Invalid gallery style '#{style_name}'" ) if style.nil?
- src_dir = File.join( directory, Webgen::SRC_DIR )
- plugin_dir = File.join( directory, Webgen::PLUGIN_DIR )
- raise ArgumentError.new( "Directory <#{src_dir}> does not exist!") unless File.exists?( src_dir )
- plugin_files = style.plugin_files
- FileUtils.mkdir( plugin_dir ) unless File.exists?( plugin_dir )
- FileUtils.cp( plugin_files, plugin_dir )
- return style.copy_to( src_dir ) + plugin_files.collect {|f| File.join( plugin_dir, File.basename( f ) )}
- end
-
- # Copies the sipttra style files for +style+ to the source directory of the website +directory+
- # overwritting exisiting files.
- def self.use_sipttra_style( directory, style_name )
- style = SipttraStyle.entries[style_name]
- raise ArgumentError.new( "Invalid sipttra style '#{style_name}'" ) if style.nil?
- src_dir = File.join( directory, Webgen::SRC_DIR )
- raise ArgumentError.new( "Directory <#{src_dir}> does not exist!") unless File.exists?( src_dir )
- return style.copy_to( src_dir )
- end
-
#######
private
#######
- def set_plugin_config( plugin_config )
- @manager.plugin_config = ( plugin_config ? plugin_config : self.class.load_config_file( @directory ) )
- @srcDir = File.join( @directory, Webgen::SRC_DIR )
- outDir = @manager.param_for_plugin( 'Core/Configuration', 'outDir' )
- @outDir = (/^(\/|[A-Za-z]:)/ =~ outDir ? outDir : File.join( @directory, outDir ) )
- @plugin_config = @manager.plugin_config
- @manager.plugin_config = self
+ # Restore the tree and the cache from +website.cache+ and returns the Tree object.
+ def restore_tree_and_cache
+ @cache = Cache.new
+ @tree = Tree.new
+ data = if config['website.cache'].first == :file
+ cache_file = File.join(@directory, config['website.cache'].last)
+ File.read(cache_file) if File.exists?(cache_file)
+ else
+ config['website.cache'].last
+ end
+ cache_data, @tree = Marshal.load(data) rescue nil
+ @cache.restore(cache_data) if cache_data
end
- end
-
-
- # Raised when a configuration file has an invalid structure
- class ConfigurationFileInvalid < RuntimeError; end
-
- # Represents the configuration file of a website.
- class ConfigurationFile
-
- # Returns the whole configuration.
- attr_reader :config
-
- # Reads the content of the given configuration file and initialize a new object with it.
- def initialize( config_file )
- if File.exists?( config_file )
- begin
- @config = YAML::load( File.read( config_file ) )
- rescue ArgumentError => e
- raise ConfigurationFileInvalid, e.message
- end
+ # Save the +tree+ and the +cache+ to +website.cache+.
+ def save_tree_and_cache
+ cache_data = [@cache.dump, @tree]
+ if config['website.cache'].first == :file
+ cache_file = File.join(@directory, config['website.cache'].last)
+ File.open(cache_file, 'wb') {|f| Marshal.dump(cache_data, f)}
else
- @config = {}
+ config['website.cache'][1] = Marshal.dump(cache_data)
end
- check_config
end
- # See PluginManager#param_for_plugin .
- def param_for_plugin( plugin_name, param )
- if @config.has_key?( plugin_name ) && @config[plugin_name].has_key?( param )
- @config[plugin_name][param]
- else
- PluginParamValueNotFound
- end
- end
-
- #######
- private
- #######
-
- def check_config
- if !@config.kind_of?( Hash ) || !@config.all? {|k,v| v.kind_of?( Hash )}
- raise ConfigurationFileInvalid.new( 'Structure of config file is not valid, has to be a Hash of Hashes' )
- end
-
- if !@config.has_key?( 'Core/FileHandler' ) || !@config['Core/FileHandler'].has_key?( 'defaultMetaInfo' )
- @config.each_key do |plugin_name|
- next unless plugin_name =~ /File\//
- if @config[plugin_name]['defaultMetaInfo'].kind_of?( Hash )
- ((@config['Core/FileHandler'] ||= {})['defaultMetaInfo'] ||= {})[plugin_name] = @config[plugin_name]['defaultMetaInfo']
- @config[plugin_name].delete( 'defaultMetaInfo' )
+ # Update the configuration object for the website with infos found in the configuration file.
+ def read_config_file
+ file = File.join(@directory, 'config.yaml')
+ if File.exists?(file)
+ begin
+ config = YAML::load(File.read(file)) || {}
+ raise 'Structure of config file is not valid, has to be a Hash' if !config.kind_of?(Hash)
+ config.each do |key, value|
+ if key == 'default_meta_info'
+ value.each do |klass_name, options|
+ @config['sourcehandler.default_meta_info'][klass_name].update(options)
+ end
+ else
+ @config[key] = value
+ end
end
+ rescue RuntimeError, ArgumentError => e
+ raise ConfigFileInvalid, "Configuration invalid: " + e.message
end
+ elsif File.exists?(File.join(@directory, 'config.yml'))
+ log(:warn) { "No configuration file called config.yaml found (there is a config.yml - spelling error?)" }
end
-
end
end
end