steamcannon-deltacloud-core 0.0.7.1-java

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 (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