sinatra-cacher 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []