# -*- encoding: utf-8 -*- require 'webgen/websiteaccess' require 'webgen/loggable' require 'webgen/page' module Webgen::SourceHandler # This module should be included in every source handler as it provides the default methods for # creating nodes. # # == Implementing Source Handlers # # A source handler is a webgen extension that processes source paths to create nodes and that # provides the rendered content of these nodes. The nodes are later written to the output # location. This can range from simply copying a path from the source to the output location to # generating a whole set of nodes from one input path! # # The paths that are handled by a source handler are specified via path patterns (see # below). During a webgen run the #create_node method for each source paths that matches a # specified path pattern is called. And when it is time to write out the node, the #content # method is called to retrieve the rendered content. # # A source handler must not take any parameters on initialization and when this module is not # mixed in, the methods #create_node and #content need to be defined. Also, a source handler does # not need to reside under the Webgen::SourceHandler namespace but all shipped ones do. # # This base class provides useful default implementations of methods that are used by nearly all # source handler classes: # * #create_node # * #output_path # * #node_exists? # # It also provides other utility methods: # * #page_from_path # * #content # * #parent_node # # == Nodes Created for Paths # # The main functions of a source handler class are to create one or more nodes for a source path # and to provide the content of these nodes. To achieve this, certain information needs to be set # on a created node. If you use the +create_node+ method provided by this base class, you don't # need to set them explicitly because this is done by the method: # # [node_info[:processor]] Has to be set to the class name of the source handler. This is # used by the Node class: all unknown method calls are forwarded # to the node processor. # [node_info[:src]] Has to be set to the string version of the path that lead to the # creation of the node. # [node_info[:creation_path]] Has to be set to the string version of the path that is # used to create the path. # [meta_info['no_output']] Has to be set to +true+ on nodes that are used during a # webgen run but do not produce an output file. # [meta_info['modified_at']] Has to be set to the current time if not already set # correctly (ie. if not a Time object). # # If meta_info['draft'] is set on a path, then no node should be created in +create_node+ # and +nil+ has to be returned. # # Note: The difference between +:src+ and +:creation_path+ is that a creation path # need not have an existing source path representation. For example, fragments created from a page # source path have a different +:creation_path+ which includes the fragment part. # # Additional information that is used only for processing purposes should be stored in the # #node_info hash of a node as the #meta_info hash is reserved for real node meta information and # should not be changed once the node is created. # # == Output Path Names # # The method for creating an output path name for a source path is stored in the meta information # +output_path+. If you don't use the provided method +output_path+, have a look at its # implementation to see how to an output path gets created. Individual output path creation # methods are stored as methods in the OutputPathHelpers module. # # == Path Patterns and Invocation order # # Path patterns define which paths are handled by a specific source handler. These patterns are # specified in the sourcehandler.patterns configuration hash as a mapping from the source # handler class name to an array of path patterns. The patterns need to have a format that # Dir.glob can handle. You can use the configuration helper +patterns+ to set this (is # shown in the example below). # # Specifying a path pattern does not mean that webgen uses the source handler. One also needs to # provide an entry in the configuration value sourcehandler.invoke. This is a hash that # maps the invocation rank (a number) to an array of source handler class names. The lower the # invocation rank the earlier the specified source handlers are used. # # The default invocation ranks are: # [1] Early. Normally there is no need to use this rank. # [5] Standard. This is the rank the normal source handler should use. # [9] Late. This rank should be used by source handlers that operate on/use already created nodes # and need to ensure that these nodes are available. # # == Default Meta Information # # Each source handler can define default meta information that gets automatically set on the # source paths that are passed to the #create_node method. # # The default meta information is specified in the sourcehandler.default_meta_info # configuration hash as a mapping from the source handler class name to the meta information # hash. # # == Sample Source Handler Class # # Following is a simple source handler class example which copies paths from the source to # the output location modifying the extension: # # class SimpleCopy # # include Webgen::SourceHandler::Base # include Webgen::WebsiteAccess # # def create_node(path) # path.ext += '.copied' # super(path) # end # # def content(node) # website.blackboard.invoke(:source_paths)[node.node_info[:src]].io # end # # end # # WebsiteAccess.website.config.patterns('SimpleCopy' => ['**/*.jpg', '**/*.png']) # WebsiteAccess.website.config.sourcehandler.invoke[5] << 'SimpleCopy' # module Base # This module is used for defining all methods that can be used for creating output paths. # # All public methods of this module are considered to be output path creation methods and must # have the following parameters: # # [+parent+] the parent node # [+path+] the path for which the output name should be created # [+use_lang_part+] controls whether the output path name has to include the language part module OutputPathHelpers # Default method for creating an output path for +parent+ and source +path+. # # The automatically set parameter +style+ (which uses the meta information +output_path_style+ # from the path's meta information hash) defines how the output name should be built (more # information about this in the user documentation). def standard_output_path(parent, path, use_lang_part, style = path.meta_info['output_path_style']) result = style.collect do |part| case part when String then part when :lang then use_lang_part ? path.meta_info['lang'] : '' when :ext then path.ext.empty? ? '' : '.' + path.ext when :parent then temp = parent; temp = temp.parent while temp.is_fragment?; temp.path when :year, :month, :day ctime = path.meta_info['created_at'] if !ctime.kind_of?(Time) raise Webgen::NodeCreationError.new("Invalid meta info 'created_at', needed because of used output path style", self.class.name, path) end ctime.send(part).to_s.rjust(2, '0') when Symbol then path.send(part) when Array then part.include?(:lang) && !use_lang_part ? '' : standard_output_path(parent, path, use_lang_part, part) else '' end end result.join('') end end include Webgen::Loggable include OutputPathHelpers # Construct the output name for the given +path+ and +parent+. First it is checked if a node # with the constructed output name already exists. If it exists, the language part is forced to # be in the output name and the resulting output name is returned. def output_path(parent, path) method = path.meta_info['output_path'] + '_output_path' use_lang_part = if path.meta_info['lang'].nil? # unlocalized files never get a lang in the filename! false else Webgen::WebsiteAccess.website.config['sourcehandler.default_lang_in_output_path'] || Webgen::WebsiteAccess.website.config['website.lang'] != path.meta_info['lang'] end if OutputPathHelpers.public_instance_methods(false).map {|c| c.to_s}.include?(method) name = send(method, parent, path, use_lang_part) name += '/' if path.path =~ /\/$/ && name !~ /\/$/ if (node = node_exists?(path, name)) && node.lang != path.meta_info['lang'] name = send(method, parent, path, (path.meta_info['lang'].nil? ? false : true)) name += '/' if path.path =~ /\/$/ && name !~ /\/$/ end name else raise Webgen::NodeCreationError.new("Unknown method for creating output path: #{path.meta_info['output_path']}", self.class.name, path) end end # Check if the node alcn and output path which would be created by #create_node exist. The # +output_path+ to check for can individually be set. def node_exists?(path, output_path = self.output_path(parent_node(path), path)) Webgen::WebsiteAccess.website.tree[path.alcn] || (!path.meta_info['no_output'] && Webgen::WebsiteAccess.website.tree[output_path, :path]) end # Create a node from +path+ if it does not already exists or re-initalize an already existing # node. The found node or the newly created node is returned afterwards. +nil+ is returned if no # node can be created (e.g. when path.meta_info['draft'] is set). # # The +options+ parameter can be used for providing the optional parameters: # # [:parent] The parent node under which the new node should be created. If this is not # specified (the usual case), the parent node is determined by the # #parent_node method. # # [:output_path] The output path that should be used for the node. If this is not # specified (the usual case), the output path is determined via the # #output_path method. # # Some additional node information like :src and :processor is set and the # meta information is checked for validness. The created/re-initialized node is yielded if a # block is given. def create_node(path, options = {}) return nil if path.meta_info['draft'] parent = options[:parent] || parent_node(path) output_path = options[:output_path] || self.output_path(parent, path) node = node_exists?(path, output_path) if node && (node.node_info[:src] != path.source_path || node.node_info[:processor] != self.class.name) log(:warn) { "Node already exists: source = #{path.source_path} | path = #{node.path} | alcn = #{node.alcn}"} return node #TODO: think! should nil be returned? elsif !node node = Webgen::Node.new(parent, output_path, path.cn, path.meta_info) elsif node.flagged?(:reinit) node.reinit(output_path, path.meta_info) else return node end if !node['modified_at'].kind_of?(Time) log(:warn) { "Meta information 'modified_at' set to current time in <#{node}> since its value '#{node['modified_at']}' was of type #{node['modified_at'].class}" } unless node['modified_at'].nil? node['modified_at'] = Time.now end node.node_info[:src] = path.source_path node.node_info[:creation_path] = path.path node.node_info[:processor] = self.class.name yield(node) if block_given? node end # Return the content of the given +node+. If the return value is not +nil+ then the node gets # written, otherwise it is ignored. # # This default +content+ method just returns +nil+. def content(node) nil end # Utility method for creating a Webgen::Page object from the +path+. Also updates # path.meta_info with the meta info from the page. def page_from_path(path) begin page = Webgen::Page.from_data(path.io.data, path.meta_info) rescue Webgen::Page::FormatError => e raise Webgen::NodeCreationError.new("Error reading source path: #{e.message}", self.class.name, path) end path.meta_info = page.meta_info page end # Return the parent node for the given +path+. def parent_node(path) parent_dir = (path.parent_path == '' ? '' : Webgen::Path.new(path.parent_path).alcn) if !(parent = Webgen::WebsiteAccess.website.tree[parent_dir]) raise Webgen::NodeCreationError.new("The needed parent path <#{parent_dir}> does not exist", self.class.name, path) end parent end end end