lib/hx.rb in hx-0.3.2 vs lib/hx.rb in hx-0.3.3

- old
+ new

@@ -1,8 +1,8 @@ # hx - A very small website generator. # -# Copyright (c) 2009 MenTaLguY <mental@rydia.net> +# Copyright (c) 2009-2010 MenTaLguY <mental@rydia.net> # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, @@ -19,21 +19,14 @@ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -require 'cgi' require 'rubygems' -require 'ostruct' require 'set' -require 'date' -require 'time' -require 'fileutils' require 'pathname' require 'yaml' -require 'liquid' -require 'redcloth' module Hx class NoSuchEntryError < RuntimeError end @@ -124,21 +117,10 @@ def initialize(*sources) @sources = sources end - def edit_entry(path, prototype=nil) - @sources.each do |source| - begin - source.edit_entry(path, prototype) { |text| yield text } - break - rescue EditingNotSupportedError - end - end - self - end - def each_entry seen = Set[] @sources.each do |source| source.each_entry do |path, entry| yield path, entry unless seen.include? path @@ -220,14 +202,15 @@ self end def each_entry unless @entries - @entries = [] + entries = [] @source.each_entry do |path, entry| - @entries << [path, entry] + entries << [path, entry] end + @entries = entries end @entries.each do |path, entry| yield path, entry.dup end self @@ -487,263 +470,20 @@ end end class FileBuilder def initialize(output_dir) - @output_dir = output_dir + @output_dir = Pathname.new(output_dir) end def build_file(path, entry) - filename = File.join(@output_dir, path) - dirname = File.dirname(filename) - FileUtils.mkdir_p dirname - File.open(filename, "wb") do |stream| + filename = @output_dir + path + dirname = filename.parent + dirname.mkpath() + filename.open("wb") do |stream| stream.write entry['content'].to_s end - end -end - -module Backend - -class Hobix - include Source - - def initialize(source, options) - @entry_dir = Hx.get_pathname(options, :entry_dir) - end - - def yaml_repr(value) - YAML.parse(YAML.dump(value)) - end - private :yaml_repr - - def edit_entry(path, prototype=nil) - entry_filename = @entry_dir + "#{path}.yaml" - begin - text = entry_filename.read - previous_mtime = entry_filename.mtime - rescue Errno::ENOENT - raise NoSuchEntryError, path unless prototype - prototype = prototype.dup - prototype['content'] = (prototype['content'] || "").dup - content = prototype['content'] - def content.to_yaml_style ; :literal ; end - native = YAML::DomainType.new('hobix.com,2004', 'entry', prototype) - text = YAML.dump(native) - previous_mtime = nil - end - text = yield text - repr = YAML.parse(text) - keys = {} - repr.value.each_key { |key| keys[key.value] = key } - %w(created updated).each { |name| keys[name] ||= yaml_repr(name) } - update_time = Time.now - update_time_repr = yaml_repr(update_time) - previous_mtime ||= update_time - previous_mtime_repr = yaml_repr(previous_mtime) - repr.add(keys['created'], previous_mtime_repr) unless repr['created'] - repr.add(keys['updated'], update_time_repr) - entry_filename.parent.mkpath() - entry_filename.open('w') { |stream| stream << repr.emit } self end - - def each_entry - Pathname.glob(@entry_dir + '**/*.yaml') do |entry_filename| - path = entry_filename.relative_path_from(@entry_dir).to_s - path.sub!(/\.yaml$/, '') - entry = entry_filename.open('r') do |stream| - YAML.load(stream).value - end - entry['updated'] ||= entry_filename.mtime - entry['created'] ||= entry['updated'] - yield path, entry - end - self - end -end - -end - -module Listing - -class RecursiveIndex - include Source - - def self.new(source, options) - listing = super(source, options) - if options.has_key? :limit - listing = Limit.new(listing, :limit => options[:limit]) - end - if options.has_key? :page_size - listing = Paginate.new(listing, :page_size => options[:page_size]) - end - listing - end - - def initialize(source, options) - @source = source - end - - def each_entry - indexes = Hash.new { |h,k| h[k] = {'items' => []} } - @source.each_entry do |path, entry| - components = path.split("/") - until components.empty? - components.pop - index_path = (components + ["index"]).join("/") - index = indexes[index_path] - index['items'] << {'path' => path, 'entry' => entry} - if entry['modified'] and - (not index['modified'] or entry['modified'] > index['modified']) - index['modified'] = entry['modified'] - end - end - end - indexes.each do |path, entry| - yield path, entry - end - self - end -end - -class Paginate - include Source - - def initialize(source, options) - @source = source - @page_size = options[:page_size] - end - - def each_entry - @source.each_entry do |index_path, index_entry| - items = index_entry['items'] || [] - if items.empty? - index_entry = index_entry.dup - index_entry['pages'] = [index_entry] - index_entry['page_index'] = 0 - yield index_path, index_entry - else - pages = [] - n_pages = (items.size + @page_size - 1) / @page_size - for num in 0...n_pages - page_items = items[@page_size * num, @page_size] - entry = index_entry.dup - entry['items'] = page_items - entry['prev_page'] = "#{num}" - entry['next_page'] = "#{num+2}" - entry['pages'] = pages - entry['page_index'] = num - pages << {'path' => "#{index_path}/#{num+1}", 'entry' => entry} - end - pages[0]['path'] = index_path - pages[0]['entry'].delete('prev_page') - if pages.size > 1 - index_name = index_path.split('/').last - pages[0]['entry']['next_page'] = "#{index_name}/2" - pages[1]['entry']['prev_page'] = "../#{index_name}" - end - pages[-1]['entry'].delete('next_page') - pages.each do |page| - yield page['path'], page['entry'] - end - end - end - self - end -end - -class Limit - include Source - - def initialize(source, options) - @source = source - @limit = options[:limit] - end - - def each_entry - @source.each_entry do |path, entry| - if entry['items'] - trimmed_entry = entry.dup - trimmed_entry['items'] = entry['items'][0...@limit] - else - trimmed_entry = entry - end - yield path, trimmed_entry - end - self - end -end - -end - -module Output - -class LiquidTemplate - include Source - - module TextFilters - def textilize(input) - RedCloth.new(input).to_html - end - - def escape_url(input) - CGI.escape(input) - end - - def escape_xml(input) - CGI.escapeHTML(input) - end - - def path_to_url(input, base_url) - "#{base_url}#{input}" - end - - def handleize(input) - "id_#{input.to_s.gsub(/[^A-Za-z0-9]/, '_')}" - end - - def xsd_datetime(input) - input = Time.parse(input) unless Time === input - input.xmlschema - end - end - - def initialize(source, options) - @source = source - @options = {} - for key, value in options - @options[key.to_s] = value - end - template_dir = Hx.get_pathname(options, :template_dir) - # global, so all LiquidTemplate instances kind of have to agree on the - # same template directory for things to work right - Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_dir) - Liquid::Template.register_filter(TextFilters) - template_file = template_dir + options[:template] - @template = template_file.open('r') { |s| Liquid::Template.parse(s.read) } - @extension = options[:extension] - end - - def each_entry - @source.each_entry do |path, entry| - unless @extension.nil? - output_path = "#{path}.#{@extension}" - else - output_path = path - end - output_entry = entry.dup - output_entry['content'] = @template.render( - 'now' => Time.now, - 'options' => @options, - 'path' => path, - 'entry' => entry - ) - yield output_path, output_entry - end - self - end -end - end end