pirj-sinatra-contrib 1.3.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.
Files changed (75) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +61 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +42 -0
  6. data/lib/sinatra/config_file.rb +151 -0
  7. data/lib/sinatra/content_for.rb +111 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +45 -0
  12. data/lib/sinatra/decompile.rb +113 -0
  13. data/lib/sinatra/engine_tracking.rb +96 -0
  14. data/lib/sinatra/extension.rb +95 -0
  15. data/lib/sinatra/json.rb +134 -0
  16. data/lib/sinatra/link_header.rb +132 -0
  17. data/lib/sinatra/namespace.rb +282 -0
  18. data/lib/sinatra/reloader.rb +384 -0
  19. data/lib/sinatra/respond_with.rb +245 -0
  20. data/lib/sinatra/streaming.rb +267 -0
  21. data/lib/sinatra/test_helpers.rb +87 -0
  22. data/sinatra-contrib.gemspec +121 -0
  23. data/spec/capture_spec.rb +80 -0
  24. data/spec/config_file/key_value.yml +6 -0
  25. data/spec/config_file/missing_env.yml +4 -0
  26. data/spec/config_file/with_envs.yml +7 -0
  27. data/spec/config_file/with_nested_envs.yml +11 -0
  28. data/spec/config_file_spec.rb +44 -0
  29. data/spec/content_for/different_key.erb +1 -0
  30. data/spec/content_for/different_key.erubis +1 -0
  31. data/spec/content_for/different_key.haml +2 -0
  32. data/spec/content_for/different_key.slim +2 -0
  33. data/spec/content_for/layout.erb +1 -0
  34. data/spec/content_for/layout.erubis +1 -0
  35. data/spec/content_for/layout.haml +1 -0
  36. data/spec/content_for/layout.slim +1 -0
  37. data/spec/content_for/multiple_blocks.erb +4 -0
  38. data/spec/content_for/multiple_blocks.erubis +4 -0
  39. data/spec/content_for/multiple_blocks.haml +8 -0
  40. data/spec/content_for/multiple_blocks.slim +8 -0
  41. data/spec/content_for/multiple_yields.erb +3 -0
  42. data/spec/content_for/multiple_yields.erubis +3 -0
  43. data/spec/content_for/multiple_yields.haml +3 -0
  44. data/spec/content_for/multiple_yields.slim +3 -0
  45. data/spec/content_for/passes_values.erb +1 -0
  46. data/spec/content_for/passes_values.erubis +1 -0
  47. data/spec/content_for/passes_values.haml +1 -0
  48. data/spec/content_for/passes_values.slim +1 -0
  49. data/spec/content_for/same_key.erb +1 -0
  50. data/spec/content_for/same_key.erubis +1 -0
  51. data/spec/content_for/same_key.haml +2 -0
  52. data/spec/content_for/same_key.slim +2 -0
  53. data/spec/content_for/takes_values.erb +1 -0
  54. data/spec/content_for/takes_values.erubis +1 -0
  55. data/spec/content_for/takes_values.haml +3 -0
  56. data/spec/content_for/takes_values.slim +3 -0
  57. data/spec/content_for_spec.rb +201 -0
  58. data/spec/decompile_spec.rb +44 -0
  59. data/spec/extension_spec.rb +33 -0
  60. data/spec/json_spec.rb +115 -0
  61. data/spec/link_header_spec.rb +100 -0
  62. data/spec/namespace/foo.erb +1 -0
  63. data/spec/namespace/nested/foo.erb +1 -0
  64. data/spec/namespace_spec.rb +623 -0
  65. data/spec/okjson.rb +581 -0
  66. data/spec/reloader/app.rb.erb +40 -0
  67. data/spec/reloader_spec.rb +441 -0
  68. data/spec/respond_with/bar.erb +1 -0
  69. data/spec/respond_with/bar.json.erb +1 -0
  70. data/spec/respond_with/foo.html.erb +1 -0
  71. data/spec/respond_with/not_html.sass +2 -0
  72. data/spec/respond_with_spec.rb +289 -0
  73. data/spec/spec_helper.rb +6 -0
  74. data/spec/streaming_spec.rb +436 -0
  75. metadata +252 -0
