steamcannon-deltacloud-core 0.0.7.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/COPYING +176 -0
  2. data/Rakefile +106 -0
  3. data/bin/deltacloudd +120 -0
  4. data/config.ru +5 -0
  5. data/deltacloud.rb +20 -0
  6. data/lib/deltacloud/base_driver/base_driver.rb +259 -0
  7. data/lib/deltacloud/base_driver/features.rb +173 -0
  8. data/lib/deltacloud/base_driver/mock_driver.rb +58 -0
  9. data/lib/deltacloud/base_driver.rb +20 -0
  10. data/lib/deltacloud/drivers/azure/azure_driver.rb +127 -0
  11. data/lib/deltacloud/drivers/ec2/ec2_driver.rb +580 -0
  12. data/lib/deltacloud/drivers/ec2/ec2_mock_driver.rb +170 -0
  13. data/lib/deltacloud/drivers/gogrid/gogrid_client.rb +50 -0
  14. data/lib/deltacloud/drivers/gogrid/gogrid_driver.rb +343 -0
  15. data/lib/deltacloud/drivers/gogrid/test.rb +13 -0
  16. data/lib/deltacloud/drivers/mock/data/buckets/blobs/blob1.yml +5 -0
  17. data/lib/deltacloud/drivers/mock/data/buckets/blobs/blob2.yml +5 -0
  18. data/lib/deltacloud/drivers/mock/data/buckets/blobs/blob3.yml +5 -0
  19. data/lib/deltacloud/drivers/mock/data/buckets/blobs/blob4.yml +5 -0
  20. data/lib/deltacloud/drivers/mock/data/buckets/blobs/blob5.yml +5 -0
  21. data/lib/deltacloud/drivers/mock/data/buckets/bucket1.yml +2 -0
  22. data/lib/deltacloud/drivers/mock/data/buckets/bucket2.yml +2 -0
  23. data/lib/deltacloud/drivers/mock/data/images/img1.yml +3 -0
  24. data/lib/deltacloud/drivers/mock/data/images/img2.yml +3 -0
  25. data/lib/deltacloud/drivers/mock/data/images/img3.yml +3 -0
  26. data/lib/deltacloud/drivers/mock/data/instances/inst0.yml +16 -0
  27. data/lib/deltacloud/drivers/mock/data/instances/inst1.yml +9 -0
  28. data/lib/deltacloud/drivers/mock/data/instances/inst2.yml +9 -0
  29. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap1.yml +4 -0
  30. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap2.yml +4 -0
  31. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap3.yml +4 -0
  32. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol1.yml +6 -0
  33. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol2.yml +6 -0
  34. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol3.yml +6 -0
  35. data/lib/deltacloud/drivers/mock/mock_driver.rb +356 -0
  36. data/lib/deltacloud/drivers/opennebula/cloud_client.rb +116 -0
  37. data/lib/deltacloud/drivers/opennebula/occi_client.rb +204 -0
  38. data/lib/deltacloud/drivers/opennebula/opennebula_driver.rb +241 -0
  39. data/lib/deltacloud/drivers/rackspace/rackspace_client.rb +130 -0
  40. data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +290 -0
  41. data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +258 -0
  42. data/lib/deltacloud/drivers/rimuhosting/rimuhosting_client.rb +85 -0
  43. data/lib/deltacloud/drivers/rimuhosting/rimuhosting_driver.rb +166 -0
  44. data/lib/deltacloud/drivers/terremark/terremark_driver.rb +295 -0
  45. data/lib/deltacloud/hardware_profile.rb +153 -0
  46. data/lib/deltacloud/helpers/application_helper.rb +122 -0
  47. data/lib/deltacloud/helpers/blob_stream.rb +51 -0
  48. data/lib/deltacloud/helpers/conversion_helper.rb +39 -0
  49. data/lib/deltacloud/helpers/hardware_profiles_helper.rb +35 -0
  50. data/lib/deltacloud/helpers.rb +5 -0
  51. data/lib/deltacloud/method_serializer.rb +85 -0
  52. data/lib/deltacloud/models/base_model.rb +59 -0
  53. data/lib/deltacloud/models/blob.rb +26 -0
  54. data/lib/deltacloud/models/bucket.rb +24 -0
  55. data/lib/deltacloud/models/image.rb +27 -0
  56. data/lib/deltacloud/models/instance.rb +38 -0
  57. data/lib/deltacloud/models/instance_profile.rb +48 -0
  58. data/lib/deltacloud/models/key.rb +35 -0
  59. data/lib/deltacloud/models/realm.rb +26 -0
  60. data/lib/deltacloud/models/storage_snapshot.rb +27 -0
  61. data/lib/deltacloud/models/storage_volume.rb +28 -0
  62. data/lib/deltacloud/state_machine.rb +84 -0
  63. data/lib/deltacloud/validation.rb +70 -0
  64. data/lib/drivers.rb +51 -0
  65. data/lib/sinatra/accept_media_types.rb +128 -0
  66. data/lib/sinatra/lazy_auth.rb +56 -0
  67. data/lib/sinatra/rabbit.rb +279 -0
  68. data/lib/sinatra/respond_to.rb +238 -0
  69. data/lib/sinatra/static_assets.rb +83 -0
  70. data/lib/sinatra/url_for.rb +53 -0
  71. data/public/favicon.ico +0 -0
  72. data/public/images/grid.png +0 -0
  73. data/public/images/logo-wide.png +0 -0
  74. data/public/images/rails.png +0 -0
  75. data/public/images/topbar-bg.png +0 -0
  76. data/public/javascripts/application.js +32 -0
  77. data/public/javascripts/jquery-1.4.2.min.js +154 -0
  78. data/public/stylesheets/compiled/application.css +613 -0
  79. data/public/stylesheets/compiled/ie.css +31 -0
  80. data/public/stylesheets/compiled/print.css +27 -0
  81. data/public/stylesheets/compiled/screen.css +456 -0
  82. data/server.rb +516 -0
  83. data/support/fedora/deltacloudd +68 -0
  84. data/support/fedora/rubygem-deltacloud-core.spec +91 -0
  85. data/tests/api_test.rb +37 -0
  86. data/tests/hardware_profiles_test.rb +120 -0
  87. data/tests/images_test.rb +111 -0
  88. data/tests/instance_states_test.rb +51 -0
  89. data/tests/instances_test.rb +222 -0
  90. data/tests/realms_test.rb +78 -0
  91. data/tests/url_for_test.rb +50 -0
  92. data/views/accounts/index.html.haml +11 -0
  93. data/views/accounts/show.html.haml +30 -0
  94. data/views/api/show.html.haml +15 -0
  95. data/views/api/show.xml.haml +5 -0
  96. data/views/blobs/show.html.haml +20 -0
  97. data/views/blobs/show.xml.haml +7 -0
  98. data/views/buckets/index.html.haml +33 -0
  99. data/views/buckets/index.xml.haml +10 -0
  100. data/views/buckets/new.html.haml +13 -0
  101. data/views/buckets/show.html.haml +19 -0
  102. data/views/buckets/show.xml.haml +8 -0
  103. data/views/docs/collection.html.haml +37 -0
  104. data/views/docs/collection.xml.haml +14 -0
  105. data/views/docs/index.html.haml +15 -0
  106. data/views/docs/index.xml.haml +5 -0
  107. data/views/docs/operation.html.haml +31 -0
  108. data/views/docs/operation.xml.haml +10 -0
  109. data/views/errors/auth_exception.html.haml +8 -0
  110. data/views/errors/auth_exception.xml.haml +2 -0
  111. data/views/errors/backend_error.html.haml +19 -0
  112. data/views/errors/backend_error.xml.haml +8 -0
  113. data/views/errors/not_found.html.haml +6 -0
  114. data/views/errors/not_found.xml.haml +2 -0
  115. data/views/errors/validation_failure.html.haml +11 -0
  116. data/views/errors/validation_failure.xml.haml +7 -0
  117. data/views/hardware_profiles/index.html.haml +25 -0
  118. data/views/hardware_profiles/index.xml.haml +4 -0
  119. data/views/hardware_profiles/show.html.haml +19 -0
  120. data/views/hardware_profiles/show.xml.haml +18 -0
  121. data/views/images/index.html.haml +30 -0
  122. data/views/images/index.xml.haml +8 -0
  123. data/views/images/show.html.haml +21 -0
  124. data/views/images/show.xml.haml +5 -0
  125. data/views/instance_states/show.html.haml +31 -0
  126. data/views/instance_states/show.png.erb +45 -0
  127. data/views/instance_states/show.xml.haml +8 -0
  128. data/views/instances/index.html.haml +30 -0
  129. data/views/instances/index.xml.haml +21 -0
  130. data/views/instances/new.html.haml +55 -0
  131. data/views/instances/show.html.haml +43 -0
  132. data/views/instances/show.xml.haml +49 -0
  133. data/views/keys/index.html.haml +26 -0
  134. data/views/keys/index.xml.haml +4 -0
  135. data/views/keys/new.html.haml +8 -0
  136. data/views/keys/show.html.haml +22 -0
  137. data/views/keys/show.xml.haml +20 -0
  138. data/views/layout.html.haml +26 -0
  139. data/views/realms/index.html.haml +29 -0
  140. data/views/realms/index.xml.haml +10 -0
  141. data/views/realms/show.html.haml +15 -0
  142. data/views/realms/show.xml.haml +9 -0
  143. data/views/root/index.html.haml +4 -0
  144. data/views/storage_snapshots/index.html.haml +20 -0
  145. data/views/storage_snapshots/index.xml.haml +9 -0
  146. data/views/storage_snapshots/show.html.haml +14 -0
  147. data/views/storage_snapshots/show.xml.haml +7 -0
  148. data/views/storage_volumes/index.html.haml +21 -0
  149. data/views/storage_volumes/index.xml.haml +23 -0
  150. data/views/storage_volumes/show.html.haml +20 -0
  151. data/views/storage_volumes/show.xml.haml +24 -0
  152. metadata +367 -0
