diff --git a/lib/sinatra/cache/helpers.rb b/lib/sinatra/cache/helpers.rb index 3bdcc95..794019f 100644 --- a/lib/sinatra/cache/helpers.rb +++ b/lib/sinatra/cache/helpers.rb @@ -1,6 +1,6 @@ -module Sinatra - +module Sinatra + # = Sinatra::Cache # # A Sinatra Extension that makes Page and Fragment Caching easy wthin your Sinatra apps. @@ -325,9 +325,9 @@ module Sinatra # # module Cache - - module Helpers - + + module Helpers + ## # This method either caches the code fragment and then renders it, # or locates the cached fragement and renders that. @@ -387,10 +387,10 @@ module Helpers # ... would use the same cached fragment. # # @api public - def cache_fragment(fragment_name, shared = nil, &block) + def cache_fragment(fragment_name, shared = nil, &block) # 1. check for a block, there must always be a block raise ArgumentError, "Missing block" unless block_given? - + # 2. get the fragment path, by combining the PATH_INFO of the request, and the fragment_name dir_structure = request.path_info.empty? ? '' : request.path_info.gsub(/^\//,'').gsub(/\/$/,'') # if we are sharing this fragment with other URLs (as in the all the articles in a category of a blog) @@ -402,7 +402,7 @@ def cache_fragment(fragment_name, shared = nil, &block) cf = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{fragment_name}.html" # 3. ensure the fragment cache directory exists for this fragment FileUtils.mkdir_p(File.dirname(cf)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cf)} ]" - + # 3. check if the fragment is already cached ? if test(?f, cf) # 4. yes. cached, so load it up into the ERB buffer . Sorry, don't know how to do this for Haml or any others. @@ -426,19 +426,41 @@ def cache_fragment(fragment_name, shared = nil, &block) end # for future versions once old habits are gone # alias_method :cache, :cache_fragment - - + + ## + # Cache some raw data + # @api public + def cache_raw(options = {}, &block) + + # 1. check for a block, there must always be a block + raise ArgumentError, "Missing block" unless block_given? + + if cache_enabled?(options) + file_path = cache_file_path + if test(?f, file_path) + IO.read(file_path) + else + content = block.call + cache_write_file(file_path, content) + content + end + else + block.call + end + end + + ## # NB!! Deprecated method. # # Just returns the content after throwing out a warning. # - def cache(content, opts={}) + def cache(content, opts={}) warn("Deprecated method, caching is now happening by default if the :cache_enabled option is true") content end - - + + ## # Expires the cached file (page) or fragment. # @@ -455,18 +477,18 @@ def cache(content, opts={}) # # # @api public - def cache_expire(path, options={}) + def cache_expire(path, options={}) # 1. bail quickly if we don't have caching enabled return unless settings.cache_enabled options = { :fragment => false }.merge(options) - + if options[:fragment] # dealing with a fragment dir_structure = path.gsub(/^\//,'').gsub(/\/$/,'') file_path = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{options[:fragment]}.html" else file_path = cache_file_path(path) end - + if test(?f, file_path) File.delete(file_path) log(:info,"Expired [#{file_path.sub(settings.root,'')}] successfully") @@ -474,8 +496,8 @@ def cache_expire(path, options={}) log(:warn,"The cached file [#{file_path}] could NOT be expired as it was NOT found") end end - - + + ## # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last. # @@ -487,7 +509,7 @@ def cache_expire(path, options={}) # <%= cache_timestamp %> # => # # @api public - def cache_timestamp + def cache_timestamp if settings.cache_enabled && settings.cache_environment == settings.environment "\n" end @@ -495,44 +517,43 @@ def cache_timestamp # backwards compat and syntactic sugar for others alias_method :cache_page_timestamp, :cache_timestamp alias_method :page_cached_at, :cache_timestamp - - + + ## PRIVATE METHODS private - + ## # Converts the PATH_INFO path into the full cached file path. - # + # # ==== GOTCHA: - # - # NB! completely ignores the URL query params passed such as + # + # NB! completely ignores the URL query params passed such as # in this example: - # - # /products?page=2 - # + # + # /products?page=2 + # # To capture and cache those query strings, please do as follows: - # + # # get 'products/page/:page' { ... } # in your Sinatra app - # + # # /products/page/2 => .../public/cache/products/page/2.html - # - # + # + # # ==== Examples - # + # # / => .../public/cache/index.html - # + # # /contact => .../public/cache/contact.html - # + # # /contact/ => .../public/cache/contact/index.html - # - # + # + # # @api public - def cache_file_path(in_path = nil) + def cache_file_path(in_path = nil) path = settings.send(:cache_output_dir).dup - - + path_info = in_path.nil? ? request.path_info : in_path - if (path_info.empty? || path_info == "/" ) + if (path_info.empty? || path_info == "/" ) path << "/index" elsif ( path_info[-1, 1] == '/' ) path << ::Rack::Utils.unescape(path_info.chomp('/') << '/index') @@ -540,73 +561,91 @@ def cache_file_path(in_path = nil) path << ::Rack::Utils.unescape(path_info.chomp('/')) end path << settings.cache_page_extension if File.extname(path) == '' - return path + path end - + ## - # Writes the cached file to disk, only during GET requests, + # Writes the cached file to disk, only during GET requests, # and then returns the content. - # + # # ==== Examples - # - # + # + # # @api private - def cache_write_file(cache_file, content) + def cache_write_file(cache_file, content) # only cache GET request [http://rack.rubyforge.org/doc/classes/Rack/Request.html#M000239] if request.get? FileUtils.mkdir_p(File.dirname(cache_file)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cache_file)} ]" File.open(cache_file, 'wb'){ |f| f << content} end - return content + content end - + ## # Establishes the file name of the cached file from the path given - # + # # @api private - def cache_file_name(path, options={}) + def cache_file_name(path, options={}) name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/')) name << settings.cache_page_extension unless (name.split('/').last || name).include? '.' - return name + name end - + ## # Sets the full path to the cached page/file # Dependent upon Sinatra.options .public and .cache_dir variables being present and set. - # - # + # + # # @api private - def cache_page_path(path, options={}) - # test if given a full path rather than relative path, otherwise join the public path to cache_dir + def cache_page_path(path, options={}) + # 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 = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ? + cache_dir = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ? settings.cache_output_dir : File.expand_path("#{settings.public}/#{settings.cache_output_dir}") cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/' "#{cache_dir}/#{cache_file_name(path, options)}" end - + + ## + # Calculate if the cache is enabled + # + # @api private + def cache_enabled? options + (settings.respond_to?(:cache_enabled) ? settings.send(:cache_enabled) : false) && + (options[:cache] || true) && + settings.send(:environment) == settings.cache_environment + end + + ## + # Extract content type from options + # + # @api private + def extract_content_type options + options.delete(:content_type) || options.delete(:default_content_type) + end + ## # Convenience method that handles logging of Cache related stuff. - # + # # Uses Sinatra::Logger's #logger method if available, otherwise just # puts out the log message. - # - def log(scope, msg) + # + def log(scope, msg) if settings.cache_logging if scope.to_sym == settings.cache_logging_level.to_sym if self.respond_to?(:logger) - logger.send(scope, msg) + logger.send(scope, msg) else puts "#{scope.to_s.upcase}: #{msg}" end end end end - - + + end #/ Helpers - - + + ## # The default options: # @@ -635,7 +674,7 @@ def log(scope, msg) def self.registered(app) app.register(Sinatra::OutputBuffer) app.helpers Cache::Helpers - + ## CONFIGURATIONS:: app.set :cache_enabled, false app.set :cache_environment, :production @@ -643,11 +682,11 @@ def self.registered(app) app.set :cache_output_dir, lambda { app.public } app.set :cache_fragments_output_dir, lambda { "#{app.root}/tmp/cache_fragments" } app.set :cache_fragments_wrap_with_html_comments, true - + app.set :cache_logging, true app.set :cache_logging_level, :info - - + + ## add the extension specific options to those inspectable by :settings_inspect method if app.respond_to?(:sinatra_settings_for_inspection) %w( cache_enabled cache_environment cache_page_extension cache_output_dir @@ -657,11 +696,11 @@ def self.registered(app) app.sinatra_settings_for_inspection << m end end - + end #/ self.registered - + end #/ Cache - + register(Sinatra::Cache) # support classic apps - + end #/ Sinatra \ No newline at end of file diff --git a/lib/sinatra/templates.rb b/lib/sinatra/templates.rb index 865f045..a3eaebc 100644 --- a/lib/sinatra/templates.rb +++ b/lib/sinatra/templates.rb @@ -23,15 +23,7 @@ def render(engine, data, options={}, locals={}, &block) @default_layout = :layout if @default_layout.nil? layout = options.delete(:layout) layout = @default_layout if layout.nil? or layout == true - content_type = options.delete(:content_type) || options.delete(:default_content_type) - - # set the cache related options - cache_enabled = settings.respond_to?(:cache_enabled) ? settings.send(:cache_enabled) : false - cache_output_dir = settings.send(:cache_output_dir) if settings.respond_to?(:cache_output_dir) - # raise Exception, "The Sinatra::Cache cache_output_dir variable is pointing to a non-existant directory cache_output_dir=[#{cache_output_dir}]" unless test(?d, cache_output_dir) - cache_option = options[:cache] - cache_option = true if cache_option.nil? - + # compile and render template layout_was = @default_layout @default_layout = false @@ -45,17 +37,16 @@ def render(engine, data, options={}, locals={}, &block) options = options.merge(:views => views, :layout => false) output = render(engine, layout, options, locals) { output } # Cache the content or just return it - (cache_enabled && cache_option && settings.send(:environment) == settings.cache_environment) ? - cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"")) : output + cache_enabled?(options) ? cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"")) : output rescue Errno::ENOENT end end # rendering without a layout - (cache_enabled && cache_option && settings.send(:environment) == settings.cache_environment) ? - cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"") ) : output - - output.extend(ContentTyped).content_type = content_type if content_type + cache_enabled?(options) ? cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"") ) : output + if content_type = extract_content_type(options) + output.extend(ContentTyped).content_type = content_type + end output end