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.
@@ -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: []