@@ -0,0 +1,128 @@
1
+ module Rack
2
+ class Request
3
+ # The media types of the HTTP_ACCEPT header ordered according to their
4
+ # "quality" (preference level), without any media type parameters.
5
+ #
6
+ # ===== Examples
7
+ #
8
+ # env['HTTP_ACCEPT'] #=> 'application/xml;q=0.8,text/html,text/plain;q=0.9'
9
+ #
10
+ # req = Rack::Request.new(env)
11
+ # req.accept_media_types #=> ['text/html', 'text/plain', 'application/xml']
12
+ # req.accept_media_types.prefered #=> 'text/html'
13
+ #
14
+ # For more information, see:
15
+ # * Acept header: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
16
+ # * Quality values: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
17
+ #
18
+ # ===== Returns
19
+ # AcceptMediaTypes:: ordered list of accept header's media types
20
+ #
21
+ def accept_media_types
22
+ @accept_media_types ||= Rack::AcceptMediaTypes.new(@env['HTTP_ACCEPT'])
23
+ end
24
+ end
25
+
26
+ # AcceptMediaTypes is intended for wrapping env['HTTP_ACCEPT'].
27
+ #
28
+ # It allows ordering of its values (accepted media types) according to their
29
+ # "quality" (preference level).
30
+ #
31
+ # This wrapper is typically used to determine the request's prefered media
32
+ # type (see example below).
33
+ #
34
+ # ===== Examples
35
+ #
36
+ # env['HTTP_ACCEPT'] #=> 'application/xml;q=0.8,text/html,text/plain;q=0.9'
37
+ #
38
+ # types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
39
+ # types #=> ['text/html', 'text/plain', 'application/xml']
40
+ # types.prefered #=> 'text/html'
41
+ #
42
+ # ===== Notes
43
+ #
44
+ # For simplicity, media type parameters are striped, as they are seldom used
45
+ # in practice. Users who need them are excepted to parse the Accept header
46
+ # manually.
47
+ #
48
+ # ===== References
49
+ #
50
+ # HTTP 1.1 Specs:
51
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
52
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
53
+ #
54
+ class AcceptMediaTypes < Array
55
+
56
+ #--
57
+ # NOTE
58
+ # Reason for special handling of nil accept header:
59
+ #
60
+ # "If no Accept header field is present, then it is assumed that the client
61
+ # accepts all media types."
62
+ #
63
+ def initialize(header)
64
+ if header.nil?
65
+ replace(['*/*'])
66
+ else
67
+ replace(order(header.gsub(/ /, '').split(/,/)))
68
+ end
69
+ end
70
+
71
+ # The client's prefered media type.
72
+ def prefered
73
+ first
74
+ end
75
+
76
+ private
77
+
78
+ # Order media types by quality values, remove invalid types, and return media ranges.
79
+ #
80
+ def order(types) #:nodoc:
81
+ types.map {|type| AcceptMediaType.new(type) }.reverse.sort.reverse.select {|type| type.valid? }.map {|type| type.range }
82
+ end
83
+
84
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
85
+ #
86
+ class AcceptMediaType #:nodoc:
87
+ include Comparable
88
+
89
+ # media-range = ( "*/*"
90
+ # | ( type "/" "*" )
91
+ # | ( type "/" subtype )
92
+ # ) *( ";" parameter )
93
+ attr_accessor :range
94
+
95
+ # qvalue = ( "0" [ "." 0*3DIGIT ] )
96
+ # | ( "1" [ "." 0*3("0") ] )
97
+ attr_accessor :quality
98
+
99
+ def initialize(type)
100
+ self.range, *params = type.split(';')
101
+ self.quality = extract_quality(params)
102
+ end
103
+
104
+ def <=>(type)
105
+ self.quality <=> type.quality
106
+ end
107
+
108
+ # "A weight is normalized to a real number in the range 0 through 1,
109
+ # where 0 is the minimum and 1 the maximum value. If a parameter has a
110
+ # quality value of 0, then content with this parameter is `not
111
+ # acceptable' for the client."
112
+ #
113
+ def valid?
114
+ self.quality.between?(0.1, 1)
115
+ end
116
+
117
+ private
118
+ # Extract value from 'q=FLOAT' parameter if present, otherwise assume 1
119
+ #
120
+ # "The default value is q=1."
121
+ #
122
+ def extract_quality(params)
123
+ q = params.detect {|p| p.match(/q=\d\.?\d{0,3}/) }
124
+ q ? q.split('=').last.to_f : 1.0
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,56 @@
1
+ require 'sinatra/base'
2
+
3
+ # Lazy Basic HTTP authentication. Authentication is only forced when the
4
+ # credentials are actually needed.
5
+ module Sinatra
6
+ module LazyAuth
7
+ class LazyCredentials
8
+ def initialize(app)
9
+ @app = app
10
+ @provided = false
11
+ end
12
+
13
+ def user
14
+ credentials!
15
+ @user
16
+ end
17
+
18
+ def password
19
+ credentials!
20
+ @password
21
+ end
22
+
23
+ def provided?
24
+ @provided
25
+ end
26
+
27
+ private
28
+ def credentials!
29
+ unless provided?
30
+ auth = Rack::Auth::Basic::Request.new(@app.request.env)
31
+ unless auth.provided? && auth.basic? && auth.credentials
32
+ @app.authorize!
33
+ end
34
+ @user = auth.credentials[0]
35
+ @password = auth.credentials[1]
36
+ @provided = true
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ def authorize!
43
+ r = "#{DRIVER}-deltacloud@#{HOSTNAME}"
44
+ response['WWW-Authenticate'] = %(Basic realm="#{r}")
45
+ throw(:halt, [401, "Not authorized\n"])
46
+ end
47
+
48
+ # Request the current user's credentials. Actual credentials are only
49
+ # requested when an attempt is made to get the user name or password
50
+ def credentials
51
+ LazyCredentials.new(self)
52
+ end
53
+ end
54
+
55
+ helpers LazyAuth
56
+ end
@@ -0,0 +1,279 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/url_for'
3
+ require 'deltacloud/validation'
4
+
5
+ module Sinatra
6
+
7
+ module Rabbit
8
+
9
+ class DuplicateParamException < Exception; end
10
+ class DuplicateOperationException < Exception; end
11
+ class DuplicateCollectionException < Exception; end
12
+
13
+ class Operation
14
+ attr_reader :name, :method
15
+
16
+ include ::Deltacloud::Validation
17
+
18
+ STANDARD = {
19
+ :index => { :method => :get, :member => false },
20
+ :show => { :method => :get, :member => true },
21
+ :create => { :method => :post, :member => false },
22
+ :update => { :method => :put, :member => true },
23
+ :destroy => { :method => :delete, :member => true }
24
+ }
25
+
26
+ def initialize(coll, name, opts, &block)
27
+ @name = name.to_sym
28
+ opts = STANDARD[@name].merge(opts) if standard?
29
+ @collection = coll
30
+ raise "No method for operation #{name}" unless opts[:method]
31
+ @method = opts[:method].to_sym
32
+ @member = opts[:member]
33
+ @description = ""
34
+ instance_eval(&block) if block_given?
35
+ generate_documentation
36
+ end
37
+
38
+ def standard?
39
+ STANDARD.keys.include?(name)
40
+ end
41
+
42
+ def description(text="")
43
+ return @description if text.blank?
44
+ @description = text
45
+ end
46
+
47
+ def generate_documentation
48
+ coll, oper = @collection, self
49
+ ::Sinatra::Application.get("/api/docs/#{@collection.name}/#{@name}") do
50
+ @collection, @operation = coll, oper
51
+ respond_to do |format|
52
+ format.html { haml :'docs/operation' }
53
+ format.xml { haml :'docs/operation' }
54
+ end
55
+ end
56
+ end
57
+
58
+ def control(opts=nil, &block)
59
+ op = self
60
+ @control = Proc.new do
61
+ if opts and
62
+ opts[:with_feature] and
63
+ !driver.respond_to?(opts[:with_feature])
64
+ raise Deltacloud::BackendFeatureUnsupported.new('501', nil,
65
+ "#{opts[:with_feature]} not supported by backend", nil)
66
+ end
67
+ op.validate(params)
68
+ instance_eval(&block)
69
+ end
70
+ end
71
+
72
+ def prefix
73
+ # FIXME: Make the /api prefix configurable
74
+ "/api"
75
+ end
76
+
77
+ def path(args = {})
78
+ l_prefix = args[:prefix] ? args[:prefix] : prefix
79
+ if @member
80
+ if standard?
81
+ "#{l_prefix}/#{@collection.name}/:id"
82
+ else
83
+ "#{l_prefix}/#{@collection.name}/:id/#{name}"
84
+ end
85
+ else
86
+ "#{l_prefix}/#{@collection.name}"
87
+ end
88
+ end
89
+
90
+ def generate
91
+ ::Sinatra::Application.send(@method, path, {}, &@control)
92
+ # Set up some Rails-like URL helpers
93
+ if name == :index
94
+ gen_route "#{@collection.name}_url"
95
+ elsif name == :show
96
+ gen_route "#{@collection.name.to_s.singularize}_url"
97
+ else
98
+ gen_route "#{name}_#{@collection.name.to_s.singularize}_url"
99
+ end
100
+ end
101
+
102
+ private
103
+ def gen_route(name)
104
+ route_url = path
105
+ if @member
106
+ ::Sinatra::Application.send(:define_method, name) do |id, *args|
107
+ url = query_url(route_url, args[0])
108
+ url_for url.gsub(/:id/, id.to_s), :full
109
+ end
110
+ else
111
+ ::Sinatra::Application.send(:define_method, name) do |*args|
112
+ url = query_url(route_url, args[0])
113
+ url_for url, :full
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ class Collection
120
+ attr_reader :name, :operations
121
+
122
+ def initialize(name, &block)
123
+ @name = name
124
+ @description = ""
125
+ @operations = {}
126
+ instance_eval(&block) if block_given?
127
+ generate_documentation
128
+ end
129
+
130
+ # Set/Return description for collection
131
+ # If first parameter is not present, full description will be
132
+ # returned.
133
+ def description(text='')
134
+ return @description if text.blank?
135
+ @description = text
136
+ end
137
+
138
+ def generate_documentation
139
+ coll, oper, features = self, @operations, driver.features(name)
140
+ ::Sinatra::Application.get("/api/docs/#{@name}") do
141
+ @collection, @operations, @features = coll, oper, features
142
+ respond_to do |format|
143
+ format.html { haml :'docs/collection' }
144
+ format.xml { haml :'docs/collection' }
145
+ end
146
+ end
147
+ end
148
+
149
+ # Add a new operation for this collection. For the standard REST
150
+ # operations :index, :show, :update, and :destroy, we already know
151
+ # what method to use and whether this is an operation on the URL for
152
+ # individual elements or for the whole collection.
153
+ #
154
+ # For non-standard operations, options must be passed:
155
+ # :method : one of the HTTP methods
156
+ # :member : whether this is an operation on the collection or an
157
+ # individual element (FIXME: custom operations on the
158
+ # collection will use a nonsensical URL) The URL for the
159
+ # operation is the element URL with the name of the operation
160
+ # appended
161
+ #
162
+ # This also defines a helper method like show_instance_url that returns
163
+ # the URL to this operation (in request context)
164
+ def operation(name, opts = {}, &block)
165
+ raise DuplicateOperationException if @operations[name]
166
+ @operations[name] = Operation.new(self, name, opts, &block)
167
+ end
168
+
169
+ def generate
170
+ operations.values.each { |op| op.generate }
171
+ app = ::Sinatra::Application
172
+ collname = name # Work around Ruby's weird scoping/capture
173
+ app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
174
+ url_for "/api/#{collname}/#{id}", :full
175
+ end
176
+
177
+ if index_op = operations[:index]
178
+ app.send(:define_method, "#{name}_url") do
179
+ url_for index_op.path.gsub(/\/\?$/,''), :full
180
+ end
181
+ end
182
+ end
183
+
184
+ def add_feature_params(features)
185
+ features.each do |f|
186
+ f.operations.each do |fop|
187
+ if cop = operations[fop.name]
188
+ fop.params.each_key do |k|
189
+ if cop.params.has_key?(k)
190
+ raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}"
191
+ else
192
+ cop.params[k] = fop.params[k]
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ def collections
202
+ @collections ||= {}
203
+ end
204
+
205
+ # Create a new collection. NAME should be the pluralized name of the
206
+ # collection.
207
+ #
208
+ # Adds a helper method #{name}_url which returns the URL to the :index
209
+ # operation on this collection.
210
+ def collection(name, &block)
211
+ raise DuplicateCollectionException if collections[name]
212
+ return unless driver.has_collection?(name.to_sym)
213
+ collections[name] = Collection.new(name, &block)
214
+ collections[name].add_feature_params(driver.features(name))
215
+ collections[name].generate
216
+ end
217
+
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
+
228
+ module RabbitHelper
229
+ def query_url(url, params)
230
+ return url if params.nil? || params.empty?
231
+ url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}"
232
+ end
233
+
234
+ def entry_points
235
+ collections.values.inject([]) do |m, coll|
236
+ url = url_for coll.operations[:index].path, :full
237
+ m << [ coll.name, url ]
238
+ end
239
+ end
240
+ end
241
+
242
+ register Rabbit
243
+ helpers RabbitHelper
244
+ end
245
+
246
+ class String
247
+ # Rails defines this for a number of other classes, including Object
248
+ # see activesupport/lib/active_support/core_ext/object/blank.rb
249
+ def blank?
250
+ self !~ /\S/
251
+ end
252
+
253
+ # Title case.
254
+ #
255
+ # "this is a string".titlecase
256
+ # => "This Is A String"
257
+ #
258
+ # CREDIT: Eliazar Parra
259
+ # Copied from facets
260
+ def titlecase
261
+ gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
262
+ end
263
+
264
+ def pluralize
265
+ self + "s"
266
+ end
267
+
268
+ def singularize
269
+ self.gsub(/s$/, '')
270
+ end
271
+
272
+ def underscore
273
+ gsub(/::/, '/').
274
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
275
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
276
+ tr("-", "_").
277
+ downcase
278
+ end
279
+ end
@@ -0,0 +1,238 @@
1
+ # respond_to (The MIT License)
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software
4
+ # and associated documentation files (the 'Software'), to deal in the Software without restriction,
5
+ # including without limitation the rights to use, copy, modify, merge, publish, distribute,
6
+ # sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
7
+ # furnished to do so, subject to the following conditions:
8
+ #
9
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
10
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
11
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
12
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
13
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
14
+
15
+ require 'sinatra/base'
16
+ require 'rack/accept'
17
+
18
+ use Rack::Accept
19
+
20
+ module Sinatra
21
+ module RespondTo
22
+
23
+ class MissingTemplate < Sinatra::NotFound; end
24
+
25
+ # Define all MIME types you want to support here.
26
+ # This conversion table will be used for auto-negotiation
27
+ # with browser in sinatra when no 'format' parameter is specified.
28
+
29
+ SUPPORTED_ACCEPT_HEADERS = {
30
+ :xml => [
31
+ 'text/xml',
32
+ 'application/xml'
33
+ ],
34
+ :html => [
35
+ 'text/html',
36
+ 'application/xhtml+xml'
37
+ ],
38
+ :json => [
39
+ 'application/json'
40
+ ]
41
+ }
42
+
43
+ # We need to pass array of available response types to
44
+ # best_media_type method
45
+ def accept_to_array
46
+ SUPPORTED_ACCEPT_HEADERS.keys.collect do |key|
47
+ SUPPORTED_ACCEPT_HEADERS[key]
48
+ end.flatten
49
+ end
50
+
51
+ # Then, when we get best media type for response, we need
52
+ # to know which format to choose
53
+ def lookup_format_from_mime(mime)
54
+ SUPPORTED_ACCEPT_HEADERS.keys.each do |format|
55
+ return format if SUPPORTED_ACCEPT_HEADERS[format].include?(mime)
56
+ end
57
+ end
58
+
59
+ def self.registered(app)
60
+
61
+ app.helpers RespondTo::Helpers
62
+
63
+ app.before do
64
+
65
+ # Skip development error image and static content
66
+ next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
67
+ next if options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
68
+
69
+ # Remove extension from URI
70
+ # Extension will be available as a 'extension' method (extension=='txt')
71
+
72
+ extension request.path_info.match(/\.([^\.\/]+)$/).to_a.first
73
+
74
+ # If ?format= is present, ignore all Accept negotiations because
75
+ # we are not dealing with browser
76
+ if request.params.has_key? 'format'
77
+ format params['format'].to_sym
78
+ end
79
+
80
+ # Let's make a little exception here to handle
81
+ # /api/instance_states[.gv/.png] calls
82
+ if extension.eql?('gv')
83
+ format :gv
84
+ elsif extension.eql?('png')
85
+ format :png
86
+ end
87
+
88
+ # Get Rack::Accept::Response object and find best possible
89
+ # mime type to output.
90
+ # This negotiation works fine with latest rest-client gem:
91
+ #
92
+ # RestClient.get 'http://localhost:3001/api', {:accept => :json } =>
93
+ # 'application/json'
94
+ # RestClient.get 'http://localhost:3001/api', {:accept => :xml } =>
95
+ # 'application/xml'
96
+ #
97
+ # Also browsers like Firefox (3.6.x) and Chromium reporting
98
+ # 'application/xml+xhtml' which is recognized as :html reponse
99
+ # In browser you can force output using ?format=[format] parameter.
100
+
101
+ rack_accept = env['rack-accept.request']
102
+
103
+ if rack_accept.media_type.to_s.strip.eql?('Accept:')
104
+ format :xml
105
+ else
106
+ format lookup_format_from_mime(rack_accept.best_media_type(accept_to_array))
107
+ end
108
+
109
+ end
110
+
111
+ app.class_eval do
112
+ # This code was copied from respond_to plugin
113
+ # http://github.com/cehoffman/sinatra-respond_to
114
+ # MIT License
115
+ alias :render_without_format :render
116
+ def render(*args, &block)
117
+ assumed_layout = args[1] == :layout
118
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
119
+ render_without_format *args, &block
120
+ rescue Errno::ENOENT => e
121
+ raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
122
+ raise e
123
+ end
124
+ private :render
125
+ end
126
+
127
+ # This code was copied from respond_to plugin
128
+ # http://github.com/cehoffman/sinatra-respond_to
129
+ app.configure :development do |dev|
130
+ dev.error MissingTemplate do
131
+ content_type :html, :charset => 'utf-8'
132
+ response.status = request.env['sinatra.error'].code
133
+
134
+ engine = request.env['sinatra.error'].message.split('.').last
135
+ engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
136
+
137
+ path = File.basename(request.path_info)
138
+ path = "root" if path.nil? || path.empty?
139
+
140
+ format = engine == 'builder' ? 'xml' : 'html'
141
+
142
+ layout = case engine
143
+ when 'haml' then "!!!\n%html\n %body= yield"
144
+ when 'erb' then "<html>\n <body>\n <%= yield %>\n </body>\n</html>"
145
+ end
146
+
147
+ layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
148
+
149
+ (<<-HTML).gsub(/^ {10}/, '')
150
+ <!DOCTYPE html>
151
+ <html>
152
+ <head>
153
+ <style type="text/css">
154
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
155
+ color:#888;margin:20px}
156
+ #c {margin:0 auto;width:500px;text-align:left;}
157
+ small {float:right;clear:both;}
158
+ pre {clear:both;text-align:left;font-size:70%;width:500px;margin:0 auto;}
159
+ </style>
160
+ </head>
161
+ <body>
162
+ <h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
163
+ <img src='/__sinatra__/500.png'>
164
+ <pre>#{request.env['sinatra.error'].backtrace.join("\n")}</pre>
165
+ <div id="c">
166
+ <small>application.rb</small>
167
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { #{engine} :#{path} }\n end\nend</pre>
168
+ </div>
169
+ </body>
170
+ </html>
171
+ HTML
172
+ end
173
+
174
+ end
175
+ end
176
+
177
+ module Helpers
178
+
179
+ # This code was copied from respond_to plugin
180
+ # http://github.com/cehoffman/sinatra-respond_to
181
+ def self.included(klass)
182
+ klass.class_eval do
183
+ alias :content_type_without_save :content_type
184
+ def content_type(*args)
185
+ content_type_without_save *args
186
+ @_format = args.first.to_sym
187
+ response['Content-Type']
188
+ end
189
+ end
190
+ end
191
+
192
+ def static_file?(path)
193
+ public_dir = File.expand_path(options.public)
194
+ path = File.expand_path(File.join(public_dir, unescape(path)))
195
+
196
+ path[0, public_dir.length] == public_dir && File.file?(path)
197
+ end
198
+
199
+
200
+ # Extension holds trimmed extension. This is extra usefull
201
+ # when you want to build original URI (with extension)
202
+ # You can simply call "#{request.env['REQUEST_URI']}.#{extension}"
203
+ def extension(val=nil)
204
+ @_extension ||= val
205
+ @_extension
206
+ end
207
+
208
+ # This helper will holds current format. Helper should be
209
+ # accesible from all places in Sinatra
210
+ def format(val=nil)
211
+ @_format ||= val
212
+ @_format
213
+ end
214
+
215
+ def respond_to(&block)
216
+ wants = {}
217
+
218
+ def wants.method_missing(type, *args, &handler)
219
+ self[type] = handler
220
+ end
221
+
222
+ # Set proper content-type and encoding for
223
+ # text based formats
224
+ if [:xml, :gv, :html, :json].include?(format)
225
+ content_type format, :charset => 'utf-8'
226
+ end
227
+ yield wants
228
+ # Raise this error if requested format is not defined
229
+ # in respond_to { } block.
230
+ raise MissingTemplate if wants[format].nil?
231
+
232
+ wants[format].call
233
+ end
234
+
235
+ end
236
+
237
+ end
238
+ end