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.
- data/LICENSE +20 -0
- data/README.md +135 -0
- data/Rakefile +75 -0
- data/ideas.md +29 -0
- data/lib/sinatra/capture.rb +42 -0
- data/lib/sinatra/config_file.rb +151 -0
- data/lib/sinatra/content_for.rb +111 -0
- data/lib/sinatra/contrib.rb +39 -0
- data/lib/sinatra/contrib/all.rb +2 -0
- data/lib/sinatra/contrib/setup.rb +53 -0
- data/lib/sinatra/contrib/version.rb +45 -0
- data/lib/sinatra/cookies.rb +331 -0
- data/lib/sinatra/decompile.rb +113 -0
- data/lib/sinatra/engine_tracking.rb +96 -0
- data/lib/sinatra/extension.rb +95 -0
- data/lib/sinatra/json.rb +134 -0
- data/lib/sinatra/link_header.rb +132 -0
- data/lib/sinatra/multi_route.rb +81 -0
- data/lib/sinatra/namespace.rb +282 -0
- data/lib/sinatra/reloader.rb +384 -0
- data/lib/sinatra/respond_with.rb +245 -0
- data/lib/sinatra/streaming.rb +267 -0
- data/lib/sinatra/test_helpers.rb +87 -0
- data/sinatra-contrib.gemspec +125 -0
- data/spec/capture_spec.rb +80 -0
- data/spec/config_file/key_value.yml +6 -0
- data/spec/config_file/missing_env.yml +4 -0
- data/spec/config_file/with_envs.yml +7 -0
- data/spec/config_file/with_nested_envs.yml +11 -0
- data/spec/config_file_spec.rb +44 -0
- data/spec/content_for/different_key.erb +1 -0
- data/spec/content_for/different_key.erubis +1 -0
- data/spec/content_for/different_key.haml +2 -0
- data/spec/content_for/different_key.slim +2 -0
- data/spec/content_for/layout.erb +1 -0
- data/spec/content_for/layout.erubis +1 -0
- data/spec/content_for/layout.haml +1 -0
- data/spec/content_for/layout.slim +1 -0
- data/spec/content_for/multiple_blocks.erb +4 -0
- data/spec/content_for/multiple_blocks.erubis +4 -0
- data/spec/content_for/multiple_blocks.haml +8 -0
- data/spec/content_for/multiple_blocks.slim +8 -0
- data/spec/content_for/multiple_yields.erb +3 -0
- data/spec/content_for/multiple_yields.erubis +3 -0
- data/spec/content_for/multiple_yields.haml +3 -0
- data/spec/content_for/multiple_yields.slim +3 -0
- data/spec/content_for/passes_values.erb +1 -0
- data/spec/content_for/passes_values.erubis +1 -0
- data/spec/content_for/passes_values.haml +1 -0
- data/spec/content_for/passes_values.slim +1 -0
- data/spec/content_for/same_key.erb +1 -0
- data/spec/content_for/same_key.erubis +1 -0
- data/spec/content_for/same_key.haml +2 -0
- data/spec/content_for/same_key.slim +2 -0
- data/spec/content_for/takes_values.erb +1 -0
- data/spec/content_for/takes_values.erubis +1 -0
- data/spec/content_for/takes_values.haml +3 -0
- data/spec/content_for/takes_values.slim +3 -0
- data/spec/content_for_spec.rb +201 -0
- data/spec/cookies_spec.rb +782 -0
- data/spec/decompile_spec.rb +44 -0
- data/spec/extension_spec.rb +33 -0
- data/spec/json_spec.rb +115 -0
- data/spec/link_header_spec.rb +100 -0
- data/spec/multi_route_spec.rb +45 -0
- data/spec/namespace/foo.erb +1 -0
- data/spec/namespace/nested/foo.erb +1 -0
- data/spec/namespace_spec.rb +623 -0
- data/spec/okjson.rb +581 -0
- data/spec/reloader/app.rb.erb +40 -0
- data/spec/reloader_spec.rb +441 -0
- data/spec/respond_with/bar.erb +1 -0
- data/spec/respond_with/bar.json.erb +1 -0
- data/spec/respond_with/foo.html.erb +1 -0
- data/spec/respond_with/not_html.sass +2 -0
- data/spec/respond_with_spec.rb +289 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/streaming_spec.rb +436 -0
- metadata +256 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'backports'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
|
7
|
+
# = Sinatra::Decompile
|
8
|
+
#
|
9
|
+
# <tt>Sinatra::Decompile</tt> is an extension that provides a method,
|
10
|
+
# conveniently called +decompile+, that will generate a String pattern for a
|
11
|
+
# given route.
|
12
|
+
#
|
13
|
+
# == Usage
|
14
|
+
#
|
15
|
+
# === Classic Application
|
16
|
+
#
|
17
|
+
# To use the extension in a classic application all you need to do is require
|
18
|
+
# it:
|
19
|
+
#
|
20
|
+
# require "sinatra"
|
21
|
+
# require "sinatra/decompile"
|
22
|
+
#
|
23
|
+
# # Your classic application code goes here...
|
24
|
+
#
|
25
|
+
# This will add the +decompile+ method to the application/class scope, but
|
26
|
+
# you can also call it as <tt>Sinatra::Decompile.decompile</tt>.
|
27
|
+
#
|
28
|
+
# === Modular Application
|
29
|
+
#
|
30
|
+
# To use the extension in a modular application you need to require it, and
|
31
|
+
# then, tell the application you will use it:
|
32
|
+
#
|
33
|
+
# require "sinatra/base"
|
34
|
+
# require "sinatra/decompile"
|
35
|
+
#
|
36
|
+
# class MyApp < Sinatra::Base
|
37
|
+
# register Sinatra::Decompile
|
38
|
+
#
|
39
|
+
# # The rest of your modular application code goes here...
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# This will add the +decompile+ method to the application/class scope. You
|
43
|
+
# can choose not to register the extension, but instead of calling
|
44
|
+
# +decompile+, you will need to call <tt>Sinatra::Decompile.decompile</tt>.
|
45
|
+
#
|
46
|
+
module Decompile
|
47
|
+
extend self
|
48
|
+
|
49
|
+
##
|
50
|
+
# Regenerates a string pattern for a given route
|
51
|
+
#
|
52
|
+
# Example:
|
53
|
+
#
|
54
|
+
# class Sinatra::Application
|
55
|
+
# routes.each do |verb, list|
|
56
|
+
# puts "#{verb}:"
|
57
|
+
# list.each do |data|
|
58
|
+
# puts "\t" << decompile(data)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# Will return the internal Regexp if unable to reconstruct the pattern,
|
64
|
+
# which likely indicates that a Regexp was used in the first place.
|
65
|
+
#
|
66
|
+
# You can also use this to check whether you could actually use a string
|
67
|
+
# pattern instead of your regexp:
|
68
|
+
#
|
69
|
+
# decompile /^/foo$/ # => '/foo'
|
70
|
+
def decompile(pattern, keys = nil, *)
|
71
|
+
# Everything in here is basically just the reverse of
|
72
|
+
# Sinatra::Base#compile
|
73
|
+
pattern, keys = pattern if pattern.respond_to? :to_ary
|
74
|
+
keys, str = keys.try(:dup), pattern.inspect
|
75
|
+
return pattern unless str.start_with? '/' and str.end_with? '/'
|
76
|
+
str.gsub! /^\/\^?|\$?\/$/, ''
|
77
|
+
str.gsub! encoded(' '), ' '
|
78
|
+
return pattern if str =~ /^[\.\+]/
|
79
|
+
str.gsub! /\([^\(\)]*\)/ do |part|
|
80
|
+
case part
|
81
|
+
when '(.*?)'
|
82
|
+
return pattern if keys.shift != 'splat'
|
83
|
+
'*'
|
84
|
+
when '([^\/?#]+)'
|
85
|
+
return pattern if keys.empty?
|
86
|
+
":" << keys.shift
|
87
|
+
when /^\(\?\:\\?(.)\|/
|
88
|
+
char = $1
|
89
|
+
return pattern unless encoded(char) == part
|
90
|
+
Regexp.escape(char)
|
91
|
+
else
|
92
|
+
return pattern
|
93
|
+
end
|
94
|
+
end
|
95
|
+
str.gsub /(.)([\.\+\(\)\/])/ do
|
96
|
+
return pattern if $1 != "\\"
|
97
|
+
$2
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def encoded(char)
|
104
|
+
return super if defined? super
|
105
|
+
enc = URI.encode(char)
|
106
|
+
enc = "(?:#{Regexp.escape enc}|#{URI.encode char, /./})" if enc == char
|
107
|
+
enc = "(?:#{enc}|#{encoded('+')})" if char == " "
|
108
|
+
enc
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
register Decompile
|
113
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
module EngineTracking
|
5
|
+
attr_reader :current_engine
|
6
|
+
|
7
|
+
def erb?
|
8
|
+
@current_engine == :erb
|
9
|
+
end
|
10
|
+
|
11
|
+
def erubis?
|
12
|
+
@current_engine == :erubis or
|
13
|
+
erb? && Tilt[:erb] == Tilt::ErubisTemplate
|
14
|
+
end
|
15
|
+
|
16
|
+
def haml?
|
17
|
+
@current_engine == :haml
|
18
|
+
end
|
19
|
+
|
20
|
+
def sass?
|
21
|
+
@current_engine == :sass
|
22
|
+
end
|
23
|
+
|
24
|
+
def scss?
|
25
|
+
@current_engine == :scss
|
26
|
+
end
|
27
|
+
|
28
|
+
def less?
|
29
|
+
@current_engine == :less
|
30
|
+
end
|
31
|
+
|
32
|
+
def builder?
|
33
|
+
@current_engine == :builder
|
34
|
+
end
|
35
|
+
|
36
|
+
def liquid?
|
37
|
+
@current_engine == :liquid
|
38
|
+
end
|
39
|
+
|
40
|
+
def markdown?
|
41
|
+
@current_engine == :markdown
|
42
|
+
end
|
43
|
+
|
44
|
+
def textile?
|
45
|
+
@current_engine == :textile
|
46
|
+
end
|
47
|
+
|
48
|
+
def rdoc?
|
49
|
+
@current_engine == :rdoc
|
50
|
+
end
|
51
|
+
|
52
|
+
def radius?
|
53
|
+
@current_engine == :radius
|
54
|
+
end
|
55
|
+
|
56
|
+
def markaby?
|
57
|
+
@current_engine == :markaby
|
58
|
+
end
|
59
|
+
|
60
|
+
def coffee?
|
61
|
+
@current_engine == :coffee
|
62
|
+
end
|
63
|
+
|
64
|
+
def nokogiri?
|
65
|
+
@current_engine == :nokogiri
|
66
|
+
end
|
67
|
+
|
68
|
+
def slim?
|
69
|
+
@current_engine == :slim
|
70
|
+
end
|
71
|
+
|
72
|
+
def creole?
|
73
|
+
@current_engine == :creole
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(*)
|
77
|
+
@current_engine = :ruby
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
def with_engine(engine)
|
82
|
+
@current_engine, engine_was = engine.to_sym, @current_engine
|
83
|
+
yield
|
84
|
+
ensure
|
85
|
+
@current_engine = engine_was
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def render(engine, *)
|
91
|
+
with_engine(engine) { super }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
helpers EngineTracking
|
96
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'backports/basic_object' unless defined? BasicObject
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
|
6
|
+
# = Sinatra::Extension
|
7
|
+
#
|
8
|
+
# <tt>Sinatra::Extension</tt> is a mixin that provides some syntactic sugar
|
9
|
+
# for your extensions. It allows you to call directly inside your extension
|
10
|
+
# module almost any <tt>Sinatra::Base</tt> method. This means you can use
|
11
|
+
# +get+ to define a route, +before+ to define a before filter, +set+ to
|
12
|
+
# define a setting, a so on.
|
13
|
+
#
|
14
|
+
# Is important to be aware that this mixin remembers the methods calls you
|
15
|
+
# make, and then, when your extension is registered, replays them on the
|
16
|
+
# Sinatra application that has been extended. In order to do that, it
|
17
|
+
# defines a <tt>registered</tt> method, so, if your extension defines one
|
18
|
+
# too, remember to call +super+.
|
19
|
+
#
|
20
|
+
# == Usage
|
21
|
+
#
|
22
|
+
# Just require the mixin and extend your extension with it:
|
23
|
+
#
|
24
|
+
# require 'sinatra/extension'
|
25
|
+
#
|
26
|
+
# module MyExtension
|
27
|
+
# extend Sinatra::Extension
|
28
|
+
#
|
29
|
+
# # set some settings for development
|
30
|
+
# configure :development do
|
31
|
+
# set :reload_stuff, true
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # define a route
|
35
|
+
# get '/' do
|
36
|
+
# 'Hello World'
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# # The rest of your extension code goes here...
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# You can also create an extension with the +new+ method:
|
43
|
+
#
|
44
|
+
# MyExtension = Sinatra::Extension.new do
|
45
|
+
# # Your extension code goes here...
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# This is useful when you just want to pass a block to
|
49
|
+
# <tt>Sinatra::Base.register</tt>.
|
50
|
+
module Extension
|
51
|
+
def self.new(&block)
|
52
|
+
ext = Module.new.extend(self)
|
53
|
+
ext.class_eval(&block)
|
54
|
+
ext
|
55
|
+
end
|
56
|
+
|
57
|
+
def settings
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def configure(*args, &block)
|
62
|
+
record(:configure, *args) { |c| c.instance_exec(c, &block) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def registered(base = nil, &block)
|
66
|
+
base ? replay(base) : record(:class_eval, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def record(method, *args, &block)
|
72
|
+
recorded_methods << [method, args, block]
|
73
|
+
end
|
74
|
+
|
75
|
+
def replay(object)
|
76
|
+
recorded_methods.each { |m, a, b| object.send(m, *a, &b) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def recorded_methods
|
80
|
+
@recorded_methods ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_missing(method, *args, &block)
|
84
|
+
return super unless Sinatra::Base.respond_to? method
|
85
|
+
record(method, *args, &block)
|
86
|
+
DontCall.new(method)
|
87
|
+
end
|
88
|
+
|
89
|
+
class DontCall < BasicObject
|
90
|
+
def initialize(method) @method = method end
|
91
|
+
def method_missing(*) fail "not supposed to use result of #@method!" end
|
92
|
+
def inspect; "#<#{self.class}: #{@method}>" end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/sinatra/json.rb
ADDED
@@ -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
|