webgen-0.5.17/0000755002342000234200000000000012154473016012307 5ustar duckdc-userswebgen-0.5.17/bin/0000755002342000234200000000000012055517620013057 5ustar duckdc-userswebgen-0.5.17/bin/webgen0000644002342000234200000000032612055517620014252 0ustar duckdc-users#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'webgen/cli' begin Webgen::CLI::CommandParser.new.parse rescue puts "An error has occurred:\n " + $!.message puts $!.backtrace if $DEBUG exit(-1) end webgen-0.5.17/setup.rb0000644002342000234200000010650212055517620014000 0ustar duckdc-users# # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end webgen-0.5.17/misc/0000755002342000234200000000000012055517620013242 5ustar duckdc-userswebgen-0.5.17/misc/htmldoc.virtual0000644002342000234200000000045112055517620016304 0ustar duckdc-usersapi.html: url: rdoc/index.html title: API in_menu: true sort_info: 60 rdoc/index.html: title: API rdoc/Webgen/Context.html: title: Webgen::Context rdoc/Webgen/ContentProcessor/Head.html: title: Webgen::ContentProcessor::Head rdoc/Webgen/Tag/Base.html: title: Webgen::Tag::Base webgen-0.5.17/misc/default.template0000644002342000234200000000453612055517620016433 0ustar duckdc-users--- pipeline:erb,tags,blocks,head,xmllint {title:} | webgen - static website generator
webgen-0.5.17/misc/logo.svg0000644002342000234200000003702512055517620014732 0ustar duckdc-users image/svg+xml generated by webgen webgen-0.5.17/misc/default.css0000644002342000234200000001574412055517620015413 0ustar duckdc-users/******************************************** AUTHOR: Erwin Aligam WEBSITE: http://www.styleshout.com/ TEMPLATE NAME: ablaze TEMPLATE CODE: S-0009 VERSION: 2.1 *******************************************/ /******************************************** HTML ELEMENTS ********************************************/ /* top elements */ * { padding: 0; margin: 0; } body { margin: 0; padding: 0; font: normal .80em/1.5em 'Helvetica Neue', Tahoma, 'Trebuchet MS', sans-serif; color: #000; text-align: center; background-color: #FCFFF5; } /* links */ a { color: #3E606F; background-color: inherit; text-decoration: underline; } a:hover { color: #193441; background-color: #D1DBBD; } /* headers */ h1, h2, h3, h4, h5, h6 { font: bold 1.3em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; color: #CE900A; } h1 { font-size: 1.6em; } h2 { font-size: 1.4em; color: #A0080D } h3 { font-size: 1.3em; } #main h1 { color: #193441; padding-top: 15px; } #sidebar h1 { font: bold 1.4em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; background: url(images/arrow.gif) no-repeat 0px .6em; padding: 7px 0 7px 25px; color: #193441; } p, h1, h2, h3 { margin: 0; padding: 5px 0px; } ul, ol { margin: 10px 0px; padding: 0 15px; color: #000; /* #3E606F; */ } /* images */ img { border: none; } pre { margin: 5px 0px; padding: 5px; text-align: left; overflow: auto; font: 'Lucida Console', 'courier new', monospace; color: #193441; background: #D1DBBD; border-bottom: 1px solid #91AA9D; border-top: 1px solid #91AA9D; } code { font: 'Lucida Console', 'courier new', monospace; color: #193441; font-size: 115%; } acronym { cursor: help; border-bottom: 1px solid #777; } blockquote.warning, blockquote.error, blockquote.information, blockquote.important { margin: 10px 0px; padding: 5px 0 0 50px; min-height: 40px; border: 1px solid #91AA9D; font: 'Helvetica Neue', 'Trebuchet MS', Sans-serif; background-color: #D1DBBD; background-image: url(images/quote.gif); background-repeat: no-repeat; background-position: 9px 5px; } blockquote.warning { background-image: url(images/warning.png); } blockquote.warning:before { content: 'Caution'; font: bold 1.3em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; } blockquote.error { background-image: url(images/error.png); } blockquote.error:before { content: 'Warning'; font: bold 1.3em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; } blockquote.information { background-image: url(images/information.png); } blockquote.information:before { content: 'Information'; font: bold 1.3em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; } blockquote.important { background-image: url(images/important.png); } blockquote.important:before { content: 'Important'; font: bold 1.3em 'Helvetica Neue', 'Trebuchet MS', Arial, Sans-serif; } blockquote pre { border: none; } /*********************** LAYOUT ************************/ #header-content, #content { width: 100%; } /* header */ #header { background: #444 url(images/headerbg.jpg) repeat-x 0 0; height: 120px; text-align: left; } #header-content { position: relative; margin: 0 auto; padding: 0; } #header-content #logo-image { position: absolute; margin: 0; padding: 0; top: 20px; left: 10px; } #header-content #logo { position: absolute; font: bold 50px Verdana, 'Trebuchet Ms', Sans-serif; letter-spacing: -2px; color: #FCFFF5; margin: 0; padding: 0; top: 5px; left: 100px; } #header-content #slogan { position: absolute; font: bold 16px 'Trebuchet Ms', Sans-serif; text-transform: none; color: #CCC; margin: 0; padding: 0; top: 65px; left: 115px; } /* header menu */ #header-content ul { position: absolute; right: 20px; top: 75px; font: bolder 1.3em 'Helvetica Neue', 'Trebuchet MS', sans-serif; color: #FCFFF5; list-style: none; margin: 0; padding: 0; } #header-content li { float: left; display: block; color: #FCFFF5; text-decoration: none; } #header-content li a, #header-content li span { display: block; color: #3E606F; text-decoration: none; padding: 3px 12px; } #header-content li a:hover { background: #FCFFF5; color: #A0080D; } #header-content li.webgen-menu-item-selected, #header-content li.webgen-menu-submenu-inhierarchy { background: #FCFFF5; color: #A0080D; } /* content */ #content-wrap { clear: both; float: left; width: 100%; } #content { text-align: left; padding: 0; margin: 0 auto; background: #91AA9D; } /* sidebar */ #sidebar { float: right; width: 340px; margin: 0; padding: 10px; background-color: #91AA9D; } #sidebar p { margin: 0px; padding-left: 10px; } #sidebar ul { list-style: inside square; margin: 0px; padding: 0px; } #sidebar ul li { padding: 0px; margin: 0px; padding-left: 1em; padding-top: 0.3em; } #sidebar ul a { color: #FCFFF5; text-decoration: none; padding: .3em 0 .3em 3px; line-height: 1.5ex; } #sidebar ul a:hover { border-left: 5px solid #193441; color: #193441; } /* main */ #main { margin: 0 360px 0 0; padding: 20px; background-color: #FCFFF5; } /* footer */ #footer { clear: both; color: #FCFFF5; margin: 0; padding: 0; border-top: 1px solid #3E60F6; font-size: 95%; text-align: left; } #footer h2, #footer p { padding-left: 0; } #footer-content { width: 100%; margin: 0 auto; position: absolute; background-color: #193441; } #footer-content a { text-decoration: none; color: #CCC; } #footer-content a:hover { text-decoration: underline; background-color: inherit; } #footer-content ul { list-style: none; margin: 0; padding: 0; } #footer-content .col { width: 32%; padding: 0 5px 30px 15px; } #footer-content .col2 { width: 30%; padding: 0 0 30px 0; } /* alignment classes */ .float-left { float: left; } .float-right { float: right; } .align-left { text-align: left; } .align-right { text-align: right; } /* additional classes */ .clear { clear: both; } .comments { text-align: right; border: 1px dashed #151515; padding: 5px 10px; margin: 20px 15px 10px 15px; } .small { font-size: 90%; color: #666; padding: 2px; } /* extensions classes */ .backlink { float: right; } table.examples { border: 1px solid black; border-collapse: collapse; margin-left: auto; margin-right: auto; width: 80%; } table.examples td, table.examples th { border: 1px solid black; padding: 3px; } /* plugin documentation classes */ .plugin-info .param-name { color: #193441; } .plugin-info .param-default-value { color: #A0080D; } .plugin-info p.param { padding: 0px 0px 5px; } /* blog related classes */ div.blog-entry { margin-bottom: 15px; } blockquote.blog-entry-details { font-size: 80%; margin-bottom: 5px; } .blog-entry-date, .blog-entry-author { font-weight: bold; } /* website style preview classes */ div.website-styles { margin: 0px 10px; } div.website-styles object { border: 1px solid black; width: 100%; height: 300px; overflow: hidden; } webgen-0.5.17/misc/htmldoc.metainfo0000644002342000234200000000051712055517620016423 0ustar duckdc-users--- name:alcn /index.en.html: routed_title: Documentation index.*.html: in_menu: true sort_info: 10 getting_started.*.html: in_menu: true sort_info: 20 manual.*.html: in_menu: true sort_info: 30 faq.*.html: in_menu: true sort_info: 40 extensions.*.html: in_menu: true sort_info: 50 images/: index_path: ~ webgen-0.5.17/misc/images/0000755002342000234200000000000012055517620014507 5ustar duckdc-userswebgen-0.5.17/misc/images/headerbg.jpg0000644002342000234200000000150612055517620016754 0ustar duckdc-usersÿØÿàJFIFddÿìDuckydÿîAdobedÀÿÛ„ÿÀxÿÄž  !1sA3Qa$4„q‘ƒDd¤´¡±"2B#TtÄä%5 1²ðAq±á2â3s³!Q¡"$r4B¢ãDÿÚ ?âÞz|z¿ŽcU¼°Ó +ismîAEøym.m´÷uôø±R©™‡yRÀAšái0A½|ÒПï.mŠÍNþÙº™˜w”Yk±chL -å·ÁVÔæÙUCSëÏS3ò¥q+癨áF“öÈKçv˜æÔ›ã7S7 õÅt3KËΘä,H…k!m¦Y\ó»´=4µ0‡lß ¼Ü#Ñf'Øô³K"'ÏRhP‰«†W=pڨߺ™¤¯¸óJQeˆ«ò‡Ò(R— SÏð6¥­ÏpÝLØŠ·G4¥\ÑIÔðôž/…ºpJߺ…͉8óùÉ’ÔA¥2)canƒQ´n3?wIǘ·UŸÊÀWâdãDï „zx¢¶«Þðè E}Tú¨(š]E!‘æûKÄG® ðš3ñ/ÿÙwebgen-0.5.17/misc/images/information.png0000644002342000234200000000356612055517620017554 0ustar duckdc-users‰PNG  IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIMEÖ38¥fƒ¢IDATXÃÅ–[lT׆ÿµ÷ÙgnÏŒ_0cCp 8£$&R,RUm¥U+«%´@ EMU©ZE}©”¢¾´$ÊC%çÒ6¢J«m‘ riJœÛ¤Øåb<_æ>s®ûœÝ‡ <U´´¥£}Öÿ­_ÎþÏÕ3yà©§B‘ ¶2ܦ úàÉ&×rö=ëµjþ6´¿yǾ¿çg{Áø¯ZW­×S]½‘¶ÖV„Ã:4Fð=‹‹‹¸tñ¼qibÔVžüÅ+/ýzðŽ <£GÖPc²åK½ý[# Èä«ÈU ¸ÒȇЀå‰0zR Dð÷ƒo™é+GËùàÀÐÐ3έòk·Œ&ì¡e«Ö?²áþ/þ5gÂSy¬j¸gu#bá Î}Ë‘ªPuüÉLNIWñoo =öîW'Ξðuêfü–¶ÿàéF[W>¹þÇBŸÎ˜èNiØ´6жDÀ†„“œ“Ô9÷"!á·7…ÕBÙ²Î_ɹ?|p:3¿²kÕšì¹³ï× °k×O㤋7{Ú»º(±iM­:41rAA2"D€(/ÞPWæÊÕÙœ¡ÝÜ9súdÿÆ }ƒ££§¬ÿ¦ÃnÀt¶½¹scØöÚ“„–„$H¹Dä0N6'²ÁæKãdrFVX×ìxCÀ(›N¡lK»¯s˜élûMunê? ìlí\ÊU,¬lÓáÃ÷ˆ)—@A9D°9#‹1f2N,3ƒs2‚U‚:«^ΔŠ}÷ݧé;ëß„¾×ÕãZ¦Š€ E.ã°‘É9Ä”d €ï3RÊW°]ÏcDÅ ®… U›V4§š¡¼Õu(åE5¡ÁS6LÇs‚Bs‰àÈaŒlÆ`A O|WùT¨¸ ÁWŒÈ7]ºà¤|?Z71­e'“˜^¨ZñˆîÌ!‚͈,²8#›sA*_ùYÓw¤´ƒ ‘""®Ÿ1ªÖ½ˆøX¹”ESTw –‘-Ze6lÎÈd&çdh Gz³ó%ó¼%åÌš‰¦ ®U‚Œ™Ë›ÂZ®X¶Ó&ëð<ó·×.Ž–W67z\#çòb5¿ñ®D*¤Ï•­t±ìN-V쉹¼9\1ÜqÁY6bÏ¥27ùh@h†®3ã m‰åŽŒT¥r_«û={jlÝÚ ßi]ÑkoY昮,´'BH%#-íMÁn®\<›ûv9ªÁ^"{K2í)”«Sl<ŸŠ7Þ­sÕyà¿—Ü/îqë;†€"×Þ:|ô¯¶å`WK<2<™7/;~µð˜*-‹…»:Û’&ãÑʃkç"Íw ¦šbk›¢úÆžŽÛÒÛ988h|îŸÑŽ=?é é¡|sÇnC‰È䊦€"Î3†éŒEBZ>à% C»xÐ/YÑ€þ£}ûž¥bñÉW^üÍŸo•ŸÝàÕ—žÿØu­ïž|÷ˆH6†™œuÕ6 FJ Î ƒ˜k_¼8‘t=çÓ†€èž7‹…òàíÄ—äÓé¹ôµXXðTž•ž™-gçÒIbL2]¹ï;ÚžžÔ5QáÄÚ®\™&yv)¹—ðòËûsRúÏ\›¡e ÁÕ†S‰}ôᱯ]úç{Û.~rtçØøÉ'V¯ŒêJpb=§N}Ó>XJnm©‘íš{ÿòúßÛ³cUª%©/L 8vñn¦€5Ý+E¿è:UuðíÃ××zqð¹Ù;ÞnßñøÁX¢ç±‡·lAOÏZ4F#à\# ›[À;oÆøøÄåùŒ¾îСì;ê´6çÚ²Ù†¯ÞxãOÈ.”àJ ÍA>7åä/x‡MØKÍY@z¦Â?»€]»¾û€+=ä ˜Ëd ”… ãY½vÕ““Õ3Ùóä³ óŽz@G"Cª£]Ý)œ8qcSp]÷õÿç´ïÞ{;00ðch\GâÂ…«èííÅúž.„Bâ[uå¬grwwóÓš¦„aL¡\6ALC¡X‚Àè¹05yÙ\ñR<îÿnfÆ”·ê†ë9Z-DÇºÆÆÐ^!O*Ì9ƒR>”bPŠg•2Ÿ9“ùåü|±üY“ €¬îçq@¨EpzºdNNf‡;;{esswßîÝ?×6mzÓÓe;®8rdäÕjյ喝Ä׃jPK ªÿ Bétzº££ãž±±±ÖÑÑQ?ŸÏ?~üžçyüšÐuTí_s£®% š¨^E Dhoooö}Ÿg2™bMÀ`×BÖÂ`Ýl ê¹ ¯[ªÕœ»Ñ^ÜPåõJ¯»qËøoMJ<ålㆈIEND®B`‚webgen-0.5.17/misc/images/quote.gif0000644002342000234200000000216512055517620016337 0ustar duckdc-usersGIF89a÷x¦ÏÁñ¿ï½ì»ê¹ç¶ã³ß™Á•¼’¸޳Œ¯ˆ«…§£~Ÿ}œzš¯Û«×¨Ó£ÊŸÅ™Á£ÊŸÅšÁ¤Î–¼“¸£Ê¤ÊŸÅšÁ¤Ê¡È—¼¯Ú«Ö¤Îޝˆ©°Û“¸³ޝŠ«¨Î‡¨£É‡¨ ªÐ  Æ”¸´® °Û ªÐ ¤Íˆ¨  ®Ù ¯ Œ« ~œ }𡯠œ ~™ƒž•³ƒœ‰£€›‡  €—¼¤Ä¤Ãˆ …œ‡#¤"–+”«2’¥6‹œ)ŒŸ5£¸7¢¶7š¬1Šš4‰˜@®<Žž<‡”Až­O «Tœ¤^£©\Ÿ¥X“˜X“—i¦©b›z¤¨ ƒª¢‰¬£‹±¥Œ©ž®¡‘¯£˜·©™·©šµ¦œµ¦£ºª¤»©!ùx,ÿñH° Áƒ*\Ȱ¡Ã‡ãdÉ ¢Å‹}haèE€Ç C~$“°Í€“(SªT™—0cÊ”ùa›8sêܹ¡Ÿ@ƒ jÂç£h :9pðÀ £&Ì18A‡Á1h ¬•‚A9ÄÜAÆV® †©`§à´gA41(…Á-@ A8b+ï|8b„…f¸pÁLÁ2Îd‚ñ„"6`0hcsÁ* `C‚KÜ0Ø!ë=(ð0Ùׂ`èÖ{Á’‚SZÔ)ˆcqãS¸HSðǃ)Hg "ù :áQ‚»÷ïgðRÀr… “+Ix 1Ï>†ƒ÷ï…„@¿¾ýûøJé¡¿ÿÿú×­E&¨à‚ BGB\A„Vhá…At±Qáᇠ†¨D$;webgen-0.5.17/misc/images/warning.png0000644002342000234200000000255712055517620016673 0ustar duckdc-users‰PNG  IHDR szzôsBIT|dˆ&IDATX…å—[hTG€¿™sÎîÙ‹»1‰«Y“&ë݈˪ˆ—xÉ‚—€Ä VD£4R%õ‚Z[郈}òÅÆXjò" &£¢` D%¼"f„ÚÍÆf“xÎéC’%&F›‹Oýa˜afþÿÿþ3ÿÌÏÿ»(#Q.e&(×Á® 9\Å_ao;ü ±28>\;b8JeoOIù3´i“‚aPwáBW<Ýö üñÙÊAi¢qnqq¶úàªJ<-ͺwþ|s¾ü:†boÈGÐ…)Á`¦[QD¬¦†Xu5Þädá RÜP4T{C(‡¢(?OYµJm½t ™š ©©Ä*+™Û¤ÇÎAÒgøŠÓ/«µ´Ðñø1ú8Ž#ÞЀÞÑ/'Ç.àûÏpÆ*º~8°|¹Özù2ʬYhùùØÂaÔ¹s‰]½JöâÅ6©ªß•Bú¨t¡ô¼<»ùð!]‘úþýÙ­î()¡óÕ+”¦&ü¡pbTJ!ÝîõgΛ§ÅªªÐ–.E[¸!B´`ÛêÕÄnÜ #Ԥñé䌀„™+WŠÎ;w0b1ô}û¢û÷öÎýû1b1ÌÆF2æÍ3%œ€³0Ë1~ü&ÿ”)jÛõëØ P§OÇ0 Z[[‰F£˜¦‰’‘cËÞÖÖâTÕë]Q +F  Â©ìü|+^SƒeY8vï ‰  ƒD"„8‹‹±…®{÷Èš?N[Ÿxì> P¹î¬¬¼Ô”åmm-ö­[‘~?B\.WbŸËåBLIÁUTDûýû¤&'K›Ï7­ ¾€BñËÔ5kx[U…p»ÑwîLœyèÎçŽÈqãˆ×Õ‘ ©N–ƒmÈç᫤ٳsܦ)Ûëë±ïÚ…LJJ8r8(Š‚¢(躞¸Òéĵw/Ožà•R¸&Mò½…]C8 ªT”“SÂaÙvå ÒïG/,L8Id¾Ó‰Óé|o^sô©Si¯«#+'GS¤üáwðüg€ Øá[°`¢ýõkÑñô)Ž={ºžˆ¾·÷xbÂëû°ó4\ºú–Ê$`ì4˜ €nöɑЄw€0 ÞM@ Ðkµè€ü¶}@L  èìiÖ(Ú™ü p ¢žáM/´IEND®B`‚webgen-0.5.17/misc/images/important.png0000644002342000234200000000305612055517620017236 0ustar duckdc-users‰PNG  IHDR szzôsBIT|dˆåIDATX…–[l×Çgn{ñ.kì5Æ€ñcS›–ª´J„(ižªH©RòEIUT‰J}@}á©´ªT¤´U“—<4m_’¨ª¢¤¡@M”ÐV$¸vHØÅÁàÛÚk÷¾³3§Þ]ïŒok>éÓÜÎùþ¿ó}ç2‚MØïckÉà‰ &ž‘ˆ>[²Õvd@UEN…9pnåJüE+òö©ËÌÕSÔ%|‚½º*~'…òÈžQµ»¯iÝFCCCm‡L6™˜%6ú…uçî´c_-9üôÔ;|ñÀgŸÂˆæøƒ¢jÏ><è88(DÊÄI™È|ne0Ý@ G ÜÈèÍayíÚͼ´í?O8uöuŠ›øã÷hÅ'.víŠö?r⨮.$±Í$H¹sՔМÆVþuñR)þÕôŽýøŸÌÔðÛã4uñÙ¡CûÛ‡ ûÞÒ±ëvG¨m ʯLæŠrðgHÖ6Q½}ÎAk ˆ«èýÚžÿHgóâe“éEÚvvˆ@ƒh˜š˜=v¨W¯ŒQ ¨x;´…9×ÓÜðëŠ=1^wÊ×3{n†þ¾¥wOËP[˜sµß\%8œí‘ :ü£ç~ØìŒÇ×M»ö­çP‡ž^~“Ò'/¯M!ÊöüõO[XÈ:§/0 ž ýœèð@frãšKá W¹Q{‰œOòo÷E‚~ÎW^W^9J)Nô|ã›Â6“«©—7=Ï‹÷ÉeéÚÛ'„Pžxå(@Nçñ®íMºÌ,ÖWw…– Št¶…œÎã.€ ÎÉ=}~™ª/Þççëêæ¤Mº÷uA“.$}--ÈB¾®@²°ùÈR‰HsHú´Ê‡’CK¸©'e®ÝÛ%ènç¤î!ÓSK{†ÐF «ö üØR´‚\p†ª©8u®û*@y!Ë{ÿAæ–'¯Ðüˆ` "eyÏS±‘`@MIÑ.ä±þ$´-dv™ž;šÛ%^µR¹8ŽLO""ˆ`+-K‡S@SH¤’‰HHÓ–µ2XÁD¦Èüò$³IÄ–öÕÅkÍ)!çÇéiD ™Œo;š"g\n-Ü¿Óж—;ÙPÊ s&2; öÊÓ´øú³l†\}++Ê,fÒ‚[.€¬ÅkñøÌ£;[ûÞºÿU³c— Û™Ùú¥qñϧ Y‹× f,Þ½“(X"´ÄŠ3jU3~ð*_$ð=ûÖ&¶ŸÌX‹w]/\ÂDá·oÆ¥V)æ•#uß÷×\r.ñH±±Y òí.aº²yN_I&inA¾ ZWÏ!só”>| Š™ Ô”öÝ\»1µÍsºòzÅÑËOò›î¨ï'GŽìGo€óà?#U wõsõúx16‘~éÅ·øy•ËÛv*Å™Øtá߯Kzï€ky0q¶«›‘ø¼›H6•âLíçѯŒá<º‡7gæò'UÛ í8Ð/sìÒæµU ½g€áÛóÎ'ÃÓ“Y‹ïž¹Œ«V«ïB kwˆ7ŠÅü±ù™d¤óà ¦ú Ȥê;ª…@¶#wõpåƒÛÖÍØÂçÿ¾Ëñ_}Èàªéj> p} ùßoô†íŽ‘[Ý>]Õ¢Cû…,ÏvÇvÍ¡ˆ†Z[Ê®nFã¦|çÊíüølñï¿üˆç?¸K†¥’ À¦|\x'¡øËU?ÞÃÞc]œ êØÝêWz»¶M[ 61t•¢åYÌ`Î-òe,aÍäœl‘büúbŒ/Bçk®+Œa/ˆ¿§‘–‡»xx_34v –àPé|‰û£s¼÷þÞ-XC¸r¿*€ðž{£œ%¥m\-§U²TÛ`•½Xãg×*7^ÑŠ°VVjb8e·= ˜+?»F¼ž)ˆÚQW¼b•,ÔBÊâžÙ_±ÿíO‰xÇ£ÐIEND®B`‚webgen-0.5.17/misc/images/error.png0000644002342000234200000000315512055517620016352 0ustar duckdc-users‰PNG  IHDR szzôsBIT|dˆ$IDATX…Å—]lW€¿{gö×ëÝìn×vŠ“ª Nì@«¤…"/ý¯¡¡‘¨ª*o}©ÁHH¼!ÄúPÞÂQÓ¢"¥2©x¨T”†¨­íÄIšD®ìlÖk{íÝÙ¹÷ò°3›Ù];NW:º3gîžóŸ¹wþÏCÜí³PhÃSqÛ~)j¥ŠJë!KJÇ’rÅh½à)uJÀÌÓ°ö?8¶eýFÃ7J££z÷¥3»ÇHä Äsôk¸««8Ë7Y¾p±Q)—-aÌ_2kÀ¸-Luâ׿"ÍJ5åVWNÃÉ7ú8eô&+åÏKÅÂÏÛ6s;FB£Æôˆ‰8 E…ÎÃõM5{‰}Çžå‹…Ã)ÖŸn þ»3R.}å{9¹¸›©Îýº0+kŒl3>Ê?~û»õ–Ö“ÓPÈ@ZÊ_84•‘m]ÛèF©·‘0JdD%Ñ}X&½QGø>{'dâRþb §!§™¾÷™',ucq[Ç¥ˆ> KèUˆº¾Ä½O|ÓVÆ<7Ù€·ñÑ…© ¥øH S®vˆƒ Èí¬F†1­u÷Z”!:¨¡=íùÄŠ»0P /´1™X!Vª“Ú u; ­5J©n£…J)|ßGkÝé•°Œ!–Ë îBÔÛŽƒŽÅo×{¥T7ò;6ΟǫV;½‹á–+!6Ã56€€[ÍJ%7”Œ£[­î临ÑÀŸŸG¤ÓÄîÇ5*=À¥úÕkCGc6Nî‰Ôƒ_.ã]¿ŽqœîöJÏîÙ={ôŽƒå:46êh¸Øàsª<ñhéKG2=MS¯£*ü¥¥Î²ÍVÛ¯ë ŸÉ±µË×6µ1§z\˜Ùج[íÅOðþµ€1 [-ŒënåÝDßd 2ÀÄ$ŽÛ²³!€85!Ä[Ëÿœõå}cxµÊu»§[¸ó…¢¸½ÕöoÍ¡^…çC ØCùƒ_Þ˜†ÍWë./•ë&›Fä³·Eš§Ü>†U`\æ3ŽS½µZ÷áGDFà,#ÄkWßý ‘xx“Œï½Ú& ®Á¤¤š`ño³ŽâÕèQÜP×ú'NÓýôïzÉGÀ¤©Ý*úžlE¢'`èËYzo®í¶ÚÕµþ)}c`k vÙ0_(åGÆ¿v$朿„¿Rëm¸-:<ª°K9†ïgù½yo³Z[nƒ[}ny¶œ{œM¥’‡ö}8e.Í ×QNsÛ×0k8Mjr‘Šóé_?nzn{® O>Õ­|m{¸½±1xU ñb~´yäÄmѺ¹Š¿î ÝÚS³ÉV.Ml,Œ[Ü:wůÝZóÚpò$üà 4¶ó³€2ÀùLNóðH2•PÙ±B*YÌb§±‡’xN“V­A{uƒõ›kMÏmYëpîMøÕÛp¨NdÞ  Gç=0ö$*. Most website styles include dynamic parts by default, for example, automatically generated menus and breadcrumb trails. All such dynamic parts are active in this showcase like they would be on a normal website. ## How to use this style This website style can be used when creating a new website by using the following command: webgen create -b default -b <%= context.content_node.parent.cn %> SITE_DIR Or it can be applied later on to an already existing website by using the following command: webgen apply <%= context.content_node.parent.cn %> ## Style Information
<% require 'webgen/websitemanager' infos = Webgen::WebsiteManager.new(context.website).bundles[context.content_node.parent.cn.chomp('/')] infos.instance_eval {@table}.sort {|(ak,av), (bk,bv)| ak.to_s <=> bk.to_s}.each do |name, value| next if name.to_s == 'paths' %>
<%= name.to_s.capitalize %>
<%= ::ERB::Util::h(value) %>
<% end %>
webgen-0.5.17/lib/0000755002342000234200000000000012055517620013055 5ustar duckdc-userswebgen-0.5.17/lib/webgen/0000755002342000234200000000000012055517620014324 5ustar duckdc-userswebgen-0.5.17/lib/webgen/webgentask.rb0000644002342000234200000001047012055517620017005 0ustar duckdc-users# -*- encoding: utf-8 -*- # -*- ruby -*- # #-- # webgentask.rb: # # Define a task library for running webgen # # Copyright (C) 2007 Jeremy Hinegardner # # Tasks restructuration by Massimiliano Filacchioni # Modifications for 0.5.0 by 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 # #++ # require 'rake' require 'rake/tasklib' module Webgen ## # Task library to manage a webgen website. # # It is assumed that you have already used the 'webgen' command to create the website directory # for the site. # # == Basics # # require 'webgen/webgentask' # # Webgen::WebgenTask.new # # == Attributes # # The attributes available in the new block are: # # [directory] # the root directory of the webgen site (default Dir.pwd) # [config] # the config block for setting additional configuration options # [clobber_outdir] # remove webgens output directory on clobber (default +false+) # # == Tasks Provided # # The tasks provided are : # # [webgen] # render the webgen website # [clobber_webgen] # remove all the files created during generation # # == Integrate webgen in other project # # To integrate webgen tasks in another project you can use rake namespaces. For example assuming # webgen's site directory is +webgen+ under the main project directory use the following code # fragment in project Rakefile: # # require 'webgen/webgentask' # # namespace :dev do # Webgen::WebgenTask.new do |site| # site.directory = File.join(Dir.pwd, "webgen") # site.clobber_outdir = true # site.config_block = lambda |config| # config['website.lang'] = 'de' # end # end # end # # task :clobber => ['dev:clobber_webgen'] # # This will create the following tasks: # # * dev:webgen # * dev:clobber_webgen # # and add dev:clobber_webgen to the main clobber task. # class WebgenTask < ::Rake::TaskLib # The directory of the webgen website. This would be the directory of your config.yaml # file. Or the parent directory of the src/ directory for webgen. # # The default for this is assumed to be Dir.pwd attr_accessor :directory # The configuration block that is invoked when the Webgen::Website object is initialized. This # can be used to set configuration parameters and to avoid having a config.yaml file # lying around. attr_accessor :config_block # During the clobber, should webgen's output directory be clobbered. The default is false. attr_accessor :clobber_outdir # Create webgen tasks. You can override the task name with the parameter +name+. def initialize(name = 'webgen') @name = name @directory = Dir.pwd @clobber_outdir = false @config_block = nil yield self if block_given? define end ####### private ####### def define # :nodoc: desc "Render the webgen website" task @name, :verbosity, :log_level do |t, args| require 'webgen/website' website = Webgen::Website.new(@directory, Webgen::Logger.new($stdout), &@config_block) website.logger.verbosity = args[:verbosity].to_s.intern unless args[:verbosity].to_s.empty? website.logger.level = args[:log_level].to_i if args[:log_level] website.render end task :clobber => paste('clobber_', @name) desc "Remove webgen products" task paste('clobber_', @name) do require 'webgen/website' website = Webgen::Website.new(@directory, Webgen::Logger.new($stdout), &@config_block) website.clean(@clobber_outdir) end end end end webgen-0.5.17/lib/webgen/websitemanager.rb0000644002342000234200000001133612055517620017652 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'ostruct' require 'fileutils' require 'webgen/website' module Webgen # This class is used for managing webgen websites. It provides access to website bundles defined # as resources and makes it easy to apply them to a webgen website. # # == General information # # Currently, the following actions are supported: # # * creating a website based on a website bundle (#create_website) # * applying a bundle to an existing website (#apply_bundle) # # Bundles are partial webgen websites that contain certain functionality. For example, most of the # bundles shipped with webgen are style bundles that define the basic page layout or how image # galleries should look like. So style bundles are basically used to change the appearance of # parts (or the whole) website. This makes them a powerful tool as this plugin makes it easy to # change to another style bundle later! # # However, not all bundles have to be style bundles. For example, you could as easily create a # bundle for a plugin or for a complete website (e.g. a blog template). # # == website bundle resource naming convention # # The shipped bundles are defined using resources. Each such resource has to be a directory # containing an optional README file in YAML format in which key-value pairs provide additional # information about the bundle (e.g. copyright information, description, ...). All other # files/directories in the directory are copied to the root of the destination webgen website when # the bundle is used. # # This class uses a special naming convention to recognize website bundles: # # * A resource named webgen-website-bundle-CATEGORY-NAME is considered to be a bundle in # the category CATEGORY called NAME (where CATEGORY is optional). There are no fixed categories, # one can use anything here! The shipped style bundles are located in the 'style' category. You # need to use the the full name, i.e. CATEGORY-NAME, for accessing a bundle later. # # Website bundle names have to be unique! class WebsiteManager # A hash with the available website bundles (mapping name to infos). attr_reader :bundles # The used Website object. attr_reader :website # Create a new WebsiteManager. # # If +dir+ is a String, then the website manager is created for the website in the directory # +dir+. # # If +dir+ is a Website object, the website manager is created for the website represented by # +dir+. If the website object is initialized if it isn't already. def initialize(dir) if dir.kind_of?(Webgen::Website) @website = dir @website.init if @website.config.nil? else @website = Webgen::Website.new(dir) @website.init end @bundles = {} @website.execute_in_env do prefix = "webgen-website-bundle-" @website.config['resources'].select {|name, data| name =~ /^#{prefix}/}.each do |name, data| add_source(Webgen::Source::Resource.new(name), name.sub(prefix, '')) end end end # Treat the +source+ as a website bundle and make it available to the WebsiteManager under # +name+. def add_source(source, name) paths = source.paths.dup readme = paths.select {|path| path == '/README' }.first paths.delete(readme) if readme infos = OpenStruct.new(readme.nil? ? {} : YAML::load(readme.io.data)) infos.paths = paths @bundles[name] = infos end # Create the basic website skeleton (without any bundle applied). def create_website raise "Directory <#{@website.directory}> does already exist!" if File.exists?(@website.directory) @website.execute_in_env do write_paths(Webgen::Source::Resource.new('webgen-website-skeleton').paths) + [FileUtils.mkdir_p(File.join(@website.directory, 'src'))] # fixes Rubygems bug (see RF#28393) end end # Apply the given +bundle+ to the website by copying the files. def apply_bundle(bundle) raise ArgumentError.new("Invalid bundle name") if !@bundles.has_key?(bundle) raise "Directory <#{@website.directory}> does not exist!" unless File.exists?(@website.directory) write_paths(@bundles[bundle].paths) end ####### private ####### # Write the paths to the website directory. def write_paths(paths) paths.each do |path| output_path = File.join(@website.directory, path.path) if path.path =~ /\/$/ FileUtils.mkdir_p(output_path) else FileUtils.mkdir_p(File.dirname(output_path)) path.io.stream do |source| File.open(output_path, 'wb') {|f| FileUtils.copy_stream(source, f) } end end end end end end webgen-0.5.17/lib/webgen/version.rb0000644002342000234200000000013712055517620016337 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # The version of webgen. VERSION = '0.5.17' end webgen-0.5.17/lib/webgen/contentprocessor/0000755002342000234200000000000012055517620017736 5ustar duckdc-userswebgen-0.5.17/lib/webgen/contentprocessor/blocks.rb0000644002342000234200000000771512055517620021552 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Replaces special xml tags with the rendered content of a node. class Blocks include Webgen::Loggable BLOCK_RE = // BLOCK_ATTR_RE = /(\w+)=('|")([^'"]+)\2/ # Replace the webgen:block xml tags with the rendered content of the specified node. def call(context) context.content.gsub!(BLOCK_RE) do |match| attr = {} match_object = $~ attr[:line_nr_proc] = lambda { match_object.pre_match.scan("\n").size + 1 } match.scan(BLOCK_ATTR_RE) {|name, sep, content| attr[name.to_sym] = content} render_block(context, attr) end context end # Render a block of a page node and return the result. # # The Webgen::Context object +context+ is used as the render context and the +options+ hash # needs to hold all relevant information, that is: # # [:name (mandatory)] # The name of the block that should be used. # [:chain] # The node chain used for rendering. If this is not specified, the node chain from the context # is used. It needs to be a String in the format (A)LCN;(A)LCN;... or an array of # nodes. # [:node] # Defines which node in the node chain should be used. Valid values are +next+ (= default # value; the next node in the node chain), +first+ (the first node in the node chain with a # block called +name+) or +current+ (the currently rendered node, ignores the +chain+ option). # [:notfound] # If this property is set to +ignore+, a missing block will not raise an error. It is unset by # default, so missing blocks will raise errors. def render_block(context, options) if options[:chain].nil? used_chain = (context[:chain].length > 1 ? context[:chain][1..-1] : context[:chain]).dup elsif options[:chain].kind_of?(Array) used_chain = options[:chain] dest_node = context.content_node else paths = options[:chain].split(';') used_chain = paths.collect do |path| temp_node = context.ref_node.resolve(path.strip, context.dest_node.lang) if temp_node.nil? raise Webgen::RenderError.new("Could not resolve <#{path.strip}>", self.class.name, context.dest_node, context.ref_node, (options[:line_nr_proc].call if options[:line_nr_proc])) end temp_node end.compact return '' if used_chain.length != paths.length dest_node = context.content_node end if options[:node] == 'first' used_chain.shift while used_chain.length > 0 && !used_chain.first.node_info[:page].blocks.has_key?(options[:name]) elsif options[:node] == 'current' used_chain = context[:chain].dup end block_node = used_chain.first if !block_node || !block_node.node_info[:page].blocks.has_key?(options[:name]) if options[:notfound] == 'ignore' return '' elsif block_node raise Webgen::RenderError.new("No block named '#{options[:name]}' found in <#{block_node}>", self.class.name, context.dest_node, context.ref_node, (options[:line_nr_proc].call if options[:line_nr_proc])) else raise Webgen::RenderError.new("No node in the render chain has a block named '#{options[:name]}'", self.class.name, context.dest_node, context.ref_node, (options[:line_nr_proc].call if options[:line_nr_proc])) end end context.dest_node.node_info[:used_nodes] << block_node.alcn tmp_context = block_node.node_info[:page].blocks[options[:name]].render(context.clone(:chain => used_chain, :dest_node => dest_node)) tmp_context.content end end end webgen-0.5.17/lib/webgen/contentprocessor/less.rb0000644002342000234200000000263012055517620021232 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Generates valid CSS from CSS-like input, supporting variables, nesting and mixins. class Less include Webgen::Loggable # Convert the content in +context+ to valid CSS. def call(context) require 'less' context.content = ::Less.parse(context.content) context rescue LoadError raise Webgen::LoadError.new('less', self.class.name, context.dest_node, 'less') rescue ::Less::SyntaxError => e line = e.message.scan(/on line (\d+):/).first.first.to_i rescue nil raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node, line) rescue ::Less::MixedUnitsError => e raise Webgen::RenderError.new("Can't mix different units together: #{e}", self.class.name, context.dest_node, context.ref_node) rescue ::Less::VariableNameError => e raise Webgen::RenderError.new("Variable name is undefined: #{e}", self.class.name, context.dest_node, context.ref_node) rescue ::Less::MixinNameError => e raise Webgen::RenderError.new("Mixin name is undefined: #{e}", self.class.name, context.dest_node, context.ref_node) rescue ::Less::ImportError => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node) end end end webgen-0.5.17/lib/webgen/contentprocessor/erb.rb0000644002342000234200000000117012055517620021032 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/common' module Webgen::ContentProcessor # Processes embedded Ruby statements. class Erb # Process the Ruby statements embedded in the content of +context+. def call(context) require 'erb' extend(ERB::Util) erb = ERB.new(context.content) erb.filename = context.ref_node.alcn context.content = erb.result(binding) context rescue Exception => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, Webgen::Common.error_file(e), Webgen::Common.error_line(e)) end end end webgen-0.5.17/lib/webgen/contentprocessor/xmllint.rb0000644002342000234200000000213412055517620021752 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'tempfile' module Webgen::ContentProcessor # Uses the external +xmllint+ program to check if the content is valid (X)HTML. class Xmllint include Webgen::Loggable # Checks the content of +context+ with the +xmllint+ program for validness. def call(context) error_file = Tempfile.new('webgen-xmllint') error_file.close `xmllint --version 2>&1` if $?.exitstatus != 0 raise Webgen::CommandNotFoundError.new('xmllint', self.class.name, context.dest_node) end cmd = "xmllint #{context.website.config['contentprocessor.xmllint.options']} - 2>'#{error_file.path}'" result = IO.popen(cmd, 'r+') do |io| io.write(context.content) io.close_write io.read end if $?.exitstatus != 0 File.read(error_file.path).scan(/^-:(\d+):(.*?\n)(.*?\n)/).each do |line, error_msg, line_context| log(:warn) { "xmllint reported problems for <#{context.dest_node}:~#{line}>: #{error_msg.strip} (context: #{line_context.strip})" } end end context end end end webgen-0.5.17/lib/webgen/contentprocessor/erubis.rb0000644002342000234200000000265012055517620021557 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/common' module Webgen::ContentProcessor # Processes embedded Ruby statements with the +erubis+ library. class Erubis # Process the Ruby statements embedded in the content of +context+. def call(context) require 'erubis' # including Erubis because of problem with resolving Erubis::XmlHelper et al self.class.class_eval "include ::Erubis" options = context.website.config['contentprocessor.erubis.options'] if context[:block] use_pi = context[:block].options['erubis_use_pi'] context[:block].options.select {|k,v| k =~ /^erubis_/}. each {|k,v| options[k.sub(/^erubis_/, '').to_sym] = v } end erubis = if (!use_pi.nil? && use_pi) || (use_pi.nil? && context.website.config['contentprocessor.erubis.use_pi']) ::Erubis::PI::Eruby.new(context.content, options) else ::Erubis::Eruby.new(context.content, options) end erubis.filename = context.ref_node.alcn context.content = erubis.result(binding) context rescue LoadError raise Webgen::LoadError.new('erubis', self.class.name, context.dest_node, 'erubis') rescue Exception => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, Webgen::Common.error_file(e), Webgen::Common.error_line(e)) end end end webgen-0.5.17/lib/webgen/contentprocessor/tags.rb0000644002342000234200000001516512055517620021231 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'yaml' require 'strscan' require 'webgen/tag' module Webgen::ContentProcessor # Processes special webgen tags to provide dynamic content. # # webgen tags are an easy way to add dynamically generated content to websites, for example menus # or breadcrumb trails. class Tags include Webgen::WebsiteAccess include Webgen::Loggable def initialize #:nodoc: @start_re = /(\\*)\{#{website.config['contentprocessor.tags.prefix']}(\w+)(::?)/ @end_re = /(\\*)\{#{website.config['contentprocessor.tags.prefix']}(\w+)\}/ end # Replace all webgen tags in the content of +context+ with the rendered content. def call(context) replace_tags(context) do |tag, param_string, body| log(:debug) { "Replacing tag #{tag} with data '#{param_string}' and body '#{body}' in <#{context.ref_node}>" } process_tag(tag, param_string, body, context) end context end # Process the +tag+ and return the result. The parameter +params+ needs to be a Hash holding all # needed and optional parameters for the tag or a parameter String in YAML format and +body+ is # the optional body for the tag. +context+ needs to be a valid Webgen::Context object. def process_tag(tag, params, body, context) result = '' processor = processor_for_tag(tag) if !processor.nil? params = if params.kind_of?(String) processor.create_tag_params(params, context.ref_node) else processor.create_params_hash(params, context.ref_node) end processor.set_params(params) result, process_output = processor.call(tag, body, context) processor.set_params(nil) result = call(context.clone(:content => result)).content if process_output else raise Webgen::RenderError.new("No tag processor for '#{tag}' found", self.class.name, context.dest_node, context.ref_node) end result end ####### private ####### BRACKETS_RE = /([{}])/ ProcessingStruct = Struct.new(:state, :tag, :simple_tag, :backslashes, :brackets, :start_pos, :end_pos, :params_start_pos, :params_end_pos, :body_end_pos) # Return the context.content provided by context.ref_node with all webgen tags # replaced. When a webgen tag is encountered by the parser, the method yields all found # information and substitutes the returned string for the tag. def replace_tags(context) #:yields: tag_name, param_string, body scanner = StringScanner.new(context.content) data = ProcessingStruct.new(:before_tag) while true case data.state when :before_tag if scanner.skip_until(@start_re) data.state = :in_start_tag data.backslashes = scanner[1].length data.brackets = 1 data.tag = scanner[2] data.simple_tag = (scanner[3] == ':') data.params_start_pos = scanner.pos data.start_pos = scanner.pos - scanner.matched.length else data.state = :done end when :in_start_tag data.brackets += (scanner[1] == '{' ? 1 : -1) while data.brackets != 0 && scanner.skip_until(BRACKETS_RE) if data.brackets != 0 raise Webgen::RenderError.new("Unbalanced curly brackets for tag '#{data.tag}'", self.class.name, context.dest_node, context.ref_node) else data.params_end_pos = data.body_end_pos = data.end_pos = scanner.pos - 1 data.state = (data.simple_tag ? :process : :in_body) end when :process if RUBY_VERSION >= '1.9' begin enc = scanner.string.encoding scanner.string.force_encoding('ASCII-8BIT') if data.backslashes % 2 == 0 result = yield(data.tag, scanner.string[data.params_start_pos...data.params_end_pos].force_encoding(enc), scanner.string[(data.params_end_pos+1)...data.body_end_pos].to_s.force_encoding(enc)).to_s scanner.string[data.start_pos..data.end_pos] = "\\" * (data.backslashes / 2) + result.force_encoding('ASCII-8BIT') scanner.pos = data.start_pos + result.length else scanner.string[data.start_pos, 1 + data.backslashes / 2] = '' scanner.pos -= 1 + data.backslashes / 2 end ensure scanner.string.force_encoding(enc) if RUBY_VERSION >= '1.9' end else if data.backslashes % 2 == 0 result = yield(data.tag, scanner.string[data.params_start_pos...data.params_end_pos], scanner.string[(data.params_end_pos+1)...data.body_end_pos]).to_s scanner.string[data.start_pos..data.end_pos] = "\\" * (data.backslashes / 2) + result scanner.pos = data.start_pos + result.length else scanner.string[data.start_pos, 1 + data.backslashes / 2] = '' scanner.pos -= 1 + data.backslashes / 2 end end data.state = :before_tag when :in_body while (result = scanner.skip_until(@end_re)) next unless scanner[2] == data.tag if scanner[1].length % 2 == 1 scanner.string[(scanner.pos - scanner.matched.length), 1 + scanner[1].length / 2] = '' scanner.pos -= 1 + scanner[1].length / 2 else break end end if result data.state = :process data.end_pos = scanner.pos - 1 data.body_end_pos = scanner.pos - scanner.matched.length + scanner[1].length / 2 else raise Webgen::RenderError.new("Invalid body part - no end tag found for '#{data.tag}'", self.class.name, context.dest_node, context.ref_node) end when :done break end end scanner.string rescue Webgen::RenderError => e e.line = scanner.string[0...scanner.pos].scan("\n").size + 1 unless e.line raise end # Return the tag processor for +tag+ or +nil+ if +tag+ is unknown. def processor_for_tag(tag) map = website.config['contentprocessor.tags.map'] klass = if map.has_key?(tag) map[tag] elsif map.has_key?(:default) map[:default] else nil end klass.nil? ? nil : website.cache.instance(klass) end end end webgen-0.5.17/lib/webgen/contentprocessor/sass.rb0000644002342000234200000000123012055517620021230 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Processes content in Sass markup (used for writing CSS files) using the +haml+ library. class Sass # Convert the content in +sass+ markup to CSS. def call(context) require 'sass' context.content = ::Sass::Engine.new(context.content, :filename => context.ref_node.alcn).render context rescue LoadError raise Webgen::LoadError.new('sass', self.class.name, context.dest_node, 'haml') rescue ::Sass::SyntaxError => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node, (e.sass_line if e.sass_line)) end end end webgen-0.5.17/lib/webgen/contentprocessor/kramdown/0000755002342000234200000000000012055517620021560 5ustar duckdc-userswebgen-0.5.17/lib/webgen/contentprocessor/kramdown/html.rb0000644002342000234200000000216012055517620023050 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'kramdown' module Webgen::ContentProcessor class KramdownHtmlConverter < ::Kramdown::Converter::Html def initialize(root, options, context) #:nodoc: super(root, options) @context = context @do_convert = if context.options.has_key?('contentprocessor.kramdown.handle_links') context.options['contentprocessor.kramdown.handle_links'] else context.website.config['contentprocessor.kramdown.handle_links'] end end # Convert the element tree under +root+ to HTML using the webgen +context+ object. def self.convert(root, options, context) new(root, options, context).convert(root) end def convert_a(el, indent) el.attr['href'] = @context.tag('relocatable', {'path' => el.attr['href']}) if @do_convert "#{inner(el, indent)}" end def convert_img(el, indent) el.attr['src'] = @context.tag('relocatable', {'path' => el.attr['src']}) if @do_convert "" end end end webgen-0.5.17/lib/webgen/contentprocessor/tidy.rb0000644002342000234200000000177712055517620021250 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'tempfile' module Webgen::ContentProcessor # Uses the external +tidy+ program to format the content as valid (X)HTML. class Tidy include Webgen::Loggable # Process the content of +context+ with the +tidy+ program. def call(context) error_file = Tempfile.new('webgen-tidy') error_file.close `tidy -v 2>&1` if $?.exitstatus != 0 raise Webgen::CommandNotFoundError.new('tidy', self.class.name, context.dest_node) end cmd = "tidy -q -f #{error_file.path} #{context.website.config['contentprocessor.tidy.options']}" result = IO.popen(cmd, 'r+') do |io| io.write(context.content) io.close_write io.read end if $?.exitstatus != 0 File.readlines(error_file.path).each do |line| log($?.exitstatus == 1 ? :warn : :error) { "Tidy reported problems for <#{context.dest_node}>: #{line}" } end end context.content = result context end end end webgen-0.5.17/lib/webgen/contentprocessor/head.rb0000644002342000234200000001324312055517620021167 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Inserts additional links to CSS/JS files and other HTML head meta info directly before the HTML # head end tag. # # The data used by this content processor is taken from the Context object. Therefore this # processor should be the last in the processing pipeline so that all other processors have been # able to set the data. # # The key :cp_head of context.persistent is used (the normal # context.options won't do because the data needs to be shared 'backwards' during the # rendering) and it has to be a Hash with the following values: # # [:js_file] An array of already resolved relative or absolute paths to Javascript files. # [:js_inline] An array of Javascript fragments to be inserted directly into the head section. # [:css_file] An array of already resolved relative or absolute paths to CSS files. # [:css_inline] An array of CSS fragments to be inserted directly into the head section. # [:meta] A hash with key-value pairs from which meta tags are generated. The keys and # the values will be properly escaped before insertion. The entries in the meta # information meta of the content node are also used and take precedence over # these entries. # # Duplicate values will be removed from the above mentioned arrays before generating the output. class Head include Webgen::Loggable HTML_HEAD_END_RE = /<\/head\s*>/i #:nodoc: LINK_DOCUMENT_ATTRS = {'type' => 'text/html'} #:nodoc: LINK_DOCUMENT_TYPES = %w{start next prev contents index glossary chapter section subsection appendix help} #:nodoc: # Insert the additional header information. def call(context) require 'erb' context.content.sub!(HTML_HEAD_END_RE) do |match| result = '' # add content set programmatically if context.persistent[:cp_head].kind_of?(Hash) context.persistent[:cp_head][:js_file].uniq.each do |js_file| result += "\n" end if context.persistent[:cp_head][:js_file].kind_of?(Array) context.persistent[:cp_head][:js_inline].uniq.each do |content| result += "\n" end if context.persistent[:cp_head][:js_inline].kind_of?(Array) context.persistent[:cp_head][:css_file].uniq.each do |css_file| result += "\n" end if context.persistent[:cp_head][:css_file].kind_of?(Array) context.persistent[:cp_head][:css_inline].uniq.each do |content| result += "\n" end if context.persistent[:cp_head][:css_inline].kind_of?(Array) end ((context.persistent[:cp_head] || {})[:meta] || {}).merge(context.node['meta'] || {}).each do |name, content| result += "\n" end # add links to other languages of same page context.dest_node.tree.node_access[:acn][context.dest_node.acn]. select {|n| n.alcn != context.dest_node.alcn}.each do |node| context.dest_node.node_info[:used_meta_info_nodes] << node.alcn result += "\n" } context.dest_node.flag(:dirty) end end file end.compact end # Add user defined javascript and CSS links handle_files.call(link.delete('javascript')).each do |file| result += "\n" end handle_files.call(link.delete('css')).each do |file| result += "\n" end # add generic links specified via the +link+ meta information link.sort.each do |link_type, vals| link_type = link_type.downcase [vals].flatten.each do |val| val = {'href' => val} if val.kind_of?(String) val['rel'] ||= link_type val = LINK_DOCUMENT_ATTRS.merge(val) if LINK_DOCUMENT_TYPES.include?(link_type) if href = val.delete('href') href = handle_files.call(href).first else log(:error) { "No link target specified for link type '#{link_type}' in 'link' meta information in <#{context.node}>" } end if href s = "\n" end end end result + match end context end end end webgen-0.5.17/lib/webgen/contentprocessor/fragments.rb0000644002342000234200000000162312055517620022253 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Uses the HTML headers h1, h2, ..., h6 to generate nested fragment nodes. class Fragments include Webgen::WebsiteAccess # Generate the nested fragment nodes from context.content under # content.content_node but only if there is no associated :block data in # +context+ or the block is named +content+. def call(context) if !context[:block] || context[:block].name == 'content' sections = website.blackboard.invoke(:parse_html_headers, context.content) website.blackboard.invoke(:create_fragment_nodes, sections, context.content_node, website.blackboard.invoke(:source_paths)[context.content_node.node_info[:src]], context.content_node.meta_info['fragments_in_menu']) end context end end end webgen-0.5.17/lib/webgen/contentprocessor/kramdown.rb0000644002342000234200000000165112055517620022110 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Processes content in kramdown format (based on Markdown) using the +kramdown+ library. class Kramdown include Webgen::Loggable # Convert the content in +context+ to HTML. def call(context) require 'kramdown' require 'webgen/contentprocessor/kramdown/html' doc = ::Kramdown::Document.new(context.content, context.website.config['contentprocessor.kramdown.options'].merge(context.options['contentprocessor.kramdown.options'] || {})) context.content = KramdownHtmlConverter.convert(doc.root, doc.options, context) doc.warnings.each do |warn| log(:warn) { "Warning while parsing <#{context.ref_node}> with kramdown: #{warn}" } end context rescue LoadError raise Webgen::LoadError.new('kramdown', self.class.name, context.dest_node, 'kramdown') end end end webgen-0.5.17/lib/webgen/contentprocessor/rdiscount.rb0000644002342000234200000000072312055517620022277 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Processes content in Markdown markup with the fast +rdiscount+ library. class RDiscount # Convert the content in +context+ to HTML. def call(context) require 'rdiscount' context.content = ::RDiscount.new(context.content).to_html context rescue LoadError raise Webgen::LoadError.new('rdiscount', self.class.name, context.dest_node, 'rdiscount') end end end webgen-0.5.17/lib/webgen/contentprocessor/builder.rb0000644002342000234200000000161012055517620021707 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/common' module Webgen::ContentProcessor # Processes content that is valid Ruby to build an XML tree. This is done by using the +builder+ # library. class Builder # Process the content of +context+ which needs to be valid Ruby code. The special variable +xml+ # should be used to construct the XML content. def call(context) require 'builder' xml = ::Builder::XmlMarkup.new(:indent => 2) eval(context.content, binding, context.ref_node.alcn) context.content = xml.target! context rescue LoadError raise Webgen::LoadError.new('builder', self.class.name, context.dest_node, 'builder') rescue Exception => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, Webgen::Common.error_file(e), Webgen::Common.error_line(e)) end end end webgen-0.5.17/lib/webgen/contentprocessor/redcloth.rb0000644002342000234200000000106012055517620022064 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Processes content in Textile format using the +redcloth+ library. class RedCloth # Convert the content in +context+ to HTML. def call(context) require 'redcloth' doc = ::RedCloth.new(context.content) doc.hard_breaks = context.website.config['contentprocessor.redcloth.hard_breaks'] context.content = doc.to_html context rescue LoadError raise Webgen::LoadError.new('redcloth', self.class.name, context.dest_node, 'RedCloth') end end end webgen-0.5.17/lib/webgen/contentprocessor/scss.rb0000644002342000234200000000125712055517620021243 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Processes content in sassy CSS markup (used for writing CSS files) using the +haml+ library. class Scss # Convert the content in +scss+ markup to CSS. def call(context) require 'sass' context.content = ::Sass::Engine.new(context.content, :filename => context.ref_node.alcn, :syntax => :scss).render context rescue LoadError raise Webgen::LoadError.new('sass', self.class.name, context.dest_node, 'haml') rescue ::Sass::SyntaxError => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node, (e.sass_line if e.sass_line)) end end end webgen-0.5.17/lib/webgen/contentprocessor/haml.rb0000644002342000234200000000177612055517620021217 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/common' module Webgen::ContentProcessor # Processes content in Haml markup using the +haml+ library. class Haml # Convert the content in +haml+ markup to HTML. def call(context) require 'haml' locals = {:context => context} context.content = ::Haml::Engine.new(context.content, :filename => context.ref_node.alcn). render(Object.new, locals) context rescue LoadError raise Webgen::LoadError.new('haml', self.class.name, context.dest_node, 'haml') rescue ::Haml::Error => e line = if e.line e.line + 1 else Webgen::Common.error_line(e) end raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node, line) rescue Exception => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, Webgen::Common.error_file(e), Webgen::Common.error_line(e)) end end end webgen-0.5.17/lib/webgen/contentprocessor/rdoc.rb0000644002342000234200000000106212055517620021211 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::ContentProcessor # Converts content in RDoc markup (the native Ruby documentation format) to HTML. Needs the newer # RDoc implementation provided as +rdoc+ gem! class RDoc # Convert the content in RDoc markup to HTML. def call(context) require 'rdoc/markup/to_html' context.content = ::RDoc::Markup::ToHtml.new.convert(context.content) context rescue LoadError raise Webgen::LoadError.new('rdoc/markup/to_html', self.class.name, context.dest_node, 'rdoc') end end end webgen-0.5.17/lib/webgen/contentprocessor/maruku.rb0000644002342000234200000000152112055517620021566 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'rexml/parsers/baseparser' class REXML::Parsers::BaseParser alias :"old_stream=" :"stream=" def stream=(source) self.old_stream=(source) @nsstack << Set.new(['webgen']) end end module Webgen::ContentProcessor # Processes content in Markdown format using the +maruku+ library. class Maruku # Convert the content in +context+ to HTML. def call(context) require 'maruku' $uid = 0 #fix for invalid fragment ids on second run context.content = ::Maruku.new(context.content, :on_error => :raise).to_html context rescue LoadError raise Webgen::LoadError.new('maruku', self.class.name, context.dest_node, 'maruku') rescue Exception => e raise Webgen::RenderError.new(e, self.class.name, context.dest_node, context.ref_node) end end end webgen-0.5.17/lib/webgen/output.rb0000644002342000234200000000536112055517620016216 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/common' module Webgen # Namespace for all classes that know how to write out node content. # # == Implementing an output class # # Output classes know how to write rendered node data to an output location. # # An output class must respond to three methods # # [exists?(path)] # Return +true+ if the output path exists. # [delete(path)] # Delete the given output path. # [write(path, data, type)] # Write the +data+ to the given output +path+. The parameter +data+ is either a String with the # content or a Webgen::Path::SourceIO object. The parameter +type+ specifies the type of the to # be written path: :file or :directory. # [read(path, mode = 'rb')] # Return the content of the given path if it exists or raise an error otherwise. The parameter # +mode+ specifies the mode in which the path should be opened and defaults to reading in binary # mode. # # It seems a bit odd that an output instance has to implement reading functionality. However, # consider the case where you want webgen to render a website programmatically and *use* the # output. In this case you need a way to get to content of the written files! This functionality # is used, for example, in the webgui. # # == Sample Output Class # # Following is a simple but actually used (by the webgui) output class which stores the written # nodes in a hash in memory: # # class MemoryOutput # include Webgen::WebsiteAccess # # attr_reader :data # # def initialize # @data = {} # end # # def exists?(path) # @data.has_key?(path) # end # # def delete(path) # @data.delete(path) # end # # def write(path, io, type = :file) # @data[path] = [(io.kind_of?(String) ? io : io.data), type] # end # # def read(path, mode = 'rb') # path = File.join('/', path) # raise "No such file #{path}" unless @data[path] && @data[path].last == :file # @data[path].first # end # end # # WebsiteAccess.website.config.output(['MemoryOutput']) # # The last line is used to tell webgen to use this new output class instead of the default one. # module Output autoload :FileSystem, 'webgen/output/filesystem' # Returns an instance of the configured output class. def self.instance classes = (WebsiteAccess.website.cache.volatile[:classes] ||= {}) unless classes.has_key?(:output_instance) klass, *args = WebsiteAccess.website.config['output'] classes[:output_instance] = Common.const_for_name(klass).new(*args) end classes[:output_instance] end end end webgen-0.5.17/lib/webgen/languages.rb0000644002342000234200000004353412055517620016630 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Describes a human language which is uniquely identfied by a three letter code and, optionally, # by an alternative three letter or a two letter code. class Language include Comparable # An array containing the language codes for the language. attr_reader :codes # The english description of the language. attr_reader :description # Create a new language. +codes+ has to be an array containing three strings: the three letter # code, the alternative three letter code and the two letter code. If one is not available for # the language, it has to be +nil+. def initialize(codes, description) @codes = codes @description = description end # The two letter code. def code2chars @codes[2] end # The three letter code. def code3chars @codes[0] end # The alternative three letter code. def code3chars_alternative @codes[1] end # The textual representation of the language. def to_s code2chars || code3chars end alias_method :to_str, :to_s def inspect #:nodoc: "#" end def <=>(other) #:nodoc: self.to_s <=> other.to_s end def eql?(other) #:nodoc: (other.is_a?(self.class) && other.to_s == self.to_s) || (other.is_a?(String) && self.to_s == other) end def hash #:nodoc: self.to_s.hash end end # Used for managing human languages. module LanguageManager # Return a +Language+ object for the given language code or +nil+ if no such object exists. def self.language_for_code(code) languages[code] end # Return an array of +Language+ objects whose description match the given +text+. def self.find_language(text) languages.values.find_all {|lang| /.*#{Regexp.escape(text)}.*/i =~ lang.description}.uniq.sort end # Return all available languages as a Hash. The keys are the language codes and the values are # the +Language+ objects for them. def self.languages unless defined?(@@languages) @@languages = {} started = nil data = File.readlines(__FILE__).each do |l| next if !started && (started = (l == '__END__')) data = l.chomp.split('|').collect {|f| f.empty? ? nil : f} lang = Language.new(data[0..2], data[3]) @@languages[lang.code2chars] ||= lang unless lang.code2chars.nil? @@languages[lang.code3chars] ||= lang unless lang.code3chars.nil? @@languages[lang.code3chars_alternative] ||= lang unless lang.code3chars_alternative.nil? end @@languages.freeze end @@languages end end end __END__ aar||aa|Afar|afar abk||ab|Abkhazian|abkhaze ace|||Achinese|aceh ach|||Acoli|acoli ada|||Adangme|adangme ady|||Adyghe; Adygei|adyghé afa|||Afro-Asiatic (Other)|afro-asiatiques, autres langues afh|||Afrihili|afrihili afr||af|Afrikaans|afrikaans ain|||Ainu|aïnou aka||ak|Akan|akan akk|||Akkadian|akkadien alb|sqi|sq|Albanian|albanais ale|||Aleut|aléoute alg|||Algonquian languages|algonquines, langues alt|||Southern Altai|altai du Sud amh||am|Amharic|amharique ang|||English, Old (ca.450-1100)|anglo-saxon (ca.450-1100) anp|||Angika|angika apa|||Apache languages|apache ara||ar|Arabic|arabe arc|||Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)|araméen d'empire (700-300 BCE) arg||an|Aragonese|aragonais arm|hye|hy|Armenian|arménien arn|||Mapudungun; Mapuche|mapudungun; mapuche; mapuce arp|||Arapaho|arapaho art|||Artificial (Other)|artificielles, autres langues arw|||Arawak|arawak asm||as|Assamese|assamais ast|||Asturian; Bable; Leonese; Asturleonese|asturien; bable; léonais; asturoléonais ath|||Athapascan languages|athapascanes, langues aus|||Australian languages|australiennes, langues ava||av|Avaric|avar ave||ae|Avestan|avestique awa|||Awadhi|awadhi aym||ay|Aymara|aymara aze||az|Azerbaijani|azéri bad|||Banda languages|banda, langues bai|||Bamileke languages|bamilékés, langues bak||ba|Bashkir|bachkir bal|||Baluchi|baloutchi bam||bm|Bambara|bambara ban|||Balinese|balinais baq|eus|eu|Basque|basque bas|||Basa|basa bat|||Baltic (Other)|baltiques, autres langues bej|||Beja; Bedawiyet|bedja bel||be|Belarusian|biélorusse bem|||Bemba|bemba ben||bn|Bengali|bengali ber|||Berber (Other)|berbères, autres langues bho|||Bhojpuri|bhojpuri bih||bh|Bihari|bihari bik|||Bikol|bikol bin|||Bini; Edo|bini; edo bis||bi|Bislama|bichlamar bla|||Siksika|blackfoot bnt|||Bantu (Other)|bantoues, autres langues bos||bs|Bosnian|bosniaque bra|||Braj|braj bre||br|Breton|breton btk|||Batak languages|batak, langues bua|||Buriat|bouriate bug|||Buginese|bugi bul||bg|Bulgarian|bulgare bur|mya|my|Burmese|birman byn|||Blin; Bilin|blin; bilen cad|||Caddo|caddo cai|||Central American Indian (Other)|indiennes d'Amérique centrale, autres langues car|||Galibi Carib|karib; galibi; carib cat||ca|Catalan; Valencian|catalan; valencien cau|||Caucasian (Other)|caucasiennes, autres langues ceb|||Cebuano|cebuano cel|||Celtic (Other)|celtiques, autres langues cha||ch|Chamorro|chamorro chb|||Chibcha|chibcha che||ce|Chechen|tchétchène chg|||Chagatai|djaghataï chi|zho|zh|Chinese|chinois chk|||Chuukese|chuuk chm|||Mari|mari chn|||Chinook jargon|chinook, jargon cho|||Choctaw|choctaw chp|||Chipewyan; Dene Suline|chipewyan chr|||Cherokee|cherokee chu||cu|Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic|slavon d'église; vieux slave; slavon liturgique; vieux bulgare chv||cv|Chuvash|tchouvache chy|||Cheyenne|cheyenne cmc|||Chamic languages|chames, langues cop|||Coptic|copte cor||kw|Cornish|cornique cos||co|Corsican|corse cpe|||Creoles and pidgins, English based (Other)|créoles et pidgins anglais, autres cpf|||Creoles and pidgins, French-based (Other)|créoles et pidgins français, autres cpp|||Creoles and pidgins, Portuguese-based (Other)|créoles et pidgins portugais, autres cre||cr|Cree|cree crh|||Crimean Tatar; Crimean Turkish|tatar de Crimé crp|||Creoles and pidgins (Other)|créoles et pidgins divers csb|||Kashubian|kachoube cus|||Cushitic (Other)|couchitiques, autres langues cze|ces|cs|Czech|tchèque dak|||Dakota|dakota dan||da|Danish|danois dar|||Dargwa|dargwa day|||Land Dayak languages|dayak, langues del|||Delaware|delaware den|||Slave (Athapascan)|esclave (athapascan) dgr|||Dogrib|dogrib din|||Dinka|dinka div||dv|Divehi; Dhivehi; Maldivian|maldivien doi|||Dogri|dogri dra|||Dravidian (Other)|dravidiennes, autres langues dsb|||Lower Sorbian|bas-sorabe dua|||Duala|douala dum|||Dutch, Middle (ca.1050-1350)|néerlandais moyen (ca. 1050-1350) dut|nld|nl|Dutch; Flemish|néerlandais; flamand dyu|||Dyula|dioula dzo||dz|Dzongkha|dzongkha efi|||Efik|efik egy|||Egyptian (Ancient)|égyptien eka|||Ekajuk|ekajuk elx|||Elamite|élamite eng||en|English|anglais enm|||English, Middle (1100-1500)|anglais moyen (1100-1500) epo||eo|Esperanto|espéranto est||et|Estonian|estonien ewe||ee|Ewe|éwé ewo|||Ewondo|éwondo fan|||Fang|fang fao||fo|Faroese|féroïen fat|||Fanti|fanti fij||fj|Fijian|fidjien fil|||Filipino; Pilipino|filipino; pilipino fin||fi|Finnish|finnois fiu|||Finno-Ugrian (Other)|finno-ougriennes, autres langues fon|||Fon|fon fre|fra|fr|French|français frm|||French, Middle (ca.1400-1600)|français moyen (1400-1600) fro|||French, Old (842-ca.1400)|français ancien (842-ca.1400) frr|||Northern Frisian|frison septentrional frs|||Eastern Frisian|frison oriental fry||fy|Western Frisian|frison occidental ful||ff|Fulah|peul fur|||Friulian|frioulan gaa|||Ga|ga gay|||Gayo|gayo gba|||Gbaya|gbaya gem|||Germanic (Other)|germaniques, autres langues geo|kat|ka|Georgian|géorgien ger|deu|de|German|allemand gez|||Geez|guèze gil|||Gilbertese|kiribati gla||gd|Gaelic; Scottish Gaelic|gaélique; gaélique écossais gle||ga|Irish|irlandais glg||gl|Galician|galicien glv||gv|Manx|manx; mannois gmh|||German, Middle High (ca.1050-1500)|allemand, moyen haut (ca. 1050-1500) goh|||German, Old High (ca.750-1050)|allemand, vieux haut (ca. 750-1050) gon|||Gondi|gond gor|||Gorontalo|gorontalo got|||Gothic|gothique grb|||Grebo|grebo grc|||Greek, Ancient (to 1453)|grec ancien (jusqu'à 1453) gre|ell|el|Greek, Modern (1453-)|grec moderne (après 1453) grn||gn|Guarani|guarani gsw|||Swiss German; Alemannic; Alsatian|suisse alémanique; alémanique; alsacien guj||gu|Gujarati|goudjrati gwi|||Gwich'in|gwich'in hai|||Haida|haida hat||ht|Haitian; Haitian Creole|haïtien; créole haïtien hau||ha|Hausa|haoussa haw|||Hawaiian|hawaïen heb||he|Hebrew|hébreu her||hz|Herero|herero hil|||Hiligaynon|hiligaynon him|||Himachali|himachali hin||hi|Hindi|hindi hit|||Hittite|hittite hmn|||Hmong|hmong hmo||ho|Hiri Motu|hiri motu hsb|||Upper Sorbian|haut-sorabe hun||hu|Hungarian|hongrois hup|||Hupa|hupa iba|||Iban|iban ibo||ig|Igbo|igbo ice|isl|is|Icelandic|islandais ido||io|Ido|ido iii||ii|Sichuan Yi; Nuosu|yi de Sichuan ijo|||Ijo languages|ijo, langues iku||iu|Inuktitut|inuktitut ile||ie|Interlingue; Occidental|interlingue ilo|||Iloko|ilocano ina||ia|Interlingua (International Auxiliary Language Association)|interlingua (langue auxiliaire internationale) inc|||Indic (Other)|indo-aryennes, autres langues ind||id|Indonesian|indonésien ine|||Indo-European (Other)|indo-européennes, autres langues inh|||Ingush|ingouche ipk||ik|Inupiaq|inupiaq ira|||Iranian (Other)|iraniennes, autres langues iro|||Iroquoian languages|iroquoises, langues (famille) ita||it|Italian|italien jav||jv|Javanese|javanais jbo|||Lojban|lojban jpn||ja|Japanese|japonais jpr|||Judeo-Persian|judéo-persan jrb|||Judeo-Arabic|judéo-arabe kaa|||Kara-Kalpak|karakalpak kab|||Kabyle|kabyle kac|||Kachin; Jingpho|kachin; jingpho kal||kl|Kalaallisut; Greenlandic|groenlandais kam|||Kamba|kamba kan||kn|Kannada|kannada kar|||Karen languages|karen, langues kas||ks|Kashmiri|kashmiri kau||kr|Kanuri|kanouri kaw|||Kawi|kawi kaz||kk|Kazakh|kazakh kbd|||Kabardian|kabardien kha|||Khasi|khasi khi|||Khoisan (Other)|khoisan, autres langues khm||km|Central Khmer|khmer central kho|||Khotanese|khotanais kik||ki|Kikuyu; Gikuyu|kikuyu kin||rw|Kinyarwanda|rwanda kir||ky|Kirghiz; Kyrgyz|kirghiz kmb|||Kimbundu|kimbundu kok|||Konkani|konkani kom||kv|Komi|kom kon||kg|Kongo|kongo kor||ko|Korean|coréen kos|||Kosraean|kosrae kpe|||Kpelle|kpellé krc|||Karachay-Balkar|karatchai balkar krl|||Karelian|carélien kro|||Kru languages|krou, langues kru|||Kurukh|kurukh kua||kj|Kuanyama; Kwanyama|kuanyama; kwanyama kum|||Kumyk|koumyk kur||ku|Kurdish|kurde kut|||Kutenai|kutenai lad|||Ladino|judéo-espagnol lah|||Lahnda|lahnda lam|||Lamba|lamba lao||lo|Lao|lao lat||la|Latin|latin lav||lv|Latvian|letton lez|||Lezghian|lezghien lim||li|Limburgan; Limburger; Limburgish|limbourgeois lin||ln|Lingala|lingala lit||lt|Lithuanian|lituanien lol|||Mongo|mongo loz|||Lozi|lozi ltz||lb|Luxembourgish; Letzeburgesch|luxembourgeois lua|||Luba-Lulua|luba-lulua lub||lu|Luba-Katanga|luba-katanga lug||lg|Ganda|ganda lui|||Luiseno|luiseno lun|||Lunda|lunda luo|||Luo (Kenya and Tanzania)|luo (Kenya et Tanzanie) lus|||Lushai|lushai mac|mkd|mk|Macedonian|macédonien mad|||Madurese|madourais mag|||Magahi|magahi mah||mh|Marshallese|marshall mai|||Maithili|maithili mak|||Makasar|makassar mal||ml|Malayalam|malayalam man|||Mandingo|mandingue mao|mri|mi|Maori|maori map|||Austronesian (Other)|malayo-polynésiennes, autres langues mar||mr|Marathi|marathe mas|||Masai|massaï may|msa|ms|Malay|malais mdf|||Moksha|moksa mdr|||Mandar|mandar men|||Mende|mendé mga|||Irish, Middle (900-1200)|irlandais moyen (900-1200) mic|||Mi'kmaq; Micmac|mi'kmaq; micmac min|||Minangkabau|minangkabau mis|||Uncoded languages|langues non codées mkh|||Mon-Khmer (Other)|môn-khmer, autres langues mlg||mg|Malagasy|malgache mlt||mt|Maltese|maltais mnc|||Manchu|mandchou mni|||Manipuri|manipuri mno|||Manobo languages|manobo, langues moh|||Mohawk|mohawk mol||mo|Moldavian|moldave mon||mn|Mongolian|mongol mos|||Mossi|moré mul|||Multiple languages|multilingue mun|||Munda languages|mounda, langues mus|||Creek|muskogee mwl|||Mirandese|mirandais mwr|||Marwari|marvari myn|||Mayan languages|maya, langues myv|||Erzya|erza nah|||Nahuatl languages|nahuatl, langues nai|||North American Indian|indiennes d'Amérique du Nord, autres langues nap|||Neapolitan|napolitain nau||na|Nauru|nauruan nav||nv|Navajo; Navaho|navaho nbl||nr|Ndebele, South; South Ndebele|ndébélé du Sud nde||nd|Ndebele, North; North Ndebele|ndébélé du Nord ndo||ng|Ndonga|ndonga nds|||Low German; Low Saxon; German, Low; Saxon, Low|bas allemand; bas saxon; allemand, bas; saxon, bas nep||ne|Nepali|népalais new|||Nepal Bhasa; Newari|nepal bhasa; newari nia|||Nias|nias nic|||Niger-Kordofanian (Other)|nigéro-congolaises, autres langues niu|||Niuean|niué nno||nn|Norwegian Nynorsk; Nynorsk, Norwegian|norvégien nynorsk; nynorsk, norvégien nob||nb|Bokmål, Norwegian; Norwegian Bokmål|norvégien bokmål nog|||Nogai|nogaï; nogay non|||Norse, Old|norrois, vieux nor||no|Norwegian|norvégien nqo|||N'Ko|n'ko nso|||Pedi; Sepedi; Northern Sotho|pedi; sepedi; sotho du Nord nub|||Nubian languages|nubiennes, langues nwc|||Classical Newari; Old Newari; Classical Nepal Bhasa|newari classique nya||ny|Chichewa; Chewa; Nyanja|chichewa; chewa; nyanja nym|||Nyamwezi|nyamwezi nyn|||Nyankole|nyankolé nyo|||Nyoro|nyoro nzi|||Nzima|nzema oci||oc|Occitan (post 1500); Provençal|occitan (après 1500); provençal oji||oj|Ojibwa|ojibwa ori||or|Oriya|oriya orm||om|Oromo|galla osa|||Osage|osage oss||os|Ossetian; Ossetic|ossète ota|||Turkish, Ottoman (1500-1928)|turc ottoman (1500-1928) oto|||Otomian languages|otomangue, langues paa|||Papuan (Other)|papoues, autres langues pag|||Pangasinan|pangasinan pal|||Pahlavi|pahlavi pam|||Pampanga; Kapampangan|pampangan pan||pa|Panjabi; Punjabi|pendjabi pap|||Papiamento|papiamento pau|||Palauan|palau peo|||Persian, Old (ca.600-400 B.C.)|perse, vieux (ca. 600-400 av. J.-C.) per|fas|fa|Persian|persan phi|||Philippine (Other)|philippines, autres langues phn|||Phoenician|phénicien pli||pi|Pali|pali pol||pl|Polish|polonais pon|||Pohnpeian|pohnpei por||pt|Portuguese|portugais pra|||Prakrit languages|prâkrit pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500) pus||ps|Pushto; Pashto|pachto qaa-qtz|||Reserved for local use|réservée à l'usage local que||qu|Quechua|quechua raj|||Rajasthani|rajasthani rap|||Rapanui|rapanui rar|||Rarotongan; Cook Islands Maori|rarotonga; maori des îles Cook roa|||Romance (Other)|romanes, autres langues roh||rm|Romansh|romanche rom|||Romany|tsigane rum|ron|ro|Romanian|roumain run||rn|Rundi|rundi rup|||Aromanian; Arumanian; Macedo-Romanian|aroumain; macédo-roumain rus||ru|Russian|russe sad|||Sandawe|sandawe sag||sg|Sango|sango sah|||Yakut|iakoute sai|||South American Indian (Other)|indiennes d'Amérique du Sud, autres langues sal|||Salishan languages|salish, langues sam|||Samaritan Aramaic|samaritain san||sa|Sanskrit|sanskrit sas|||Sasak|sasak sat|||Santali|santal scc|srp|sr|Serbian|serbe scn|||Sicilian|sicilien sco|||Scots|écossais scr|hrv|hr|Croatian|croate sel|||Selkup|selkoupe sem|||Semitic (Other)|sémitiques, autres langues sga|||Irish, Old (to 900)|irlandais ancien (jusqu'à 900) sgn|||Sign Languages|langues des signes shn|||Shan|chan sid|||Sidamo|sidamo sin||si|Sinhala; Sinhalese|singhalais sio|||Siouan languages|sioux, langues sit|||Sino-Tibetan (Other)|sino-tibétaines, autres langues sla|||Slavic (Other)|slaves, autres langues slo|slk|sk|Slovak|slovaque slv||sl|Slovenian|slovène sma|||Southern Sami|sami du Sud sme||se|Northern Sami|sami du Nord smi|||Sami languages (Other)|sami, autres langues smj|||Lule Sami|sami de Lule smn|||Inari Sami|sami d'Inari smo||sm|Samoan|samoan sms|||Skolt Sami|sami skolt sna||sn|Shona|shona snd||sd|Sindhi|sindhi snk|||Soninke|soninké sog|||Sogdian|sogdien som||so|Somali|somali son|||Songhai languages|songhai, langues sot||st|Sotho, Southern|sotho du Sud spa||es|Spanish; Castilian|espagnol; castillan srd||sc|Sardinian|sarde srn|||Sranan Tongo|sranan tongo srr|||Serer|sérère ssa|||Nilo-Saharan (Other)|nilo-sahariennes, autres langues ssw||ss|Swati|swati suk|||Sukuma|sukuma sun||su|Sundanese|soundanais sus|||Susu|soussou sux|||Sumerian|sumérien swa||sw|Swahili|swahili swe||sv|Swedish|suédois syc|||Classical Syriac|syriaque classique syr|||Syriac|syriaque tah||ty|Tahitian|tahitien tai|||Tai (Other)|thaïes, autres langues tam||ta|Tamil|tamoul tat||tt|Tatar|tatar tel||te|Telugu|télougou tem|||Timne|temne ter|||Tereno|tereno tet|||Tetum|tetum tgk||tg|Tajik|tadjik tgl||tl|Tagalog|tagalog tha||th|Thai|thaï tib|bod|bo|Tibetan|tibétain tig|||Tigre|tigré tir||ti|Tigrinya|tigrigna tiv|||Tiv|tiv tkl|||Tokelau|tokelau tlh|||Klingon; tlhIngan-Hol|klingon tli|||Tlingit|tlingit tmh|||Tamashek|tamacheq tog|||Tonga (Nyasa)|tonga (Nyasa) ton||to|Tonga (Tonga Islands)|tongan (Îles Tonga) tpi|||Tok Pisin|tok pisin tsi|||Tsimshian|tsimshian tsn||tn|Tswana|tswana tso||ts|Tsonga|tsonga tuk||tk|Turkmen|turkmène tum|||Tumbuka|tumbuka tup|||Tupi languages|tupi, langues tur||tr|Turkish|turc tut|||Altaic (Other)|altaïques, autres langues tvl|||Tuvalu|tuvalu twi||tw|Twi|twi tyv|||Tuvinian|touva udm|||Udmurt|oudmourte uga|||Ugaritic|ougaritique uig||ug|Uighur; Uyghur|ouïgour ukr||uk|Ukrainian|ukrainien umb|||Umbundu|umbundu und|||Undetermined|indéterminée urd||ur|Urdu|ourdou uzb||uz|Uzbek|ouszbek vai|||Vai|vaï ven||ve|Venda|venda vie||vi|Vietnamese|vietnamien vol||vo|Volapük|volapük vot|||Votic|vote wak|||Wakashan languages|wakashennes, langues wal|||Walamo|walamo war|||Waray|waray was|||Washo|washo wel|cym|cy|Welsh|gallois wen|||Sorbian languages|sorabes, langues wln||wa|Walloon|wallon wol||wo|Wolof|wolof xal|||Kalmyk; Oirat|kalmouk; oïrat xho||xh|Xhosa|xhosa yao|||Yao|yao yap|||Yapese|yapois yid||yi|Yiddish|yiddish yor||yo|Yoruba|yoruba ypk|||Yupik languages|yupik, langues zap|||Zapotec|zapotèque zbl|||Blissymbols; Blissymbolics; Bliss|symboles Bliss; Bliss zen|||Zenaga|zenaga zha||za|Zhuang; Chuang|zhuang; chuang znd|||Zande languages|zandé, langues zul||zu|Zulu|zoulou zun|||Zuni|zuni zxx|||No linguistic content|pas de contenu linguistique zza|||Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki|zaza; dimili; dimli; kirdki; kirmanjki; zazaki webgen-0.5.17/lib/webgen/sourcehandler/0000755002342000234200000000000012055517620017162 5ustar duckdc-userswebgen-0.5.17/lib/webgen/sourcehandler/template.rb0000644002342000234200000000427512055517620021332 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Source handler for handling template files in Webgen Page Format. class Template include Webgen::WebsiteAccess include Webgen::Loggable include Base # Create a template node for +path+. def create_node(path) page = page_from_path(path) super(path) do |node| node.node_info[:page] = page end end # Return the template chain for +node+. def templates_for_node(node, lang = node.lang) cached_template = (website.cache.volatile[[node.alcn, :templates]] ||= {}) if cached_template[lang] template_node = cached_template[lang] elsif node['template'].kind_of?(String) template_node = node.resolve(node['template'], lang) if template_node.nil? log(:warn) { "Specified template '#{node['template']}' for <#{node}> not found, using default template!" } template_node = default_template(node.parent, lang) end cached_template[lang] = template_node elsif node.meta_info.has_key?('template') && node['template'].nil? template_node = cached_template[lang] = nil else log(:info) { "Using default template in language '#{lang}' for <#{node}>" } template_node = default_template(node.parent, lang) if template_node == node && !node.parent.is_root? template_node = default_template(node.parent.parent, lang) end cached_template[lang] = template_node end if template_node.nil? [] else (template_node == node ? [] : templates_for_node(template_node, lang) + [template_node]) end end # Return the default template for the directory node +dir+. If the template node is not found, # the parent directories are searched. def default_template(dir_node, lang) template_node = dir_node.resolve(website.config['sourcehandler.template.default_template'], lang) if template_node.nil? if dir_node.is_root? log(:warn) { "No default template in root directory found!" } else template_node = default_template(dir_node.parent, lang) end end template_node end end end webgen-0.5.17/lib/webgen/sourcehandler/feed.rb0000644002342000234200000000622312055517620020415 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Source handler for creating atom and/or rss feeds. class Feed include Webgen::WebsiteAccess include Base # The mandatory keys that need to be set in a feed file. MANDATORY_INFOS = %W[site_url author entries] def initialize # :nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Create atom and/or rss feed files from +path+. def create_node(path) page = page_from_path(path) path.meta_info['link'] ||= path.parent_path if MANDATORY_INFOS.any? {|t| path.meta_info[t].nil?} raise Webgen::NodeCreationError.new("At least one of #{MANDATORY_INFOS.join('/')} is missing", self.class.name, path) end create_feed_node = lambda do |type| path.ext = type super(path) do |node| node.node_info[:feed] = page node.node_info[:feed_type] = type end end nodes = [] nodes << create_feed_node['atom'] if path.meta_info['atom'] nodes << create_feed_node['rss'] if path.meta_info['rss'] nodes end # Return the rendered feed represented by +node+. def content(node) website.cache[[:sourcehandler_feed, node.node_info[:src]]] = feed_entries(node).map {|n| n.alcn} block_name = node.node_info[:feed_type] + '_template' if node.node_info[:feed].blocks.has_key?(block_name) node.node_info[:feed].blocks[block_name].render(Webgen::Context.new(:chain => [node])).content else chain = [node.resolve("/templates/#{node.node_info[:feed_type]}_feed.template"), node] node.node_info[:used_nodes] << chain.first.alcn chain.first.node_info[:page].blocks['content'].render(Webgen::Context.new(:chain => chain)).content end end # Return the entries for the feed +node+. def feed_entries(node) nr_items = (node['number_of_entries'].to_i == 0 ? 10 : node['number_of_entries'].to_i) patterns = [node['entries']].flatten.map {|pat| Webgen::Path.make_absolute(node.parent.alcn, pat)} node.tree.node_access[:alcn].values. select {|node| patterns.any? {|pat| node =~ pat} && node.node_info[:page]}. sort {|a,b| a['modified_at'] <=> b['modified_at']}.reverse[0, nr_items] end # Return the feed link URL for the feed +node+. def feed_link(node) Webgen::Node.url(File.join(node['site_url'], node.tree[node['link']].path), false) end # Return the content of an +entry+ of the feed +node+. def entry_content(node, entry) entry.node_info[:page].blocks[node['content_block_name'] || 'content'].render(Webgen::Context.new(:chain => [entry])).content end ####### private ####### # Check if the any of the nodes used by this feed +node+ have changed and then mark the node as # dirty. def node_changed?(node) return if node.node_info[:processor] != self.class.name entries = node.feed_entries node.flag(:dirty) if entries.map {|n| n.alcn } != website.cache[[:sourcehandler_feed, node.node_info[:src]]] || entries.any? {|n| n.changed?} end end end webgen-0.5.17/lib/webgen/sourcehandler/directory.rb0000644002342000234200000000157112055517620021517 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Handles directory source paths. class Directory include Base include Webgen::WebsiteAccess def initialize # :nodoc: website.blackboard.add_service(:create_directories, method(:create_directories)) end # Recursively create the directories specified in +dirname+ under +parent+ (a leading slash is # ignored). The path +path+ is the path that lead to the creation of these directories. def create_directories(parent, dirname, path) dirname.sub(/^\//, '').split('/').each do |dir| dir_path = Webgen::Path.new(File.join(parent.alcn, dir, '/'), path) nodes = website.blackboard.invoke(:create_nodes, dir_path, self) do |dir_path| node_exists?(dir_path) || create_node(dir_path) end parent = nodes.first end parent end end end webgen-0.5.17/lib/webgen/sourcehandler/fragment.rb0000644002342000234200000000504612055517620021317 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Handles page fragment nodes and provides utility methods for parsing HTML headers and generating # fragment nodes from them. class Fragment include Base include Webgen::WebsiteAccess HTML_HEADER_REGEXP = /|\s([^>]*)>)(.*?)<\/h\1\s*>/i HTML_ATTR_REGEXP = /\s*(\w+)\s*=\s*('|")(.+?)\2\s*/ # Parse the string +content+ for headers +h1+, ..., +h6+ and return the found, nested sections. # # Only those headers are used which have an +id+ attribute set. The method returns a list of # arrays with entries level, id, title, sub sections where sub sections is # such a list again. def parse_html_headers(content) sections = [] stack = [] content.scan(HTML_HEADER_REGEXP).each do |level,attrs,title| next if attrs.nil? id_attr = attrs.scan(HTML_ATTR_REGEXP).find {|name,sep,value| name == 'id'} next if id_attr.nil? id = id_attr[2] section = [level.to_i, id, title, []] success = false while !success if stack.empty? sections << section stack << section success = true elsif stack.last.first < section.first stack.last.last << section stack << section success = true else stack.pop end end end sections end # Create nested fragment nodes under +parent+ from +sections+ (which can be created using # +parse_html_headers+). +path+ is the source path that defines the fragments (which is not the # same as the creation path for +parent+). The meta information +in_menu+ of the fragment nodes # is set to the parameter +in_menu+ and the meta info +sort_info+ is calculated from the base # +si+ value. def create_fragment_nodes(sections, parent, path, in_menu, si = 1000) sections.each do |level, id, title, sub_sections| fragment_path = parent.alcn.sub(/#.*$/, '') + '#' + id node = website.blackboard.invoke(:create_nodes, Webgen::Path.new(fragment_path, path.source_path), self) do |cn_path| cn_path.meta_info['title'] = title cn_path.meta_info['in_menu'] = in_menu cn_path.meta_info['sort_info'] = si = si.succ create_node(cn_path, :parent => parent) end.first create_fragment_nodes(sub_sections, node, path, in_menu, si.succ) end end end end webgen-0.5.17/lib/webgen/sourcehandler/metainfo.rb0000644002342000234200000001060012055517620021306 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'pathname' require 'yaml' module Webgen::SourceHandler # Handles meta information files which provide meta information for other files. class Metainfo include Base include Webgen::WebsiteAccess CKEY = [:metainfo, :nodes] # Upon creation the object registers itself as listener for some node hooks. def initialize website.blackboard.add_listener(:node_meta_info_changed?, method(:node_meta_info_changed?)) website.blackboard.add_listener(:before_node_created, method(:before_node_created)) website.blackboard.add_listener(:before_node_deleted, method(:before_node_deleted)) website.blackboard.add_listener(:after_node_created, method(:after_node_created)) self.nodes ||= [] end # Create a meta info node from +path+. def create_node(path) page = page_from_path(path) super(path) do |node| [[:mi_paths, 'paths'], [:mi_alcn, 'alcn']].each do |mi_key, block_name| node.node_info[mi_key] = {} if page.blocks.has_key?(block_name) && (data = YAML::load(page.blocks[block_name].content)) data.each do |key, value| key = Webgen::Path.make_absolute(path.parent_path, key) node.node_info[mi_key][key] = value end end end mark_all_matched_dirty(node, :no_old_data) website.cache.permanent[[:sh_metainfo_node_mi, node.alcn]] = { :mi_paths => node.node_info[:mi_paths], :mi_alcn => node.node_info[:mi_alcn] } self.nodes << node unless self.nodes.include?(node) self.nodes = self.nodes.sort_by {|n| n.alcn} end end def nodes #:nodoc: website.cache.permanent[CKEY] end def nodes=(val) #:nodoc: website.cache.permanent[CKEY] = val end ####### private ####### # Return +true+ if any meta information for +node+ provided by +mi_node+ has changed. def meta_info_changed?(mi_node, node, option = nil) cached = website.cache.permanent[[:sh_metainfo_node_mi, mi_node.alcn]] (mi_node.node_info[:mi_paths].any? do |pattern, mi| Webgen::Path.match(node.node_info[:creation_path], pattern) && (option == :force || (!cached && option == :no_old_data) || mi != cached[:mi_paths][pattern]) end || mi_node.node_info[:mi_alcn].any? do |pattern, mi| node =~ pattern && (option == :force || (!cached && option == :no_old_data) || mi != cached[:mi_alcn][pattern]) end || (option == :no_old_data && cached && ((cached[:mi_paths].keys - mi_node.node_info[:mi_paths].keys).any? do |p| Webgen::Path.match(node.node_info[:creation_path], p) end || (cached[:mi_alcn].keys - mi_node.node_info[:mi_alcn].keys).any? do |p| node =~ p end) ) ) end # Mark all nodes that are matched by a path or an alcn specifcation in the meta info node +node+ # as dirty. def mark_all_matched_dirty(node, option = nil) node.tree.node_access[:alcn].each do |path, n| n.flag(:dirty_meta_info) if meta_info_changed?(node, n, option) end end # Update the meta info of matched path before a node is created. def before_node_created(path) self.nodes.each do |node| node.node_info[:mi_paths].each do |pattern, mi| path.meta_info.update(mi) if Webgen::Path.match(path, pattern) end end end # Update the meta information of a matched alcn after the node has been created. def after_node_created(node) self.nodes.each do |n| n.node_info[:mi_alcn].each do |pattern, mi| node.meta_info.update(mi) if node =~ pattern end end end # Check if the +node+ has meta information from any meta info node and if so, if the meta info # node in question has changed. def node_meta_info_changed?(node) self.nodes.each do |n| if n.flagged?(:created) && meta_info_changed?(n, node) node.flag(:dirty_meta_info) return end end end # Delete the meta info node +node+ from the internal array. def before_node_deleted(node) return unless node.node_info[:processor] == self.class.name mark_all_matched_dirty(node, :force) website.cache.permanent.delete([:sh_metainfo_node_mi, node.alcn]) self.nodes.delete(node) end end end webgen-0.5.17/lib/webgen/sourcehandler/virtual.rb0000644002342000234200000001005012055517620021171 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'uri' require 'yaml' module Webgen::SourceHandler # Handles files which contain specifications for "virtual" nodes, ie. nodes that don't have real # source path. # # This can be used, for example, to provide multiple links to the same node. class Virtual include Base include Webgen::WebsiteAccess def initialize # :nodoc: website.blackboard.add_listener(:node_meta_info_changed?, method(:node_meta_info_changed?)) @path_data = {} end # Create all virtual nodes which are specified in +path+. def create_node(path) nodes = [] read_data(path).each do |key, meta_info| cache_data = [key, meta_info.dup] key = Webgen::Path.make_absolute(path.parent_path, key) + (key =~ /\/$/ ? '/' : '') temp_parent = create_directories(File.dirname(key), path) output_path = meta_info.delete('url') || key output_path = (URI::parse(output_path).absolute? || output_path =~ /^\// ? output_path : File.join(temp_parent.alcn, output_path)) if key =~ /\/$/ nodes << create_directory(key, path, meta_info) else nodes += website.blackboard.invoke(:create_nodes, Webgen::Path.new(key, path.source_path), self) do |cn_path| cn_path.meta_info.update(meta_info) super(cn_path, :output_path => output_path) do |n| n.node_info[:sh_virtual_cache_data] = cache_data end end end end nodes.compact end ####### private ####### # Read the entries from the virtual file +data+ and yield the path, and the meta info hash for # each entry. The +parent+ parameter is used for making absolute path values if relative ones # are given. def read_data(path) if !@path_data.has_key?(path) || path.changed? page = page_from_path(path) @path_data[path] = YAML::load(page.blocks['content'].content).collect do |key, meta_info| meta_info ||= {} meta_info['modified_at'] = path.meta_info['modified_at'] meta_info['no_output'] = true [key, meta_info] end if page.blocks.has_key?('content') @path_data[path] ||= [] end @path_data[path] end # Create the needed parent directories for a virtual node. def create_directories(dirname, path) parent = website.tree.root dirname.sub(/^\//, '').split('/').inject('/') do |parent_path, dir| parent_path = File.join(parent_path, dir) parent = create_directory(parent_path, path) parent_path end parent end # Create a virtual directory if it does not already exist. def create_directory(dir, path, meta_info = nil) dir_handler = website.cache.instance('Webgen::SourceHandler::Directory') parent = website.tree.root website.blackboard.invoke(:create_nodes, Webgen::Path.new(File.join(dir, '/'), path.source_path), dir_handler) do |temp_path| parent = dir_handler.node_exists?(temp_path) if (parent && (parent.node_info[:src] == path.source_path) && !meta_info.nil?) || !parent temp_path.meta_info.update(meta_info) if meta_info parent.flag(:reinit) if parent parent = dir_handler.create_node(temp_path) end parent end parent end # Check if the +node+ is virtual and if, if its meta information has changed. This can only be # the case if the node has been recreated in this run. def node_meta_info_changed?(node) path = website.blackboard.invoke(:source_paths)[node.node_info[:src]] return if node.node_info[:processor] != self.class.name || (path && !path.changed?) if !path node.flag(:dirty_meta_info) else old_data = node.node_info[:sh_virtual_cache_data] new_data = read_data(path).find {|key, mi| key == old_data.first} node.flag(:dirty_meta_info) if !new_data || old_data.last != new_data.last end end end end webgen-0.5.17/lib/webgen/sourcehandler/sitemap.rb0000644002342000234200000000332412055517620021153 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'uri' require 'time' module Webgen::SourceHandler # Source handler for creating an XML sitemap based on the specification of http://sitemaps.org. # # Uses Webgen::Common::Sitemap to generate the needed sitemap tree and to check if a sitemap has # changed. class Sitemap include Webgen::WebsiteAccess include Base # Create an XML sitemap from +path+. def create_node(path) page = page_from_path(path) path.ext = 'xml' if path.meta_info['site_url'].nil? raise Webgen::NodeCreationError.new("Needed information site_url is missing", self.class.name, path) end super(path) do |node| node.node_info[:sitemap] = page end end # Return the rendered feed represented by +node+. def content(node) if node.node_info[:sitemap].blocks.has_key?('template') node.node_info[:sitemap].blocks['template'].render(Webgen::Context.new(:chain => [node])).content else chain = [node.resolve("/templates/sitemap.template"), node] node.node_info[:used_nodes] << chain.first.alcn chain.first.node_info[:page].blocks['content'].render(Webgen::Context.new(:chain => chain)).content end end # Return the alcns of the sitemap +node+ as a flat list. def alcns(node) website.blackboard.invoke(:create_sitemap, node, node.lang, options_for_node(node)).to_lcn_list.flatten end ####### private ####### # Return a hash with the sitemap-creation-options set on the +node+. def options_for_node(node) options = {} node.meta_info.each {|k,v| options[k] = v if k =~ /\./} options end end end webgen-0.5.17/lib/webgen/sourcehandler/base.rb0000644002342000234200000003241412055517620020425 0ustar duckdc-users# -*- 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 webgen-0.5.17/lib/webgen/sourcehandler/copy.rb0000644002342000234200000000262212055517620020463 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Simple source handler for copying files from the source tree, either verbatim or by applying a # content processor. class Copy include Webgen::WebsiteAccess include Base # Create the node for +path+. If the +path+ has the name of a content processor as the first # part in the extension, it is preprocessed. def create_node(path) if path.ext.index('.') processor, *rest = path.ext.split('.') if website.blackboard.invoke(:content_processor_names).include?(processor) path.ext = rest.join('.') else processor = nil end end super(path) do |node| node.node_info[:preprocessor] = processor end end # Return either the preprocessed content of the +node+ or the IO object for the node's source # path depending on the node type. def content(node) io = website.blackboard.invoke(:source_paths)[node.node_info[:src]].io if node.node_info[:preprocessor] is_binary = website.blackboard.invoke(:content_processor_binary?, node.node_info[:preprocessor]) context = Webgen::Context.new(:content => io.data(is_binary ? 'rb' : 'r'), :chain => [node]) website.blackboard.invoke(:content_processor, node.node_info[:preprocessor]).call(context) context.content else io end end end end webgen-0.5.17/lib/webgen/sourcehandler/page.rb0000644002342000234200000000425012055517620020424 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # Source handler for handling content files in Webgen Page Format. class Page include Webgen::WebsiteAccess include Base def initialize #:nodoc: website.blackboard.add_listener(:node_meta_info_changed?, method(:meta_info_changed?)) end # Create a page file from +path+. def create_node(path) page = page_from_path(path) path.meta_info['lang'] ||= website.config['website.lang'] path.ext = 'html' if path.ext == 'page' super(path) do |node| node.node_info[:sh_page_node_mi] = Webgen::Page.meta_info_from_data(path.io.data) node.node_info[:page] = page end end # Render the block called +block_name+ of the given +node+. The parameter +templates+ is set to # the default template chain for the given +node+ but you can assign a custom template chain (an # array of template nodes) if need arises. Return +nil+ if an error occurred. def render_node(node, block_name = 'content', templates = website.blackboard.invoke(:templates_for_node, node)) chain = [templates, node].flatten if chain.first.node_info[:page].blocks.has_key?(block_name) node.node_info[:used_nodes] << chain.first.alcn context = chain.first.node_info[:page].blocks[block_name].render(Webgen::Context.new(:chain => chain)) context.content else raise Webgen::RenderError.new("No block named '#{block_name}'", self.class.name, node, chain.first) end end alias_method :content, :render_node ####### private ####### # Checks if the meta information provided by the file in Webgen Page Format changed. def meta_info_changed?(node) path = website.blackboard.invoke(:source_paths)[node.node_info[:src]] return if node.node_info[:processor] != self.class.name || (path && !path.changed?) if !path node.flag(:dirty_meta_info) else old_mi = node.node_info[:sh_page_node_mi] new_mi = Webgen::Page.meta_info_from_data(path.io.data) node.flag(:dirty_meta_info) if old_mi && old_mi != new_mi end end end end webgen-0.5.17/lib/webgen/sourcehandler/memory.rb0000644002342000234200000000260212055517620021017 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::SourceHandler # This source handler should be used for handling nodes that are created during the write # phase. class Memory include Webgen::WebsiteAccess include Base def initialize #:nodoc: website.blackboard.add_listener(:node_flagged) do |node, *flags| node.tree[node.node_info[:memory_source_alcn]].flag(:dirty) if node.node_info[:memory_source_alcn] end end # Create a node for the +path+. The +source_alcn+ specifies the node that creates this memory # node when written. You have two options for providing the content for this node: either you # set +data+ to a string (or a Webgen::Path::SourceIO object) or you provide a block which takes # the created node as argument and returns a string (or a Webgen::Path::SourceIO object). def create_node(path, source_alcn, data = nil) super(path) do |node| node.node_info[:memory_source_alcn] = source_alcn (@data ||= {})[node.alcn] = lambda { data || yield(node) } end end # Return the content of the memory +node+. If the memory node was not created in this webgen # run, it will be flagged for reinitialization (and therefore recreation). def content(node) if @data && @data[node.alcn] @data[node.alcn].call else node.flag(:reinit) nil end end end end webgen-0.5.17/lib/webgen/contentprocessor.rb0000644002342000234200000001134212055517620020264 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Namespace for all content processors. # # == Implementing a content processor # # Content processors are used to process the content of files, normally of files in Webgen Page # Format. A content processor only needs to respond to one method called +call+ and must not take # any parameters in the +initialize+ method. This method is invoked with a # Webgen::Context object that provides the whole context (especially the content # and the node chain) and the method needs to return this object. During processing a content # processor normally changes the content of the context but it does not need to. # # A self-written content processor does not need to be in the Webgen::ContentProcessor namespace # but all shipped ones do. # # After writing the content processor class, one needs to add it to the # contentprocessor.map hash so that it is used by webgen. The key for the entry needs to # be a short name without special characters or spaces and the value can be: # # * the class name, not as constant but as a string - then this content processor is assumed to # work with textual data -, or # # * an array with the class name like before and the type, which needs to be :binary or # :text. # # == Sample Content Processor # # The following sample content processor checks for a meta information +replace_key+ and replaces # strings of the form replace_key:path/to/node with a link to the specified node if it is # found. # # Note how the content node, the reference node and the destination node are used sothat the # correct meta information is used, the node is correctly resolved and the correct relative link # is calculated respectively! # # class SampleProcessor # # def call(context) # if !context.content_node['replace_key'].to_s.empty? # context.content.gsub!(/#{context.content_node['replace_key']}:([\w\/.]+)/ ) do |match| # link_node = context.ref_node.resolve($1, context.content_node.lang) # if link_node # context.dest_node.link_to(link_node, :lang => context.content_node.lang) # else # match # end # end # end # context # rescue Exception => e # raise "Error while replacing special key: #{e.message}" # end # # end # # Webgen::WebsiteAccess.website.config['contentprocessor.map']['replacer'] = 'SampleProcessor' # # Or one could equally write # # Webgen::WebsiteAccess.website.config['contentprocessor.map']['replacer'] = ['SampleProcessor', :text] # module ContentProcessor autoload :Tags, 'webgen/contentprocessor/tags' autoload :Blocks, 'webgen/contentprocessor/blocks' autoload :Maruku, 'webgen/contentprocessor/maruku' autoload :RedCloth, 'webgen/contentprocessor/redcloth' autoload :Erb, 'webgen/contentprocessor/erb' autoload :Haml, 'webgen/contentprocessor/haml' autoload :Sass, 'webgen/contentprocessor/sass' autoload :Scss, 'webgen/contentprocessor/scss' autoload :RDoc, 'webgen/contentprocessor/rdoc' autoload :Builder, 'webgen/contentprocessor/builder' autoload :Erubis, 'webgen/contentprocessor/erubis' autoload :RDiscount, 'webgen/contentprocessor/rdiscount' autoload :Fragments, 'webgen/contentprocessor/fragments' autoload :Head, 'webgen/contentprocessor/head' autoload :Tidy, 'webgen/contentprocessor/tidy' autoload :Xmllint, 'webgen/contentprocessor/xmllint' autoload :Kramdown, 'webgen/contentprocessor/kramdown' autoload :Less, 'webgen/contentprocessor/less' # Return the list of all available content processors. def self.list WebsiteAccess.website.config['contentprocessor.map'].keys end # Return the content processor object identified by +name+. def self.for_name(name) klass, cp_type = WebsiteAccess.website.config['contentprocessor.map'][name] klass.nil? ? nil : WebsiteAccess.website.cache.instance(klass) end # Return whether the content processor identified by +name+ is processing binary data. def self.is_binary?(name) WebsiteAccess.website.config['contentprocessor.map'][name].kind_of?(Array) && WebsiteAccess.website.config['contentprocessor.map'][name].last == :binary end # Helper class for accessing content processors in a Webgen::Context object. class AccessHash # Check if a content processor called +name+ exists. def has_key?(name) Webgen::ContentProcessor.list.include?(name) end # Return (and proboably initialize) the content processor called +name+. def [](name) Webgen::ContentProcessor.for_name(name) end end end end webgen-0.5.17/lib/webgen/tag.rb0000644002342000234200000000152312055517620015425 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Namespace for all classes that are useable by Webgen::ContentProcessor::Tag. # # Have a look at the documentation for Webgen::Tag::Base for details on how to implement a tag # class. module Tag autoload :Base, 'webgen/tag/base' autoload :Relocatable, 'webgen/tag/relocatable' autoload :Metainfo, 'webgen/tag/metainfo' autoload :Menu, 'webgen/tag/menu' autoload :BreadcrumbTrail, 'webgen/tag/breadcrumbtrail' autoload :Langbar, 'webgen/tag/langbar' autoload :IncludeFile, 'webgen/tag/includefile' autoload :ExecuteCommand, 'webgen/tag/executecommand' autoload :Coderay, 'webgen/tag/coderay' autoload :Date, 'webgen/tag/date' autoload :Sitemap, 'webgen/tag/sitemap' autoload :TikZ, 'webgen/tag/tikz' autoload :Link, 'webgen/tag/link' end end webgen-0.5.17/lib/webgen/sourcehandler.rb0000644002342000234200000002604412055517620017515 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/loggable' require 'webgen/common' require 'benchmark' module Webgen # Namespace for all classes that handle source paths. # # Have a look at Webgen::SourceHandler::Base for details on how to implement a source handler # class. module SourceHandler autoload :Base, 'webgen/sourcehandler/base' autoload :Copy, 'webgen/sourcehandler/copy' autoload :Directory, 'webgen/sourcehandler/directory' autoload :Metainfo, 'webgen/sourcehandler/metainfo' autoload :Template, 'webgen/sourcehandler/template' autoload :Page, 'webgen/sourcehandler/page' autoload :Fragment, 'webgen/sourcehandler/fragment' autoload :Virtual, 'webgen/sourcehandler/virtual' autoload :Feed, 'webgen/sourcehandler/feed' autoload :Sitemap, 'webgen/sourcehandler/sitemap' autoload :Memory, 'webgen/sourcehandler/memory' # This class is used by Website to do the actual rendering of the website. It # # * collects all source paths using the source classes # * creates nodes using the source handler classes # * writes changed nodes out using an output class # * deletes old nodes class Main include WebsiteAccess include Loggable def initialize #:nodoc: website.blackboard.add_service(:create_nodes, method(:create_nodes)) website.blackboard.add_service(:create_nodes_from_paths, method(:create_nodes_from_paths)) website.blackboard.add_service(:source_paths, method(:find_all_source_paths)) website.blackboard.add_listener(:node_meta_info_changed?, method(:meta_info_changed?)) website.blackboard.add_listener(:before_node_deleted) do |node| unless node.is_fragment? || node['no_output'] || node.path == '/' || node == node.tree.dummy_root website.blackboard.invoke(:output_instance).delete(node.path) end end if website.config['output.do_deletion'] end # Render the current website. Before the actual rendering is done, the sources are checked for # changes, i.e. nodes for deleted sources are deleted, nodes for new and changed sources are # updated. def render begin website.logger.mark_new_cycle if website.logger puts "Updating tree..." time = Benchmark.measure do website.cache.reset_volatile_cache update_tree end puts "...done in " + ('%2.4f' % time.real) + ' seconds' if !website.tree.root puts 'No source files found - maybe not a webgen website?' return nil end puts "Writing changed nodes..." time = Benchmark.measure do write_tree end puts "...done in " + ('%2.4f' % time.real) + ' seconds' end while website.tree.node_access[:alcn].any? {|name,node| node.flagged?(:created) || node.flagged?(:reinit)} :success rescue Webgen::Error raise rescue Exception => e raise Webgen::Error.new(e) end ####### private ####### # Update the website.tree by creating/reinitializing all needed nodes. def update_tree unused_paths = Set.new referenced_nodes = Set.new all_but_passive_paths = Set.new(find_all_source_paths.select {|name, path| !path.passive?}.collect {|name, path| name}) begin used_paths = all_but_passive_paths - unused_paths paths_to_use = Set.new nodes_to_delete = Set.new passive_nodes = Set.new website.tree.node_access[:alcn].each do |alcn, node| next if node == website.tree.dummy_root begin used_paths.delete(node.node_info[:src]) src_path = find_all_source_paths[node.node_info[:src]] if !src_path nodes_to_delete << node elsif (!node.flagged?(:created) && src_path.changed?) || node.meta_info_changed? node.flag(:reinit) paths_to_use << node.node_info[:src] elsif node.changed? # nothing to be done here but method node.changed? has to be called end if src_path && src_path.passive? passive_nodes << node elsif src_path referenced_nodes += node.node_info[:used_meta_info_nodes] + node.node_info[:used_nodes] end rescue Webgen::Error => e e.alcn = node.alcn unless e.alcn raise rescue Exception => e raise Webgen::Error.new(e, nil, node) end end # add unused passive nodes to node_to_delete set unreferenced_passive_nodes, other_passive_nodes = passive_nodes.partition do |pnode| !referenced_nodes.include?(pnode.alcn) end refs = other_passive_nodes.collect {|n| (n.node_info[:used_meta_info_nodes] + n.node_info[:used_nodes]).to_a}.flatten unreferenced_passive_nodes.each {|n| nodes_to_delete << n if !refs.include?(n.alcn)} nodes_to_delete.each {|node| website.tree.delete_node(node)} used_paths.merge(paths_to_use) paths = create_nodes_from_paths(used_paths).collect {|n| n.node_info[:src]} unused_paths.merge(used_paths - paths) website.tree.node_access[:alcn].each {|name, node| website.tree.delete_node(node) if node.flagged?(:reinit)} website.cache.reset_volatile_cache end until used_paths.empty? end # Write out all changed nodes of the website.tree. def write_tree output = website.blackboard.invoke(:output_instance) website.tree.node_access[:alcn].select do |name, node| use_node = (node != website.tree.dummy_root && node.flagged?(:dirty)) node.unflag(:dirty_meta_info) node.unflag(:created) node.unflag(:dirty) use_node end.sort.each do |name, node| begin next if node['no_output'] || !(content = node.content) puts " "*4 + name, :verbose type = if node.is_directory? :directory elsif node.is_fragment? :fragment else :file end output.write(node.path, content, type) rescue Webgen::Error => e e.alcn = node.alcn unless e.alcn raise rescue Exception => e raise Webgen::RenderError.new(e, nil, node) end end end # Return a hash with all source paths. def find_all_source_paths if !defined?(@paths) active_source = Webgen::Source::Stacked.new(website.config['sources'].collect do |mp, name, *args| [mp, Webgen::Common.const_for_name(name).new(*args)] end) passive_source = Webgen::Source::Stacked.new(website.config['passive_sources'].collect do |mp, name, *args| [mp, Webgen::Common.const_for_name(name).new(*args)] end, true) passive_source.paths.each {|path| path.passive = true } source = Webgen::Source::Stacked.new([['/', active_source], ['/', passive_source]]) @paths = {} source.paths.each do |path| if !(website.config['sourcehandler.ignore'].any? {|pat| File.fnmatch(pat, path, File::FNM_CASEFOLD|File::FNM_DOTMATCH)}) @paths[path.source_path] = path end end end @paths end # Return only the subset of +paths+ which are handled by the source handler +name+. def paths_for_handler(name, paths) patterns = website.config['sourcehandler.patterns'][name] return [] if patterns.nil? options = (website.config['sourcehandler.casefold'] ? File::FNM_CASEFOLD : 0) | (website.config['sourcehandler.use_hidden_files'] ? File::FNM_DOTMATCH : 0) | File::FNM_PATHNAME find_all_source_paths.values_at(*paths).compact.select do |path| patterns.any? {|pat| File.fnmatch(pat, path, options)} end end # Use the source handlers to create nodes for the +paths+ in the website.tree and # return the nodes that have been created. def create_nodes_from_paths(paths) nodes = Set.new website.config['sourcehandler.invoke'].sort.each do |priority, shns| shns.each do |shn| sh = website.cache.instance(shn) handler_paths = paths_for_handler(shn, paths) handler_paths.sort {|a,b| a.path.length <=> b.path.length}.each do |path| if !website.tree[path.parent_path] nodes.merge(create_nodes_from_paths([path.parent_path])) end nodes += create_nodes(path, sh) end end end nodes end # Prepare everything to create from the +path+ using the +source_handler+. If a block is # given, the actual creation of the nodes is deferred to it. Otherwise the #create_node method # of the +source_handler+ is used. After the nodes are created, it is also checked if they # have all needed properties. def create_nodes(path, source_handler) #:yields: path path = path.dup path.meta_info = default_meta_info(path, source_handler.class.name) (website.cache[:sourcehandler_path_mi] ||= {})[[path.path, source_handler.class.name]] = path.meta_info.dup website.blackboard.dispatch_msg(:before_node_created, path) *nodes = if block_given? yield(path) else source_handler.create_node(path) end nodes = nodes.flatten.compact nodes.each {|node| website.blackboard.dispatch_msg(:after_node_created, node)} nodes rescue Webgen::Error => e e.alcn = path unless e.alcn raise rescue Exception => e raise Webgen::NodeCreationError.new(e, source_handler.class.name, path) end # Return the default meta info for the pair of +path+ and +sh_name+. def default_meta_info(path, sh_name) path.meta_info.merge(website.config['sourcehandler.default_meta_info'][:all]). merge(website.config['sourcehandler.default_meta_info'][sh_name] || {}) end # Check if the default meta information for +node+ has changed since the last run. But don't # take the node's path's +modified_at+ meta information into account since that changes on # every path change. def meta_info_changed?(node) path = node.node_info[:creation_path] old_mi = website.cache[:sourcehandler_path_mi][[path, node.node_info[:processor]]] old_mi.delete('modified_at') new_mi = default_meta_info(@paths[path] || Webgen::Path.new(path), node.node_info[:processor]) new_mi.delete('modified_at') node.flag(:dirty_meta_info) if !old_mi || old_mi != new_mi end end end end webgen-0.5.17/lib/webgen/default_config.rb0000644002342000234200000003755012055517620017634 0ustar duckdc-users# -*- encoding: utf-8 -*- website = Webgen::WebsiteAccess.website config = website.config # General configuration parameters config.website.cache([:file, 'webgen.cache'], :doc => 'The file name (or String) from/to which the cache is read/written') config.website.lang('en', :doc => 'The default language used for the website') config.website.link_to_current_page(false, :doc => 'Specifies whether links to the current page should be used') # All things regarding logging config.logger.mask(nil, :doc => 'Only show logging events which match the regexp mask') # All things regarding resources config.resources({}, :doc => 'A mapping from resource names to source identifiers') resources = YAML::load(File.read(File.join(Webgen.data_dir, 'resources.yaml'))) resources.each do |res_path_template, res_name| Dir.glob(File.join(Webgen.data_dir, res_path_template), File::FNM_CASEFOLD).each do |res_path| substs = Hash.new {|h,k| h[k] = "$" + k } substs.merge!({ 'basename' => File.basename(res_path), 'basename_no_ext' => File.basename(res_path, '.*'), 'extname' => File.extname(res_path)[1..-1], :dirnames => File.dirname(res_path).split(File::SEPARATOR), }) name = res_name.to_s.gsub(/\$\w+/) do |m| if m =~ /^\$dir(\d+)$/ substs[:dirnames][-($1.to_i)] else substs[m[1..-1]] end end config['resources'][name] = if File.directory?(res_path) ["Webgen::Source::FileSystem", res_path] else ["Webgen::Source::FileSystem", File.dirname(res_path), File.basename(res_path)] end end end # All things regarding sources config.sources [['/', "Webgen::Source::FileSystem", 'src']], :doc => 'One or more sources from which files are read, relative to website directory' config.passive_sources([['/', "Webgen::Source::Resource", "webgen-passive-sources"]], :doc => 'One or more sources for delayed node creation on node resolution') # All things regarding source handler config.sourcehandler.patterns({ 'Webgen::SourceHandler::Copy' => ['**/*.css', '**/*.js', '**/*.html', '**/*.gif', '**/*.jpg', '**/*.png', '**/*.ico'], 'Webgen::SourceHandler::Directory' => ['**/'], 'Webgen::SourceHandler::Metainfo' => ['**/metainfo', '**/*.metainfo'], 'Webgen::SourceHandler::Template' => ['**/*.template'], 'Webgen::SourceHandler::Page' => ['**/*.page'], 'Webgen::SourceHandler::Virtual' => ['**/virtual', '**/*.virtual'], 'Webgen::SourceHandler::Feed' => ['**/*.feed'], 'Webgen::SourceHandler::Sitemap' => ['**/*.sitemap'] }, :doc => 'Source handler to path pattern map') config.sourcehandler.invoke({ 1 => ['Webgen::SourceHandler::Directory', 'Webgen::SourceHandler::Metainfo'], 5 => ['Webgen::SourceHandler::Copy', 'Webgen::SourceHandler::Template', 'Webgen::SourceHandler::Page', 'Webgen::SourceHandler::Feed', 'Webgen::SourceHandler::Sitemap'], 9 => ['Webgen::SourceHandler::Virtual'] }, :doc => 'All source handlers listed here are used by webgen and invoked according to their priority setting') config.sourcehandler.casefold(true, :doc => 'Specifies whether path are considered to be case-sensitive') config.sourcehandler.use_hidden_files(false, :doc => 'Specifies whether hidden files (those starting with a dot) are used') config.sourcehandler.ignore(['**/*~', '**/.svn/**'], :doc => 'Path patterns that should be ignored') config.sourcehandler.default_lang_in_output_path(false, :doc => 'Specifies whether output paths in the default language should have the language in the name') config.sourcehandler.default_meta_info({ :all => { 'output_path' => 'standard', 'output_path_style' => [:parent, :basename, ['.', :lang], :ext] }, 'Webgen::SourceHandler::Copy' => { 'kind' => 'asset' }, 'Webgen::SourceHandler::Directory' => { 'index_path' => 'index.html', 'kind' => 'directory' }, 'Webgen::SourceHandler::Page' => { 'kind' => 'page', 'fragments_in_menu' => true, 'blocks' => {'default' => {'pipeline' => 'erb,tags,markdown,blocks,fragments'}} }, 'Webgen::SourceHandler::Fragment' => { 'kind' => 'fragment' }, 'Webgen::SourceHandler::Template' => { 'blocks' => {'default' => {'pipeline' => 'erb,tags,blocks,head'}} }, 'Webgen::SourceHandler::Metainfo' => { 'blocks' => {1 => {'name' => 'paths'}, 2 => {'name' => 'alcn'}} }, 'Webgen::SourceHandler::Feed' => { 'rss' => true, 'atom' => true, 'blocks' => {'default' => {'pipeline' => 'erb'}} }, 'Webgen::SourceHandler::Sitemap' => { 'default_priority' => 0.5, 'default_change_freq' => 'weekly', 'common.sitemap.any_lang' => true, 'blocks' => {'default' => {'pipeline' => 'erb'}} } }, :doc => "Default meta information for all nodes and for nodes belonging to a specific source handler") config.sourcehandler.template.default_template('default.template', :doc => 'The name of the default template file of a directory') website.autoload_service(:templates_for_node, 'Webgen::SourceHandler::Template') website.autoload_service(:create_fragment_nodes, 'Webgen::SourceHandler::Fragment') website.autoload_service(:parse_html_headers, 'Webgen::SourceHandler::Fragment') # All things regarding output config.output ["Webgen::Output::FileSystem", 'out'], :doc => 'The class which is used to output the generated paths.' config.output.do_deletion(false, :doc => 'Specifies whether the generated output paths should be deleted once the sources are deleted') Webgen::WebsiteAccess.website.blackboard.add_service(:output_instance, Webgen::Output.method(:instance)) # All things regarding content processors config.contentprocessor.map({ 'markdown' => 'Webgen::ContentProcessor::Kramdown', 'maruku' => 'Webgen::ContentProcessor::Maruku', 'textile' => 'Webgen::ContentProcessor::RedCloth', 'redcloth' => 'Webgen::ContentProcessor::RedCloth', 'tags' => 'Webgen::ContentProcessor::Tags', 'blocks' => 'Webgen::ContentProcessor::Blocks', 'erb' => 'Webgen::ContentProcessor::Erb', 'haml' => 'Webgen::ContentProcessor::Haml', 'sass' => 'Webgen::ContentProcessor::Sass', 'scss' => 'Webgen::ContentProcessor::Scss', 'rdoc' => 'Webgen::ContentProcessor::RDoc', 'builder' => 'Webgen::ContentProcessor::Builder', 'erubis' => 'Webgen::ContentProcessor::Erubis', 'rdiscount' => 'Webgen::ContentProcessor::RDiscount', 'fragments' => 'Webgen::ContentProcessor::Fragments', 'head' => 'Webgen::ContentProcessor::Head', 'tidy' => 'Webgen::ContentProcessor::Tidy', 'xmllint' => 'Webgen::ContentProcessor::Xmllint', 'kramdown' => 'Webgen::ContentProcessor::Kramdown', 'less' => 'Webgen::ContentProcessor::Less' }, :doc => 'Content processor name to class map') Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor_names, Webgen::ContentProcessor.method(:list)) Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor, Webgen::ContentProcessor.method(:for_name)) Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor_binary?, Webgen::ContentProcessor.method(:is_binary?)) # All things regarding tags config.contentprocessor.tags.prefix('', :doc => 'The prefix used for tag names to avoid name clashes when another content processor uses similar markup.') config.contentprocessor.tags.map({ 'relocatable' => 'Webgen::Tag::Relocatable', 'menu' => 'Webgen::Tag::Menu', 'breadcrumb_trail' => 'Webgen::Tag::BreadcrumbTrail', 'langbar' => 'Webgen::Tag::Langbar', 'include_file' => 'Webgen::Tag::IncludeFile', 'execute_cmd' => 'Webgen::Tag::ExecuteCommand', 'coderay' => 'Webgen::Tag::Coderay', 'date' => 'Webgen::Tag::Date', 'sitemap' => 'Webgen::Tag::Sitemap', 'tikz' => 'Webgen::Tag::TikZ', 'link' => 'Webgen::Tag::Link', :default => 'Webgen::Tag::Metainfo' }, :doc => 'Tag processor name to class map') config.contentprocessor.erubis.use_pi(false, :doc => 'Specifies whether processing instructions should be used') config.contentprocessor.erubis.options({}, :doc => 'A hash of additional options') config.contentprocessor.redcloth.hard_breaks(false, :doc => 'Specifies whether new lines are turned into hard breaks') config.contentprocessor.tidy.options("-raw", :doc => "The options passed to the tidy command") config.contentprocessor.xmllint.options("--catalogs --noout --valid", :doc => 'Options passed to the xmllint command') config.contentprocessor.kramdown.options({:auto_ids => true}, :doc => 'The options hash for the kramdown processor') config.contentprocessor.kramdown.handle_links(true, :doc => 'Whether all links in a kramdown document should be handled by webgen') config.tag.metainfo.escape_html(true, :doc => 'Special HTML characters in the output will be escaped if true') config.tag.relocatable.path(nil, :doc => 'The path which should be made relocatable', :mandatory => 'default') config.tag.menu.start_level(1, :doc => 'The level at which the menu starts.') config.tag.menu.min_levels(1, :doc => 'The minimum number of menu levels that should always be shown.') config.tag.menu.max_levels(3, :doc => 'The maximum number of menu levels that should be shown.') config.tag.menu.show_current_subtree_only(true, :doc => 'Specifies whether only the current subtree should be shown.') config.tag.menu.used_nodes('all', :doc => 'Specifies the kind of nodes that should be used: all, files, or fragments') config.tag.menu.nested(true, :doc => 'Specifies whether a nested menu list should be generated.') config.tag.breadcrumbtrail.separator(' / ', :doc => 'Separates the hierachy entries from each other.') config.tag.breadcrumbtrail.omit_index_path(false, :doc => 'Omits the last path component if it is an index path.') config.tag.breadcrumbtrail.start_level(0, :doc => 'The level at which the breadcrumb trail starts.') config.tag.breadcrumbtrail.end_level(-1, :doc => 'The level at which the breadcrumb trail ends.') config.tag.langbar.separator(' | ', :doc => 'Separates the languages from each other.') config.tag.langbar.show_single_lang(true, :doc => 'Should the link be shown although the page is only available in one language?') config.tag.langbar.show_own_lang(true, :doc => 'Should the link to the currently displayed language page be shown?') config.tag.langbar.lang_names({}, :doc => 'A map from language code to language names') config.tag.langbar.process_output(false, :doc => 'The content of the language bar will be scanned for tags if true.') config.tag.includefile.filename(nil, :doc => 'The name of the file which should be included (relative to the website).', :mandatory => 'default') config.tag.includefile.process_output(true, :doc => 'The file content will be scanned for tags if true.') config.tag.includefile.escape_html(true, :doc => 'Special HTML characters in the file content will be escaped if true.') config.tag.executecommand.command(nil, :doc => 'The command which should be executed', :mandatory => 'default') config.tag.executecommand.process_output(true, :doc => 'The output of the command will be scanned for tags if true') config.tag.executecommand.escape_html(true, :doc => 'Special HTML characters in the output will be escaped if true') config.tag.coderay.lang('ruby', :doc => 'The highlighting language', :mandatory => 'default') config.tag.coderay.process_body(true, :doc => 'The tag body will be scanned for tags first if true') config.tag.coderay.wrap(:div, :doc => 'Specifies how the code should be wrapped, either :div or :span') config.tag.coderay.line_numbers(true, :doc => 'Show line numbers') config.tag.coderay.line_number_start(1, :doc => 'Line number of first line') config.tag.coderay.bold_every(10, :doc => 'The interval at which the line number appears bold') config.tag.coderay.tab_width(8, :doc => 'Number of spaces used for a tabulator') config.tag.coderay.css(:style, :doc => 'Specifies how the highlighted code should be styled') config.tag.date.format('%Y-%m-%d %H:%M:%S', :doc => 'The format of the date (same options as Ruby\'s Time#strftime)') config.tag.tikz.path(nil, :doc => 'The source path of the created image', :mandatory => 'default') config.tag.tikz.libraries(nil, :doc => 'An array of additional TikZ library names') config.tag.tikz.opts(nil, :doc => 'A string with global options for the tikzpicture environment') config.tag.tikz.resolution('72 72', :doc => 'A string specifying the render and output resolutions') config.tag.tikz.transparent(false, :doc => 'Specifies whether the generated image should be transparent (only png)') config.tag.tikz.img_attr({}, :doc => 'A hash of additional HTML attributes for the created img tag') config.tag.link.path(nil, :doc => 'The (A)LCN path to which a link should be generated', :mandatory => 'default') config.tag.link.attr({}, :doc => 'A hash of additional HTML attributes that should be set on the link') # All things regarding common functionality website.autoload_service(:create_sitemap, 'Webgen::Common::Sitemap') config.common.sitemap.honor_in_menu(false, :doc => 'Only include pages that are also in the menu if true') config.common.sitemap.any_lang(false, :doc => 'Use nodes in any language if true') config.common.sitemap.used_kinds(['page'], :doc => 'Array of node kinds that is used for the sitemap') webgen-0.5.17/lib/webgen/source/0000755002342000234200000000000012055517620015624 5ustar duckdc-userswebgen-0.5.17/lib/webgen/source/tararchive.rb0000644002342000234200000000460012055517620020301 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'pathname' require 'webgen/websiteaccess' require 'webgen/path' require 'open-uri' require 'zlib' begin require 'archive/tar/minitar' rescue LoadError raise Webgen::LoadError.new('archive/tar/minitar', self.class.name, nil, 'archive-tar-minitar') end module Webgen # This class is used to read source paths from a (gzipped) tar archive. The archive can be remote # (http(s) or ftp) or local. # # For example, the following are all valid URIs: # http://example.com/directory/file.tgz # /home/test/my.tar.gz # ftp://ftp.example.com/archives/archive.tar # class Source::TarArchive # A special Webgen::Path class for handling paths from a tar archive. class Path < Webgen::Path # Create a new tar archive path object for the entry +entry+. def initialize(path, data, mtime, uri) super(path) {|mode| StringIO.new(data.to_s, mode) } @uri = uri @mtime = mtime WebsiteAccess.website.cache[[:tararchive_path, @uri, path]] = @mtime if WebsiteAccess.website @meta_info['modified_at'] = @mtime end # Return +true+ if the tar archive path used by the object has been modified. def changed? !WebsiteAccess.website || @mtime > WebsiteAccess.website.cache[[:tararchive_path, @uri, path]] end end # The URI of the tar archive. attr_reader :uri # The glob (see File.fnmatch for details) that is used to specify which paths in the archive should # be returned by #paths. attr_reader :glob # Create a new tar archive source for the URI string +uri+. def initialize(uri, glob = '**/*') @uri = uri @glob = glob end # Return all paths in the tar archive available at #uri. def paths if !defined?(@paths) stream = open(@uri) stream = Zlib::GzipReader.new(stream) if @uri =~ /(\.tar\.gz|\.tgz)$/ Archive::Tar::Minitar::Input.open(stream) do |input| @paths = input.collect do |entry| path = entry.full_name next unless File.fnmatch(@glob, path, File::FNM_DOTMATCH|File::FNM_CASEFOLD|File::FNM_PATHNAME) path += '/' if entry.directory? && path[-1,1] != '/' path = '/' + path unless path[0,1] == '/' Path.new(path, entry.read, Time.at(entry.mtime), @uri) end.compact.to_set end end @paths end end end webgen-0.5.17/lib/webgen/source/resource.rb0000644002342000234200000000265312055517620020006 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/websiteaccess' require 'webgen/source' require 'webgen/common' module Webgen::Source # This class is used to provide access to sources provided by resources. class Resource include Webgen::WebsiteAccess # The glob (see File.fnmatch) specifying the resources. attr_reader :glob # The glob (see File.fnmatch) specifying the paths that should be used from the resources. attr_reader :paths_glob # The prefix that should optionally be stripped from the paths. attr_reader :strip_prefix # Create a new resource source for the the +glob+ and use only those paths matching +paths_glob+ # while stripping +strip_prefix+ off the path. def initialize(glob, paths_glob = nil, strip_prefix = nil) @glob, @paths_glob, @strip_prefix = glob, paths_glob, strip_prefix end # Return all paths associated with the resources identified by #glob. def paths if !defined?(@paths) stack = Stacked.new website.config['resources'].select {|name, infos| File.fnmatch(@glob, name)}.sort.each do |name, infos| stack.add([['/', Webgen::Common.const_for_name(infos.first).new(*infos[1..-1])]]) end @paths = stack.paths @paths = @paths.select {|p| File.fnmatch(@paths_glob, p)} if @paths_glob @paths.collect! {|p| p.mount_at('/', @strip_prefix)} if @strip_prefix end @paths end end end webgen-0.5.17/lib/webgen/source/stacked.rb0000644002342000234200000000451012055517620017567 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Source # This source class is used to stack several sources together. # # It serves two purposes: # # * First, it can be used to access more than one source. This is useful when your website # consists of more than one source directory and you want to use all of them. # # * Second, sources can be mounted on specific directories. For example, a folder with images that # you don't want to copy to the website source directory can be mounted under /images # sothat they are available nonetheless. # # Also be aware that when a path is returned by a source that has already be returned by a prior # source, it is discarded and not used. class Stacked # Return the stack of mount point to Webgen::Source object maps. attr_reader :stack # Specifies whether the result of #paths calls should be cached (default: +false+). If caching # is activated, new maps cannot be added to the stacked source anymore! attr_accessor :cache_paths # Create a new stack. The optional +map+ parameter can be used to provide initial mappings of # mount points to source objects (see #add for details). You cannot add other maps after a call # to #paths if +cache_paths+ is +true+ def initialize(map = {}, cache_paths = false) @stack = [] @cache_paths = cache_paths add(map) end # Add all mappings found in +maps+ to the stack. The parameter +maps+ should be an array of # two-element arrays which contain an absolute directory (ie. starting and ending with a slash) # and a source object. def add(maps) raise "Cannot add new maps since caching is activated for this source" if defined?(@paths) && @cache_paths maps.each do |mp, source| raise "Invalid mount point specified: #{mp}" unless mp =~ /^\// @stack << [mp, source] end end # Return all paths returned by the sources in the stack. Since the stack is ordered, paths # returned by later source objects are not used if a prior source object has returned the same # path. def paths return @paths if defined?(@paths) && @cache_paths @paths = Set.new @stack.each do |mp, source| source.paths.each do |path| @paths.add?(path.mount_at(mp)) end end @paths end end end webgen-0.5.17/lib/webgen/source/filesystem.rb0000644002342000234200000000355412055517620020344 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'pathname' require 'webgen/websiteaccess' require 'webgen/path' module Webgen # This class is used to read source paths from a directory in the file system. class Source::FileSystem # A special Webgen::Path class for handling with file system paths. class Path < Webgen::Path # Create a new object with absolute path +path+ for the file system path +fs_path+. def initialize(path, fs_path) super(path) {|mode| File.open(fs_path, mode) } @fs_path = fs_path WebsiteAccess.website.cache[[:fs_path, @fs_path]] = File.mtime(@fs_path) @meta_info['modified_at'] = File.mtime(@fs_path) end # Return +true+ if the file system path used by the object has been modified. def changed? data = WebsiteAccess.website.cache[[:fs_path, @fs_path]] File.mtime(@fs_path) > data end end # The root path from which paths read. attr_reader :root # The glob (see Dir.glob for details) that is used to specify which paths under the root path # should be returned by #paths. attr_reader :glob # Create a new file system source for the root path +root+ using the provided +glob+. def initialize(root, glob = '**/*') if root =~ /^([a-zA-Z]:|\/)/ @root = root else @root = File.join(WebsiteAccess.website.directory, root) end @glob = glob end # Return all paths under #root which match #glob. def paths @paths ||= Dir.glob(File.join(@root, @glob), File::FNM_DOTMATCH|File::FNM_CASEFOLD).to_set.collect do |f| next unless File.exists?(f) # handle invalid links temp = Pathname.new(f.sub(/^#{Regexp.escape(@root)}\/?/, '/')).cleanpath.to_s temp += '/' if File.directory?(f) && temp[-1] != ?/ path = Path.new(temp, f) path end.compact end end end webgen-0.5.17/lib/webgen/loggable.rb0000644002342000234200000000147512055517620016434 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/websiteaccess' module Webgen # This module should be included in all classes that need a logging facility. module Loggable # Log the result of the +block+ using the log level +log_level+. def log(sev_level, &block) source = (self.kind_of?(Class) ? self.name : self.class.name) + '#' + caller[0][%r"`.*"][1..-2] if WebsiteAccess.website && WebsiteAccess.website.logger && (!WebsiteAccess.website.config['logger.mask'] || source =~ WebsiteAccess.website.config['logger.mask']) WebsiteAccess.website.logger.send(sev_level, source, &block) end end # Shortcut for writing a line to the normal log output. def puts(*args) (args.last == :verbose ? log(:verbose) { args[0..-2].join } : log(:stdout) { args.join }) end end end webgen-0.5.17/lib/webgen/error.rb0000644002342000234200000001004412055517620016001 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Custom webgen error. class Error < StandardError # The name of the class where the error happened. attr_reader :class_name # This is either the source path or the node alcn which is responsible for the error. attr_accessor :alcn # The plain error message. attr_reader :plain_message # Create a new Error using the provided values. # # If +msg_or_error+ is a String, it is treated as the error message. If it is an exception, the # exception is wrapped. def initialize(msg_or_error, class_name = nil, alcn = nil) if msg_or_error.kind_of?(String) super(msg_or_error) @plain_message = msg_or_error else super(msg_or_error.message) set_backtrace(msg_or_error.backtrace) @plain_message = msg_or_error.message end @class_name, @alcn = class_name, (alcn.kind_of?(Node) ? alcn.to_s : alcn) end def message # :nodoc: msg = 'Error while working' msg += (@alcn ? " on <#{@alcn}>" : '') msg += " with #{@class_name}" if @class_name msg + ":\n " + plain_message end end # This error is raised when an error condition occurs during the creation of a node. class NodeCreationError < Error def message # :nodoc: msg = 'Error while creating a node' msg += (@alcn ? " from <#{@alcn}>" : '') msg += " with #{@class_name}" if @class_name msg + ":\n " + plain_message end end # This error is raised when an error condition occurs during rendering of a node. class RenderError < Error # The alcn of the file where the error happened. This can be different from #alcn (e.g. a page # file is rendered but the error happens in a used template). attr_accessor :error_alcn # The line number in the +error_alcn+ where the errror happened. attr_accessor :line # Create a new RenderError using the provided values. # # If +msg_or_error+ is a String, it is treated as the error message. If it is an exception, the # exception is wrapped. def initialize(msg_or_error, class_name = nil, alcn = nil, error_alcn = nil, line = nil) super(msg_or_error, class_name, alcn) @error_alcn, @line = (error_alcn.kind_of?(Node) ? error_alcn.to_s : error_alcn), line end def message # :nodoc: msg = 'Error ' if @error_alcn msg += "in <#{@error_alcn}" msg += ":~#{@line}" if @line msg += "> " end msg += 'while rendering ' msg += (@alcn ? "<#{@alcn}>" : 'the website') msg += " with #{@class_name}" if @class_name msg + ":\n " + plain_message end end # This error is raised when a needed library is not found. class LoadError < Error # The name of the library that is missing. attr_reader :library # The name of the Rubygem that provides the missing library. attr_reader :gem # Create a new LoadError using the provided values. # # If +library_or_error+ is a String, it is treated as the missing library name and an approriate # error message is created. If it is an exception, the exception is wrapped. def initialize(library_or_error, class_name = nil, alcn = nil, gem = nil) if library_or_error.kind_of?(String) msg = "The needed library '#{library_or_error}' is missing." msg += " You can install it via rubygems with 'gem install #{gem}'!" if gem super(msg, class_name, alcn) @library = library_or_error else super(library_or_error, class_name, alcn) @library = nil end @gem = gem end end # This error is raised when a needed external command is not found. class CommandNotFoundError < Error # The command that is missing. attr_reader :cmd # Create a new CommandNotFoundError using the provided values. # # The parameter +cmd+ specifies the command that is missing. def initialize(cmd, class_name = nil, alcn = nil) super("The needed command '#{cmd}' is missing!", class_name, alcn) @cmd = cmd end end end webgen-0.5.17/lib/webgen/configuration.rb0000644002342000234200000001204512055517620017522 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Stores the configuration for a webgen website. # # Configuration options should be created like this: # # config.my.new.config 'value', :doc => 'some', :meta => 'info' # # and later accessed or set using the accessor methods #[] and #[]= or a configuration helper. # These helpers are defined in the Helpers module and provide easier access to complex # configuration options. Also see the {webgen # manual}[http://webgen.rubyforge.org/documentation/manual.html#website-configfile] for # information about the configuration helpers. class Configuration # Helper class for providing an easy method to define configuration options. class MethodChain def initialize(config) #:nodoc: @config = config @name = '' end def method_missing(id, *args) #:nodoc: @name += (@name.empty? ? '' : '.') + id.id2name.sub(/(!|=)$/,'') if args.length > 0 value = args.shift @config.data[@name] = value unless @config.data.has_key?(@name) # value is set only the first time @config.meta_info[@name] ||= {} @config.meta_info[@name].update(*args) if args.length > 0 nil else self end end end # This module provides methods for setting more complex configuration options. It is mixed into # Webgen::Configuration so that its methods can be used. Detailed information on the use of the # methods can be found in the "User Manual" in the "Configuration File" section. # # All public methods defined in this module are available for direct use in the # configuration file, e.g. the method named +default_meta_info+ can be used like this: # # default_meta_info: # Webgen::SourceHandler::Page: # in_menu : true # :action : replace # # All methods have to take exactly one argument, a Hash. # # The special key :action should be used for specifying how the configuration option # should be set: # # [replace] Replace the configuration option with the new values. # [modify] Replace old values with new values and add missing ones (useful for hashes and # normally the default value) module Helpers # Set the default meta information for source handlers. def default_meta_info(args) args.each do |sh_name, mi| raise ArgumentError, 'Invalid argument for configuration helper default_meta_info' unless mi.kind_of?(Hash) action = mi.delete(:action) || 'modify' mi_hash = (self['sourcehandler.default_meta_info'][complete_source_handler_name(sh_name)] ||= {}) case action when 'replace' then mi_hash.replace(mi) else mi_hash.update(mi) end end end # Set the path patterns used by source handlers. def patterns(args) args.each do |sh_name, data| pattern_arr = (self['sourcehandler.patterns'][complete_source_handler_name(sh_name)] ||= []) case data when Array then pattern_arr.replace(data) when Hash (data['del'] || []).each {|pat| pattern_arr.delete(pat)} (data['add'] || []).each {|pat| pattern_arr << pat} else raise ArgumentError, 'Invalid argument for configuration helper patterns' end end end # Set the default processing pipeline for a source handler. def default_processing_pipeline(args) args.each do |sh_name, pipeline| raise ArgumentError, 'Invalid argument for configuration helper pipeline' unless pipeline.kind_of?(String) mi_hash = (self['sourcehandler.default_meta_info'][complete_source_handler_name(sh_name)] ||= {}) ((mi_hash['blocks'] ||= {})['default'] ||= {})['pipeline'] = pipeline end end # Complete +sh_name+ by checking if a source handler called # Webgen::SourceHandler::SH_NAME exists. def complete_source_handler_name(sh_name) (Webgen::SourceHandler.constants.map {|c| c.to_s}.include?(sh_name) ? 'Webgen::SourceHandler::' + sh_name : sh_name) end private :complete_source_handler_name end include Helpers # The hash which stores the meta info for the configuration options. attr_reader :meta_info # The configuration options hash. attr_reader :data # Create a new Configuration object. def initialize @data = {} @meta_info = {} end # Return the configuration option +name+. def [](name) if @data.has_key?(name) @data[name] else raise ArgumentError, "No such configuration option: #{name}" end end # Set the configuration option +name+ to the provided +value+. def []=(name, value) if @data.has_key?(name) @data[name] = value else raise ArgumentError, "No such configuration option: #{name}" end end def method_missing(id, *args) #:nodoc: MethodChain.new(self).method_missing(id, *args) end end end webgen-0.5.17/lib/webgen/tree.rb0000644002342000234200000000602312055517620015611 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/websiteaccess' require 'webgen/node' module Webgen # Represents a tree of nodes. class Tree include WebsiteAccess # The dummy root. This is the default node that gets created when the Tree is created sothat the # real root node can be treated like any other node. It has only one child, namely the real root # node of the tree. attr_reader :dummy_root # Direct access to the hashes for node resolving. Only use this for reading purposes! If you # just want to get a specific node for an alcn/acn/output path, use #node instead. attr_reader :node_access # The hash containing processing information for each node. This is normally not accessed # directly but via the Node#node_info method. attr_reader :node_info # Create a new Tree object. def initialize @node_access = {:alcn => {}, :acn => {}, :path => {}} @node_info = {} @dummy_root = Node.new(self, '', '') end # The real root node of the tree. def root @dummy_root.children.first end # Access a node via a +path+ of a specific +type+. If type is +alcn+ then +path+ has to be an # absolute localized canonical name, if type is +acn+ then +path+ has to be an absolute # canonical name and if type is +path+ then +path+ needs to be an output path. # # Returns the requested Node or +nil+ if such a node does not exist. def node(path, type = :alcn) (type == :acn ? @node_access[type][path] && @node_access[type][path].first : @node_access[type][path]) end alias_method :[], :node # A utility method called by Node#initialize. This method should not be used directly! def register_node(node) if @node_access[:alcn].has_key?(node.alcn) raise "Can't have two nodes with same absolute lcn: #{node}" else @node_access[:alcn][node.alcn] = node end (@node_access[:acn][node.acn] ||= []) << node register_path(node) end # A utility method called by Node#reinit. This method should not be used directly! def register_path(node) return if node['no_output'] if @node_access[:path].has_key?(node.path) raise "Can't have two nodes with same output path: #{node.path}" else @node_access[:path][node.path] = node end end # Delete the node identified by +node_or_alcn+ and all of its children from the tree. # # The message :before_node_deleted is sent with the to-be-deleted node before this node # is actually deleted from the tree. def delete_node(node_or_alcn) n = node_or_alcn.kind_of?(Node) ? node_or_alcn : @node_access[:alcn][node_or_alcn] return if n.nil? || n == @dummy_root n.children.dup.each {|child| delete_node(child)} website.blackboard.dispatch_msg(:before_node_deleted, n) n.parent.children.delete(n) @node_access[:alcn].delete(n.alcn) @node_access[:acn][n.acn].delete(n) @node_access[:path].delete(n.path) node_info.delete(n.alcn) end end end webgen-0.5.17/lib/webgen/path.rb0000644002342000234200000002502212055517620015606 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'pathname' require 'webgen/languages' module Webgen # == General Information # # A Path object provides information about a path that is used to create one or more nodes as well # as methods for accessing the path's content. So a Path object always refers to a source path. In # contrast, output paths are always strings and just specify the location where a specific node # should be written to. # # Note the +path+ and +source_path+ attributes of a Path object: # # * The +source_path+ specifies a path string that was directly created by a Source object. Each # Path object must have such a valid source path sothat webgen can infer the Path the lead to # the creation of a Node object later. # # * In contrast, the +path+ attribute specifies the path that is used to create the canonical name # (and by default the output path) of a Node object. Normally it is the same as the # +source_path+ but can differ (e.g. when fragment nodes are created for page file nodes). # # A Path object can represent one of three different things: a directory, a file or a fragment. If # the +path+ ends with a slash character, then the path object represents a directory, if the path # contains a hash character anywhere, then the path object represents a fragment and else it # represents a file. Have a look at the webgen manual to see the exact format of a path! # # == Relation to Source classes # # A webgen source class needs to derive a specialized path class from this class and implement an # approriate #changed? method that returns +true+ if the path's content has changed since the last # webgen run. class Path # Helper class for easy access to the content of a path. # # This class is used sothat the creation of the real IO object for #stream can be delayed till # it is actually needed. This is done by not directly requiring the user of this class to supply # the IO object, but by requiring a block that creates the real IO object. class SourceIO # Create a new SourceIO object. A block has to be specified that returns the to-be-wrapped IO # object. def initialize(&block) @block = block raise ArgumentError, 'You need to provide a block which returns an IO object' if @block.nil? end # Provide direct access to the wrapped IO object by yielding it. After the method block # returns the IO object is automatically closed. # # The parameter +mode+ specifies the mode in which the wrapped IO object should be opened. # This can be used, for example, to open a file in binary mode (or specify a certain input # encoding under Ruby 1.9). def stream(mode = 'r') io = @block.call(mode) yield(io) ensure io.close end # Return the whole content of the wrapped IO object as string. For a description of the # parameter +mode+ see #stream. def data(mode = 'r') stream(mode) {|io| io.read} end end # Make the given +path+ absolute by prepending the absolute directory path +base+ if necessary. # Also resolves all '..' and '.' references in +path+. def self.make_absolute(base, path) raise(ArgumentError, 'base has to be an absolute path, ie. needs to start with a slash') unless base =~ /^\// Pathname.new(path =~ /^\// ? path : File.join(base, path)).cleanpath.to_s + (path =~ /.\/$/ ? '/' : '') end # Return +true+ if the given +path+ matches the given +pattern+. For information on which # patterns are supported, have a look at the documentation of File.fnmatch. def self.match(path, pattern) pattern += '/' if path.to_s =~ /\/$/ && pattern !~ /\/$|^$/ File.fnmatch(pattern, path.to_s, File::FNM_DOTMATCH|File::FNM_CASEFOLD|File::FNM_PATHNAME) end include Comparable # The full path for which this Path object was created. attr_reader :path # A string specifying the path that lead to the creation of this path. attr_reader :source_path # The string specifying the parent path attr_reader :parent_path # The canonical name of the path without the extension. attr_accessor :basename # The extension of the +path+. attr_accessor :ext # Extracted meta information for the path. attr_accessor :meta_info # Specifies whether this path should be used during the "tree update" phase of a webgen run or # only later during node resolution. attr_writer :passive # Is this path only used later during node resolution? Defaults to +false+, i.e. used during the # "tree update" phase. def passive?; @passive; end # Create a new Path object for +path+. The optional +source_path+ parameter specifies the path # string that lead to the creation of this path. The optional block needs to return an IO object # for getting the content of the path. # # The +path+ needs to be in a well defined format which can be looked up in the webgen manual. def initialize(path, source_path = path, &ioblock) @meta_info = {} @io = block_given? ? SourceIO.new(&ioblock) : nil @source_path = source_path @passive = false analyse(path) end # Mount this path at the mount point +mp+, optionally stripping +prefix+ from the parent path, # and return the new path object. # # The parameters +mp+ and +prefix+ have to be absolute directory paths, ie. they have to start # and end with a slash and must not contain any hash characters! # #-- # Can't use self.class.new(...) here because the semantics of the sub constructors is not know #++ def mount_at(mp, prefix = nil) raise(ArgumentError, "The mount point (#{mp}) must be a valid directory path") if mp =~ /^[^\/]|#|[^\/]$/ raise(ArgumentError, "The strip prefix (#{prefix}) must be a valid directory path") if !prefix.nil? && prefix =~ /^[^\/]|#|[^\/]$/ temp = dup strip_re = /^#{Regexp.escape(prefix.to_s)}/ temp.instance_variable_set(:@path, temp.path.sub(strip_re, '')) reanalyse = (@path == '/' || temp.path == '') temp.instance_variable_set(:@path, File.join(mp, temp.path)) temp.instance_variable_set(:@source_path, temp.path) if @path == @source_path if reanalyse temp.send(:analyse, temp.path) else temp.instance_variable_set(:@parent_path, File.join(mp, temp.parent_path.sub(strip_re, ''))) end temp end # Duplicate the path object. def dup temp = super temp.instance_variable_set(:@meta_info, @meta_info.dup) temp end # Has the content of this path changed since the last webgen run? This default implementation # always returns +true+, a specialized sub class needs to override this behaviour! def changed? true end # The SourceIO object associated with the path. def io if @io @io else raise "No IO object defined for the path #{self}" end end # The canonical name created from the +path+ (namely from the parts +basename+ and +extension+). def cn @basename + (@ext.length > 0 ? '.' + @ext : '') + (@basename != '/' && @path =~ /.\/$/ ? '/' : '') end # Utility method for creating the lcn from the +cn+ and the language +lang+. def self.lcn(cn, lang) if lang.nil? cn else cn.split('.').insert((cn =~ /^\./ ? 2 : 1), lang.to_s).join('.') end end # The localized canonical name created from the +path+. def lcn self.class.lcn(cn, @meta_info['lang']) end # The absolute canonical name of this path. def acn if @path =~ /#/ self.class.new(@parent_path).acn + cn else @parent_path + cn end end # The absolute localized canonical name of this path. def alcn if @path =~ /#/ self.class.new(@parent_path).alcn + lcn else @parent_path + lcn end end # Equality -- Return +true+ if +other+ is a Path object with the same #path or if +other+ is a # String equal to the #path. Else return +false+. def ==(other) if other.kind_of?(Path) other.path == @path elsif other.kind_of?(String) other == @path else false end end alias_method(:eql?, :==) # Compare the #path of this object to other.path def <=>(other) @path <=> other.path end def hash #:nodoc: @path.hash end def to_s #:nodoc: @path.dup end alias_method :to_str, :to_s def inspect #:nodoc: "#" end ####### private ####### # Analyse the +path+ and fill the object with the extracted information. def analyse(path) @path = path if @path =~ /#/ analyse_fragment elsif @path =~ /\/$/ analyse_directory else analyse_file end @meta_info['title'] = @basename.tr('_-', ' ').capitalize @ext ||= '' raise "The basename of a path may not be empty: #{@path}" if @basename.empty? || @basename == '#' raise "The parent path must start with a slash: #{@path}" if @path !~ /^\// && @path != '/' end # Analyse the path assuming it is a directory. def analyse_directory @parent_path = (@path == '/' ? '' : File.join(File.dirname(@path), '/')) @basename = File.basename(@path) end FILENAME_RE = /^(?:(\d+)\.)?(\.?[^.]*?)(?:\.(\w\w\w?)(?=\.))?(?:\.(.*))?$/ # Analyse the path assuming it is a file. def analyse_file @parent_path = File.join(File.dirname(@path), '/') match_data = FILENAME_RE.match(File.basename(@path)) if !match_data[1].nil? && match_data[3].nil? && match_data[4].nil? # handle special case of sort_info.basename as basename.ext @basename = match_data[1] @ext = match_data[2] else @meta_info['sort_info'] = (match_data[1].nil? ? nil : match_data[1].to_i) @basename = match_data[2] @meta_info['lang'] = Webgen::LanguageManager.language_for_code(match_data[3]) @ext = (@meta_info['lang'].nil? && !match_data[3].nil? ? match_data[3].to_s + '.' : '') + match_data[4].to_s end end # Analyse the path assuming it is a fragment. def analyse_fragment @parent_path, @basename = @path.scan(/^(.*?)(#.*?)$/).first raise "The parent path of a fragment path must be a file path and not a directory path: #{@path}" if @parent_path =~ /\/$/ raise "A fragment path must only contain one hash character: #{path}" if @path.count("#") > 1 end end end webgen-0.5.17/lib/webgen/cli/0000755002342000234200000000000012055517620015073 5ustar duckdc-userswebgen-0.5.17/lib/webgen/cli/run_command.rb0000644002342000234200000000060612055517620017724 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/cli' module Webgen::CLI # The CLI command for rendering a webgen website. class RunCommand < CmdParse::Command def initialize # :nodoc: super('render', false) self.short_desc = 'Render the webgen website' end # Render the website. def execute(args) commandparser.create_website.render end end end webgen-0.5.17/lib/webgen/cli/webgui_command.rb0000644002342000234200000000405112055517620020400 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/cli' module Webgen::CLI # The CLI command for starting the webgen webgui. class WebguiCommand < CmdParse::Command def initialize # :nodoc: super('webgui', false) self.short_desc = 'Starts the webgen webgui' end # Render the website. def execute(args) # some fixes for ramaze-2009.04 # - fix for Windows when win32console is not installed # - fix for message displayed on shutdown # - fix for warning message $:.unshift File.join(Webgen.data_dir, 'webgui', 'overrides') require 'win32console' $:.shift begin oldv, $VERBOSE = $VERBOSE, nil begin require 'ramaze/snippets/object/__dir__' Object.__send__(:include, Ramaze::CoreExtensions::Object) require 'ramaze' rescue LoadError puts "The Ramaze web framework which is needed for the webgui was not found." puts "You can install it via 'gem install ramaze --version 2009.04'" return end ensure $VERBOSE = oldv end def Ramaze.shutdown; # :nodoc: end require File.join(Webgen.data_dir, 'webgui', 'app.rb') Ramaze::Log.loggers = [] Ramaze.options[:middleware_compiler]::COMPILED[:dev].middlewares.delete_if do |app, args, block| app == Rack::CommonLogger end puts 'Starting webgui on http://localhost:7000, press Control-C to stop' Thread.new do begin require 'launchy' sleep 1 puts 'Launching web browser' Launchy.open('http://localhost:7000') rescue LoadError puts "Can't open browser because the launchy library was not found." puts "You can install it via 'gem install launchy'" puts "Please open a browser window and enter 'http://localhost:7000' into the address bar!" end end Ramaze.start(:adapter => :webrick, :port => 7000, :file => File.join(Webgen.data_dir, 'webgui', 'app.rb')) puts 'webgui finished' end end end webgen-0.5.17/lib/webgen/cli/apply_command.rb0000644002342000234200000000442512055517620020250 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/cli' require 'webgen/websitemanager' module Webgen::CLI # The CLI command for applying a bundle to a webgen website. class ApplyCommand < CmdParse::Command def initialize #:nodoc: super('apply', false) @force = false self.short_desc = 'Apply a website bundle to an existing webgen website' self.options = CmdParse::OptionParserWrapper.new do |opts| opts.separator "Options:" opts.on('-f', '--[no-]force', 'Specifies whether files should be overwritten (default: no)') do |val| @force = val end opts.separator "" opts.separator "Arguments:" opts.separator opts.summary_indent + "BUNDLE_NAME: The name of a bundle shipped with webgen. The name is " opts.separator opts.summary_indent + " matched against all possible bundle names and if there is only " opts.separator opts.summary_indent + " match the bundle is applied." opts.separator opts.summary_indent + "BUNDLE_URL: The URL of a bundle (needs to be a tar archive" end end def usage # :nodoc: "Usage: #{commandparser.program_name} [global options] apply [options] (BUNDLE_NAME|BUNDLE_URL)" end def show_help # :nodoc: super wm = Webgen::WebsiteManager.new(commandparser.directory) puts puts "Available bundles:" puts Utils.headline('Bundles') wm.bundles.sort.each {|name, entry| Utils.hash_output(name, entry.instance_eval { @table }) } end # Apply the style specified in args[0] to the webgen website. def execute(args) wm = Webgen::WebsiteManager.new(commandparser.directory) if !File.directory?(wm.website.directory) raise "You need to specify a valid webgen website directory!" elsif args.length == 0 raise OptionParser::MissingArgument.new('STYLE') else name = Utils.match_bundle_name(wm, args[0]) puts "The following files in the website directory will be created or overwritten:" puts wm.bundles[name].paths.sort.join("\n") continue = @force if !continue print "Procede? (yes/no): " continue = ($stdin.readline =~ /y(es)?/) end wm.apply_bundle(name) if continue end end end end webgen-0.5.17/lib/webgen/cli/create_command.rb0000644002342000234200000000432612055517620020366 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/cli' require 'webgen/websitemanager' module Webgen::CLI # The CLI command for creating a webgen website. class CreateCommand < CmdParse::Command def initialize #:nodoc: super('create', false) self.description = Utils.format("If the verbosity level is set to verbose, the created files are listed.") @bundles = [] self.short_desc = 'Create a basic webgen website from website bundles' self.options = CmdParse::OptionParserWrapper.new do |opts| opts.separator "Options:" opts.on('-b', '--bundle BUNDLE', String, "A website bundle name/URL or 'none'. Can be used more than once (default: [default, style-andreas07])") do |val| if val.downcase == 'none' @bundles = nil elsif !@bundles.nil? @bundles << val end end opts.separator "" opts.separator "Arguments:" opts.separator opts.summary_indent + "DIR: the directory in which the website should be created" end end def usage # :nodoc: "Usage: #{commandparser.program_name} [global options] create [options] DIR" end def show_help # :nodoc: super wm = Webgen::WebsiteManager.new(commandparser.directory) puts puts "Available bundles:" puts Utils.headline('Bundles') wm.bundles.sort.each {|name, entry| Utils.hash_output(name, entry.instance_eval { @table }) } end # Create a webgen website in the directory args[0]. def execute(args) if args.length == 0 raise OptionParser::MissingArgument.new('DIR') else wm = Webgen::WebsiteManager.new(args[0]) paths = wm.create_website begin if @bundles @bundles = ['default', 'style-andreas07'] if @bundles.empty? @bundles.each {|name| paths += wm.apply_bundle(Utils.match_bundle_name(wm, name)) } end rescue require 'fileutils' FileUtils.rm_rf(args[0]) raise end if commandparser.verbosity == :verbose puts "The following files were created in the directory #{args[0]}:" puts paths.sort.join("\n") end end end end end webgen-0.5.17/lib/webgen/cli/utils.rb0000644002342000234200000001035112055517620016560 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'rbconfig' module Webgen::CLI # Provides methods for other CLI classes for formatting text in a consistent manner. class Utils USE_ANSI_COLORS = Config::CONFIG['host_os'] !~ /mswin|mingw/ DEFAULT_WIDTH = if Config::CONFIG['host_os'] =~ /mswin|mingw/ 72 else ((size = %x{stty size 2>/dev/null}).length > 0 && (size = size.split.last.to_i) > 0 ? size : 72) rescue 72 end module Color @@colors = {:bold => [0, 1], :green => [0, 32], :lred => [1, 31], :reset => [0, 0]} @@colors.each do |color, values| module_eval <<-EOF def Color.#{color.to_s}(text = nil) "\e[#{values[0]};#{values[1]}m" + (text.nil? ? '' : text + self.reset) end EOF end end # Used for dynamically formatting the text (setting color, bold face, ...). def self.method_missing(id, text = nil) if USE_ANSI_COLORS && Color.respond_to?(id) Color.send(id, text.to_s) else text.to_s end end # Return an array of lines which represents the text in +content+ formatted sothat no line is # longer than +width+ characters. The +indent+ parameter specifies the amount of spaces # prepended to each line. If +first_line_indented+ is +true+, then the first line is indented. def self.format(content, indent = 0, first_line_indented = false, width = DEFAULT_WIDTH) content = (content || '').dup length = width - indent paragraphs = content.split(/\n\n/) if (0..1) === paragraphs.length pattern = /^(.{0,#{length}})[ \n]/m lines = [] while content.length > length if content =~ pattern str = $1 len = $&.length else str = content[0, length] len = length end lines << (lines.empty? && !first_line_indented ? '' : ' '*indent) + str.gsub(/\n/, ' ') content.slice!(0, len) end lines << (lines.empty? && !first_line_indented ? '' : ' '*indent) + content.gsub(/\n/, ' ') unless content.strip.empty? lines else ((format(paragraphs.shift, indent, first_line_indented, width) << '') + paragraphs.collect {|p| format(p, indent, true, width) << '' }).flatten[0..-2] end end # Return a headline with the given +text+ and amount of +indent+. def self.headline(text, indent = 2) ' '*indent + "#{bold(text)}" end # Return a section header with the given +text+ formatted in the given +color+ and indented # according to +indent+. The whole text is also left justified to the column specified with # +ljustlength+. def self.section(text, ljustlength = 0, indent = 4, color = :green) ' '*indent + "#{send(color, text)}".ljust(ljustlength - indent + send(color).length) end # Creates a listing of the key-value pairs of +hash+ under a section called +name+. def self.hash_output(name, hash) ljust = 20 puts section('Name', ljust) + "#{lred(name)}" hash.sort_by {|k,v| k.to_s }.each do |name, value| next unless value.respond_to?(:to_str) desc = format(value.to_str, ljust) puts section(name.to_s.capitalize, ljust) + desc.shift desc.each {|line| puts line} end puts end # Tries to match +name+ to a unique bundle name of the WebsiteManager +wm+. If this can not be # done, it is checked whether +name+ is actually a valid bundle URL and if so, the URL source is # added to the bundles of +wm+. # # Returns the correct bundle name or raises an error. def self.match_bundle_name(wm, name) matches = wm.bundles.keys.select {|k| k =~ /#{Regexp.escape(name)}/} if matches.size > 1 raise ArgumentError.new("#{name} matches more than one bundle: #{matches.join(", ")}") elsif matches.size == 0 begin source = Webgen::Source::TarArchive.new(name) wm.add_source(source, 'custom-URL-source') name = 'custom-URL-source' rescue raise ArgumentError.new("#{name} is neither a valid bundle name nor a valid URL") end else name = matches.first end name end end end webgen-0.5.17/lib/webgen/common/0000755002342000234200000000000012055517620015614 5ustar duckdc-userswebgen-0.5.17/lib/webgen/common/sitemap.rb0000644002342000234200000000576512055517620017620 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/tag' require 'webgen/websiteaccess' module Webgen::Common # This class provides functionality for creating sitemaps and checking if a sitemap has changed. class Sitemap include Webgen::WebsiteAccess def initialize #:nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Return the sitemap tree as Webgen::Tag::Menu::MenuNode created for the +node+ in the language # +lang+ using the provided +options+ which can be any configuration option starting with # common.sitemap. def create_sitemap(node, lang, options) @options = options tree = recursive_create(nil, node.tree.root, lang).sort! @options = nil (node.node_info[:common_sitemap] ||= {})[[options.to_a.sort, lang]] = tree.to_lcn_list tree end ####### private ####### # Recursively create the sitemap. def recursive_create(parent, node, lang, in_sitemap = true) mnode = Webgen::Tag::Menu::MenuNode.new(parent, node) node.children.map do |n| sub_in_sitemap = in_sitemap?(n, lang) [(!n.children.empty? || sub_in_sitemap ? n : nil), sub_in_sitemap] end.each do |n, sub_in_sitemap| next if n.nil? sub_node = recursive_create(mnode, n, lang, sub_in_sitemap) mnode.children << sub_node unless sub_node.nil? end (mnode.children.empty? && !in_sitemap ? nil : mnode) end # Return +true+ if the +child+ of the +node+ is in the sitemap for the language +lang+. def in_sitemap?(child, lang, allow_index_file = false) ((option('common.sitemap.used_kinds').empty? || option('common.sitemap.used_kinds').include?(child['kind']) || (child.routing_node(lang, false) != child && in_sitemap?(child.routing_node(lang), lang, true))) && (option('common.sitemap.any_lang') || child.lang.nil? || child.lang == lang) && (!option('common.sitemap.honor_in_menu') || child['in_menu']) && (allow_index_file || child.parent == child.tree.root || child.parent.routing_node(lang) != child)) end # Retrieve the configuration option value for +name+. The value is taken from the current # configuration options hash if +name+ is specified there or from the website configuration # otherwise. def option(name) (@options && @options.has_key?(name) ? @options[name] : website.config[name]) end # Check if the sitemaps for +node+ have changed. def node_changed?(node) return if !node.node_info[:common_sitemap] node.node_info[:common_sitemap].each do |(options, lang), cached_tree| @options = options.to_hash tree = recursive_create(nil, node.tree.root, lang).sort!.to_lcn_list @options = nil if (tree != cached_tree) || (tree.flatten.any? do |alcn| (n = node.tree[alcn]) && (r = n.routing_node(lang)) && r.meta_info_changed? end) node.flag(:dirty) break end end end end end webgen-0.5.17/lib/webgen/cli.rb0000644002342000234200000001163712055517620015430 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'cmdparse' require 'webgen/website' require 'webgen/version' module Webgen # Namespace for all classes that act as CLI commands. # # == Implementing a CLI command # # Each CLI command class needs to be put into this module and has to end with +Command+, otherwise # it is not used. A CLI command is an extension that can be invoked from the webgen command and # thus needs to be derived from CmdParse::Command. For detailed information on this class and the # whole cmdparse package have a look at http://cmdparse.rubyforge.org! # # == Sample CLI command # # Here is a sample CLI command extension which could be put, for example, into the # ext/init.rb of a webgen website: # # require 'webgen/cli' # # class Webgen::CLI::SampleCommand < CmdParse::Command # # def initialize # super('sample', false) # self.short_desc = "This sample command just outputs its parameters" # self.description = Webgen::CLI::Utils.format("Uses the global verbosity level and outputs additional " + # "information when the level is set to verbose!") # @username = nil # self.options = CmdParse::OptionParserWrapper.new do |opts| # opts.separator "Options:" # opts.on('-u', '--user USER', String, # 'Specify an additional user name to output') {|username| @username = username} # end # end # # def execute(args) # if args.length == 0 # raise OptionParser::MissingArgument.new('ARG1 [ARG2 ...]') # else # puts "Command line arguments:" # args.each {|arg| puts arg} # if commandparser.verbosity == :verbose # puts "Yeah, some additional information is always cool!" # end # puts "The entered username: #{@username}" if @username # end # end # # end # # Note the use of Webgen::CLI::Utils.format in the initialize method so that the long text gets # wrapped correctly! The Utils class provides some other useful methods, too! # # For information about which attributes are available on the webgen command parser instance have # a look at Webgen::CLI::CommandParser! module CLI autoload :RunCommand, 'webgen/cli/run_command' autoload :CreateCommand, 'webgen/cli/create_command' autoload :WebguiCommand, 'webgen/cli/webgui_command' autoload :ApplyCommand, 'webgen/cli/apply_command' autoload :Utils, 'webgen/cli/utils' # This is the command parser class used for handling the webgen command line interface. After # creating an instance, the inherited #parse method can be used for parsing the command line # arguments and executing the requested command. class CommandParser < CmdParse::CommandParser # The website directory. Default: the current working directory. attr_reader :directory # The verbosity level. Default: :normal attr_reader :verbosity # The log level. Default: Logger::WARN attr_reader :log_level # Create a new CommandParser class. The default webgen website (if not specified via the # -d option) is taken from the environment variable +WEBGEN_WEBSITE+ or, if it is not # set or empty, the current working directory. def initialize # :nodoc: super(true) @directory = nil @verbosity = :normal @log_level = ::Logger::WARN @log_filter = nil self.program_name = "webgen" self.program_version = Webgen::VERSION self.options = CmdParse::OptionParserWrapper.new do |opts| opts.separator "Global options:" opts.on("--directory DIR", "-d", String, "The website directory (default: the current directory)") {|p| @directory = p} opts.on("--verbose", "-v", "Print more output") { @verbosity = :verbose } opts.on("--quiet", "-q", "No output") { @verbosity = :quiet } opts.on("--log-level LEVEL", "-l", Integer, "The logging level (0..debug, 3..error)") {|p| @log_level = p} opts.on("--log-filter", "-f", Regexp, 'Filter for logging events') {|p| @log_filter = p} end self.add_command(CmdParse::HelpCommand.new) self.add_command(CmdParse::VersionCommand.new) end # Utility method for sub-commands to create the correct Webgen::Website object. def create_website if !defined?(@website) @website = Webgen::Website.new(@directory) do |config| config['logger.mask'] = @log_filter end @website.logger.level = @log_level @website.logger.verbosity = @verbosity end @website end # :nodoc: def parse(argv = ARGV) Webgen::CLI.constants.select {|c| c =~ /.+Command$/ }.each do |c| self.add_command(Webgen::CLI.const_get(c).new, (c.to_s == 'RunCommand' ? true : false)) end super end end end end webgen-0.5.17/lib/webgen/node.rb0000644002342000234200000004500212055517620015577 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/websiteaccess' require 'webgen/loggable' require 'webgen/path' require 'uri' require 'set' require 'pathname' module Webgen # Represents a file, a directory or a fragment. A node always belongs to a Tree. # # All needed meta and processing information is associated with a Node. The meta information is # available throught the #[] and #meta_info accessors, the processing information through the # #node_info accessor. # # Although node information should be changed by code, it is not advised to change meta # information values in code since this may lead to unwanted behaviour! class Node include WebsiteAccess include Loggable # The parent node. This is in all but one case a Node object. The one exception is that the # parent of the Tree#dummy_node is a Tree object. attr_reader :parent # The child nodes of this node. attr_reader :children # The full output path of this node. attr_reader :path # The tree to which this node belongs. attr_reader :tree # The canonical name of this node. attr_reader :cn # The absolute canonical name of this node. attr_reader :acn # The localized canonical name of this node. attr_reader :lcn # The absolute localized canonical name of this node. attr_reader :alcn # The level of the node. The level specifies how deep the node is in the hierarchy. attr_reader :level # The language of this node. attr_reader :lang # Meta information associated with the node. attr_reader :meta_info # Create a new Node instance. # # [+parent+ (immutable)] # The parent node under which this nodes should be created. # [+path+ (immutable)] # The full output path for this node. If this node is a directory, the path must have a # trailing slash (dir/). If it is a fragment, the hash sign must be the first # character of the path (#fragment). This can also be an absolute path like # http://myhost.com/. # [+cn+ (immutable)] # The canonical name for this node. Needs to be of the form basename.ext or # basename where +basename+ does not contain any dots. Also, the +basename+ must not # include a language part! # [+meta_info+] # A hash with meta information for the new node. # # The language of a node is taken from the meta information +lang+ and the entry is deleted from # the meta information hash. The language cannot be changed afterwards! If no +lang+ key is # found, the node is language neutral. def initialize(parent, path, cn, meta_info = {}) @parent = parent @cn = cn.freeze @children = [] reinit(path, meta_info) init_rest end # Re-initializes an already initialized node and resets it to its pristine state. def reinit(path, meta_info = {}) old_path = @path if defined?(@path) @path = path.freeze @lang = Webgen::LanguageManager.language_for_code(meta_info.delete('lang')) @lang = nil unless is_file? @meta_info = meta_info @flags = Set.new([:dirty, :created]) if defined?(@tree) @tree.node_access[:path].delete(old_path) if old_path @tree.register_path(self) self.node_info.clear self.node_info[:used_nodes] = Set.new self.node_info[:used_meta_info_nodes] = Set.new end end # Return the meta information item for +key+. def [](key) @meta_info[key] end # Assign +value+ to the meta information item for +key+. def []=(key, value) @meta_info[key] = value end # Return the node information hash which contains information for processing the node. def node_info tree.node_info[@alcn] ||= {} end # Check if the node is a directory. def is_directory?; @path[-1] == ?/ && !is_fragment?; end # Check if the node is a file. def is_file?; !is_directory? && !is_fragment?; end # Check if the node is a fragment. def is_fragment?; @cn[0] == ?# end # Check if the node is the root node. def is_root?; self == tree.root; end # Check if the node is flagged with one of the following: # # [:created] Has the node been created or has it been read from the cache? # [:reinit] Does the node need to be reinitialized? # [:dirty] Set by other objects to +true+ if they think the object has changed since the last # run. Must not be set to +false+ once it is +true+! # [:dirty_meta_info] Set by other objects to +true+ if the meta information of the node has # changed since the last run. Must not be set to +false+ once it is +true+! def flagged?(key) @flags.include?(key) end # Flag the node with the +keys+ and dispatch the message :node_flagged with +self+ and # +keys+ as arguments. See #flagged for valid keys. def flag(*keys) @flags += keys website.blackboard.dispatch_msg(:node_flagged, self, keys) end # Remove the flags +keys+ from the node and dispatch the message :node_unflagged with # +self+ and +keys+ as arguments. def unflag(*keys) @flags.subtract(keys) website.blackboard.dispatch_msg(:node_unflagged, self, keys) end # Return +true+ if the node has changed since the last webgen run. If it has changed, +dirty+ is # set to +true+. # # Sends the message :node_changed? with +self+ as argument unless the node is already # dirty. A listener to this message should set the flag :dirty on the passed node if he # thinks it is dirty. def changed? if_not_checked(:node) do flag(:dirty) if meta_info_changed? || user_nodes_changed? || node_info[:used_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].changed?)} || node_info[:used_meta_info_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].meta_info_changed?)} website.blackboard.dispatch_msg(:node_changed?, self) unless flagged?(:dirty) end flagged?(:dirty) end # Return +true+ if any node matching a pattern from the meta information +used_nodes+ has changed. def user_nodes_changed? pattern = [@meta_info['used_nodes']].flatten.compact.collect {|pat| Webgen::Path.make_absolute(parent.alcn, pat)} tree.node_access[:alcn].any? do |path, n| pattern.any? {|pat| n =~ pat && n.changed?} end if pattern.length > 0 end private :user_nodes_changed? # Return +true+ if the meta information of the node has changed. # # Sends the message :node_meta_info_changed? with +self+ as argument unless the meta # information of the node is already dirty. A listener to this message should set the flag # :dirt_meta_info on the passed node if he thinks that the node's meta information is # dirty. def meta_info_changed? if_not_checked(:meta_info) do website.blackboard.dispatch_msg(:node_meta_info_changed?, self) unless flagged?(:dirty_meta_info) end flagged?(:dirty_meta_info) end # Return the string representation of the node which is just the alcn. def to_s @alcn end # Return an informative representation of the node. def inspect "<##{self.class.name}: alcn=#{@alcn}>" end # Return +true+ if the alcn matches the pattern. See Webgen::Path.match for more information. def =~(pattern) Webgen::Path.match(@alcn, pattern) end # Sort nodes by using the meta info +sort_info+ (or +title+ if +sort_info+ is not set) of both # involved nodes. def <=>(other) self_so = (@meta_info['sort_info'] && @meta_info['sort_info'].to_s) || @meta_info['title'] || '' other_so = (other['sort_info'] && other['sort_info'].to_s) || other['title'] || '' if self_so !~ /\D/ && other_so !~ /\D/ self_so = self_so.to_i other_so = other_so.to_i end self_so <=> other_so end # This pattern is the the same as URI::UNSAFE except that the hash character (#) is also # not escaped. This is needed sothat paths with fragments work correctly. URL_UNSAFE_PATTERN = Regexp.new("[^#{URI::PATTERN::UNRESERVED}#{URI::PATTERN::RESERVED}#]") # :nodoc: # Construct an internal URL for the given +name+ which can be an acn/alcn/path. If the parameter # +make_absolute+ is +true+, then a relative URL will be made absolute by prepending the special # URL webgen:://webgen.localhost/. def self.url(name, make_absolute = true) url = URI::parse(URI::escape(name, URL_UNSAFE_PATTERN)) url = URI::parse('webgen://webgen.localhost/') + url unless url.absolute? || !make_absolute url end # Check if the this node is in the subtree which is spanned by +node+. The check is performed # using only the +parent+ information of the involved nodes, NOT the actual path/alcn values! def in_subtree_of?(node) temp = self temp = temp.parent while temp != tree.dummy_root && temp != node temp != tree.dummy_root end # Return the node with the same canonical name but in language +lang+ or, if no such node # exists, an unlocalized version of the node. If no such node is found either, +nil+ is # returned. def in_lang(lang) avail = @tree.node_access[:acn][@acn] avail.find do |n| n = n.parent while n.is_fragment? n.lang == lang end || avail.find do |n| n = n.parent while n.is_fragment? n.lang.nil? end end # Return the node representing the given +path+ which can be an acn/alcn. The path can be # absolute (i.e. starting with a slash) or relative to the current node. If no node exists for # the given path or if the path is invalid, +nil+ is returned. # # If the +path+ is an alcn and a node is found, it is returned. If the +path+ is an acn, the # correct localized node according to +lang+ is returned or if no such node exists but an # unlocalized version does, the unlocalized node is returned. def resolve(path, lang = nil, use_passive_sources = true) orig_path = path url = self.class.url(@alcn) + self.class.url(path, false) path = url.path + (url.fragment.nil? ? '' : '#' + url.fragment) return nil if path =~ /^\/\.\./ node = @tree[path, :alcn] if !node || node.acn == path (node = (@tree[path, :acn] || @tree[path + '/', :acn])) && (node = node.in_lang(lang)) end if !node && use_passive_sources && !website.config['passive_sources'].empty? nodes = website.blackboard.invoke(:create_nodes_from_paths, [path]) node = resolve(orig_path, lang, false) node.node_info[:used_meta_info_nodes] += nodes.collect {|n| n.alcn} if node end node end # Return the relative path to the given path +other+. The parameter +other+ can be a Node or a # String. def route_to(other) my_url = self.class.url(@path) other_url = if other.kind_of?(Node) self.class.url(other.routing_node(@lang).path) elsif other.kind_of?(String) my_url + other else raise ArgumentError, "improper class for argument" end # resolve any '.' and '..' paths in the target url if other_url.path =~ /\/\.\.?\// && other_url.scheme == 'webgen' other_url.path = Pathname.new(other_url.path).cleanpath.to_s end route = my_url.route_to(other_url).to_s (route == '' ? File.basename(self.path) : route) end # Return the routing node in language +lang+ which is the node that is used when routing to this # node. The returned node can differ from the node itself in case of a directory where the # routing node is the directory index node. If +show_warning+ is +true+ and this node is a # directory node, then a warning is logged if no associated index file is found. def routing_node(lang, log_warning = true) if !is_directory? self else key = [alcn, :index_node, lang] vcache = website.cache.volatile return vcache[key] if vcache.has_key?(key) index_path = self.meta_info['index_path'] if index_path.nil? vcache[key] = self else index_node = resolve(index_path, lang) if index_node vcache[key] = index_node log(:info) { "Directory index path for <#{alcn}> => <#{index_node}>" } elsif log_warning vcache[key] = self log(:warn) { "No directory index path found for directory <#{alcn}>" } end end vcache[key] || self end end # Return a HTML link from this node to the +node+ or, if this node and +node+ are the same and # the parameter website.link_to_current_page is +false+, a +span+ element with the link # text. # # You can optionally specify additional attributes for the HTML element in the +attr+ Hash. # Also, the meta information +link_attrs+ of the given +node+ is used, if available, to set # attributes. However, the +attr+ parameter takes precedence over the +link_attrs+ meta # information. Be aware that all key-value pairs with Symbol keys are removed before the # attributes are written. Therefore you always need to specify general attributes with Strings! # # If the special value :link_text is present in the attributes, it will be used as the # link text; otherwise the title of the +node+ will be used. # # If the special value :lang is present in the attributes, it will be used as parameter # to the node.routing_node call for getting the linked-to node instead of this node's # +lang+ attribute. *Note*: this is only useful when linking to a directory. def link_to(node, attr = {}) attr = node['link_attrs'].merge(attr) if node['link_attrs'].kind_of?(Hash) rnode = node.routing_node(attr[:lang] || @lang) link_text = attr[:link_text] || (rnode != node && rnode['routed_title']) || node['title'] attr.delete_if {|k,v| k.kind_of?(Symbol)} use_link = (rnode != self || website.config['website.link_to_current_page']) attr['href'] = self.route_to(rnode) if use_link attrs = attr.collect {|name,value| "#{name.to_s}=\"#{value}\"" }.sort.unshift('').join(' ') (use_link ? "#{link_text}" : "#{link_text}") end def find(opts = {}) if opts[:alcn] opts[:alcn] = Path.make_absolute(is_directory? ? alcn : parent.alcn.sub(/#.*$/, ''), opts[:alcn].to_s) end opts[:levels] = 100000 unless opts.has_key?(:levels) result = find_nodes(opts, nil, 1) result.flatten! if result && (opts[:limit] || opts[:offset]) result.sort!(opts[:sort]) if result result.children = result.children[(opts[:offset].to_s.to_i)..(opts[:limit] ? opts[:offset].to_s.to_i + opts[:limit].to_s.to_i - 1 : -1)] result end def find_nodes(opts, parent, level) result = ProxyNode.new(parent, self) children.each do |child| c_result = child.find_nodes(opts, result, level + 1) result.children << c_result unless c_result.nil? end if opts[:levels] && level <= opts[:levels] (!result.children.empty? || find_match?(opts) ? result : nil) end protected :find_nodes def find_match?(opts) (!opts[:alcn] || self =~ opts[:alcn]) end private :find_match? ####### private ####### # Do the rest of the initialization. def init_rest @lcn = Path.lcn(@cn, @lang) @acn = (@parent.kind_of?(Tree) ? '' : @parent.acn.sub(/#.*$/, '') + @cn) @alcn = (@parent.kind_of?(Tree) ? '' : @parent.alcn.sub(/#.*$/, '') + @lcn) @level = -1 @tree = @parent (@level += 1; @tree = @tree.parent) while !@tree.kind_of?(Tree) @tree.register_node(self) @parent.children << self unless @parent == @tree self.node_info[:used_nodes] = Set.new self.node_info[:used_meta_info_nodes] = Set.new end # Only run the code in the block if this node has not already been checked. Different checks are # supported by setting a different +type+ value. def if_not_checked(type) array = (website.cache.volatile[:node_change_checking] ||= {})[type] ||= [] if !array.include?(self) array << self yield array.delete(self) end end # Delegate missing methods to a processor. The current node is placed into the argument array as # the first argument before the method +name+ is invoked on the processor. def method_missing(name, *args, &block) if node_info[:processor] website.cache.instance(node_info[:processor]).send(name, *([self] + args), &block) else super end end end # Encapsulates a node. This class is needed when a hierarchy of nodes should be created but the # original hierarchy should not be destroyed. class ProxyNode # Array of child proxy nodes. attr_accessor :children # The parent proxy node. attr_accessor :parent # The encapsulated node. attr_reader :node # Create a new proxy node under +parent+ (also has to be a ProxyNode object) for the real node # +node+. def initialize(parent, node) @parent = parent @node = node @children = [] end # Sort recursively all children of the node using the wrapped nodes. If +value+ is +false+, no # sorting is done at all. If it is +true+, then the default sort mechanism is used (see # Node#<=>). Otherwise +value+ has to be a meta information key on which should be sorted. def sort!(value = true) return self unless value if value.kind_of?(String) self.children.sort! do |a,b| aval, bval = a.node[value].to_s, b.node[value].to_s if aval !~ /\D/ && aval !~ /\D/ aval = aval.to_i bval = bval.to_i end aval <=> bval end else self.children.sort! {|a,b| a.node <=> b.node} end self.children.each {|child| child.sort!(value)} self end # Turn the hierarchy of proxy nodes into a flat list. def flatten! result = [] while !self.children.empty? result << self.children.shift result.last.parent = self self.children.unshift(*result.last.children) result.last.children = [] end self.children = result end # Return the hierarchy under this node as nested list of alcn values. def to_list self.children.inject([]) {|temp, n| temp << n.node.alcn; temp += ((t = n.to_list).empty? ? [] : [t]) } end end end webgen-0.5.17/lib/webgen/context/0000755002342000234200000000000012055517620016010 5ustar duckdc-userswebgen-0.5.17/lib/webgen/context/nodes.rb0000644002342000234200000000236212055517620017450 0ustar duckdc-usersmodule Webgen class Context # Return the node which represents the file into which everything gets rendered. This is normally # the same node as #content_node but can differ in special cases. For example, when # rendering the content of node called my.page into the output of the node # this.page, this.page would be the +dest_node+ and my.page would be # the +content_node+. # # The +dest_node+ is not included in the chain but can be set via the option :dest_node! # # The returned node should be used as source node for calculating relative paths to other nodes. def dest_node @options[:dest_node] || self.content_node end # Return the reference node, ie. the node which provided the original content for this context # object. # # The returned node should be used, for example, for resolving relative paths. def ref_node @options[:chain] && @options[:chain].first end # Return the node that is ultimately rendered. # # This node should be used, for example, for retrieving meta information. def content_node @options[:chain] && @options[:chain].last end alias :node :content_node end end webgen-0.5.17/lib/webgen/context/tags.rb0000644002342000234200000000135512055517620017277 0ustar duckdc-usersmodule Webgen class Context # Returns the result of evaluating the webgen tag +name+ with the tag parameters +params+ and # the +body+ in the current context. # # Have a look at Webgen::Tag::Base for more information about webgen tags! # # This method is useful when you want to have the functionality of webgen tags available but you # don't want to use the content processor for them. Or, for example, if the used markup language # uses a similar markup as webgen tags do and therefore you can't use the normal webgen tags # content processor. def tag(name, params = {}, body = '') website.cache.instance('Webgen::ContentProcessor::Tags').process_tag(name, params, body, self) end end end webgen-0.5.17/lib/webgen/context/render.rb0000644002342000234200000000246612055517620017624 0ustar duckdc-usersmodule Webgen class Context # Render the named block and return the result. # # call-seq: # context.render_block(block_name)
# context.render_block(:name => block_name, :option => value, ...) # # This method uses the functionality of the content processor +blocks+ for doing the actual # work, so you may also want to look at Webgen::ContentProcessor::Blocks#render_block. You can # call this method in two ways: # # [#render_block(block_name)] # Renders the block named +block_name+ of the next node in the current node chain. This is the # version that most want to use since it is equivalent to the use of . It is equivalent to #render_block(:name => # block_name). # # [#render_block(opts_hash)] # This version allows the same level of control over the output as the blocks content # processor. For a list of valid options have a look at the documentation of the # Webgen::ContentProcessor::Blocks#render_block method! def render_block(name_or_hash) name_or_hash = {:name => name_or_hash} if name_or_hash.kind_of?(String) website.cache.instance('Webgen::ContentProcessor::Blocks').render_block(self, name_or_hash) end end end webgen-0.5.17/lib/webgen/output/0000755002342000234200000000000012055517620015664 5ustar duckdc-userswebgen-0.5.17/lib/webgen/output/filesystem.rb0000644002342000234200000000363012055517620020377 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'fileutils' module Webgen::Output # This class uses the file systems as output device. On initialization a +root+ path is set and # all other operations are taken relative to this root path. class FileSystem include Webgen::WebsiteAccess # The root path, ie. the path to which the root node gets rendered. attr_reader :root # Create a new object with the given +root+ path. If +root+ is not absolute, it is taken # relative to the website directory. def initialize(root) #TODO: copied from source/filesystem.rb if root =~ /^([a-zA-Z]:|\/)/ @root = root else @root = File.join(website.directory, root) end end # Return +true+ if the given path exists. def exists?(path) File.exists?(File.join(@root, path)) end # Delete the given +path+ def delete(path) dest = File.join(@root, path) if File.directory?(dest) FileUtils.rm_rf(dest) else FileUtils.rm(dest) end end # Write the +data+ to the given +path+. The +type+ parameter specifies the type of the path to # be created which can either be :file or :directory. def write(path, data, type = :file) dest = File.join(@root, path) FileUtils.makedirs(File.dirname(dest)) if type == :directory FileUtils.makedirs(dest) elsif type == :file if data.kind_of?(String) File.open(dest, 'wb') {|f| f.write(data) } else data.stream('rb') do |source| File.open(dest, 'wb') {|f| FileUtils.copy_stream(source, f) } end end else raise "Unsupported path type '#{type}' for #{path}" end end # Return the content of the given +path+ which is opened in +mode+. def read(path, mode = 'rb') File.open(File.join(@root, path), mode) {|f| f.read} end end end webgen-0.5.17/lib/webgen/blackboard.rb0000644002342000234200000000557512055517620016751 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # A blackboard object provides two features for inter-object communication: # # * *services*: An object can add a service to the blackboard which can be called by any other # object by just specifing the service name. Therefore it is easy to change the underlying # implementation of the service and there are no hard dependencies on specific class or method # names. # # * *listeners*: Objects may register themselves for specific messsage names and get notified when # such a message gets dispatched. # # For a list of all available services and messages have a look at the main Webgen documentation # page. class Blackboard # Create a new Blackboard object. def initialize @listener = {} @services = {} end # Add the +callable_object+ or the given block as listener for the messages +msg_names+ (one # message name or an array of message names). def add_listener(msg_names = nil, callable_object = nil, &block) callable_object = callable_object || block if !callable_object.nil? raise ArgumentError, "The listener needs to respond to 'call'" unless callable_object.respond_to?(:call) [msg_names].flatten.compact.each {|name| (@listener[name] ||= []) << callable_object} else raise ArgumentError, "You have to provide a callback object or a block" end end # Remove the given object from the dispatcher queues of the message names specified in # +msg_names+. def del_listener(msg_names, callable_object) [msg_names].flatten.each {|name| @listener[name].delete(callable_object) if @listener[name]} end # Dispatch the message +msg_name+ to all listeners for this message, passing the given # arguments. def dispatch_msg(msg_name, *args) return unless @listener[msg_name] @listener[msg_name].each {|obj| obj.call(*args)} end # Add a service named +service_name+ provided by the +callable_object+ or a block to the # blackboard. def add_service(service_name, callable_object = nil, &block) callable_object = callable_object || block if @services.has_key?(service_name) raise "The service name '#{service_name}' is already taken" else raise ArgumentError, "An object providing a service needs to respond to 'call'" unless callable_object.respond_to?(:call) @services[service_name] = callable_object end end # Delete the service +service_name+. def del_service(service_name) @services.delete(service_name) end # Invoke the service called +service_name+ with the given arguments. def invoke(service_name, *args, &block) if @services.has_key?(service_name) @services[service_name].call(*args, &block) else raise ArgumentError, "No such service named '#{service_name}' available" end end end end webgen-0.5.17/lib/webgen/websiteaccess.rb0000644002342000234200000000114512055517620017476 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Should be mixed into modules/classes that need access to the current website object. module WebsiteAccess # The methods of this module are available on classes that include WebsiteAccess. module ClassMethods # See WebsiteAccess.website def website WebsiteAccess.website end end def self.included(klass) #:nodoc: super klass.extend(ClassMethods) end # Return the current website object or +nil+. def website Thread.current[:webgen_website] end module_function :website end end webgen-0.5.17/lib/webgen/logger.rb0000644002342000234200000000601312055517620016130 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'stringio' require 'logger' module Webgen # The class used by a Website to do the logging and the normal output. class Logger # Specifies whether log output should be synchronous with normal output. attr_reader :sync # Normal output verbosity (:normal, :verbose, :quiet). attr_accessor :verbosity # Create a new Logger object which uses +outdev+ as output device. If +sync+ is set to +true+, # log messages are interspersed with normal output. def initialize(outdev=$stdout, sync=false) @sync = sync @outdev = outdev @logger = (@sync ? ::Logger.new(@outdev) : ::Logger.new(@logio = StringIO.new)) @logger.formatter = Proc.new do |severity, timestamp, progname, msg| if self.level == ::Logger::DEBUG "%5s -- %s: %s\n" % [severity, progname, msg ] else "%5s -- %s\n" % [severity, msg] end end self.level = ::Logger::WARN self.verbosity = :normal @marks = [] end # Returns the output of the logger when #sync is +false+. Otherwise an empty string is returned. def log_output if @sync '' else out = @logio.string.dup @marks.reverse.each_with_index do |mark, index| out.insert(mark, " INFO -- Log messages for run #{@marks.length - index} are following\n") end if out.length > 0 out end end # Only used when #sync is +false: Mark the location in the log stream where a new update/write # run begins. def mark_new_cycle if !@sync @marks << @logio.string.length end end # The severity threshold level. def level @logger.level end # Set the severity threshold to +value+ which can be one of the stdlib Logger severity levels. def level=(value) @logger.level = value end # Log a message of +sev_level+ from +source+. The mandatory block has to return the message. def log(sev_level, source='', &block) if sev_level == :stdout @outdev.write(block.call + "\n") if @verbosity == :normal || @verbosity == :verbose elsif sev_level == :verbose @outdev.write(block.call + "\n") if @verbosity == :verbose else @logger.send(sev_level, source, &block) end end # Utiltity method for logging an error message. def error(source='', &block); log(:error, source, &block); end # Utiltity method for logging a warning message. def warn(source='', &block); log(:warn, source, &block); end # Utiltity method for logging an informational message. def info(source='', &block); log(:info, source, &block); end # Utiltity method for logging a debug message. def debug(source='', &block); log(:debug, source, &block); end # Utiltity method for writing a normal output message. def stdout(source='', &block); log(:stdout, source, &block); end # Utiltity method for writing a verbose output message. def verbose(source='', &block); log(:verbose, source, &block); end end end webgen-0.5.17/lib/webgen/coreext.rb0000644002342000234200000000021212055517620016315 0ustar duckdc-users# -*- encoding: utf-8 -*- # :stopdoc: class Array def to_hash h = {} self.each {|k,v| h[k] = v} h end end # :startdoc: webgen-0.5.17/lib/webgen/context.rb0000644002342000234200000000454412055517620016344 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'webgen/context/nodes' require 'webgen/context/tags' require 'webgen/context/render' module Webgen # This class represents the context object that is passed, for example, to the +call+ method of a # content processor. # # The needed context variables are stored in the +options+ hash. You can set any options you like, # however, there are three noteworthy options: # # [:content] # The content string that should be processed. # # [:processors] # Normally an ContentProcessor::AccessHash object providing access to all available content # processors. # # [:chain] # The chain of nodes that is processed. There are some utiltity methods for getting # special nodes of the chain (see #ref_node, #content_node and #dest_node). # # The +persistent+ options hash is shared by all cloned Context objects. class Context include Webgen::WebsiteAccess public :website # The persistent options. Once initialized, all cloned objects refer to the same hash. attr_reader :persistent # Processing options. attr_accessor :options # Create a new Context object. You can use the +options+ hash to set needed options. # # The following options are set by default and can be overridden via the +options+ hash: # # [:content] # Is set to an empty string. # # [:processors] # Is set to a new AccessHash. def initialize(options = {}, persistent = {}) @options = { :content => '', :processors => Webgen::ContentProcessor::AccessHash.new }.merge(options) @persistent = persistent end # Create a copy of the current object. You can use the +options+ parameter to override options # of the current Context object in the newly created Context object. def clone(options = {}) self.class.new(@options.merge(options), @persistent) end # Return the value of the option +name+. def [](name) @options[name] end # Set the option +name+ to the given +value. def []=(name, value) @options[name] = value end # Return the :content option. def content @options[:content] end # Set the :content option to the given +value+. def content=(value) @options[:content] = value end end end webgen-0.5.17/lib/webgen/tag/0000755002342000234200000000000012055517620015077 5ustar duckdc-userswebgen-0.5.17/lib/webgen/tag/tikz.rb0000644002342000234200000001074212055517620016411 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'erb' require 'tempfile' require 'fileutils' module Webgen::Tag # This tag allows the creation and inclusion of complex graphics using the PGF/TikZ library of # LaTeX. You will need a current LaTeX distribution and the +convert+ utility from ImageMagick. class TikZ include Webgen::Tag::Base include Webgen::WebsiteAccess LATEX_TEMPLATE = < \\usetikzlibrary{<%= param('tag.tikz.libraries').join(',') %>} <% end %> \\begin{document} <% if param('tag.tikz.opts') %> \\begin{tikzpicture}[<%= param('tag.tikz.opts') %>] <% else %> \\begin{tikzpicture} <% end %> <%= body %> \\end{tikzpicture} \\end{document} EOF # Create a graphic from the commands in the body of the tag. def call(tag, body, context) path = param('tag.tikz.path') path = Webgen::Path.make_absolute(context.ref_node.parent.alcn, path) mem_handler = website.cache.instance('Webgen::SourceHandler::Memory') src_path = context.ref_node.node_info[:src] parent = website.blackboard.invoke(:create_directories, context.ref_node.tree.root, File.dirname(path), src_path) params = @params node = website.blackboard.invoke(:create_nodes, Webgen::Path.new(path, src_path), mem_handler) do |node_path| mem_handler.create_node(node_path, context.ref_node.alcn) do |pic_node| set_params(params) document = ERB.new(LATEX_TEMPLATE).result(binding) pic_path = compile(document, File.extname(path), context) set_params(nil) if pic_path io = Webgen::Path::SourceIO.new { File.open(pic_path, 'rb') } else pic_node.flag(:dirty) nil end end end.first attrs = {'alt' => ''}.merge(param('tag.tikz.img_attr')).collect {|name,value| "#{name.to_s}=\"#{value}\"" }.sort.unshift('').join(' ') "" end ####### private ####### # Compile the LaTeX +document+ and convert the resulting PDF to the correct output image format # specified by +ext+ (the extension needs to include the dot). def compile(document, ext, context) file = Tempfile.new('webgen-tikz') file.write(document) file.close FileUtils.mv(file.path, file.path + '.tex') cmd_prefix = "cd #{File.dirname(file.path)}; " output = `#{cmd_prefix} pdflatex --shell-escape -interaction=batchmode #{File.basename(file.path)}.tex` if $?.exitstatus != 0 errors = output.scan(/^!(.*\n.*)/).join("\n") raise Webgen::RenderError.new("Error while creating a TikZ picture: #{errors}", self.class.name, context.dest_node, context.ref_node) else cmd = cmd_prefix + "pdfcrop #{File.basename(file.path)}.pdf #{File.basename(file.path)}.pdf; " return unless run_command(cmd, context) render_res, output_res = param('tag.tikz.resolution').split(' ') if param('tag.tikz.transparent') && ext =~ /\.png/i cmd = cmd_prefix + "gs -dSAFER -dBATCH -dNOPAUSE -r#{render_res} -sDEVICE=pngalpha -dGraphicsAlphaBits=4 -dTextAlphaBits=4 " + "-sOutputFile=#{File.basename(file.path)}#{ext} #{File.basename(file.path)}.pdf" else cmd = cmd_prefix + "convert -density #{render_res} #{File.basename(file.path)}.pdf #{File.basename(file.path)}#{ext}" end return unless run_command(cmd, context) if render_res != output_res cmd = cmd_prefix + "identify #{File.basename(file.path)}#{ext}" return unless (output = run_command(cmd, context)) width, height = output.scan(/\s\d+x\d+\s/).first.strip.split('x').collect {|s| s.to_f * output_res.to_f / render_res.to_f } cmd = cmd_prefix + "convert -resize #{width}x#{height} #{File.basename(file.path)}#{ext} #{File.basename(file.path)}#{ext}" return unless run_command(cmd, context) end file.path + ext end end # Runs the command +cmd+ and returns it's output if successful or +nil+ otherwise. def run_command(cmd, context) output = `#{cmd}` if $?.exitstatus != 0 raise Webgen::RenderError.new("Error while running a command for a TikZ picture: #{output}", self.class.name, context.dest_node, context.ref_node) else output end end end end webgen-0.5.17/lib/webgen/tag/coderay.rb0000644002342000234200000000305012055517620017050 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Provides syntax highlighting via the +coderay+ library. class Coderay include Webgen::Tag::Base include Webgen::WebsiteAccess # Highlight the body of the block. def call(tag, body, context) require 'coderay' options = {} if param('tag.coderay.css').to_s == 'other' options[:css] = :class elsif param('tag.coderay.css').to_s == 'class' options[:css] = :class default_css_style_node = context.dest_node.resolve('/stylesheets/coderay-default.css') ((context.persistent[:cp_head] ||= {})[:css_file] ||= []) << context.dest_node.route_to(default_css_style_node) context.dest_node.node_info[:used_meta_info_nodes] << default_css_style_node.alcn else options[:css] = :style end options.merge!(:wrap => param('tag.coderay.wrap').to_sym, :line_numbers => (param('tag.coderay.line_numbers') ? :inline : nil), :line_number_start => param('tag.coderay.line_number_start'), :tab_width => param('tag.coderay.tab_width'), :bold_every => param('tag.coderay.bold_every')) if param('tag.coderay.process_body') body = website.blackboard.invoke(:content_processor, 'tags').call(context.clone(:content => body)).content end CodeRay.scan(body, param('tag.coderay.lang').to_sym).html(options) rescue LoadError raise Webgen::LoadError.new('coderay', self.class.name, context.dest_node, 'coderay') end end end webgen-0.5.17/lib/webgen/tag/metainfo.rb0000644002342000234200000000126412055517620017231 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'cgi' module Webgen::Tag # Provides easy access to the meta information of a node. class Metainfo include Base # Return the meta information key specified in +tag+ of the content node. def call(tag, body, context) output = '' if tag == 'lang' output = context.content_node.lang elsif context.content_node[tag] output = context.content_node[tag].to_s output = CGI::escapeHTML(output) if param('tag.metainfo.escape_html') else log(:error) { "No value for meta info key '#{tag}' in <#{context.ref_node}> found in <#{context.content_node}>" } end output end end end webgen-0.5.17/lib/webgen/tag/includefile.rb0000644002342000234200000000232112055517620017705 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'cgi' module Webgen::Tag # Includes a file verbatim and optionally escapes all special HTML characters and processes webgen # tags in it. class IncludeFile include Webgen::Tag::Base include Webgen::WebsiteAccess def initialize #:nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Include the specified file verbatim in the output, optionally escaping special HTML characters # and processing tags in it. def call(tag, body, context) filename = param('tag.includefile.filename') filename = File.join(website.directory, filename) unless filename =~ /^(\/|\w:)/ content = File.open(filename, 'rb') {|f| f.read} content = CGI::escapeHTML(content) if param('tag.includefile.escape_html') (context.dest_node.node_info[:tag_includefile_filenames] ||= []) << [filename, File.mtime(filename)] [content, param('tag.includefile.process_output')] end ####### private ####### def node_changed?(node) if filenames = node.node_info[:tag_includefile_filenames] node.flag(:dirty) if filenames.any? {|f, mtime| File.mtime(f) > mtime} end end end end webgen-0.5.17/lib/webgen/tag/breadcrumbtrail.rb0000644002342000234200000000433512055517620020573 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Generates a breadcrumb trail for the page. This is especially useful when pages are in deep # hierarchies of directories. class BreadcrumbTrail include Webgen::WebsiteAccess include Base def initialize #:nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Create the breadcrumb trail. def call(tag, body, context) out = breadcrumb_trail_list(context.content_node) (context.dest_node.node_info[:tag_breadcrumb_trail] ||= {})[[@params.to_a.sort, context.content_node.alcn]] = out.map {|n| n.alcn} out = out.map {|n| context.dest_node.link_to(n, :lang => context.content_node.lang) }. join(param('tag.breadcrumbtrail.separator')) log(:debug) { "Breadcrumb trail for <#{context.content_node}>: #{out}" } out end ####### private ####### # Return the list of nodes that make up the breadcrumb trail of +node+ according to the current # parameters. def breadcrumb_trail_list(node) list = [] omit_index_path = if node.meta_info.has_key?('omit_index_path') node['omit_index_path'] else param('tag.breadcrumbtrail.omit_index_path') end omit_index_path = omit_index_path && node.parent.routing_node(node.lang) == node node = node.parent if omit_index_path until node == node.tree.dummy_root list.unshift(node) node = node.parent end list[param('tag.breadcrumbtrail.start_level')..param('tag.breadcrumbtrail.end_level')].to_a end # Check if the breadcrumb trails for +node+ have changed. def node_changed?(node) return if !node.node_info[:tag_breadcrumb_trail] node.node_info[:tag_breadcrumb_trail].each do |(params, cn_alcn), cached_list| cn = node.tree[cn_alcn] set_params(params.to_hash) list = breadcrumb_trail_list(cn) set_params({}) if (list.map {|n| n.alcn} != cached_list) || list.any? {|n| (r = n.routing_node(cn.lang)) && r != node && r.meta_info_changed?} node.flag(:dirty) break end end end end end webgen-0.5.17/lib/webgen/tag/relocatable.rb0000644002342000234200000000314712055517620017706 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'uri' module Webgen::Tag # Makes a path relative. This is very useful for templates. For example, you normally include a # stylesheet in a template. If you specify the filename of the stylesheet directly, the reference # to the stylesheet in the output file of a page file that is not in the same directory as the # template would be invalid. # # By using the +relocatable+ tag you ensure that the path stays valid. class Relocatable include Webgen::Tag::Base # Return the relativized path for the path provided in the tag definition. def call(tag, body, context) path = param('tag.relocatable.path') result = '' begin result = (Webgen::Node.url(path, false).absolute? ? path : resolve_path(path, context)) rescue URI::InvalidURIError => e raise Webgen::RenderError.new("Error while parsing path '#{path}': #{e.message}", self.class.name, context.dest_node, context.ref_node) end result end ####### private ####### # Resolve the path +path+ using the reference node and return the correct relative path from the # destination node. def resolve_path(path, context) dest_node = context.ref_node.resolve(path, context.dest_node.lang) if dest_node context.dest_node.node_info[:used_meta_info_nodes] << dest_node.alcn context.dest_node.route_to(dest_node) else log(:error) { "Could not resolve path '#{path}' in <#{context.ref_node}>" } context.dest_node.flag(:dirty) '' end end end end webgen-0.5.17/lib/webgen/tag/menu.rb0000644002342000234200000001614112055517620016373 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Generates a menu that can be configured extensively. class Menu include Webgen::WebsiteAccess include Base # Special menu node class. It encapsulates the original node for later access. class MenuNode # Array of the child nodes. attr_reader :children # The parent node. attr_reader :parent # The encapsulated node. attr_reader :node # Set to +true+ if the menu node is in the tree of files. attr_writer :is_in_tree_of_files # Create a new menu node under +parent+ for the real node +node+. def initialize(parent, node) @parent = parent @node = node @children = [] end # Return +true+ if the menu node is in the menu tree of only files. def is_in_tree_of_files? @is_in_tree_of_files end # Sort recursively all children of the node using the wrapped nodes. def sort! self.children.sort! {|a,b| a.node <=> b.node} self.children.each {|child| child.sort!} self end # Return the menu tree under the node as nested list of alcn values. def to_lcn_list self.children.inject([]) {|temp, n| temp << n.node.alcn; temp += ((t = n.to_lcn_list).empty? ? [] : [t]) } end end def initialize #:nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Generate the menu. def call(tag, body, context) tree = specific_menu_tree_for(context.content_node) (context.dest_node.node_info[:tag_menu_menus] ||= {})[[@params.to_a.sort, context.content_node.alcn]] = (tree ? tree.to_lcn_list : nil) if !tree || tree.children.empty? '' elsif param('tag.menu.nested') create_output_nested(context, tree) else create_output_not_nested(context, tree) end end ######### protected ######### # Check if the menus for +node+ have changed. def node_changed?(node) return if !node.node_info[:tag_menu_menus] node.node_info[:tag_menu_menus].each do |(params, cn_alcn), cached_tree| cn = node.tree[cn_alcn] menu_tree = menu_tree_for_lang(cn.lang, cn.tree.root) set_params(params.to_hash) tree = build_specific_menu_tree(cn, menu_tree) tree_list = tree.to_lcn_list if tree set_params({}) if (tree.nil? && !cached_tree.nil?) || (tree_list && tree_list != cached_tree) || (tree_list && tree_list.flatten.any? do |alcn| (n = node.tree[alcn]) && (r = n.routing_node(cn.lang)) && r != node && r.meta_info_changed? end) node.flag(:dirty) break end end end # Wrapper method for returning the specific menu tree for +content_node+. def specific_menu_tree_for(content_node) tree = menu_tree_for_lang(content_node.lang, content_node.tree.root) if param('tag.menu.used_nodes') == 'fragments' @params['tag.menu.start_level'] = param('tag.menu.start_level') + content_node.level end build_specific_menu_tree(content_node, tree) end # Build a menu tree for +content_node+ from the tree +menu_node+. def build_specific_menu_tree(content_node, menu_node, level = 1) if menu_node.nil? \ || level > param('tag.menu.max_levels') + param('tag.menu.start_level') - 1 \ || ((level > param('tag.menu.min_levels') + param('tag.menu.start_level') - 1) \ && (menu_node.node.level >= content_node.level \ || (param('tag.menu.show_current_subtree_only') && !content_node.in_subtree_of?(menu_node.node)) ) ) \ || (level == param('tag.menu.start_level') && !content_node.in_subtree_of?(menu_node.node)) return nil end sub_menu_tree = MenuNode.new(nil, menu_node.node) menu_tree = MenuNode.new(nil, menu_node.node) menu_node.children.each do |child| next if param('tag.menu.used_nodes') == 'files' && !child.is_in_tree_of_files? menu_tree.children << (this_node = MenuNode.new(menu_tree, child.node)) sub_node = child.children.length > 0 ? build_specific_menu_tree(content_node, child, level + 1) : nil sub_node.children.each {|n| this_node.children << n; sub_menu_tree.children << n} if sub_node end if level < param('tag.menu.start_level') sub_menu_tree else menu_tree end end # Create the nested HTML menu of the +tree+ using the provided +context+. def create_output_nested(context, tree, level = 1) out = "
    " tree.children.each do |child| menu = child.children.length > 0 ? create_output_nested(context, child, level + 1) : '' style, link = menu_item_details(context.dest_node, child.node, context.content_node.lang, level) out << "
  • #{link}" out << menu out << "
  • " end out << "
" out end # Create the not nested HTML menu of the +tree+ using the provided +context+. def create_output_not_nested(context, tree, level = 1) submenu = '' out = "
    " tree.children.each do |child| submenu << (child.children.length > 0 ? create_output_not_nested(context, child, level + 1) : '') style, link = menu_item_details(context.dest_node, child.node, context.content_node.lang, level) out << "
  • #{link}
  • " end out << "
" out << submenu out end # Return style information (node is selected, ...) and a link from +dest_node+ to +node+. def menu_item_details(dest_node, node, lang, level) styles = ['webgen-menu-level' + level.to_s] styles << 'webgen-menu-submenu' if node.is_directory? || (node.is_fragment? && node.children.length > 0) styles << 'webgen-menu-submenu-inhierarchy' if (node.is_directory? || (node.is_fragment? && node.children.length > 0)) && dest_node.in_subtree_of?(node) styles << 'webgen-menu-item-selected' if node == dest_node style = "class=\"#{styles.join(' ')}\"" if styles.length > 0 link = dest_node.link_to(node, :lang => lang) [style, link] end # Return the menu tree for the language +lang+. def menu_tree_for_lang(lang, root) menus = (website.cache.volatile[:menutrees] ||= {}) unless menus[lang] menus[lang] = create_menu_tree(root, nil, lang) menus[lang].sort! if menus[lang] end menus[lang] end # Create and return a menu tree if at least one node is in the menu or +nil+ otherwise. def create_menu_tree(node, parent, lang) menu_node = MenuNode.new(parent, node) node.children.select do |child| child.lang == lang || child.lang.nil? || child.is_directory? end.each do |child| sub_node = create_menu_tree(child, menu_node, lang) menu_node.children << sub_node unless sub_node.nil? end menu_node.is_in_tree_of_files = (!node.is_fragment? && node['in_menu']) || menu_node.children.any? {|c| c.is_in_tree_of_files?} menu_node.children.empty? ? (node['in_menu'] ? menu_node : nil) : menu_node end end end webgen-0.5.17/lib/webgen/tag/executecommand.rb0000644002342000234200000000163412055517620020431 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'cgi' require 'tempfile' require 'rbconfig' module Webgen::Tag # Executes the given command and returns the standard output. All special HTML characters are # escaped. class ExecuteCommand include Webgen::Tag::Base BIT_BUCKET = (Config::CONFIG['host_os'] =~ /mswin|mingw/ ? "nul" : "/dev/null") # Execute the command and return the standard output. def call(tag, body, context) command = param('tag.executecommand.command') output = `#{command} 2> #{BIT_BUCKET}` if $?.exitstatus != 0 raise Webgen::RenderError.new("Command '#{command}' has return value != 0: #{output}", self.class.name, context.dest_node, context.ref_node) end output = CGI::escapeHTML(output) if param('tag.executecommand.escape_html') [output, param('tag.executecommand.process_output')] end end end webgen-0.5.17/lib/webgen/tag/sitemap.rb0000644002342000234200000000214112055517620017064 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Generates a sitemap. The sitemap contains the hierarchy of all pages on the web site. class Sitemap include Base include Webgen::WebsiteAccess # Create the sitemap. def call(tag, body, context) tree = website.blackboard.invoke(:create_sitemap, context.dest_node, context.content_node.lang, @params) (tree.children.empty? ? '' : output_sitemap(tree, context)) end ####### private ####### # The modified tag base to support the easy specification of common.sitemap.* options. def tag_config_base 'common.sitemap' end # Create the HTML representation of the sitemap nodes in +tree+ in respect to +context+. def output_sitemap(tree, context) out = "
    " tree.children.each do |child| sub = (child.children.length > 0 ? output_sitemap(child, context) : '') out << "
  • " + context.dest_node.link_to(child.node, :lang => child.node.lang || context.content_node.lang) out << sub out << "
  • " end out << "
" out end end end webgen-0.5.17/lib/webgen/tag/base.rb0000644002342000234200000001547112055517620016346 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'yaml' require 'webgen/loggable' require 'webgen/websiteaccess' # This module should be mixed into any class that wants to serve as a webgen tag class. Have a look # a the example below to see how a basic tag class looks like. # # == Tag classes # # A tag class is a webgen extension that handles specific webgen tags. webgen tags are used to add # dynamic content to page and template files and are made for ease of use. # # A tag class can handle multiple different tags. Just add a (tag name)-(class name) pair to the # contentprocessor.tags.map configuration entry for each tag name you want to associate # with the tag class. The special name :default is used for the default tag class which is # called if a tag with an unknown tag name is encountered. # # The only method needed to be written is +call+ which is called by the tags content processor to # the actual processing. And the +initialize+ method must not take any parameters! # # Tag classes *can* also choose to not use this module. If they don't use it they have to provide # the following methods: +set_params+, +create_tag_params+, +create_params_hash+, +call+. # # == Tag parameters # # webgen tags allow the specification of parameters in the tag definition. The method # +tag_params_list+ returns all configuration entries that can be set this way. And the method # +tag_config_base+ is used to resolve partially stated configuration entries. The default method # uses the full class name, strips a Webgen:: part at the beginning away, substitutes # . for :: and makes everything lowercase. # # An additional configuration entry option is also used: :mandatory. If this key is set to # +true+ for a configuration entry, the entry counts as mandatory and needs to be set in the tag # definition. If this key is set to +default+, this means that this entry should be the default # mandatory parameter (used when only a string is provided in the tag definition). There *should* be # only one default mandatory parameter. # # == Sample Tag Class # # Following is a simple tag class example which just reverses the body text and adds some # information about the context to the result. Note that the class does not reside in the # Webgen::Tag namespace and that the configuration entry is therefore also not under the # tag. namespace. # # class Reverser # # include Webgen::Tag::Base # # def call(tag, body, context) # result = param('do_reverse') ? body.reverse : body # result += "Node: " + context.content_node.alcn + " (" + context.content_node['title'] + ")" # result += "Reference node: " + context.ref_node.alcn # result # end # # end # # WebsiteAccess.website.config.reverser.do_reverse nil, :mandatory => 'default' # WebsiteAccess.website.config['contentprocessor.tags.map']['reverse'] = 'Reverser' # module Webgen::Tag::Base include Webgen::Loggable include Webgen::WebsiteAccess # Create a hash with parameter values extracted from the string +tag_config+ and return it. def create_tag_params(tag_config, ref_node) begin config = YAML::load("--- #{tag_config}") rescue ArgumentError => e raise Webgen::RenderError.new("Could not parse the tag params '#{tag_config}': #{e.message}", self.class.name, nil, ref_node) end create_params_hash(config, ref_node) end # Create and return the parameter hash from +config+ which needs to be a Hash, a String or +nil+. def create_params_hash(config, node) params = tag_params_list result = case config when Hash then create_from_hash(config, params, node) when String then create_from_string(config, params, node) when NilClass then {} else raise Webgen::RenderError.new("Invalid parameter type (#{config.class})", self.class.name, nil, node) end unless params.all? {|k| !website.config.meta_info[k][:mandatory] || result.has_key?(k)} raise Webgen::RenderError.new("Not all mandatory parameters set", self.class.name, nil, node) end result end # Set the current parameter configuration to +params+. def set_params(params) @params = params end # Retrieve the parameter value for +name+. The value is taken from the current parameter # configuration if the parameter is specified there or from the website configuration otherwise. def param(name) (defined?(@params) && @params.has_key?(name) ? @params[name] : website.config[name]) end # Default implementation for processing a tag. The parameter +tag+ specifies the name of the tag # which should be processed (useful for tag classes which process different tags). # # The parameter +body+ holds the optional body value for the tag. # # The +context+ parameter holds all relevant information for processing. Have a look at the # Webgen::Context class to see what is available. # # The method has to return the result of the tag processing and, optionally, a boolean value # specifying if the result should further be processed (ie. webgen tags replaced). # # Needs to be redefined by classes that mixin this module! def call(tag, body, context) raise NotImplementedError end ####### private ####### # The base part of the configuration name. This isthe class name without the Webgen module # downcased and all "::" substituted with "." (e.g. Webgen::Tag::Menu -> tag.menu). By overriding # this method one can provide a different way of specifying the base part of the configuration # name. def tag_config_base self.class.name.gsub('::', '.').gsub(/^Webgen\./, '').downcase end # Return the list of all parameters for the tag class. All configuration options starting with # +tag_config_base+ are used. def tag_params_list regexp = /^#{tag_config_base}/ website.config.data.keys.select {|key| key =~ regexp} end # Return a valid parameter hash taking values from +config+ which has to be a Hash. def create_from_hash(config, params, node) result = {} config.each do |key, value| if params.include?(key) result[key] = value elsif params.include?(tag_config_base + '.' + key) result[tag_config_base + '.' + key] = value else log(:warn) { "Invalid parameter '#{key}' for tag '#{self.class.name}' in <#{node}>" } end end result end # Return a valid parameter hash by setting +value+ to the default mandatory parameter. def create_from_string(value, params, node) param_name = params.find {|k| website.config.meta_info[k][:mandatory] == 'default'} if param_name.nil? log(:error) { "No default mandatory parameter specified for tag '#{self.class.name}' but set in <#{node}>"} {} else {param_name => value} end end end webgen-0.5.17/lib/webgen/tag/langbar.rb0000644002342000234200000000343312055517620017035 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Generates a list with all the languages of the page. class Langbar include Webgen::Tag::Base include Webgen::WebsiteAccess def initialize #:nodoc: website.blackboard.add_listener(:node_changed?, method(:node_changed?)) end # Return a list of all translations of the content page. def call(tag, body, context) lang_nodes = all_lang_nodes(context.content_node) (context.dest_node.node_info[:tag_langbar_data] ||= {})[context.content_node.acn] = lang_nodes.map {|n| n.alcn} result = lang_nodes. reject {|n| (context.content_node.lang == n.lang && !param('tag.langbar.show_own_lang'))}. sort {|a, b| a.lang <=> b.lang}. collect do |n| attrs = {:link_text => (param('tag.langbar.lang_names')[n.lang] || n.lang), :lang => n.lang} attrs['class'] = 'webgen-langbar-current-lang' if context.content_node.lang == n.lang context.dest_node.link_to(n, attrs) end.join(param('tag.langbar.separator')) [(param('tag.langbar.show_single_lang') || lang_nodes.length > 1 ? result : ""), param('tag.langbar.process_output')] end ####### private ####### # Return all nodes with the same absolute cn as +node+. def all_lang_nodes(node) node.tree.node_access[:acn][node.acn] end # Check if the langbar tag for +node+ changed. def node_changed?(node) return unless (cdata = node.node_info[:tag_langbar_data]) cdata.each do |acn, clang_nodes| lang_nodes = all_lang_nodes(node.tree[acn, :acn]) rescue nil if !lang_nodes || lang_nodes.length != clang_nodes.length || lang_nodes.any? {|n| n.meta_info_changed?} node.flag(:dirty) break end end end end end webgen-0.5.17/lib/webgen/tag/link.rb0000644002342000234200000000163312055517620016364 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Create a link to a given (A)LCN. class Link include Webgen::Tag::Base # Return a HTML link to the given (A)LCN. def call(tag, body, context) if (dest_node = context.ref_node.resolve(param('tag.link.path').to_s, context.dest_node.lang)) context.dest_node.node_info[:used_meta_info_nodes] << dest_node.alcn context.dest_node.link_to(dest_node, param('tag.link.attr').merge(:lang => context.content_node.lang)) else log(:error) { "Could not resolve path '#{param('tag.link.path')}' in <#{context.ref_node}>" } context.dest_node.flag(:dirty) '' end rescue URI::InvalidURIError => e raise Webgen::RenderError.new("Error while parsing path '#{param('tag.link.path')}': #{e.message}", self.class.name, context.dest_node, context.ref_node) end end end webgen-0.5.17/lib/webgen/tag/date.rb0000644002342000234200000000057412055517620016347 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen::Tag # Prints out the date using a format string which will be passed to Time#strftime. Therefore you # can use everything Time#strftime offers. class Date include Base # Return the current date formatted as specified. def call(tag, body, context) Time.now.strftime(param('tag.date.format')) end end end webgen-0.5.17/lib/webgen/cache.rb0000644002342000234200000000545412055517620015724 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'set' require 'webgen/common' module Webgen # A cache object provides access to various caches to speed up rendering of a website: # # [permanent] The permanent cache should be used for data that should be available between webgen # runs. # # [volatile] The volatile cache is used for data that can easily be regenerated but might be # expensive to do so. This cache is not stored between webgen runs. # # [standard] The standard cache saves data between webgen runs and returns the cached data (not # the newly set data) if it is available. This is useful, for example, to store file # modifcation times and check if a file has been changed between runs. # # The standard cache should be accessed through the [] method which returns the correct # value and the []= method should be used for setting the new value. However, if you # really need to access a particular value of the old or new standard cache, you can use the # accessors +old_data+ and +new_data+. class Cache # The permanent cache hash. attr_reader :permanent # The volatile cache hash. attr_reader :volatile # The cache data stored in the previous webgen run. attr_reader :old_data # The cache data stored in the current webgen run. attr_reader :new_data # Create a new cache object. def initialize() @old_data = {} @new_data = {} @volatile = {} @permanent = {:classes => []} end # Return the cached data (or, if it is not available, the new data) identified by +key+ from the # standard cache. def [](key) if @old_data.has_key?(key) @old_data[key] else @new_data[key] end end # Store +value+ identified by +key+ in the standard cache. def []=(key, value) @new_data[key] = value end # Restore the caches from +data+ and recreate all cached instances (see #instance). def restore(data) @old_data, @permanent = *data @permanent[:classes].each {|klass| instance(klass)} end # Return all caches that should be available between webgen runs. def dump [@old_data.merge(@new_data), @permanent] end # Reset the volatile cache. def reset_volatile_cache @volatile = {:classes => @volatile[:classes]} end # Return the unique instance of the class +name+ (a String). This method should be used when it # is essential that webgen uses only one object of a class or when an object should # automatically be recreated upon cache restoration (see #restore). def instance(name) @permanent[:classes] << name unless @permanent[:classes].include?(name) (@volatile[:classes] ||= {})[name] ||= Common.const_for_name(name).new end end end webgen-0.5.17/lib/webgen/source.rb0000644002342000234200000000366512055517620016163 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Namespace for all classes that provide source paths. # # == Implementing a source class # # Source classes provide access to the source paths on which the source handlers act. # # A source class only needs to respond to the method +paths+ which needs to return a set of paths # for the source. The returned paths must respond to the method changed? (has to return # +true+ if the paths has changed since the last webgen run). If a path represents a directory, it # needs to have a trailing slash! The default implementation in the Path class just returns # +true+. One can either derive a specialized path class or define singleton methods on each path # object. # # Also note that the returned Path objects should have the meta information modified_at # set to the correct last modification time of the path, ie. the value has to be a Time object! # # == Sample Source Class # # Following is a simple source class which has stored the paths and their contents in a hash: # # require 'stringio' # # class MemorySource # # CONTENT = { # '/directory/' => nil, # '/directory/file.page' => "This is the content of the file" # } # # def paths # CONTENT.collect do |path, content| # Webgen::Path.new(path) { StringIO.new(content.to_s) } # end.to_set # end # # end # # You can use this source class in your website (after placing the code in, for example, # ext/init.rb) by updating the sources configuration option: # # WebsiteAccess.website.config['sources'] << ['/', 'MemorySource'] # module Source autoload :Base, 'webgen/source/base' autoload :FileSystem, 'webgen/source/filesystem' autoload :Stacked, 'webgen/source/stacked' autoload :Resource, 'webgen/source/resource' autoload :TarArchive, 'webgen/source/tararchive' end end webgen-0.5.17/lib/webgen/page.rb0000644002342000234200000001330512055517620015567 0ustar duckdc-users# -*- encoding: utf-8 -*- require 'yaml' module Webgen # A Page object wraps a meta information hash and an array of Block objects. It is normally # generated from a file or string in Webgen Page Format using the provided class methods. class Page # A single block within a Page object. The content of the block can be rendered using the #render method. class Block # The name of the block. attr_reader :name # The content of the block. attr_reader :content # The options set specifically for this block. attr_reader :options # Create a new block with the name +name+ and the given +content+ and +options+. def initialize(name, content, options) @name, @content, @options = name, content, options end # Render the block using the provided context object. # # The context object needs to respond to #[] and #[]= (e.g. a Hash is a valid # context object) and the key :processors needs to contain a Hash which maps processor # names to processor objects that respond to #call. # # Uses the content processors specified in the +pipeline+ key of the +options+ attribute to do # the actual rendering. # # Returns the given context with the rendered content. def render(context) context[:content] = @content.dup context[:block] = self @options['pipeline'].to_s.split(/,/).each do |processor| raise "No such content processor available: #{processor}" unless context[:processors].has_key?(processor) context[:processors][processor].call(context) end context end end # Raised during parsing of data in Webgen Page Format if the data is invalid. class FormatError < StandardError; end # :stopdoc: RE_META_INFO_START = /\A---\s*(?:\n|\r|\r\n)/m RE_META_INFO = /\A---\s*(?:\n|\r|\r\n).*?(?:\n|\r|\r\n)(?=---.*?(?:\n|\r|\r\n)|\Z)/m RE_BLOCKS_OPTIONS = /^--- *?(?: *((?:\w+:[^\s]* *)*))?$|^$/ RE_BLOCKS_START = /^--- .*?$|^--- *$/ RE_BLOCKS = /(?:(#{RE_BLOCKS_START})|\A)\n?(.*?)(?:(?=#{RE_BLOCKS_START})|\z)/m # :startdoc: class << self # Parse the given string +data+ in Webgen Page Format and initialize a new Page object with # the information. The +meta_info+ parameter can be used to provide default meta information. def from_data(data, meta_info = {}) md = /(#{RE_META_INFO})?(.*)/m.match(normalize_eol(data)) meta_info = meta_info.merge(parse_meta_info(md[1], data)) blocks = parse_blocks(md[2] || '', meta_info) new(meta_info, blocks) end # Parse the given string +data+ in Webgen Page Format and return the found meta information. def meta_info_from_data(data) md = /(#{RE_META_INFO})?/m.match(normalize_eol(data)) parse_meta_info(md[1], data) end ####### private ####### # Normalize the end-of-line encodings to Unix style. def normalize_eol(data) data.gsub(/\r\n?/, "\n") end # Parse the meta info string in +mi_data+ and return the hash with the meta information. The # original +data+ is used for checking the validness of the meta information block. def parse_meta_info(mi_data, data) if mi_data.nil? && data =~ RE_META_INFO_START raise FormatError, 'Found start line for meta information block but no valid meta information block' elsif mi_data.nil? {} else begin meta_info = YAML::load(mi_data.to_s) unless meta_info.kind_of?(Hash) raise FormatError, "Invalid structure of meta information block: expected YAML hash but found #{meta_info.class}" end rescue ArgumentError => e raise FormatError, "Invalid YAML syntax in meta information block: #{e.message}" end meta_info end end # Parse all blocks in +data+ and return them. Meta information can be provided in +meta_info+ # which is used for setting the block names and options. def parse_blocks(data, meta_info) scanned = data.scan(RE_BLOCKS) raise(FormatError, 'No content blocks specified') if scanned.length == 0 blocks = {} scanned.each_with_index do |block_data, index| options, content = *block_data md = RE_BLOCKS_OPTIONS.match(options.to_s) raise(FormatError, "Found invalid blocks starting line for block #{index+1}: #{options}") if content =~ /\A---/ || md.nil? options = Hash[*md[1].to_s.scan(/(\w+):([^\s]*)/).map {|k,v| [k, (v == '' ? nil : YAML::load(v))]}.flatten] options = (meta_info['blocks']['default'] || {} rescue {}). merge((meta_info['blocks'][index+1] || {} rescue {})). merge(options) name = options.delete('name') || (index == 0 ? 'content' : 'block' + (index + 1).to_s) raise(FormatError, "Previously used name '#{name}' also used for block #{index+1}") if blocks.has_key?(name) content ||= '' content.gsub!(/^(\\+)(---.*?)$/) {|m| "\\" * ($1.length / 2) + $2} content.chomp!("\n") unless index + 1 == scanned.length blocks[name] = blocks[index+1] = Block.new(name, content, options) end meta_info.delete('blocks') blocks end end # The contents of the meta information block. attr_reader :meta_info # The hash of blocks for the page. attr_reader :blocks # Create a new Page object with the meta information provided in +meta_info+ and the given # +blocks+. def initialize(meta_info = {}, blocks = {}) @meta_info = meta_info @blocks = blocks end end end webgen-0.5.17/lib/webgen/common.rb0000644002342000234200000000145612055517620016147 0ustar duckdc-users# -*- encoding: utf-8 -*- module Webgen # Namespace for classes and methods that provide common functionality. module Common autoload :Sitemap, 'webgen/common/sitemap' # Return the constant object for the given absolute constant +name+. def self.const_for_name(name) name.split('::').inject(Object) {|b,n| b.const_get(n)} end # Return the error line by inspecting the backtrace of the given +error+ instance. def self.error_line(error) (error.is_a?(::SyntaxError) ? error.message : error.backtrace[0]).scan(/:(\d+)/).first.first.to_i rescue nil end # Return the file name where the error occured. def self.error_file(error) (error.is_a?(::SyntaxError) ? error.message : error.backtrace[0]).scan(/(?:^|\s)(.*?):(\d+)/).first.first end end end webgen-0.5.17/lib/webgen/website.rb0000644002342000234200000003573212055517620016325 0ustar duckdc-users# -*- encoding: utf-8 -*- ## # Welcome to the API documentation of wegen! # # Have a look at the base webgen module which provides a good starting point! # Standard lib requires require 'logger' require 'set' require 'fileutils' # Requirements for Website require 'webgen/coreext' require 'webgen/loggable' require 'webgen/logger' require 'webgen/configuration' require 'webgen/websiteaccess' require 'webgen/blackboard' require 'webgen/cache' require 'webgen/tree' require 'webgen/error' # Files for autoloading require 'webgen/common' require 'webgen/context' require 'webgen/source' require 'webgen/output' require 'webgen/sourcehandler' require 'webgen/contentprocessor' require 'webgen/tag' # Load other needed files require 'webgen/path' require 'webgen/node' require 'webgen/page' require 'webgen/version' # The Webgen namespace houses all classes/modules used by webgen. # # = webgen # # webgen is a command line application for generating a web site from templates and content # files. Despite this fact, the implementation also provides adequate support for using webgen as a # library and *full* *support* for extending it. # # == Extending webgen # # webgen can be extended very easily. Any file called init.rb put into the ext/ # directory of the website or into one of its sub-directories is automatically loaded on # Website#init. # # You can extend webgen in several ways. However, no magic or special knowledge is needed since # webgen relies on the power of Ruby itself. So, for example, an extension is just a normal Ruby # class. Most extension types provide a Base module for mixing into an extension which provides # default implementations for needed methods. # # Following are links to detailed descriptions on how to develop specific types of extensions: # # [Webgen::Source] Information on how to implement a class that provides source paths for # webgen. For example, one could implement a source class that uses a database as # backend. # # [Webgen::Output] Information on how to implement a class that writes content to an output # location. The default output class just writes to the file system. One could, for # example, implement an output class that writes the generated files to multiple # locations or to a remote server. # # [Webgen::ContentProcessor] Information on how to develop an extension that processes the # content. For example, markup-to-HTML converters are implemented as # content processors in webgen. # # [Webgen::SourceHandler::Base] Information on how to implement a class that handles objects of type # source Path and creates Node instances. For example, # Webgen::SourceHandler::Page handles the conversion of .page # files to .html files. # # [Webgen::Tag::Base] Information on how to implement a webgen tag. webgen tags are used to provide # an easy way for users to include dynamic content such as automatically # generated menus. # # # == General information # # Here are some detail on the internals of webgen that will help you while developing for webgen: # # * webgen uses a not so complex system for determining whether a node needs to be recreated or # re-rendered. However, for this to work correctly all extensions have to follow some rules: # # * It is necessary that all node alcns from which the content is used are put into the # destination node's node_info[:used_nodes] set. # # * It is necessary that all other node alcns that are used for a node in any way, even if they # are only referenced or the route to their output path used, have to be put into the node's # node_info[:used_meta_info_nodes] set. # # * Any node that is created during the rendering phase, ie. via a content processor, a tag or in # the #content method of a source handler needs to be put into the rendered node's # node_info[:used_meta_info_nodes] or node_info[:used_nodes] set (see above for # details)! This is especially necessary when using resolved nodes since resolved nodes can be # created by passive sources! # # * webgen provides various Error classes. However, errors should only be raised if additional runs # won't correct the problem. For example, if a path cannot be resolved, it is possible that in the # next run a node will be created and that the path can be resolved then. This is always the case, # for example, with fragment nodes! In such cases an error message should be written out to the # log to inform the user that there is a potential problem. # # == Blackboard services # # The Blackboard class provides an easy communication facility between objects. It implements the # Observer pattern on the one side and allows the definition of services on the other side. One # advantage of a service over the direct use of an object instance method is that the caller does # not need to how to find the object that provides the service. It justs uses the Website#blackboard # instance. An other advantage is that one can easily exchange the place where the service was # defined without breaking extensions that rely on it. # # Following is a list of all services available in the stock webgen distribution by the name and the # method that implements it (which is useful for looking up the parameters of service). # # :create_fragment_nodes:: SourceHandler::Fragment#create_fragment_nodes # :templates_for_node:: SourceHandler::Template#templates_for_node # :parse_html_headers:: SourceHandler::Fragment#parse_html_headers # :output_instance:: Output.instance # :content_processor_names:: ContentProcessor.list # :content_processor:: ContentProcessor.for_name # :create_sitemap:: Common::Sitemap#create_sitemap # :create_directories:: SourceHandler::Directory#create_directories # :create_nodes:: SourceHandler::Main#create_nodes # :create_nodes_from_paths:: SourceHandler::Main#create_nodes_from_paths # :source_paths:: SourceHandler::Main#find_all_source_paths # # Following is the list of all messages that can be listened to: # # :node_flagged:: # See Node#flag # # :node_unflagged:: # See Node#unflag # # :node_changed?:: # See Node#changed? # :node_meta_info_changed?:: # See Node#meta_info_changed? # # :before_node_created:: # Sent by the :create_nodes service before a node is created (handled by a source handler) # with the +parent+ and the +path+ as arguments. # # :after_node_created:: # Sent by the :create_nodes service after a node has been created with the created node # as argument. # # :before_node_deleted:: # See Tree#delete_node # # == Other places to look at # # Here is a list of modules/classes that are primarily used throughout webgen or provide useful # methods for developing extensions: # # Common, Tree, Node, Path, Cache, Page module Webgen # 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 @@data_dir end # Represents a webgen website and is used to render it. # # Normally, webgen is used from the command line via the +webgen+ command or from Rakefiles via # Webgen::WebgenTask. However, you can also easily use webgen as a library and this class provides # the interface for this usage! # # Since a webgen website is, basically, just a directory, the only parameter needed for creating a # new Website object is the website directory. After that you can work with the website: # # * If you want to render the website, you just need to call Website#render which initializes the # website and does all the rendering. When the method call returns, everything has been rendered. # # * If you want to remove the generated output, you just need to invoke Website#clean and it will # be done. # # * Finally, if you want to retrieve data from the website, you first have to call Website#init to # initialize the website. After that you can use the various accessors to retrieve the needed # data. *Note*: This is generally only useful if the website has been rendered because otherwise # there is no data to retrieve. # class Website # Raised when the configuration file of the website is invalid. class ConfigFileInvalid < RuntimeError; end include Loggable # The website configuration. Can only be used after #init has been called (which is # automatically done in #render). attr_reader :config # The blackboard used for inter-object communication. Can only be used after #init has been # called. attr_reader :blackboard # A cache to store information that should be available between runs. Can only be used after # #init has been called. attr_reader :cache # The internal data structure used to store information about individual nodes. attr_reader :tree # The logger used for logging. If set to +nil+, logging is disabled. attr_accessor :logger # The website directory. attr_reader :directory # Create a new webgen website for the website in the directory +dir+. If +dir+ is +nil+, the # environment variable +WEBGEN_WEBSITE+ or, if it is not set either, the current working # directory is used. You can provide a block (has to take the configuration object as parameter) # for adjusting the configuration values during the initialization. def initialize(dir = nil, logger=Webgen::Logger.new($stdout, false), &block) @blackboard = nil @cache = nil @config = nil @logger = logger @config_block = block @directory = (dir.nil? ? (ENV['WEBGEN_WEBSITE'].to_s.empty? ? Dir.pwd : ENV['WEBGEN_WEBSITE']) : dir) end # 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 # 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 load 'webgen/default_config.rb' Dir.glob(File.join(@directory, 'ext', '**/init.rb')) {|f| load(f)} read_config_file @config_block.call(@config) if @config_block restore_tree_and_cache end self end # Render the website (after calling #init if the website is not already initialized) and return # a status code not equal to +nil+ if rendering was successful. def render result = nil execute_in_env do init puts "Starting webgen..." shm = SourceHandler::Main.new result = shm.render save_tree_and_cache if result puts "Finished" if @logger && @logger.log_output.length > 0 puts "\nLog messages:" puts @logger.log_output end end result end # 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 if @config['website.cache'].first == :file FileUtils.rm(File.join(@directory, @config['website.cache'].last)) rescue nil end if del_outdir output.delete('/') rescue nil end end end # 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] Thread.current[:webgen_website] = self yield ensure Thread.current[:webgen_website] = set_back end ####### private ####### # 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.open(cache_file, 'rb') {|f| f.read} if File.exists?(cache_file) else config['website.cache'].last end cache_data, tree, version = Marshal.load(data) rescue nil if cache_data && version == Webgen::VERSION @cache.restore(cache_data) @tree = tree end end # Save the +tree+ and the +cache+ to +website.cache+. def save_tree_and_cache cache_data = [@cache.dump, @tree, Webgen::VERSION] 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['website.cache'][1] = Marshal.dump(cache_data) end end # 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| case key when *Webgen::Configuration::Helpers.public_instance_methods(false).map {|c| c.to_s} then @config.send(key, value) 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 webgen-0.5.17/AUTHORS0000644002342000234200000000046312055517620013362 0ustar duckdc-usersThe author of webgen is Thomas Leitner . The following persons have contributed to this version of webgen: * Arnaud Cornet * Jeremy Hinegardner * Massimiliano Filacchioni * Paul van Tilburg webgen-0.5.17/data/0000755002342000234200000000000012055517620013220 5ustar duckdc-userswebgen-0.5.17/data/webgen/0000755002342000234200000000000012055517620014467 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/0000755002342000234200000000000012055517620015751 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/overrides/0000755002342000234200000000000012055517620017753 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/overrides/win32console.rb0000644002342000234200000000000012055517620022613 0ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/start.rb0000644002342000234200000000057512055517620017442 0ustar duckdc-users# Use this file directly like `ruby start.rb` if you don't want to use the # `ramaze start` command. # All application related things should go into `app.rb`, this file is simply # for options related to running the application locally. require 'ramaze' require File.expand_path('app', File.dirname(__FILE__)) Ramaze.start(:adapter => :webrick, :port => 7000, :file => __FILE__) webgen-0.5.17/data/webgen/webgui/app.rb0000644002342000234200000000057112055517620017061 0ustar duckdc-users# This file contains your application, it requires dependencies and necessary # parts of the application. # # It will be required from either `config.ru` or `start.rb` # Add the directory this file resides in to the load path, so you can run the # app from any other working directory $LOAD_PATH.unshift(__DIR__) # Initialize controllers and models require 'controller/main' webgen-0.5.17/data/webgen/webgui/view/0000755002342000234200000000000012055517620016723 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/view/manage_website.xhtml0000644002342000234200000000160312055517620022753 0ustar duckdc-users

#{CGI::escapeHTML(@log.to_s)}
webgen-0.5.17/data/webgen/webgui/view/create_website.xhtml0000644002342000234200000000122512055517620022766 0ustar duckdc-users

Choose a

webgen-0.5.17/data/webgen/webgui/view/error.xhtml0000644002342000234200000000375012055517620021137 0ustar duckdc-users

The backtrace gives filenames and line numbers for all parts of the call stack.
Click on any of them to view the surrounding source code.

#@title

FileLineMethod
#{file}#{lineno}
#{meth}
Thread.current[:session], 'Request' => Thread.current[:request], 'Response' => Thread.current[:response], 'Global' => Global, }.each do |title, content| hash = [title, content].object_id.abs ?>