@@ -0,0 +1,134 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ # = Sinatra::JSON
6
+ #
7
+ # <tt>Sinatra::JSON</tt> adds a helper method, called +json+, for (obviously)
8
+ # json generation.
9
+ #
10
+ # == Usage
11
+ #
12
+ # === Classic Application
13
+ #
14
+ # In a classic application simply require the helper, and start using it:
15
+ #
16
+ # require "sinatra"
17
+ # require "sinatra/json"
18
+ #
19
+ # # define a route that uses the helper
20
+ # get '/' do
21
+ # json :foo => 'bar'
22
+ # end
23
+ #
24
+ # # The rest of your classic application code goes here...
25
+ #
26
+ # === Modular Application
27
+ #
28
+ # In a modular application you need to require the helper, and then tell the
29
+ # application you will use it:
30
+ #
31
+ # require "sinatra/base"
32
+ # require "sinatra/json"
33
+ #
34
+ # class MyApp < Sinatra::Base
35
+ # helpers Sinatra::JSON
36
+ #
37
+ # # define a route that uses the helper
38
+ # get '/' do
39
+ # json :foo => 'bar'
40
+ # end
41
+ #
42
+ # # The rest of your modular application code goes here...
43
+ # end
44
+ #
45
+ # === Encoders
46
+ #
47
+ # Per default it will try to call +to_json+ on the object, but if it doesn't
48
+ # respond to that message, will use its own, rather simple encoder. You can
49
+ # easily change that anyways. To use +JSON+, simply require it:
50
+ #
51
+ # require 'json'
52
+ #
53
+ # The same goes for <tt>Yajl::Encoder</tt>:
54
+ #
55
+ # require 'yajl'
56
+ #
57
+ # For other encoders, besides requiring them, you need to define the
58
+ # <tt>:json_encoder</tt> setting. For instance, for the +Whatever+ encoder:
59
+ #
60
+ # require 'whatever'
61
+ # set :json_encoder, Whatever
62
+ #
63
+ # To force +json+ to simply call +to_json+ on the object:
64
+ #
65
+ # set :json_encoder, :to_json
66
+ #
67
+ # Actually, it can call any method:
68
+ #
69
+ # set :json_encoder, :my_fancy_json_method
70
+ #
71
+ # === Content-Type
72
+ #
73
+ # It will automatically set the content type to "application/json". As
74
+ # usual, you can easily change that, with the <tt>:json_content_type</tt>
75
+ # setting:
76
+ #
77
+ # set :json_content_type, :js
78
+ #
79
+ # === Overriding the Encoder and the Content-Type
80
+ #
81
+ # The +json+ helper will also take two options <tt>:encoder</tt> and
82
+ # <tt>:content_type</tt>. The values of this options are the same as the
83
+ # <tt>:json_encoder</tt> and <tt>:json_content_type</tt> settings,
84
+ # respectively. You can also pass those to the json method:
85
+ #
86
+ # get '/' do
87
+ # json({:foo => 'bar'}, :encoder => :to_json, :content_type => :js)
88
+ # end
89
+ #
90
+ module JSON
91
+ class << self
92
+ def encode(object)
93
+ enc object, Array, Hash
94
+ end
95
+
96
+ private
97
+
98
+ def enc(o, *a)
99
+ o = o.to_s if o.is_a? Symbol
100
+ fail "invalid: #{o.inspect}" unless a.empty? or a.include? o.class
101
+ case o
102
+ when Float then o.nan? || o.infinite? ? 'null' : o.inspect
103
+ when TrueClass, FalseClass, NilClass, Numeric, String then o.inspect
104
+ when Array then map(o, "[%s]") { |e| enc(e) }
105
+ when Hash then map(o, "{%s}") { |k,v| enc(k, String) + ":" + enc(v) }
106
+ end
107
+ end
108
+
109
+ def map(o, wrapper, &block)
110
+ wrapper % o.map(&block).join(',')
111
+ end
112
+ end
113
+
114
+ def json(object, options = {})
115
+ encoder = options[:encoder] || settings.json_encoder
116
+ content_type options[:content_type] || settings.json_content_type
117
+ if encoder.respond_to? :encode then encoder.encode(object)
118
+ elsif encoder.respond_to? :generate then encoder.generate(object)
119
+ elsif encoder.is_a? Symbol then object.__send__(encoder)
120
+ else fail "#{encoder} does not respond to #generate nor #encode"
121
+ end
122
+ end
123
+ end
124
+
125
+ Base.set :json_encoder do
126
+ return Yajl::Encoder if defined? Yajl::Encoder
127
+ return JSON if defined? JSON
128
+ return :to_json if {}.respond_to? :to_json and [].respond_to? :to_json
129
+ Sinatra::JSON
130
+ end
131
+
132
+ Base.set :json_content_type, :json
133
+ helpers JSON
134
+ end
@@ -0,0 +1,132 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ # = Sinatra::LinkHeader
6
+ #
7
+ # <tt>Sinatra::LinkHeader</tt> adds a set of helper methods to generate link
8
+ # HTML tags and their corresponding Link HTTP headers.
9
+ #
10
+ # == Usage
11
+ #
12
+ # Once you had set up the helpers in your application (see below), you will
13
+ # be able to call the following methods from inside your route handlers,
14
+ # filters and templates:
15
+ #
16
+ # +prefetch+::
17
+ # Sets the Link HTTP headers and returns HTML tags to prefetch the given
18
+ # resources.
19
+ #
20
+ # +stylesheet+::
21
+ # Sets the Link HTTP headers and returns HTML tags to use the given
22
+ # stylesheets.
23
+ #
24
+ # +link+::
25
+ # Sets the Link HTTP headers and returns the corresponding HTML tags
26
+ # for the given resources.
27
+ #
28
+ # +link_headers+::
29
+ # Returns the corresponding HTML tags for the current Link HTTP headers.
30
+ #
31
+ # === Classic Application
32
+ #
33
+ # In a classic application simply require the helpers, and start using them:
34
+ #
35
+ # require "sinatra"
36
+ # require "sinatra/link_header"
37
+ #
38
+ # # The rest of your classic application code goes here...
39
+ #
40
+ # === Modular Application
41
+ #
42
+ # In a modular application you need to require the helpers, and then tell
43
+ # the application you will use them:
44
+ #
45
+ # require "sinatra/base"
46
+ # require "sinatra/link_header"
47
+ #
48
+ # class MyApp < Sinatra::Base
49
+ # helpers Sinatra::LinkHeader
50
+ #
51
+ # # The rest of your modular application code goes here...
52
+ # end
53
+ #
54
+ module LinkHeader
55
+ ##
56
+ # Set Link HTTP header and returns HTML tags for telling the browser to
57
+ # prefetch given resources (only supported by Opera and Firefox at the
58
+ # moment).
59
+ def prefetch(*urls)
60
+ link(:prefetch, *urls)
61
+ end
62
+
63
+ ##
64
+ # Sets Link HTTP header and returns HTML tags for using stylesheets.
65
+ def stylesheet(*urls)
66
+ urls << {} unless urls.last.respond_to? :to_hash
67
+ urls.last[:type] ||= mime_type(:css)
68
+ link(:stylesheet, *urls)
69
+ end
70
+
71
+ ##
72
+ # Sets Link HTTP header and returns corresponding HTML tags.
73
+ #
74
+ # Example:
75
+ #
76
+ # # Sets header:
77
+ # # Link: </foo>; rel="next"
78
+ # # Returns String:
79
+ # # '<link href="/foo" rel="next" />'
80
+ # link '/foo', :rel => :next
81
+ #
82
+ # # Multiple URLs
83
+ # link :stylesheet, '/a.css', '/b.css'
84
+ def link(*urls)
85
+ opts = urls.last.respond_to?(:to_hash) ? urls.pop : {}
86
+ opts[:rel] = urls.shift unless urls.first.respond_to? :to_str
87
+ options = opts.map { |k, v| " #{k}=#{v.to_s.inspect}" }
88
+ html_pattern = "<link href=\"%s\"#{options.join} />"
89
+ http_pattern = ["<%s>", *options].join ";"
90
+ link = (response["Link"] ||= "")
91
+
92
+ urls.map do |url|
93
+ link << "\n" unless link.empty?
94
+ link << (http_pattern % url)
95
+ html_pattern % url
96
+ end.join "\n"
97
+ end
98
+
99
+ ##
100
+ # Takes the current value of th Link header(s) and generates HTML tags
101
+ # from it.
102
+ #
103
+ # Example:
104
+ #
105
+ # get '/' do
106
+ # # You can of course use fancy helpers like #link, #stylesheet
107
+ # # or #prefetch
108
+ # response["Link"] = '</foo>; rel="next"'
109
+ # haml :some_page
110
+ # end
111
+ #
112
+ # __END__
113
+ #
114
+ # @@ layout
115
+ # %head= link_headers
116
+ # %body= yield
117
+ def link_headers
118
+ yield if block_given?
119
+ return "" unless response.include? "Link"
120
+ response["Link"].lines.map do |line|
121
+ url, *opts = line.split(';').map(&:strip)
122
+ "<link href=\"#{url[1..-2]}\" #{opts.join " "} />"
123
+ end.join "\n"
124
+ end
125
+
126
+ def self.registered(base)
127
+ puts "WARNING: #{self} is a helpers module, not an extension."
128
+ end
129
+ end
130
+
131
+ helpers LinkHeader
132
+ end
@@ -0,0 +1,282 @@
1
+ require 'backports'
2
+ require 'sinatra/base'
3
+ require 'sinatra/decompile'
4
+
5
+ module Sinatra
6
+
7
+ # = Sinatra::Namespace
8
+ #
9
+ # <tt>Sinatra::Namespace</tt> is an extension that adds namespaces to an
10
+ # application. This namespaces will allow you to share a path prefix for the
11
+ # routes within the namespace, and define filters, conditions and error
12
+ # handlers exclusively for them. Besides that, you can also register helpers
13
+ # and extensions that will be used only within the namespace.
14
+ #
15
+ # == Usage
16
+ #
17
+ # Once you have loaded the extension (see below), you use the +namespace+
18
+ # method to define namespaces in your application.
19
+ #
20
+ # You can define a namespace by a path prefix:
21
+ #
22
+ # namespace '/blog' do
23
+ # get() { haml :blog }
24
+ # get '/:entry_permalink' do
25
+ # @entry = Entry.find_by_permalink!(params[:entry_permalink])
26
+ # haml :entry
27
+ # end
28
+ #
29
+ # # More blog routes...
30
+ # end
31
+ #
32
+ # by a condition:
33
+ #
34
+ # namespace :host_name => 'localhost' do
35
+ # get('/admin/dashboard') { haml :dashboard }
36
+ # get('/admin/login') { haml :login }
37
+ #
38
+ # # More admin routes...
39
+ # end
40
+ #
41
+ # or both:
42
+ #
43
+ # namespace '/admin', :host_name => 'localhost' do
44
+ # get('/dashboard') { haml :dashboard }
45
+ # get('/login') { haml :login }
46
+ # post('/login') { login_user }
47
+ #
48
+ # # More admin routes...
49
+ # end
50
+ #
51
+ # When you define a filter or an error handler, or register an extension or a
52
+ # set of helpers within a namespace, they only affect the routes defined in
53
+ # it. For instance, lets define a before filter to prevent the access of
54
+ # unauthorized users to the admin section of the application:
55
+ #
56
+ # namespace '/admin' do
57
+ # helpers AdminHelpers
58
+ # before { authenticate unless request.path_info == '/admin/login' }
59
+ #
60
+ # get '/dashboard' do
61
+ # # Only authenticated users can access here...
62
+ # haml :dashboard
63
+ # end
64
+ #
65
+ # # More admin routes...
66
+ # end
67
+ #
68
+ # get '/' do
69
+ # # Any user can access here...
70
+ # haml :index
71
+ # end
72
+ #
73
+ # Well, they actually also affect the nested namespaces:
74
+ #
75
+ # namespace '/admin' do
76
+ # helpers AdminHelpers
77
+ # before { authenticate unless request.path_info == '/admin/login' }
78
+ #
79
+ # namespace '/users' do
80
+ # get do
81
+ # # Only authenticated users can access here...
82
+ # @users = User.all
83
+ # haml :users
84
+ # end
85
+ #
86
+ # # More user admin routes...
87
+ # end
88
+ #
89
+ # # More admin routes...
90
+ # end
91
+ #
92
+ # === Classic Application Setup
93
+ #
94
+ # To be able to use namespaces in a classic application all you need to do is
95
+ # require the extension:
96
+ #
97
+ # require "sinatra"
98
+ # require "sinatra/namespace"
99
+ #
100
+ # # The rest of your classic application code goes here...
101
+ #
102
+ # === Modular Application Setup
103
+ #
104
+ # To be able to use namespaces in a modular application all you need to do is
105
+ # require the extension, and then, register it:
106
+ #
107
+ # require "sinatra/base"
108
+ # require "sinatra/namespace"
109
+ #
110
+ # class MyApp < Sinatra::Base
111
+ # register Sinatra::Namespace
112
+ #
113
+ # # The rest of your modular application code goes here...
114
+ # end
115
+ #
116
+ module Namespace
117
+ def self.new(base, pattern, conditions = {}, &block)
118
+ Module.new do
119
+ extend NamespacedMethods
120
+ include InstanceMethods
121
+ @base, @extensions = base, []
122
+ @pattern, @conditions = compile(pattern, conditions)
123
+ @templates = Hash.new { |h,k| @base.templates[k] }
124
+ namespace = self
125
+ before { extend(@namespace = namespace) }
126
+ class_eval(&block)
127
+ end
128
+ end
129
+
130
+ module InstanceMethods
131
+ def settings
132
+ @namespace
133
+ end
134
+
135
+ def template_cache
136
+ super.fetch(:nested, @namespace) { Tilt::Cache.new }
137
+ end
138
+
139
+ def error_block!(*keys)
140
+ if block = keys.inject(nil) { |b,k| b ||= @namespace.errors[k] }
141
+ instance_eval(&block)
142
+ else
143
+ super
144
+ end
145
+ end
146
+ end
147
+
148
+ module SharedMethods
149
+ def namespace(pattern, conditions = {}, &block)
150
+ Sinatra::Namespace.new(self, pattern, conditions, &block)
151
+ end
152
+ end
153
+
154
+ module NamespacedMethods
155
+ include SharedMethods
156
+ include Sinatra::Decompile
157
+ attr_reader :base, :templates
158
+
159
+ def self.prefixed(*names)
160
+ names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }}
161
+ end
162
+
163
+ prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
164
+
165
+ def helpers(*extensions, &block)
166
+ class_eval(&block) if block_given?
167
+ include(*extensions) if extensions.any?
168
+ end
169
+
170
+ def register(*extensions, &block)
171
+ extensions << Module.new(&block) if block_given?
172
+ @extensions += extensions
173
+ extensions.each do |extension|
174
+ extend extension
175
+ extension.registered(self) if extension.respond_to?(:registered)
176
+ end
177
+ end
178
+
179
+ def invoke_hook(name, *args)
180
+ @extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
181
+ end
182
+
183
+ def errors
184
+ @errors ||= {}
185
+ end
186
+
187
+ def not_found(&block)
188
+ error(404, &block)
189
+ end
190
+
191
+ def error(codes = Exception, &block)
192
+ [*codes].each { |c| errors[c] = block }
193
+ end
194
+
195
+ def respond_to(*args)
196
+ return @conditions[:provides] || base.respond_to if args.empty?
197
+ @conditions[:provides] = args
198
+ end
199
+
200
+ def set(key, value = self, &block)
201
+ raise ArgumentError, "may not set #{key}" if key != :views
202
+ return key.each { |k,v| set(k, v) } if block.nil? and value == self
203
+ block ||= proc { value }
204
+ singleton_class.send(:define_method, key, &block)
205
+ end
206
+
207
+ def enable(*opts)
208
+ opts.each { |key| set(key, true) }
209
+ end
210
+
211
+ def disable(*opts)
212
+ opts.each { |key| set(key, false) }
213
+ end
214
+
215
+ def template(name, &block)
216
+ filename, line = caller_locations.first
217
+ templates[name] = [block, filename, line.to_i]
218
+ end
219
+
220
+ def layout(name=:layout, &block)
221
+ template name, &block
222
+ end
223
+
224
+ private
225
+
226
+ def app
227
+ base.respond_to?(:base) ? base.base : base
228
+ end
229
+
230
+ def compile(pattern, conditions, default_pattern = nil)
231
+ if pattern.respond_to? :to_hash
232
+ conditions = conditions.merge pattern.to_hash
233
+ pattern = nil
234
+ end
235
+ base_pattern, base_conditions = @pattern, @conditions
236
+ pattern ||= default_pattern
237
+ base_pattern ||= base.pattern if base.respond_to? :pattern
238
+ base_conditions ||= base.conditions if base.respond_to? :conditions
239
+ [ prefixed_path(base_pattern, pattern),
240
+ (base_conditions || {}).merge(conditions) ]
241
+ end
242
+
243
+ def prefixed_path(a, b)
244
+ return a || b || // unless a and b
245
+ a, b = decompile(a), decompile(b) unless a.class == b.class
246
+ a, b = regexpify(a), regexpify(b) unless a.class == b.class
247
+ path = a.class.new "#{a}#{b}"
248
+ path = /^#{path}$/ if path.is_a? Regexp and base == app
249
+ path
250
+ end
251
+
252
+ def regexpify(pattern)
253
+ pattern = Sinatra::Base.send(:compile, pattern).first.inspect
254
+ pattern.gsub! /^\/(\^|\\A)?|(\$|\\Z)?\/$/, ''
255
+ Regexp.new pattern
256
+ end
257
+
258
+ def prefixed(method, pattern = nil, conditions = {}, &block)
259
+ default = '*' if method == :before or method == :after
260
+ pattern, conditions = compile pattern, conditions, default
261
+ result = base.send(method, pattern, conditions, &block)
262
+ invoke_hook :route_added, method.to_s.upcase, pattern, block
263
+ result
264
+ end
265
+
266
+ def method_missing(meth, *args, &block)
267
+ base.send(meth, *args, &block)
268
+ end
269
+ end
270
+
271
+ module BaseMethods
272
+ include SharedMethods
273
+ end
274
+
275
+ def self.extend_object(base)
276
+ base.extend BaseMethods
277
+ end
278
+ end
279
+
280
+ register Sinatra::Namespace
281
+ Delegator.delegate :namespace
282
+ end