module Gumdrop
WEB_PAGE_EXTS= %w(.html .htm)
JETSAM_FILES= %w(**/.DS_Store .git* .git/**/* .svn/**/* **/.sass-cache/**/* Gumdrop)
DEFAULT_CONFIG= {
relative_paths: true,
relative_paths_exts: WEB_PAGE_EXTS,
default_layout: 'site',
layout_exts: WEB_PAGE_EXTS,
proxy_enabled: false,
output_dir: "./output",
source_dir: "./source",
data_dir: './data',
log: STDOUT,
log_level: :info,
ignore: JETSAM_FILES,
blacklist: [],
server_timeout: 5,
server_port: 4567,
env: :production,
file_change_test: :checksum
}
class Site
include Util::Configurable
include Util::Eventable
include Util::Loggable
config_accessor :source_dir, :output_dir, :data_dir,
:mode, :env, :ignore, :blacklist
attr_reader :sitefile, :options, :root, :contents, :data,
:layouts, :generators, :partials, :last_run
# hmmm...
attr_accessor :active_renderer, :active_builder
# You shouldn't call this yourself! Access it via Gumdrop.site
def initialize(sitefile, opts={})
Gumdrop.send :set_current_site, self
@sitefile= sitefile.expand_path
@options= Util::HashObject.from opts
_options_updated!
@root= File.dirname @sitefile
@last_run= 0
clear
end
def options=(opts={})
@options.merge!(opts)
_options_updated!
end
def clear()
@contents= ContentList.new
@layouts= SpecialContentList.new ".layout"
@partials= SpecialContentList.new
@generators= []
@data= DataManager.new
@is_scanned= false
_reset_config!
_load_sitefile
self
end
def scan(force=false)
if !@is_scanned or force
clear if @is_scanned # ????
_content_scanner
@is_scanned= true
generate
end
self
end
def scan_only # For testing...
if !@is_scanned or force
clear if @is_scanned # ????
_content_scanner
end
self
end
def generate
_execute_generators
self
end
def in_blacklist?(path)
blacklist.any? do |pattern|
path.path_match? pattern
end
end
def ignore_path?(path)
config.ignore.any? do |pattern|
path.path_match? pattern
end
end
def source_path
@source_path ||= source_dir.expand_path(root)
end
def output_path
@output_path ||= output_dir.expand_path(root)
end
def data_path
@data_path ||= data_dir.expand_path(root)
end
def resolve(path=nil, opts={})
case
when path.is_a?(Content)
path
when !path.nil?
contents.first(path) || partials.first(path)
when opts[:page]
contents.first opts[:page]
when opts[:partial]
partials.first opts[:partial]
when opts[:layout]
layouts.first opts[:layout]
when opts[:generator]
generators.first opts[:generator]
else
nil
end
end
# Events stop bubbling here.
def parent
nil
end
def config_did_change
Gumdrop.init_logging
end
private
def _reset_config!
config.clear.merge! DEFAULT_CONFIG
config.env= @options.env.to_sym if @options.env
config.mode= @options.mode.nil? ? :unknown : @options.mode.to_sym
end
def _options_updated!
config.env= @options.env.to_sym if @options.env
config.mode= @options.mode.nil? ? :unknown : @options.mode.to_sym
end
def _load_sitefile
clear_events
load sitefile
data.dir= data_path
rescue Exception => ex
msg= "There is an error in your Gumdrop file!"
# $stderr.puts msg
log.error msg
log.error ex
raise ex
end
def _content_scanner
log.info "Gumdrop v#{ Gumdrop::VERSION } - #{ Time.new }"
log.debug "(config)"
log.debug config
log.debug "(options)"
log.debug @options
log.debug "[Scanning: #{source_path}]"
# Report ignore list
ignore.each {|p| log.debug " ignoring: #{p}" }
# Scan Filesystem
event_block :scan do
scanner= Util::Scanner.new(source_path, {}, &method(:_scanner_validator))
scanner.each do |path, rel|
content= Content.new(path)
layouts.add content and next if content.layout?
partials.add content and next if content.partial?
generators << Generator.new(content) and next if content.generator?
contents.add content
log.debug " including: #{ rel }"
end
contents.keys.size
end
@is_scanned= true
end
def _scanner_validator(source_path, full_path)
return true if ignore_path? source_path
# in_blacklist? source_path
false
end
def _execute_generators
log.debug "[Executing Generators]"
event_block :generate do
generators.each do |generator|
generator.execute()
end
end
end
end
class << self
# This will look for a nearby Gumdrop file and load the Site
# the first time it is called. Each time after it will just
# return the already created Site.
def site(opts={}, force_new=false)
opts= opts.to_symbolized_hash
unless @current_site.nil? or force_new
@current_site.options= opts unless opts.empty?
@current_site
else
site_file= fetch_site_file
unless site_file.nil?
Site.new site_file, opts
else
nil
end
end
end
# Listen for life-cycle events in your Gumdrop file like this:
#
# Gumdrop.on :start do |event|
# puts "Here we go!"
# end
#
# Complete list of events fired by Gumdrop:
#
# :start
# :before_scan
# :scan
# :after_scan
# :before_generate
# :generate
# :after_generate
# :before_generate_item
# :generate_item
# :after_generate_item
# :before_build
# :build
# :after_build
# :before_checksum
# :checksum
# :after_checksum
# :before_render
# :render
# :after_render
# :before_render_item
# :render_item
# :after_render_item
# :before_write
# :write
# :after_write
# :before_copy_file
# :copy_file
# :after_copy_file
# :before_write_file
# :write_file
# :after_write_file
# :end
#
# In the block parem `event` you will have access to :site, the
# current executing site instance, and :payload, where applicable.
#
# :render_item is a special event because you can add/override the
# compiled output by modifying `event.data.return_value`
#
def on(event_type, options={}, &block)
site.on event_type, options, &block
end
# Yield a configuration object. You can update Gumdrop behavior
# as well as add your own config information.
def configure(&block)
site.configure &block
end
# Short cut to the current Site config object.
def config
site.config
end
# The env Gumdrop is run in -- You can set this in the congig
# or, better, via command line: `gumdrop build -e test`
def env
site.env
end
# Mostly for internal use, but you can update/change it in the
# configure block.
def mode
site.mode
end
# Returns true if this, or a parent, folder has a Gumdrop file.
def in_site_folder?(filename="Gumdrop")
!fetch_site_file(filename).nil?
end
# Specified paths will be added to the ignore list, preventing
# matching files from appearing in the source tree
def ignore(*paths)
paths.each do |path|
if path.is_a? Array
config.ignore.concat path
else
config.ignore << path
end
end
end
# Specified paths will not be renderd to output (matching against
# the source tree).
def blacklist(*paths)
paths.each do |path|
if path.is_a? Array
config.blacklist.concat path
else
config.blacklist << path
end
end
end
protected
# Walks up the filesystem, starting from Dir.pwd, looking for
# a Gumdrop file. Returns nil if not found.
def fetch_site_file(filename="Gumdrop")
here= Dir.pwd
found= File.file? here / filename
# TODO: Should be smarter -- This is a hack for Windows support "C:\"
while !found and File.directory?(here) and File.dirname(here).length > 3
here= File.expand_path here /'..'
found= File.file? here / filename
end
if found
File.expand_path here / filename
else
nil
end
end
def set_current_site(site)
@current_site= site
end
end
end