#{title}

webgen-0.5.17/data/webgen/webgui/view/index.xhtml0000644002342000234200000000142412055517620021111 0ustar duckdc-users

Welcome to the webgen webgui!

This web application lets you create and manage webgen website. Just set the directory of an existing or a to-be-created website in the top right corner and click the button!

  • If you select an existing directory, the webgui assumes that it is a webgen website directory and presents you with page for managing a webgen website.
  • If you select a non-existing directory, the webgui shows you a page for creating a webgen website.

The text input field uses auto-completion to show you the available directories on your computer. You can either start typing an absolute path name or relative one which will be resolved from the directory in which you started the webgui.

webgen-0.5.17/data/webgen/webgui/public/0000755002342000234200000000000012055517620017227 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/public/js/0000755002342000234200000000000012055517620017643 5ustar duckdc-userswebgen-0.5.17/data/webgen/webgui/public/js/jquery.js0000644002342000234200000015473612055517620021540 0ustar duckdc-users/* * jQuery 1.2.6 - New Wave Javascript * * Copyright (c) 2008 John Resig (jquery.com) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ * $Rev: 5685 $ */ (function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})();webgen-0.5.17/data/webgen/webgui/public/js/jquery.autocomplete.js0000644002342000234200000003162212055517620024224 0ustar duckdc-users/* * Autocomplete - jQuery plugin 1.0.2 * * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $ * */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else $input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"$1");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else if(data[q]){return data[q];}else if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("
").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("