require 'fileutils'
# require 'sinatra/base'
module Sinatra
# Sinatra Caching module
#
# TODO:: Need to write documentation here
#
module Cache
VERSION = 'Sinatra::Cache v0.2.2'
def self.version; VERSION; end
module Helpers
# Caches the given URI to a html file in /public
#
# Usage:
# >> cache( erb(:contact, :layout => :layout))
# => returns the HTML output written to /public//contact.html
#
# Also accepts an Options Hash, with the following options:
# * :extension => in case you need to change the file extension
#
# TODO:: implement the opts={} hash functionality. What other options are needed?
#
def cache(content, opts={})
return content unless options.cache_enabled
unless content.nil?
content = "#{content}\n#{page_cached_timestamp}"
path = cache_page_path(request.path_info,opts)
FileUtils.makedirs(File.dirname(path))
open(path, 'wb+') { |f| f << content }
log("Cached Page: [#{path}]",:info)
content
end
end
# Expires the cached URI (as .html file) in /public
#
# Usage:
# >> cache_expire('/contact')
# => deletes the /public/contact.html page
#
# get '/contact' do
# cache_expire # deletes the /public/contact.html page as well
# end
#
# TODO:: implement the options={} hash functionality. What options are really needed ?
def cache_expire(path = nil, opts={})
return unless options.cache_enabled
path = (path.nil?) ? cache_page_path(request.path_info) : cache_page_path(path, opts)
if File.exist?(path)
File.delete(path)
log("Expired Page deleted at: [#{path}]",:info)
else
log("No Expired Page was found at the path: [#{path}]",:info)
end
end
# Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
#
# *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
# inside the tag. Many hours wasted on that lesson ;-)
#
# Usage:
# >> <%= page_cached_timestamp %>
# =>
#
def page_cached_timestamp
"\n" if options.cache_enabled
end
# Simple method to see if a file already exists in the cache
#
# TODO: Testcases!
def cached_file_exists(path, opts={})
path = (path.nil?) ? cache_page_path(request.path_info) : cache_page_path(path, opts)
return File.exist?(path)
end
private
# Establishes the file name of the cached file from the path given
#
# TODO:: implement the opts={} functionality, and support for custom extensions on a per request basis.
#
def cache_file_name(path,opts={})
name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
opts.each do |key, value|
name = name + "." + key.to_s.downcase + "." + value.to_s.downcase
end
name << options.cache_page_extension # unless (name.split('/').last || name).include? '.'
return name
end
# Sets the full path to the cached page/file
# Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
#
def cache_page_path(path,opts={})
# test if given a full path rather than relative path, otherwise join the public path to cache_dir
# and ensure it is a full path
cache_dir = (options.cache_output_dir == File.expand_path(options.cache_output_dir)) ?
options.cache_output_dir : File.expand_path("#{options.public}/#{options.cache_output_dir}")
cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
"#{cache_dir}/#{cache_file_name(path,opts)}"
end
# TODO:: this implementation really stinks, how do I incorporate Sinatra's logger??
def log(msg,scope=:debug)
if options.cache_logging
"Log: msg=[#{msg}]" if scope == options.cache_logging_level
else
# just ignore the stuff...
# puts "just ignoring msg=[#{msg}] since cache_logging => [#{options.cache_logging.to_s}]"
end
end
end #/module Helpers
# Sets the default options:
#
# * +:cache_enabled+ => toggle for the cache functionality. Default is: +true+
# * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
# * +:cache_dir+ => sets cache directory where cached files are stored. Default is: ''(empty) == root of /public.
# set to empty, since the ideal 'system/cache/' does not work with Passenger & mod_rewrite :(
# * +cache_logging+ => toggle for logging the cache calls. Default is: +true+
# * +cache_logging_level+ => sets the level of the cache logger. Default is: :info.
# Options:(unused atm) [:info, :warn, :debug]
#
def self.registered(app)
app.helpers(Cache::Helpers)
app.set :cache_enabled, true
app.set :cache_page_extension, '.html'
app.set :cache_output_dir, ''
app.set :cache_logging, true
app.set :cache_logging_level, :info
end
end #/module Cache
register(Sinatra::Cache)
end #/module Sinatra