sinatra-cacher 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/sinatra/cacher.rb +206 -0
- data/lib/sinatra-cacher.rb +1 -0
- metadata +48 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'sinatra/base'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module Cacher
|
6
|
+
VERSION = '0.9.0'
|
7
|
+
|
8
|
+
def self.registered(app)
|
9
|
+
app.helpers Helpers
|
10
|
+
# Special value :environment means environment is used
|
11
|
+
app.set :cache_enabled, :environment
|
12
|
+
app.set :cache_enabled_in, :production
|
13
|
+
app.set :cache_generate_etags, true
|
14
|
+
app.set :cache_path, 'tmp/cache'
|
15
|
+
# Internal use only
|
16
|
+
app.set :cache_last_tag, nil
|
17
|
+
app.set :cache_overwrite, false
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_enabled?
|
21
|
+
settings.cache_enabled == true || (settings.cache_enabled == :environment && [*settings.cache_enabled_in].include?(settings.environment))
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache_get(path, opts={}, &blk)
|
25
|
+
conditions = @conditions.dup
|
26
|
+
cache_route('GET', path, opts, &blk)
|
27
|
+
@conditions = conditions
|
28
|
+
route('HEAD', path, opts, &blk)
|
29
|
+
end
|
30
|
+
alias_method :get_cache, :cache_get
|
31
|
+
|
32
|
+
def cache_get_tag(tag)
|
33
|
+
return nil if !tag
|
34
|
+
path = cache_gen_path(tag)
|
35
|
+
return nil unless File.file?(path)
|
36
|
+
time, content_type, content = File.open(path, 'rb') do |f|
|
37
|
+
[f.gets.chomp.to_i, f.gets.chomp, f.read]
|
38
|
+
end
|
39
|
+
if content_type == 'marshal'
|
40
|
+
content = Marshal.load(content)
|
41
|
+
content_type = nil
|
42
|
+
elsif content_type.empty?
|
43
|
+
content_type = nil
|
44
|
+
end
|
45
|
+
[content, time, content_type]
|
46
|
+
end
|
47
|
+
|
48
|
+
def cache_put_tag(tag, content, content_type=nil)
|
49
|
+
return unless cache_enabled?
|
50
|
+
raise "Cache tag should not end with a slash" if tag.end_with?('/')
|
51
|
+
raise "Cache tag should not be empty" if tag.empty?
|
52
|
+
unless content.is_a?(String)
|
53
|
+
raise "Can't cache a route which doesn't return a string" if content_type
|
54
|
+
content = Marshal.dump(content)
|
55
|
+
content_type = 'marshal'
|
56
|
+
end
|
57
|
+
path = cache_gen_path(tag)
|
58
|
+
FileUtils.mkdir_p(File.dirname(path))
|
59
|
+
time = Time.now.to_i
|
60
|
+
# We can get \r\n => \n\n conversion unless we open in binary mode
|
61
|
+
File.open(path, 'wb') do |f|
|
62
|
+
f.puts(time)
|
63
|
+
f.puts(content_type)
|
64
|
+
f.print(content)
|
65
|
+
end
|
66
|
+
time
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_clear(tag='/')
|
70
|
+
path = File.join(settings.root, settings.cache_path, tag.to_s)
|
71
|
+
# If they gave us e.g. 'path/', make it into a glob. Otherwise add a file extension
|
72
|
+
path << (path.end_with?('/') ? '*' : '.html')
|
73
|
+
FileUtils.rm_r(Dir.glob(path))
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def cache_gen_path(tag)
|
79
|
+
path = File.join(settings.root, settings.cache_path, tag)
|
80
|
+
path << '.html' if File.extname(path).empty?
|
81
|
+
path
|
82
|
+
end
|
83
|
+
|
84
|
+
def cache_route_pre(tag, context)
|
85
|
+
# Guess a suitable tag if we're told to
|
86
|
+
tag = context.cache_guess_tag(tag)
|
87
|
+
|
88
|
+
# If they gave us a tag upfront, (as an arg to cache_get/etc) see whether we can get it
|
89
|
+
if tag
|
90
|
+
cache_content, cache_time, content_type = cache_get_tag(File.join('pages', tag))
|
91
|
+
if cache_content
|
92
|
+
context.etag cache_time if settings.cache_generate_etags
|
93
|
+
context.content_type content_type if content_type
|
94
|
+
return tag, cache_content
|
95
|
+
end
|
96
|
+
end
|
97
|
+
return tag, nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def cache_route_post(ret, tag, context)
|
101
|
+
# If ret is an array with the first element :cache_hit, it means we hit the cache
|
102
|
+
# This could happen if they call cache_tag
|
103
|
+
if ret.is_a?(Array) && ret.first == :cache_hit
|
104
|
+
_, cache_content, cache_time, content_type = ret
|
105
|
+
context.etag cache_time if settings.cache_generate_etags
|
106
|
+
context.content_type content_type if content_type
|
107
|
+
return cache_content
|
108
|
+
end
|
109
|
+
|
110
|
+
# If we got this far, we didn't hit a cache anywhere
|
111
|
+
# Update tag if it was set from cache_tag, and write to the cache
|
112
|
+
tag ||= settings.cache_last_tag
|
113
|
+
if tag
|
114
|
+
tag = context.cache_guess_tag(tag)
|
115
|
+
time = cache_put_tag(File.join('pages', tag), ret, context.response['Content-Type'])
|
116
|
+
context.etag time if settings.cache_generate_etags
|
117
|
+
end
|
118
|
+
settings.cache_last_tag = nil
|
119
|
+
settings.cache_overwrite = false
|
120
|
+
ret
|
121
|
+
end
|
122
|
+
|
123
|
+
def cache_route(verb, path, opts={}, &blk)
|
124
|
+
unless cache_enabled?
|
125
|
+
route(verb, path, opts, &blk)
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
# If we change tag when calling the route once, it remains change for the next time
|
130
|
+
# the route is called.
|
131
|
+
# Therefore don't change tag
|
132
|
+
tag = opts.delete(:tag)
|
133
|
+
method = generate_method :"C#{verb} #{path} #{opts.hash}", &blk
|
134
|
+
|
135
|
+
cache_blk = Proc.new { |context, *args|
|
136
|
+
updated_tag, cache_content = cache_route_pre(tag, context)
|
137
|
+
next cache_content if cache_content
|
138
|
+
|
139
|
+
ret = catch(:cache_stop){ method.bind(context).call(*args) }
|
140
|
+
|
141
|
+
ret = cache_route_post(ret, updated_tag, context)
|
142
|
+
ret
|
143
|
+
}
|
144
|
+
|
145
|
+
route(verb, path, opts) do |*bargs|
|
146
|
+
cache_blk.call(self, *bargs)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
module Helpers
|
151
|
+
# def cache_get_tag(*args); settings.cache_get_tag(*args); end
|
152
|
+
# def cache_put_tag(*args); settings.cache_put_tag(*args); end
|
153
|
+
def cache_clear(*args); settings.cache_clear(*args); end
|
154
|
+
|
155
|
+
def cache_tag(tag=:auto, opts={})
|
156
|
+
return unless settings.cache_enabled?
|
157
|
+
cache_overwrite if opts[:overwrite]
|
158
|
+
tag = cache_guess_tag(tag)
|
159
|
+
unless settings.cache_overwrite
|
160
|
+
content = settings.cache_get_tag(File.join('pages', tag))
|
161
|
+
throw :cache_stop, content.unshift(:cache_hit) if content
|
162
|
+
end
|
163
|
+
settings.cache_last_tag = tag
|
164
|
+
end
|
165
|
+
|
166
|
+
def cache_overwrite
|
167
|
+
settings.cache_overwrite = true
|
168
|
+
end
|
169
|
+
|
170
|
+
def cache_guess_tag(tag)
|
171
|
+
return (tag ? tag.to_s : nil) unless [true, :auto].include?(tag)
|
172
|
+
tag = request.path_info
|
173
|
+
tag = File.join(tag, 'index') if tag.empty? || tag.end_with?('/')
|
174
|
+
tag
|
175
|
+
end
|
176
|
+
|
177
|
+
def cache_block(tag, opts={})
|
178
|
+
raise "No block given to cache_block" unless block_given?
|
179
|
+
tag = "blocks/#{tag}"
|
180
|
+
unless opts[:overwrite]
|
181
|
+
content = settings.cache_get_tag(tag)
|
182
|
+
return content.first if content
|
183
|
+
end
|
184
|
+
content = yield
|
185
|
+
settings.cache_put_tag(tag, content)
|
186
|
+
content
|
187
|
+
end
|
188
|
+
|
189
|
+
def cache_fragment(tag, opts={}, &blk)
|
190
|
+
raise "You must install sinatra-outputbuffer, require sinatra/outputbuffer, and register Sinatra::OutputBuffer to use cache_fragment" unless respond_to?(:capture_html)
|
191
|
+
raise "No block given to cache_fragment" unless block_given?
|
192
|
+
tag = "fragments/#{tag}"
|
193
|
+
unless opts[:overwrite]
|
194
|
+
content, = settings.cache_get_tag(tag)
|
195
|
+
return block_is_template?(blk) ? concat_content(content) : content if content
|
196
|
+
end
|
197
|
+
content = capture_html(&blk)
|
198
|
+
settings.cache_put_tag(tag, content)
|
199
|
+
block_is_template?(blk) ? concat_content(content) : content
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
register Cacher
|
206
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'sinatra/cacher'
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-cacher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Antony Male
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Simple and effective file-based caching for sinatra. Caches routes, blocks,
|
15
|
+
or HTML fragments. Sets ETag correctly. Automatic or manual tag generation. Easy
|
16
|
+
cache clearing.
|
17
|
+
email: antony dot mail at gmail
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/sinatra/cacher.rb
|
23
|
+
- lib/sinatra-cacher.rb
|
24
|
+
homepage: https://github.com/canton7/sinatra-cacher
|
25
|
+
licenses: []
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.9.2
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 1.8.21
|
45
|
+
signing_key:
|
46
|
+
specification_version: 3
|
47
|
+
summary: Simple and effective file-based caching for sinatra
|
48
|
+
test_files: []
|