benjaminjackson-sinatra-cache 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sinatra-cache"
8
+ gem.summary = %Q{A Sinatra Extension that makes Page and Fragment Caching easy.}
9
+ gem.description = %Q{A Sinatra Extension that makes Page and Fragment Caching easy.}
10
+ gem.email = "kematzy@gmail.com"
11
+ gem.homepage = "http://github.com/kematzy/sinatra-cache"
12
+ gem.authors = ["kematzy"]
13
+ gem.add_dependency('sinatra', '>=1.1.0')
14
+ gem.add_dependency('sinatra-outputbuffer', '>=0.1.0')
15
+ gem.add_development_dependency "sinatra-tests", ">= 0.1.6"
16
+ gem.add_development_dependency "rspec", ">= 1.3.0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ namespace :spec do
38
+
39
+ desc "Run all specifications quietly"
40
+ Spec::Rake::SpecTask.new(:quiet) do |t|
41
+ t.libs << "lib"
42
+ t.spec_opts = ["--color", "--require", "spec/spec_helper.rb"]
43
+ end
44
+
45
+ desc "Run specific spec verbosely (SPEC=/path/2/file)"
46
+ Spec::Rake::SpecTask.new(:select) do |t|
47
+ t.libs << "lib"
48
+ t.spec_files = [ENV["SPEC"]]
49
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
50
+ end
51
+
52
+ end
53
+
54
+ task :spec => :check_dependencies
55
+
56
+ task :default => :spec
57
+
58
+ require 'rake/rdoctask'
59
+ Rake::RDocTask.new do |rdoc|
60
+ version = File.exist?('VERSION') ? IO.read('VERSION').chomp : "[Unknown]"
61
+
62
+ rdoc.rdoc_dir = 'rdoc'
63
+ rdoc.title = "Sinatra::Cache v#{version}"
64
+ rdoc.rdoc_files.include('README*')
65
+ rdoc.rdoc_files.include('lib/**/*.rb')
66
+ end
67
+
68
+ desc 'Build the rdoc HTML Files'
69
+ task :docs do
70
+ version = File.exist?('VERSION') ? IO.read('VERSION').chomp : "[Unknown]"
71
+
72
+ sh "sdoc -N --title 'Sinatra::Cache v#{version}' lib/ README.rdoc"
73
+ end
74
+
75
+ namespace :docs do
76
+
77
+ desc 'Remove rdoc products'
78
+ task :remove => [:clobber_rdoc]
79
+
80
+ desc 'Force a rebuild of the RDOC files'
81
+ task :rebuild => [:rerdoc]
82
+
83
+ desc 'Build docs, and open in browser for viewing (specify BROWSER)'
84
+ task :open => [:docs] do
85
+ browser = ENV["BROWSER"] || "safari"
86
+ sh "open -a #{browser} doc/index.html"
87
+ end
88
+
89
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.8
@@ -0,0 +1,13 @@
1
+
2
+ require 'sinatra/outputbuffer'
3
+
4
+ module Sinatra
5
+ module Cache
6
+ VERSION = '0.3.8' unless const_defined?(:VERSION)
7
+ def self.version; "Sinatra::Cache v#{VERSION}"; end
8
+ end #/ Cache
9
+ end #/ Sinatra
10
+
11
+ %w(templates cache/helpers).each do |lib|
12
+ require "sinatra/#{lib}"
13
+ end
@@ -0,0 +1,667 @@
1
+
2
+ module Sinatra
3
+
4
+ # = Sinatra::Cache
5
+ #
6
+ # A Sinatra Extension that makes Page and Fragment Caching easy wthin your Sinatra apps.
7
+ #
8
+ # == Installation
9
+ #
10
+ # # Add RubyGems.org (former Gemcutter) to your RubyGems sources
11
+ # $ gem sources -a http://rubygems.org
12
+ #
13
+ # $ (sudo)? gem install sinatra-cache
14
+ #
15
+ # == Dependencies
16
+ #
17
+ # This Gem depends upon the following:
18
+ #
19
+ # === Runtime:
20
+ #
21
+ # * sinatra ( >= 1.0.a )
22
+ # * sinatra-outputbuffer[http://github.com/kematzy/sinatra-outputbuffer] (>= 0.1.0)
23
+ #
24
+ # Optionals:
25
+ #
26
+ # * sinatra-settings[http://github.com/kematzy/sinatra-settings] (>= 0.1.1) # to view default settings in a browser display.
27
+ #
28
+ # === Development & Tests:
29
+ #
30
+ # * rspec (>= 1.3.0 )
31
+ # * rack-test (>= 0.5.3)
32
+ # * rspec_hpricot_matchers (>= 0.1.0)
33
+ # * sinatra-tests (>= 0.1.6)
34
+ # * fileutils
35
+ # * sass
36
+ # * ostruct
37
+ # * yaml
38
+ # * json
39
+ #
40
+ #
41
+ # == Getting Started
42
+ #
43
+ # To start caching your app's ouput, just require and register
44
+ # the extension in your sub-classed Sinatra app:
45
+ #
46
+ # require 'sinatra/cache'
47
+ #
48
+ # class YourApp < Sinatra::Base
49
+ #
50
+ # # NB! you need to set the root of the app first
51
+ # set :root, '/path/2/the/root/of/your/app'
52
+ #
53
+ # register(Sinatra::Cache)
54
+ #
55
+ # set :cache_enabled, true # turn it on
56
+ #
57
+ # <snip...>
58
+ #
59
+ # end
60
+ #
61
+ #
62
+ # That's more or less it.
63
+ #
64
+ # You should now be caching your output by default, in <tt>:production</tt> mode, as long as you use
65
+ # one of Sinatra's render methods:
66
+ #
67
+ # erb(), erubis(), haml(), sass(), builder(), etc..
68
+ #
69
+ # ...or any render method that uses <tt>Sinatra::Templates#render()</tt> as its base.
70
+ #
71
+ #
72
+ #
73
+ # == Configuration Settings
74
+ #
75
+ # The default settings should help you get moving quickly, and are fairly common sense based.
76
+ #
77
+ #
78
+ # === <tt>:cache_enabled</tt>
79
+ #
80
+ # This setting toggles the cache functionality On / Off.
81
+ # Default is: <tt>false</tt>
82
+ #
83
+ #
84
+ # === <tt>:cache_environment</tt>
85
+ #
86
+ # Sets the environment during which the cache functionality is active.
87
+ # Default is: <tt>:production</tt>
88
+ #
89
+ #
90
+ # === <tt>:cache_page_extension</tt>+
91
+ #
92
+ # Sets the default file extension for cached files.
93
+ # Default is: <tt>.html</tt>
94
+ #
95
+ #
96
+ # === <tt>:cache_output_dir</tt>
97
+ #
98
+ # Sets cache directory where the cached files are stored.
99
+ # Default is: == "/path/2/your/app/public"
100
+ #
101
+ # Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
102
+ # if you can get that to work with your webserver setup.
103
+ #
104
+ #
105
+ # === <tt>:cache_fragments_output_dir</tt>
106
+ #
107
+ # Sets the directory where cached fragments are stored.
108
+ # Default is the '../tmp/cache_fragments/' directory at the root of your app.
109
+ #
110
+ # This is for security reasons since you don't really want your cached fragments publically available.
111
+ #
112
+ #
113
+ # === <tt>:cache_fragments_wrap_with_html_comments</tt>
114
+ #
115
+ # This setting toggles the wrapping of cached fragments in HTML comments. (see below)
116
+ # Default is: <tt>true</tt>
117
+ #
118
+ #
119
+ # === <tt>:cache_logging</tt>
120
+ #
121
+ # This setting toggles the logging of various cache calls. If the app has access to the <tt>#logger</tt> method,
122
+ # curtesy of Sinatra::Logger[http://github.com/kematzy/sinatra-logger] then it will log there, otherwise logging
123
+ # is silent.
124
+ #
125
+ # Default is: <tt>true</tt>
126
+ #
127
+ #
128
+ # === <tt>:cache_logging_level</tt>
129
+ #
130
+ # Sets the level at which the cache logger should log it's messages.
131
+ # Default is: <tt>:info</tt>
132
+ #
133
+ # Available options are: [:fatal, :error, :warn, :info, :debug]
134
+ #
135
+ #
136
+ # == Basic Page Caching
137
+ #
138
+ # By default caching only happens in <tt>:production</tt> mode, and via the Sinatra render methods, erb(), etc,
139
+ #
140
+ # So asuming we have the following setup (continued from above)
141
+ #
142
+ #
143
+ # class YourApp
144
+ #
145
+ # <snip...>
146
+ #
147
+ # set :cache_output_dir, "/full/path/2/app/root/public/system/cache"
148
+ #
149
+ # <snip...>
150
+ #
151
+ # get('/') { erb(:index) } # => is cached as '../index.html'
152
+ #
153
+ # get('/contact') { erb(:contact) } # => is cached as '../contact.html'
154
+ #
155
+ # # NB! the trailing slash on the URL
156
+ # get('/about/') { erb(:about) } # => is cached as '../about/index.html'
157
+ #
158
+ # get('/feed.rss') { builder(:feed) } # => is cached as '../feed.rss'
159
+ # # NB! uses the extension of the passed URL,
160
+ # # but DOES NOT ensure the format of the content based on the extension provided.
161
+ #
162
+ # # complex URL with multiple possible params
163
+ # get %r{/articles/?([\s\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?} do
164
+ # erb(:articles)
165
+ # end
166
+ # # with the '/articles/a/b/c => is cached as ../articles/a/b/c.html
167
+ #
168
+ # # NB! the trailing slash on the URL
169
+ # # with the '/articles/a/b/c/ => is cached as ../articles/a/b/c/index.html
170
+ #
171
+ # # CSS caching via Sass # => is cached as '.../css/screen.css'
172
+ # get '/css/screen.css' do
173
+ # content_type 'text/css'
174
+ # sass(:'css/screen')
175
+ # end
176
+ #
177
+ # # to turn off caching on certain pages.
178
+ # get('/dont/cache/this/page') { erb(:aview, :cache => false) } # => is NOT cached
179
+ #
180
+ #
181
+ # # NB! any query string params - [ /?page=X&id=y ] - are stripped off and TOTALLY IGNORED
182
+ # # during the caching process.
183
+ #
184
+ # end
185
+ #
186
+ # OK, that's about all you need to know about basic Page Caching right there. Read the above example
187
+ # carefully until you understand all the variations.
188
+ #
189
+ #
190
+ # == Fragment Caching
191
+ #
192
+ # If you just need to cache a fragment of a page, then you would do as follows:
193
+ #
194
+ # class YourApp
195
+ #
196
+ # set :cache_fragments_output_dir, "/full/path/2/fragments/store/location"
197
+ #
198
+ # end
199
+ #
200
+ # Then in your views / layouts add the following:
201
+ #
202
+ # <% cache_fragment(:name_of_fragment) do %>
203
+ # # do something worth caching
204
+ # <% end %>
205
+ #
206
+ #
207
+ # Each fragment is stored in the same directory structure as your request
208
+ # so, if you have a request like this:
209
+ #
210
+ # get '/articles/2010/02' ...
211
+ #
212
+ # ...the cached fragment will be stored as:
213
+ #
214
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
215
+ #
216
+ # This enables you to use similar names for your fragments or have
217
+ # multiple URLs use the same view / layout.
218
+ #
219
+ #
220
+ # === An important limitation
221
+ #
222
+ # The fragment caching is dependent upon the final URL, so in the case of
223
+ # a blog, where each article uses the same view, but through different URLs,
224
+ # each of the articles would cache it's own fragment, which is ineffecient.
225
+ #
226
+ # To sort-of deal with this limitation I have temporarily added a very hackish
227
+ # 'fix' through adding a 2nd parameter (see example below), which will remove the
228
+ # last part of the URL and use the rest of the URL as the stored fragment path.
229
+ #
230
+ # So given the URL:
231
+ #
232
+ # get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
233
+ #
234
+ # and the following <tt>#cache_fragment</tt> declaration in your view
235
+ #
236
+ # <% cache_fragment(:name_of_fragment, :shared) do %>
237
+ # # do something worth caching
238
+ # <% end %>
239
+ #
240
+ # ...the cached fragment would be stored as:
241
+ #
242
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
243
+ #
244
+ # Any other URLs with the same URL root, like...
245
+ #
246
+ # get '/articles/2010/02/writing-sinatra-extensions' ...
247
+ #
248
+ # ... would use the same cached fragment.
249
+ #
250
+ #
251
+ # <b>NB!</b> currently only supports one level, but Your fork might fix that ;-)
252
+ #
253
+ #
254
+ # == Cache Expiration
255
+ #
256
+ # <b>Under development, and not entirely final.</b> See Todo's below for more info.
257
+ #
258
+ #
259
+ # To expire a cached item - file or fragment - you use the :cache_expire() method.
260
+ #
261
+ #
262
+ # cache_expire('/contact') => expires ../contact.html
263
+ #
264
+ #
265
+ # # NB! notice the trailing slash
266
+ # cache_expire('/contact/') => expires ../contact/index.html
267
+ #
268
+ #
269
+ # cache_expire('/feed.rss') => expires ../feed.rss
270
+ #
271
+ #
272
+ # To expire a cached fragment:
273
+ #
274
+ # cache_expire('/some/path', :fragment => :name_of_fragment )
275
+ #
276
+ # => expires ../some/path/:name_of_fragment.html
277
+ #
278
+ #
279
+ #
280
+ # == A few important points to consider
281
+ #
282
+ #
283
+ # === The DANGERS of URL query string params
284
+ #
285
+ # By default the caching ignores the query string params, but that's not the only problem with query params.
286
+ #
287
+ # Let's say you have a URL like this:
288
+ #
289
+ # /products/?product_id=111
290
+ #
291
+ # and then inside that template [ .../views/products.erb ], you use the <tt>params[:product_id]</tt>
292
+ # param passed in for some purpose.
293
+ #
294
+ # <ul>
295
+ # <li>Product ID: <%= params[:product_id] %></li> # => 111
296
+ # ...
297
+ # </ul>
298
+ #
299
+ # If you cache this URL, then the cached file [ ../cache/products.html ] will be stored with that
300
+ # value embedded. Obviously not ideal for any other similar URLs with different <tt>product_id</tt>'s
301
+ #
302
+ # To overcome this issue, use either of these two methods.
303
+ #
304
+ # # in your_app.rb
305
+ #
306
+ # # turning off caching on this page
307
+ #
308
+ # get '/products/' do
309
+ # ...
310
+ # erb(:products, :cache => false)
311
+ # end
312
+ #
313
+ # # or
314
+ #
315
+ # # rework the URLs to something like '/products/111 '
316
+ #
317
+ # get '/products/:product_id' do
318
+ # ...
319
+ # erb(:products)
320
+ # end
321
+ #
322
+ #
323
+ #
324
+ # Thats's about all the information you need to know.
325
+ #
326
+ #
327
+ module Cache
328
+
329
+ module Helpers
330
+
331
+ ##
332
+ # This method either caches the code fragment and then renders it,
333
+ # or locates the cached fragement and renders that.
334
+ #
335
+ # By default the cached fragement is stored in the ../tmp/cache_fragments/
336
+ # directory at the root of your app.
337
+ #
338
+ # Each fragment is stored in the same directory structure as your request
339
+ # so, if you have a request like this:
340
+ #
341
+ # get '/articles/2010/02' ...
342
+ #
343
+ # ...the cached fragment will be stored as:
344
+ #
345
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
346
+ #
347
+ # This enables you to use similar names for your fragments or have
348
+ # multiple URLs use the same view / layout.
349
+ #
350
+ # ==== Examples
351
+ #
352
+ # <% cache_fragment(:name_of_fragment) do %>
353
+ # # do something worth caching
354
+ # <% end %>
355
+ #
356
+ # === GOTCHA
357
+ #
358
+ # The fragment caching is dependent upon the final URL, so in the case of
359
+ # a blog, where each article uses the same view, but through different URLs,
360
+ # each of the articles would cache it's own fragment.
361
+ #
362
+ # To sort-of deal with this limitation I have added a very hackish 'fix'
363
+ # through adding a 2nd parameter (see example below), which will
364
+ # remove the last part of the URL and use the rest of the URL as
365
+ # the stored fragment path.
366
+ #
367
+ # ==== Example
368
+ #
369
+ # Given the URL:
370
+ #
371
+ # get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
372
+ #
373
+ # and the following <tt>#cache_fragment</tt> declaration in your view
374
+ #
375
+ # <% cache_fragment(:name_of_fragment, :shared) do %>
376
+ # # do something worth caching
377
+ # <% end %>
378
+ #
379
+ # ...the cached fragment would be stored as:
380
+ #
381
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
382
+ #
383
+ # Any other URLs with the same URL root, like...
384
+ #
385
+ # get '/articles/2010/02/writing-sinatra-extensions' ...
386
+ #
387
+ # ... would use the same cached fragment.
388
+ #
389
+ # @api public
390
+ def cache_fragment(fragment_name, shared = nil, &block)
391
+ # 1. check for a block, there must always be a block
392
+ raise ArgumentError, "Missing block" unless block_given?
393
+
394
+ # 2. get the fragment path, by combining the PATH_INFO of the request, and the fragment_name
395
+ dir_structure = request.path_info.empty? ? '' : request.path_info.gsub(/^\//,'').gsub(/\/$/,'')
396
+ # if we are sharing this fragment with other URLs (as in the all the articles in a category of a blog)
397
+ # then lob off the last part of the URL
398
+ unless shared.nil?
399
+ dirs = dir_structure.split('/')
400
+ dir_structure = dirs.first(dirs.length-1).join('/')
401
+ end
402
+ cf = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{fragment_name}.html"
403
+ # 3. ensure the fragment cache directory exists for this fragment
404
+ FileUtils.mkdir_p(File.dirname(cf)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cf)} ]"
405
+
406
+ # 3. check if the fragment is already cached ?
407
+ if test(?f, cf)
408
+ # 4. yes. cached, so load it up into the ERB buffer . Sorry, don't know how to do this for Haml or any others.
409
+ block_content = IO.read(cf)
410
+ else
411
+ # 4. not cached, so process the block and then cache it
412
+ block_content = capture_html(&block) if block_given?
413
+ # 5. add some timestamp comments around the fragment, if the end user wants it
414
+ if settings.cache_fragments_wrap_with_html_comments
415
+ content_2_cache = "<!-- cache fragment: #{dir_structure}/#{fragment_name} -->"
416
+ content_2_cache << block_content
417
+ content_2_cache << "<!-- /cache fragment: #{fragment_name} cached at [ #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] -->\n"
418
+ else
419
+ content_2_cache = block_content
420
+ end
421
+ # 6. write it to cache
422
+ cache_write_file(cf, content_2_cache)
423
+ end
424
+ # 5. 'return' the content by
425
+ block_is_template?(block) ? concat_content(block_content) : block_content
426
+ end
427
+ # for future versions once old habits are gone
428
+ # alias_method :cache, :cache_fragment
429
+
430
+
431
+ ##
432
+ # <b>NB!! Deprecated method.</b>
433
+ #
434
+ # Just returns the content after throwing out a warning.
435
+ #
436
+ def cache(content, opts={})
437
+ warn("Deprecated method, caching is now happening by default if the :cache_enabled option is true")
438
+ content
439
+ end
440
+
441
+
442
+ ##
443
+ # Expires the cached file (page) or fragment.
444
+ #
445
+ # ==== Examples
446
+ #
447
+ # cache_expire('/contact') => expires ../contact.html
448
+ #
449
+ # cache_expire('/feed.rss') => expires ../feed.rss
450
+ #
451
+ # To expire a cached fragment:
452
+ #
453
+ # cache_expire('/some/path', :fragment => :name_of_fragment )
454
+ # => expires ../some/path/:name_of_fragment.html
455
+ #
456
+ #
457
+ # @api public
458
+ def cache_expire(path, options={})
459
+ # 1. bail quickly if we don't have caching enabled
460
+ return unless settings.cache_enabled
461
+ options = { :fragment => false }.merge(options)
462
+
463
+ if options[:fragment] # dealing with a fragment
464
+ dir_structure = path.gsub(/^\//,'').gsub(/\/$/,'')
465
+ file_path = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{options[:fragment]}.html"
466
+ else
467
+ file_path = cache_file_path(path)
468
+ end
469
+
470
+ if test(?f, file_path)
471
+ File.delete(file_path)
472
+ log(:info,"Expired [#{file_path.sub(settings.root,'')}] successfully")
473
+ else
474
+ log(:warn,"The cached file [#{file_path}] could NOT be expired as it was NOT found")
475
+ end
476
+ end
477
+
478
+
479
+ ##
480
+ # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
481
+ #
482
+ # *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
483
+ # inside the <head> tag. Many hours wasted on that lesson ;-)
484
+ #
485
+ # ==== Examples
486
+ #
487
+ # <%= cache_timestamp %> # => <!-- page cached: 2009-12-21 12:00:00 -->
488
+ #
489
+ # @api public
490
+ def cache_timestamp
491
+ if settings.cache_enabled && settings.cache_environment == settings.environment
492
+ "<!-- page cached: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} -->\n"
493
+ end
494
+ end
495
+ # backwards compat and syntactic sugar for others
496
+ alias_method :cache_page_timestamp, :cache_timestamp
497
+ alias_method :page_cached_at, :cache_timestamp
498
+
499
+
500
+ ## PRIVATE METHODS
501
+ private
502
+
503
+ ##
504
+ # Converts the PATH_INFO path into the full cached file path.
505
+ #
506
+ # ==== GOTCHA:
507
+ #
508
+ # <b>NB!</b> completely ignores the URL query params passed such as
509
+ # in this example:
510
+ #
511
+ # /products?page=2
512
+ #
513
+ # To capture and cache those query strings, please do as follows:
514
+ #
515
+ # get 'products/page/:page' { ... } # in your Sinatra app
516
+ #
517
+ # /products/page/2 => .../public/cache/products/page/2.html
518
+ #
519
+ #
520
+ # ==== Examples
521
+ #
522
+ # / => .../public/cache/index.html
523
+ #
524
+ # /contact => .../public/cache/contact.html
525
+ #
526
+ # /contact/ => .../public/cache/contact/index.html
527
+ #
528
+ #
529
+ # @api public
530
+ def cache_file_path(in_path = nil)
531
+ path = settings.send(:cache_output_dir).dup
532
+
533
+
534
+ path_info = in_path.nil? ? request.path_info : in_path
535
+ if (path_info.empty? || path_info == "/" )
536
+ path << "/index"
537
+ elsif ( path_info[-1, 1] == '/' )
538
+ path << ::Rack::Utils.unescape(path_info.chomp('/') << '/index')
539
+ else
540
+ path << ::Rack::Utils.unescape(path_info.chomp('/'))
541
+ end
542
+ path << settings.cache_page_extension if File.extname(path) == ''
543
+ return path
544
+ end
545
+
546
+ ##
547
+ # Writes the cached file to disk, only during GET requests,
548
+ # and then returns the content.
549
+ #
550
+ # ==== Examples
551
+ #
552
+ #
553
+ # @api private
554
+ def cache_write_file(cache_file, content)
555
+ # only cache GET request [http://rack.rubyforge.org/doc/classes/Rack/Request.html#M000239]
556
+ if request.get?
557
+ FileUtils.mkdir_p(File.dirname(cache_file)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cache_file)} ]"
558
+ File.open(cache_file, 'wb'){ |f| f << content}
559
+ end
560
+ return content
561
+ end
562
+
563
+ ##
564
+ # Establishes the file name of the cached file from the path given
565
+ #
566
+ # @api private
567
+ def cache_file_name(path, options={})
568
+ name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
569
+ name << settings.cache_page_extension unless (name.split('/').last || name).include? '.'
570
+ return name
571
+ end
572
+
573
+ ##
574
+ # Sets the full path to the cached page/file
575
+ # Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
576
+ #
577
+ #
578
+ # @api private
579
+ def cache_page_path(path, options={})
580
+ # test if given a full path rather than relative path, otherwise join the public path to cache_dir
581
+ # and ensure it is a full path
582
+ cache_dir = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ?
583
+ settings.cache_output_dir : File.expand_path("#{settings.public_folder}/#{settings.cache_output_dir}")
584
+ cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
585
+ "#{cache_dir}/#{cache_file_name(path, options)}"
586
+ end
587
+
588
+ ##
589
+ # Convenience method that handles logging of Cache related stuff.
590
+ #
591
+ # Uses Sinatra::Logger's #logger method if available, otherwise just
592
+ # puts out the log message.
593
+ #
594
+ def log(scope, msg)
595
+ if settings.cache_logging
596
+ if scope.to_sym == settings.cache_logging_level.to_sym
597
+ if self.respond_to?(:logger)
598
+ logger.send(scope, msg)
599
+ else
600
+ puts "#{scope.to_s.upcase}: #{msg}"
601
+ end
602
+ end
603
+ end
604
+ end
605
+
606
+
607
+ end #/ Helpers
608
+
609
+
610
+ ##
611
+ # The default options:
612
+ #
613
+ # * +:cache_enabled+ => toggle for the cache functionality. Default is: +false+
614
+ #
615
+ # * +:cache_environment+ => sets the environment during which to cache. Default is: +:production+
616
+ #
617
+ # * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
618
+ #
619
+ # * +:cache_output_dir+ => sets cache directory where cached files are stored. Default is: == "/path/2/your/app/public"
620
+ # Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
621
+ # if you can get that to work with your webserver setup.
622
+ #
623
+ # * +:cache_fragments_output_dir+ => sets the directory where cached fragments are stored.
624
+ # Default is the '../tmp/cache_fragments/' directory at the root of your app.
625
+ #
626
+ # * +:cache_fragments_wrap_with_html_comments+ => toggle for wrapping the cached fragment in HTML comments.
627
+ # Default is: +true+
628
+ #
629
+ # * +:cache_logging+ => toggle for logging the cache calls. Default is: +true+
630
+ #
631
+ # * +:cache_logging_level+ => sets the level of the cache logger. Default is: <tt>:info</tt>.<br>
632
+ # Available options are: [:fatal, :error, :warn, :info, :debug]
633
+ #
634
+ #
635
+ def self.registered(app)
636
+ app.register(Sinatra::OutputBuffer)
637
+ app.helpers Cache::Helpers
638
+
639
+ ## CONFIGURATIONS::
640
+ app.set :cache_enabled, false
641
+ app.set :cache_environment, :production
642
+ app.set :cache_page_extension, '.html'
643
+ app.set :cache_output_dir, lambda { app.public_folder }
644
+ app.set :cache_fragments_output_dir, lambda { "#{app.root}/tmp/cache_fragments" }
645
+ app.set :cache_fragments_wrap_with_html_comments, true
646
+
647
+ app.set :cache_logging, true
648
+ app.set :cache_logging_level, :info
649
+
650
+
651
+ ## add the extension specific options to those inspectable by :settings_inspect method
652
+ if app.respond_to?(:sinatra_settings_for_inspection)
653
+ %w( cache_enabled cache_environment cache_page_extension cache_output_dir
654
+ cache_fragments_output_dir cache_fragments_wrap_with_html_comments
655
+ cache_logging cache_logging_level
656
+ ).each do |m|
657
+ app.sinatra_settings_for_inspection << m
658
+ end
659
+ end
660
+
661
+ end #/ self.registered
662
+
663
+ end #/ Cache
664
+
665
+ register(Sinatra::Cache) # support classic apps
666
+
667
+ end #/ Sinatra