wakame-vdc-agents 10.11.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 +202 -0
- data/NOTICE +1 -0
- data/Rakefile +142 -0
- data/bin/hva +972 -0
- data/bin/nsa +147 -0
- data/bin/sta +182 -0
- data/config/hva.conf.example +10 -0
- data/config/initializers/isono.rb +43 -0
- data/config/initializers/passenger.rb +6 -0
- data/config/initializers/sequel.rb +21 -0
- data/config/nsa.conf.example +9 -0
- data/config/path_resolver.rb +12 -0
- data/lib/dcmgr.rb +115 -0
- data/lib/dcmgr/endpoints/core_api.rb +1004 -0
- data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
- data/lib/dcmgr/endpoints/errors.rb +55 -0
- data/lib/dcmgr/endpoints/metadata.rb +129 -0
- data/lib/dcmgr/logger.rb +44 -0
- data/lib/dcmgr/models/account.rb +104 -0
- data/lib/dcmgr/models/account_resource.rb +16 -0
- data/lib/dcmgr/models/base.rb +69 -0
- data/lib/dcmgr/models/base_new.rb +371 -0
- data/lib/dcmgr/models/frontend_system.rb +38 -0
- data/lib/dcmgr/models/host_pool.rb +102 -0
- data/lib/dcmgr/models/image.rb +46 -0
- data/lib/dcmgr/models/instance.rb +255 -0
- data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
- data/lib/dcmgr/models/instance_nic.rb +68 -0
- data/lib/dcmgr/models/instance_spec.rb +21 -0
- data/lib/dcmgr/models/ip_lease.rb +42 -0
- data/lib/dcmgr/models/netfilter_group.rb +88 -0
- data/lib/dcmgr/models/netfilter_rule.rb +21 -0
- data/lib/dcmgr/models/network.rb +32 -0
- data/lib/dcmgr/models/physical_host.rb +67 -0
- data/lib/dcmgr/models/request_log.rb +25 -0
- data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
- data/lib/dcmgr/models/storage_pool.rb +134 -0
- data/lib/dcmgr/models/tag.rb +126 -0
- data/lib/dcmgr/models/tag_mapping.rb +28 -0
- data/lib/dcmgr/models/volume.rb +130 -0
- data/lib/dcmgr/models/volume_snapshot.rb +47 -0
- data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
- data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
- data/lib/dcmgr/scheduler.rb +12 -0
- data/lib/dcmgr/scheduler/find_last.rb +16 -0
- data/lib/dcmgr/scheduler/find_random.rb +16 -0
- data/lib/dcmgr/stm/instance.rb +25 -0
- data/lib/dcmgr/stm/snapshot_context.rb +33 -0
- data/lib/dcmgr/stm/volume_context.rb +65 -0
- data/lib/dcmgr/web/base.rb +21 -0
- data/lib/sinatra/accept_media_types.rb +128 -0
- data/lib/sinatra/lazy_auth.rb +56 -0
- data/lib/sinatra/rabbit.rb +278 -0
- data/lib/sinatra/respond_to.rb +272 -0
- data/lib/sinatra/sequel_transaction.rb +27 -0
- data/lib/sinatra/static_assets.rb +83 -0
- data/lib/sinatra/url_for.rb +44 -0
- metadata +270 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/url_for'
|
3
|
+
require 'sinatra/respond_to'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module Rabbit
|
7
|
+
|
8
|
+
class DuplicateParamException < Exception; end
|
9
|
+
class DuplicateOperationException < Exception; end
|
10
|
+
class DuplicateCollectionException < Exception; end
|
11
|
+
|
12
|
+
class Operation
|
13
|
+
attr_reader :name, :method
|
14
|
+
|
15
|
+
STANDARD = {
|
16
|
+
:index => { :method => :get, :member => false },
|
17
|
+
:show => { :method => :get, :member => true },
|
18
|
+
:create => { :method => :post, :member => false },
|
19
|
+
:update => { :method => :put, :member => true },
|
20
|
+
:destroy => { :method => :delete, :member => true }
|
21
|
+
}
|
22
|
+
|
23
|
+
def initialize(coll, name, opts, &block)
|
24
|
+
@name = name.to_sym
|
25
|
+
opts = STANDARD[@name].merge(opts) if standard?
|
26
|
+
@collection = coll
|
27
|
+
raise "No method for operation #{name}" unless opts[:method]
|
28
|
+
@method = opts[:method].to_sym
|
29
|
+
@member = opts[:member]
|
30
|
+
@description = ""
|
31
|
+
instance_eval(&block) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def standard?
|
35
|
+
STANDARD.keys.include?(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def description(text="")
|
39
|
+
return @description if text.blank?
|
40
|
+
@description = text
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_documentation(app)
|
44
|
+
raise ArgumentError unless app < Sinatra::Base
|
45
|
+
coll, oper = @collection, self
|
46
|
+
app.get("/api/docs/#{@collection.name}/#{@name}") do
|
47
|
+
@collection, @operation = coll, oper
|
48
|
+
respond_to do |format|
|
49
|
+
format.html { haml :'docs/operation' }
|
50
|
+
format.xml { haml :'docs/operation' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def control(&block)
|
56
|
+
op = self
|
57
|
+
@control = Proc.new do
|
58
|
+
#op.validate(params)
|
59
|
+
instance_eval(&block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def prefix
|
64
|
+
# FIXME: Make the /api prefix configurable
|
65
|
+
"/api"
|
66
|
+
end
|
67
|
+
|
68
|
+
def path(args = {})
|
69
|
+
l_prefix = args[:prefix] ? args[:prefix] : prefix
|
70
|
+
if @member
|
71
|
+
if standard?
|
72
|
+
"#{l_prefix}/#{@collection.name}/:id"
|
73
|
+
else
|
74
|
+
"#{l_prefix}/#{@collection.name}/:id/#{name}"
|
75
|
+
end
|
76
|
+
else
|
77
|
+
"#{l_prefix}/#{@collection.name}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate(app)
|
82
|
+
raise ArgumentError unless app < Sinatra::Base
|
83
|
+
|
84
|
+
app.send(@method, path, {}, &@control)
|
85
|
+
# Set up some Rails-like URL helpers
|
86
|
+
if name == :index
|
87
|
+
gen_route "#{@collection.name}_url", app
|
88
|
+
elsif name == :show
|
89
|
+
gen_route "#{@collection.name.to_s.singularize}_url", app
|
90
|
+
else
|
91
|
+
gen_route "#{name}_#{@collection.name.to_s.singularize}_url", app
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def authz(&blk)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def gen_route(name, app)
|
100
|
+
route_url = path
|
101
|
+
if @member
|
102
|
+
app.send(:define_method, name) do |id, *args|
|
103
|
+
url = query_url(route_url, args[0])
|
104
|
+
url_for url.gsub(/:id/, id.to_s), :full
|
105
|
+
end
|
106
|
+
else
|
107
|
+
app.send(:define_method, name) do |*args|
|
108
|
+
url = query_url(route_url, args[0])
|
109
|
+
url_for url, :full
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Collection
|
116
|
+
attr_reader :name, :operations
|
117
|
+
|
118
|
+
def initialize(name, &block)
|
119
|
+
@name = name
|
120
|
+
@description = ""
|
121
|
+
@operations = {}
|
122
|
+
instance_eval(&block) if block_given?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Set/Return description for collection
|
126
|
+
# If first parameter is not present, full description will be
|
127
|
+
# returned.
|
128
|
+
def description(text='')
|
129
|
+
return @description if text.blank?
|
130
|
+
@description = text
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_documentation(app)
|
134
|
+
raise ArgumentError unless app < Sinatra::Base
|
135
|
+
coll, oper, features = self, @operations, driver.features(name)
|
136
|
+
app.get("/api/docs/#{@name}") do
|
137
|
+
@collection, @operations, @features = coll, oper, features
|
138
|
+
respond_to do |format|
|
139
|
+
format.html { haml :'docs/collection' }
|
140
|
+
format.xml { haml :'docs/collection' }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Add a new operation for this collection. For the standard REST
|
146
|
+
# operations :index, :show, :update, and :destroy, we already know
|
147
|
+
# what method to use and whether this is an operation on the URL for
|
148
|
+
# individual elements or for the whole collection.
|
149
|
+
#
|
150
|
+
# For non-standard operations, options must be passed:
|
151
|
+
# :method : one of the HTTP methods
|
152
|
+
# :member : whether this is an operation on the collection or an
|
153
|
+
# individual element (FIXME: custom operations on the
|
154
|
+
# collection will use a nonsensical URL) The URL for the
|
155
|
+
# operation is the element URL with the name of the operation
|
156
|
+
# appended
|
157
|
+
#
|
158
|
+
# This also defines a helper method like show_instance_url that returns
|
159
|
+
# the URL to this operation (in request context)
|
160
|
+
def operation(name, opts = {}, &block)
|
161
|
+
raise DuplicateOperationException if @operations[name]
|
162
|
+
@operations[name] = Operation.new(self, name, opts, &block)
|
163
|
+
end
|
164
|
+
|
165
|
+
def generate(app = ::Sinatra::Application)
|
166
|
+
raise ArgumentError unless app < Sinatra::Base
|
167
|
+
operations.values.each { |op| op.generate(app) }
|
168
|
+
collname = name # Work around Ruby's weird scoping/capture
|
169
|
+
app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
|
170
|
+
url_for "/api/#{collname}/#{id}", :full
|
171
|
+
end
|
172
|
+
|
173
|
+
if index_op = operations[:index]
|
174
|
+
app.send(:define_method, "#{name}_url") do
|
175
|
+
url_for index_op.path.gsub(/\/\?$/,''), :full
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def add_feature_params(features)
|
181
|
+
features.each do |f|
|
182
|
+
f.operations.each do |fop|
|
183
|
+
if cop = operations[fop.name]
|
184
|
+
fop.params.each_key do |k|
|
185
|
+
if cop.params.has_key?(k)
|
186
|
+
raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}"
|
187
|
+
else
|
188
|
+
cop.params[k] = fop.params[k]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def collections
|
198
|
+
@collections ||= {}
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create a new collection. NAME should be the pluralized name of the
|
202
|
+
# collection.
|
203
|
+
#
|
204
|
+
# Adds a helper method #{name}_url which returns the URL to the :index
|
205
|
+
# operation on this collection.
|
206
|
+
def collection(name, &block)
|
207
|
+
raise DuplicateCollectionException if collections[name]
|
208
|
+
#return unless driver.has_collection?(name.to_sym)
|
209
|
+
collections[name] = Collection.new(name, &block)
|
210
|
+
#collections[name].add_feature_params(driver.features(name))
|
211
|
+
collections[name].generate(self)
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.registered(app)
|
215
|
+
app.register(Sinatra::RespondTo)
|
216
|
+
app.helpers RabbitHelper
|
217
|
+
app.class_eval {
|
218
|
+
# Generate a root route for API docs
|
219
|
+
get '/api/docs\/?' do
|
220
|
+
respond_to do |format|
|
221
|
+
format.html { haml :'docs/index' }
|
222
|
+
format.xml { haml :'docs/index' }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
}
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
module RabbitHelper
|
230
|
+
def query_url(url, params)
|
231
|
+
return url if params.nil? || params.empty?
|
232
|
+
url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}"
|
233
|
+
end
|
234
|
+
|
235
|
+
def entry_points
|
236
|
+
collections.values.inject([]) do |m, coll|
|
237
|
+
url = url_for coll.operations[:index].path, :full
|
238
|
+
m << [ coll.name, url ]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
class String
|
246
|
+
# Rails defines this for a number of other classes, including Object
|
247
|
+
# see activesupport/lib/active_support/core_ext/object/blank.rb
|
248
|
+
def blank?
|
249
|
+
self !~ /\S/
|
250
|
+
end
|
251
|
+
|
252
|
+
# Title case.
|
253
|
+
#
|
254
|
+
# "this is a string".titlecase
|
255
|
+
# => "This Is A String"
|
256
|
+
#
|
257
|
+
# CREDIT: Eliazar Parra
|
258
|
+
# Copied from facets
|
259
|
+
def titlecase
|
260
|
+
gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
|
261
|
+
end
|
262
|
+
|
263
|
+
def pluralize
|
264
|
+
self + "s"
|
265
|
+
end
|
266
|
+
|
267
|
+
def singularize
|
268
|
+
self.gsub(/s$/, '')
|
269
|
+
end
|
270
|
+
|
271
|
+
def underscore
|
272
|
+
gsub(/::/, '/').
|
273
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
274
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
275
|
+
tr("-", "_").
|
276
|
+
downcase
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/accept_media_types'
|
3
|
+
|
4
|
+
# Accept header parsing was looked at but deemed
|
5
|
+
# too much of an irregularity to deal with. Problems with the header
|
6
|
+
# differences from IE, Firefox, Safari, and every other UA causes
|
7
|
+
# problems with the expected output. The general expected behavior
|
8
|
+
# would be serve html when no extension provided, but most UAs say
|
9
|
+
# they will accept application/xml with out a quality indicator, meaning
|
10
|
+
# you'd get the xml block served insead. Just plain retarded, use the
|
11
|
+
# extension and you'll never be suprised.
|
12
|
+
|
13
|
+
module Sinatra
|
14
|
+
module RespondTo
|
15
|
+
class UnhandledFormat < Sinatra::NotFound; end
|
16
|
+
class MissingTemplate < Sinatra::NotFound
|
17
|
+
def code; 500 end
|
18
|
+
end
|
19
|
+
|
20
|
+
TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
|
21
|
+
:csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
|
22
|
+
:log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
|
23
|
+
:s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
|
24
|
+
:yml, :ics, :png]
|
25
|
+
|
26
|
+
def self.registered(app)
|
27
|
+
app.helpers RespondTo::Helpers
|
28
|
+
|
29
|
+
app.set :default_charset, 'utf-8'
|
30
|
+
app.set :default_content, :html
|
31
|
+
app.set :assume_xhr_is_js, true
|
32
|
+
|
33
|
+
# We remove the trailing extension so routes
|
34
|
+
# don't have to be of the style
|
35
|
+
#
|
36
|
+
# get '/resouce.:format'
|
37
|
+
#
|
38
|
+
# They can instead be of the style
|
39
|
+
#
|
40
|
+
# get '/resource'
|
41
|
+
#
|
42
|
+
# and the format will automatically be available in <tt>format</tt>
|
43
|
+
app.before do
|
44
|
+
# Let through sinatra image urls in development
|
45
|
+
next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
|
46
|
+
|
47
|
+
unless options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
|
48
|
+
rpi = request.path_info.sub(%r{\.([^\./]+)$}, '')
|
49
|
+
|
50
|
+
if (not $1) or ($1 and TEXT_MIME_TYPES.include?($1.to_sym))
|
51
|
+
request.path_info, ext = rpi, nil
|
52
|
+
if ! $1.nil?
|
53
|
+
ext = $1
|
54
|
+
elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
|
55
|
+
ext = options.default_content
|
56
|
+
end
|
57
|
+
if ext
|
58
|
+
@mime_types = [ Helpers::mime_type(ext) ]
|
59
|
+
format ext
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
app.configure :development do |dev|
|
66
|
+
dev.error UnhandledFormat do
|
67
|
+
content_type :html, :charset => 'utf-8'
|
68
|
+
|
69
|
+
(<<-HTML).gsub(/^ {10}/, '')
|
70
|
+
<!DOCTYPE html>
|
71
|
+
<html>
|
72
|
+
<head>
|
73
|
+
<style type="text/css">
|
74
|
+
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
75
|
+
color:#888;margin:20px}
|
76
|
+
#c {margin:0 auto;width:500px;text-align:left}
|
77
|
+
</style>
|
78
|
+
</head>
|
79
|
+
<body>
|
80
|
+
<h2>Sinatra doesn't know this ditty.</h2>
|
81
|
+
<img src='/__sinatra__/404.png'>
|
82
|
+
<div id="c">
|
83
|
+
Try this:
|
84
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n end\nend</pre>
|
85
|
+
</div>
|
86
|
+
</body>
|
87
|
+
</html>
|
88
|
+
HTML
|
89
|
+
end
|
90
|
+
|
91
|
+
dev.error MissingTemplate do
|
92
|
+
content_type :html, :charset => 'utf-8'
|
93
|
+
response.status = request.env['sinatra.error'].code
|
94
|
+
|
95
|
+
engine = request.env['sinatra.error'].message.split('.').last
|
96
|
+
engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
|
97
|
+
|
98
|
+
path = File.basename(request.path_info)
|
99
|
+
path = "root" if path.nil? || path.empty?
|
100
|
+
|
101
|
+
format = engine == 'builder' ? 'xml' : 'html'
|
102
|
+
|
103
|
+
layout = case engine
|
104
|
+
when 'haml' then "!!!\n%html\n %body= yield"
|
105
|
+
when 'erb' then "<html>\n <body>\n <%= yield %>\n </body>\n</html>"
|
106
|
+
when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml << yield" : "builder do |xml|\n xml << yield\nend"
|
107
|
+
end
|
108
|
+
|
109
|
+
layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
|
110
|
+
|
111
|
+
(<<-HTML).gsub(/^ {10}/, '')
|
112
|
+
<!DOCTYPE html>
|
113
|
+
<html>
|
114
|
+
<head>
|
115
|
+
<style type="text/css">
|
116
|
+
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
117
|
+
color:#888;margin:20px}
|
118
|
+
#c {margin:0 auto;width:500px;text-align:left;}
|
119
|
+
small {float:right;clear:both;}
|
120
|
+
pre {clear:both;}
|
121
|
+
</style>
|
122
|
+
</head>
|
123
|
+
<body>
|
124
|
+
<h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
|
125
|
+
<img src='/__sinatra__/500.png'>
|
126
|
+
<div id="c">
|
127
|
+
Try this:<br />
|
128
|
+
#{layout}
|
129
|
+
<small>#{path}.#{format}.#{engine}</small>
|
130
|
+
<pre>Hello World!</pre>
|
131
|
+
<small>application.rb</small>
|
132
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n end\nend</pre>
|
133
|
+
</div>
|
134
|
+
</body>
|
135
|
+
</html>
|
136
|
+
HTML
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
app.class_eval do
|
142
|
+
private
|
143
|
+
def accept_list
|
144
|
+
@mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
|
145
|
+
end
|
146
|
+
|
147
|
+
# Changes in 1.0 Sinatra reuse render for layout so we store
|
148
|
+
# the original value to tell us if this is an automatic attempt
|
149
|
+
# to do a layout call. If it is, it might fail with Errno::ENOENT
|
150
|
+
# and we want to pass that back to sinatra since it isn't a MissingTemplate
|
151
|
+
# error
|
152
|
+
def render_with_format(*args, &block)
|
153
|
+
assumed_layout = args[1] == :layout
|
154
|
+
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
|
155
|
+
render_without_format *args, &block
|
156
|
+
rescue Errno::ENOENT => e
|
157
|
+
raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
|
158
|
+
raise e
|
159
|
+
end
|
160
|
+
alias_method :render_without_format, :render
|
161
|
+
alias_method :render, :render_with_format
|
162
|
+
|
163
|
+
if ::Sinatra::VERSION =~ /^0\.9/
|
164
|
+
def lookup_layout_with_format(*args)
|
165
|
+
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
|
166
|
+
lookup_layout_without_format *args
|
167
|
+
end
|
168
|
+
alias_method :lookup_layout_without_format, :lookup_layout
|
169
|
+
alias_method :lookup_layout, :lookup_layout_with_format
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module Helpers
|
175
|
+
# Patch the content_type function to remember the set type
|
176
|
+
# This helps cut down on time in the format helper so it
|
177
|
+
# doesn't have to do a reverse lookup on the header
|
178
|
+
def self.included(klass)
|
179
|
+
klass.class_eval do
|
180
|
+
def content_type_with_save(*args)
|
181
|
+
content_type_without_save *args
|
182
|
+
@_format = args.first.to_sym
|
183
|
+
response['Content-Type']
|
184
|
+
end
|
185
|
+
alias_method :content_type_without_save, :content_type
|
186
|
+
alias_method :content_type, :content_type_with_save
|
187
|
+
end if ::Sinatra::VERSION =~ /^1.0/
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.mime_type(sym)
|
191
|
+
::Sinatra::Base.respond_to?(:mime_type) && ::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
|
192
|
+
end
|
193
|
+
|
194
|
+
def format(val=nil)
|
195
|
+
unless val.nil?
|
196
|
+
mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
|
197
|
+
fail "Unknown media type #{val}\nTry registering the extension with a mime type" if mime_type.nil?
|
198
|
+
|
199
|
+
@_format = val.to_sym
|
200
|
+
response['Content-Type'].sub!(/^[^;]+/, mime_type)
|
201
|
+
charset options.default_charset if Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
|
202
|
+
end
|
203
|
+
|
204
|
+
@_format
|
205
|
+
end
|
206
|
+
|
207
|
+
# This is mostly just a helper so request.path_info isn't changed when
|
208
|
+
# serving files from the public directory
|
209
|
+
def static_file?(path)
|
210
|
+
public_dir = File.expand_path(options.public)
|
211
|
+
path = File.expand_path(File.join(public_dir, unescape(path)))
|
212
|
+
|
213
|
+
path[0, public_dir.length] == public_dir && File.file?(path)
|
214
|
+
end
|
215
|
+
|
216
|
+
def charset(val=nil)
|
217
|
+
fail "Content-Type must be set in order to specify a charset" if response['Content-Type'].nil?
|
218
|
+
|
219
|
+
if response['Content-Type'] =~ /charset=[^;]+/
|
220
|
+
response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') || "charset=#{val}")
|
221
|
+
else
|
222
|
+
response['Content-Type'] += ";charset=#{val}"
|
223
|
+
end unless val.nil?
|
224
|
+
|
225
|
+
response['Content-Type'][/charset=([^;]+)/, 1]
|
226
|
+
end
|
227
|
+
|
228
|
+
def respond_to(&block)
|
229
|
+
wants = Format.new
|
230
|
+
yield wants
|
231
|
+
fmt, type, handler = match_accept_type(accept_list, wants)
|
232
|
+
raise UnhandledFormat if fmt.nil?
|
233
|
+
format fmt
|
234
|
+
handler.nil? ? nil : handler.call
|
235
|
+
end
|
236
|
+
|
237
|
+
def match_accept_type(mime_types, format)
|
238
|
+
selected = []
|
239
|
+
accepted_types = mime_types.map {|type| Regexp.escape(type).gsub(/\\\*/,'.*') }
|
240
|
+
# Fix for Chrome based browsers which returns XML when 'xhtml' is requested.
|
241
|
+
if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
|
242
|
+
accepted_types[0], accepted_types[1] = accepted_types[1], accepted_types[0]
|
243
|
+
if accepted_types[0].eql?('application/xhtml\\+xml')
|
244
|
+
accepted_types[0] = 'text/html'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
accepted_types.each do |at|
|
248
|
+
format.each do |fmt, ht, handler|
|
249
|
+
(selected = [fmt, ht, handler]) and break if ht.match(at)
|
250
|
+
end
|
251
|
+
break unless selected.empty?
|
252
|
+
end
|
253
|
+
selected
|
254
|
+
end
|
255
|
+
|
256
|
+
# NOTE Array instead of hash because order matters (wildcard type
|
257
|
+
# matches first handler)
|
258
|
+
class Format < Array #:nodoc:
|
259
|
+
def method_missing(format, *args, &handler)
|
260
|
+
mt = Sinatra::RespondTo::Helpers.mime_type(format)
|
261
|
+
if mt.nil?
|
262
|
+
Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{format}\nTry registering the extension with a mime type")
|
263
|
+
end
|
264
|
+
self << [format.to_s, mt, handler]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
|
272
|
+
Sinatra::Application.register Sinatra::RespondTo
|