rackful 0.1.4 → 0.2.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.
- checksums.yaml +7 -0
- data/CHANGES.md +18 -8
- data/RACKFUL.md +46 -34
- data/README.md +11 -5
- data/example/config.ru +19 -25
- data/lib/rackful.rb +11 -2
- data/lib/rackful/httpstatus.rb +250 -0
- data/lib/rackful/middleware.rb +2 -3
- data/lib/rackful/middleware/headerspoofing.rb +49 -0
- data/lib/rackful/middleware/methodoverride.rb +136 -0
- data/lib/rackful/parser.rb +315 -0
- data/lib/rackful/request.rb +103 -53
- data/lib/rackful/resource.rb +158 -221
- data/lib/rackful/serializer.rb +133 -215
- data/lib/rackful/server.rb +76 -86
- data/lib/rackful/uri.rb +150 -0
- data/mkdoc.sh +4 -2
- data/rackful.gemspec +6 -5
- metadata +66 -58
- data/lib/rackful/http_status.rb +0 -285
- data/lib/rackful/middleware/header_spoofing.rb +0 -72
- data/lib/rackful/middleware/method_spoofing.rb +0 -101
- data/lib/rackful/middleware/relative_location.rb +0 -71
- data/lib/rackful/path.rb +0 -179
data/lib/rackful/serializer.rb
CHANGED
@@ -1,240 +1,204 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Required for running:
|
4
|
-
require 'rack/utils'
|
5
|
-
require 'uri'
|
6
|
-
require 'base64'
|
7
|
-
require 'json'
|
8
|
-
require 'time'
|
9
|
-
#require 'json/pure'
|
1
|
+
# encoding: utf-8
|
10
2
|
|
11
3
|
|
12
4
|
module Rackful
|
13
5
|
|
14
6
|
|
15
|
-
=begin markdown
|
16
|
-
Base class for all serializers.
|
17
7
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
8
|
+
# Base class for all serializers.
|
9
|
+
#
|
10
|
+
# The serializers {Serializer::XHTML} and {Serializer::JSON} defined in this
|
11
|
+
# library depend on the presence of method
|
12
|
+
# {Rackful::Resource#to_rackful resource.to_rackful}.
|
13
|
+
# @abstract Subclasses must implement method `#each` end define constant
|
14
|
+
# `CONTENT_TYPES`
|
15
|
+
# @!attribute [r] request
|
16
|
+
# @return [Request]
|
17
|
+
# @!attribute [r] resource
|
18
|
+
# @return [Resource]
|
19
|
+
# @!attribute [r] content_type
|
20
|
+
# @return [String] The content type to be served by this Serializer. This will
|
21
|
+
# always be one of the content types listed in constant `CONTENT_TYPES`.
|
23
22
|
class Serializer
|
24
23
|
|
25
24
|
|
26
25
|
include Enumerable
|
27
26
|
|
28
27
|
|
29
|
-
attr_reader :resource, :content_type
|
30
|
-
|
28
|
+
attr_reader :request, :resource, :content_type
|
31
29
|
|
32
|
-
def initialize resource, content_type
|
33
|
-
@resource, @content_type = resource, content_type
|
34
|
-
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
|
42
|
-
def each
|
43
|
-
raise "Class #{self.class} doesn't implement #each()."
|
31
|
+
# @param request [Request]
|
32
|
+
# @param resource [Resource]
|
33
|
+
# @param content_type [String]
|
34
|
+
def initialize request, resource, content_type
|
35
|
+
@request, @resource, @content_type =
|
36
|
+
request, resource, content_type
|
44
37
|
end
|
45
38
|
|
46
39
|
|
47
|
-
|
48
|
-
|
49
|
-
|
40
|
+
# @!method headers()
|
41
|
+
# Extra response headers that a serializer likes to return.
|
42
|
+
#
|
43
|
+
# You don't have to include the `Content-Type` header, as this is done
|
44
|
+
# _for_ you.
|
45
|
+
#
|
46
|
+
# This method is optional.
|
47
|
+
# @return [Hash]
|
48
|
+
# @abstract
|
50
49
|
|
51
|
-
You don't have to include the `Content-Type` header, as this is done _for_ you.
|
52
50
|
|
53
|
-
|
54
|
-
@
|
55
|
-
|
56
|
-
|
51
|
+
# @abstract Every serializer must implement this method.
|
52
|
+
# @yieldparam block [String] (part of) the entity body
|
53
|
+
def each
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
57
56
|
|
58
57
|
|
59
58
|
end # class Serializer
|
60
59
|
|
61
60
|
|
62
|
-
|
63
|
-
=end
|
64
|
-
class XHTML < Serializer
|
61
|
+
class Serializer::XHTML < Serializer
|
65
62
|
|
66
63
|
|
67
64
|
# The content types served by this serializer.
|
68
65
|
# @see Serializer::CONTENT_TYPES
|
69
66
|
CONTENT_TYPES = [
|
70
|
-
'application/
|
71
|
-
'text/html; charset=UTF-8',
|
67
|
+
'application/xml; charset=UTF-8',
|
72
68
|
'text/xml; charset=UTF-8',
|
73
|
-
'
|
69
|
+
'text/html; charset=UTF-8',
|
70
|
+
'application/xhtml+xml; charset=UTF-8',
|
74
71
|
]
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
# @return [URI::HTTP]
|
75
|
+
def html_base_uri
|
76
|
+
@html_base_uri ||= begin
|
77
|
+
retval = self.request.canonical_uri.dup
|
78
|
+
retval.path = retval.path.sub( %r{[^/]+\z}, '' )
|
79
|
+
retval.query = nil
|
80
|
+
retval
|
81
|
+
end
|
82
|
+
end
|
75
83
|
|
76
|
-
|
77
|
-
=begin
|
78
|
-
@yieldparam xhtml [String]
|
79
|
-
=end
|
84
|
+
# @yieldparam xhtml [String]
|
80
85
|
def each &block
|
81
|
-
|
86
|
+
tmp = ''
|
87
|
+
# The XML header is only sent for XML media types:
|
82
88
|
if /xml/ === self.content_type
|
83
|
-
|
89
|
+
tmp += <<EOS
|
84
90
|
<?xml version="1.0" encoding="UTF-8"?>
|
85
91
|
EOS
|
86
92
|
end
|
87
|
-
|
93
|
+
tmp += <<EOS
|
88
94
|
<!DOCTYPE html>
|
89
95
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
90
96
|
<head>
|
91
|
-
<title>#{ Rack::Utils.escape_html(resource.title) }</title>
|
97
|
+
<title>#{ Rack::Utils.escape_html(self.resource.title) }</title>
|
98
|
+
<base href="#{self.html_base_uri}"/>
|
92
99
|
EOS
|
93
|
-
|
94
|
-
|
95
|
-
<
|
96
|
-
EOS
|
97
|
-
#~ end
|
98
|
-
unless '/' == request.path
|
99
|
-
yield <<EOS
|
100
|
-
<link rel="contents" href="#{'/' === request.content_path[-1] ? '../' : './' }"/>
|
100
|
+
unless '/' == self.request.canonical_uri.path
|
101
|
+
tmp += <<EOS
|
102
|
+
<link rel="contents" href="#{'/' === self.request.canonical_uri.path[-1] ? '../' : './' }"/>
|
101
103
|
EOS
|
102
104
|
end
|
103
|
-
|
104
|
-
|
105
|
+
r = self.resource.to_rackful
|
106
|
+
tmp += self.header + '<div id="rackful-content"' + self.xsd_type( r ) + '>'
|
107
|
+
yield tmp
|
108
|
+
each_nested( r, &block )
|
105
109
|
yield '</div>' + footer
|
106
110
|
end
|
107
111
|
|
108
112
|
|
109
|
-
#
|
113
|
+
# @api private
|
110
114
|
def header
|
111
115
|
self.class.class_variable_defined?( :@@header ) && @@header ?
|
112
116
|
@@header.call( self ) :
|
113
117
|
"</head><body>"
|
114
118
|
end
|
115
119
|
|
116
|
-
|
120
|
+
|
121
|
+
# Set a header generator.
|
122
|
+
# @yieldparam serializer [Serializer::XHTML] This serializer
|
123
|
+
# @yieldreturn [String] some XHTML
|
117
124
|
def self.header &block
|
118
125
|
@@header = block
|
119
126
|
self
|
120
127
|
end
|
121
128
|
|
122
129
|
|
123
|
-
#
|
130
|
+
# @api private
|
124
131
|
def footer
|
125
132
|
self.class.class_variable_defined?( :@@footer ) && @@footer ?
|
126
133
|
@@footer.call( self ) :
|
127
|
-
'<div class="
|
134
|
+
'<div class="rackful-powered">Powered by <a href="http://github.com/pieterb/Rackful">Rackful</a></div></body></html>'
|
128
135
|
end
|
129
136
|
|
130
|
-
|
137
|
+
|
138
|
+
# Set a footer generator.
|
139
|
+
# @yieldparam serializer [Serializer::XHTML] This serializer
|
140
|
+
# @yieldreturn [String] some XHTML
|
131
141
|
def self.footer &block
|
132
142
|
@@footer = block
|
133
143
|
self
|
134
144
|
end
|
135
145
|
|
136
146
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
*
|
143
|
-
|
144
|
-
|
145
|
-
*
|
146
|
-
|
147
|
-
|
148
|
-
*
|
149
|
-
|
150
|
-
*
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
@
|
156
|
-
@yieldparam xhtml[String]
|
157
|
-
=end
|
147
|
+
# Serializes many kinds of objects to XHTML.
|
148
|
+
#
|
149
|
+
# How an object is serialized, depends:
|
150
|
+
#
|
151
|
+
# * A *{Resource}* will be serialized by its own {Resource#serializer serializer}.
|
152
|
+
# * A *{URI}* will be serialized as a hyperlink.
|
153
|
+
# * An Object responding to *`#each_pair`* (i.e. something {Hash}-like) will
|
154
|
+
# be represented by
|
155
|
+
# * a descriptive list, with
|
156
|
+
# * An Object responding to *`#each`* (i.e. something {Enumerable}) will
|
157
|
+
# be represented as a JSON array.
|
158
|
+
# * A *binary encoded {String}* (i.e. a blob} is represented by a JSON string,
|
159
|
+
# containing the base64 encoded version of the data.
|
160
|
+
# * A *{Time}* is represented by a string containing a dateTime as defined by
|
161
|
+
# XMLSchema.
|
162
|
+
# * On *all the rest,* method `#to_json` is invoked.
|
163
|
+
# @overload each_nested
|
164
|
+
# @yieldparam xhtml [String]
|
165
|
+
# @api private
|
158
166
|
def each_nested p = self.resource.to_rackful, &block
|
159
|
-
|
167
|
+
|
168
|
+
# A Resource:
|
160
169
|
if p.kind_of?( Resource ) && ! p.equal?( self.resource )
|
161
|
-
p.serializer( self.content_type ).each_nested &block
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
) ) + '</a>'
|
168
|
-
|
169
|
-
#
|
170
|
-
# if p.recurse?
|
171
|
-
# yield '<dl class="rackful-resources">'
|
172
|
-
# p.each_pair do
|
173
|
-
# |path, child|
|
174
|
-
# yield '<dt>'
|
175
|
-
# self.each_nested path, &block
|
176
|
-
# yield "</dt><dd#{self.xsd_type(child)}>"
|
177
|
-
# self.each_nested child, &block
|
178
|
-
# yield "</dd>\n"
|
179
|
-
# end
|
180
|
-
# yield '</dl>'
|
181
|
-
#~ elsif ( q = p.first ) and
|
182
|
-
#~ q.kind_of?( Enumerable )
|
183
|
-
#~ q.respond_to?( :keys ) && ( keys = q.keys ) &&
|
184
|
-
#~ p.all? { |r| r.respond_to?( :keys ) && r.keys == keys }
|
185
|
-
#~ )
|
186
|
-
#~ yield '<table class="rackful-objects"><thead><tr>' +
|
187
|
-
#~ keys.collect {
|
188
|
-
#~ |column|
|
189
|
-
#~ '<th>' +
|
190
|
-
#~ Rack::Utils.escape_html( column.to_s.split('_').join(' ') ) +
|
191
|
-
#~ "</th>\n"
|
192
|
-
#~ }.join + '</tr></thead><tbody>'
|
193
|
-
#~ p.each do
|
194
|
-
#~ |h|
|
195
|
-
#~ yield '<tr>'
|
196
|
-
#~ h.each_pair do
|
197
|
-
#~ |key, value|
|
198
|
-
#~ yield "<td class=\"rackful-objects-#{Rack::Utils.escape_html( key.to_s )}\"#{self.xsd_type(value)}>"
|
199
|
-
#~ self.each_nested value, &block
|
200
|
-
#~ yield "</td>\n"
|
201
|
-
#~ end
|
202
|
-
#~ yield '</tr>'
|
203
|
-
#~ end
|
204
|
-
#~ yield "</tbody></table>"
|
205
|
-
# else
|
206
|
-
# yield '<ul class="rackful-resources">'
|
207
|
-
# p.each do
|
208
|
-
# |value|
|
209
|
-
# yield "<li#{self.xsd_type(value)}>"
|
210
|
-
# self.each_nested value, &block
|
211
|
-
# yield "</li>\n"
|
212
|
-
# end
|
213
|
-
# yield '</ul>'
|
214
|
-
# end
|
215
|
-
|
170
|
+
p.serializer( self.request, self.content_type ).each_nested( &block )
|
171
|
+
|
172
|
+
# A URI:
|
173
|
+
elsif p.kind_of?( URI )
|
174
|
+
rel_path = p.relative? ? p : p.route_from( self.html_base_uri )
|
175
|
+
yield "<a href=\"#{rel_path}\">" +
|
176
|
+
Rack::Utils.escape_html( Rack::Utils.unescape( rel_path.to_s ) ) + '</a>'
|
177
|
+
|
178
|
+
# An Object:
|
216
179
|
elsif p.respond_to?( :each_pair )
|
217
|
-
yield '<br/><dl
|
180
|
+
yield '<br/><dl>'
|
218
181
|
p.each_pair do
|
219
182
|
|key, value|
|
220
|
-
yield
|
221
|
-
|
222
|
-
|
183
|
+
yield '<dt xs:type="xs:string">' +
|
184
|
+
Rack::Utils.escape_html( key.to_s.split('_').join(' ') ) +
|
185
|
+
"</dt><dd#{self.xsd_type(value)}>"
|
223
186
|
self.each_nested value, &block
|
224
187
|
yield "</dd>\n"
|
225
188
|
end
|
226
189
|
yield '</dl>'
|
227
|
-
|
190
|
+
|
191
|
+
# A List of Objects with identical keys:
|
228
192
|
elsif p.kind_of?( Enumerable ) and
|
229
193
|
( q = p.first ) and
|
230
194
|
(
|
231
195
|
q.respond_to?( :keys ) && ( keys = q.keys ) &&
|
232
196
|
p.all? { |r| r.respond_to?( :keys ) && r.keys == keys }
|
233
197
|
)
|
234
|
-
yield '<table
|
198
|
+
yield '<table><thead><tr>' +
|
235
199
|
keys.collect {
|
236
200
|
|column|
|
237
|
-
'<th>' +
|
201
|
+
'<th xs:type="xs:string">' +
|
238
202
|
Rack::Utils.escape_html( column.to_s.split('_').join(' ') ) +
|
239
203
|
"</th>\n"
|
240
204
|
}.join + '</tr></thead><tbody>'
|
@@ -243,16 +207,17 @@ How an object is serialized, depends:
|
|
243
207
|
yield '<tr>'
|
244
208
|
h.each_pair do
|
245
209
|
|key, value|
|
246
|
-
yield "<td
|
210
|
+
yield "<td#{self.xsd_type(value)}>"
|
247
211
|
self.each_nested value, &block
|
248
212
|
yield "</td>\n"
|
249
213
|
end
|
250
214
|
yield '</tr>'
|
251
215
|
end
|
252
216
|
yield "</tbody></table>"
|
253
|
-
|
217
|
+
|
218
|
+
# A List:
|
254
219
|
elsif p.kind_of?( Enumerable )
|
255
|
-
yield '<ul
|
220
|
+
yield '<ul>'
|
256
221
|
p.each do
|
257
222
|
|value|
|
258
223
|
yield "<li#{self.xsd_type(value)}>"
|
@@ -260,21 +225,24 @@ How an object is serialized, depends:
|
|
260
225
|
yield "</li>\n"
|
261
226
|
end
|
262
227
|
yield '</ul>'
|
263
|
-
|
228
|
+
|
229
|
+
# A Time:
|
264
230
|
elsif p.kind_of?( Time )
|
265
231
|
yield p.utc.xmlschema
|
266
|
-
|
232
|
+
|
233
|
+
# A Blob:
|
267
234
|
elsif p.kind_of?( String ) && p.encoding == Encoding::BINARY
|
268
235
|
yield Base64.encode64(p).chomp
|
269
|
-
|
236
|
+
|
237
|
+
# Something serializable (including nil, true, false, Numeric):
|
270
238
|
else
|
271
239
|
yield Rack::Utils.escape_html( p.to_s )
|
272
|
-
|
240
|
+
|
273
241
|
end
|
274
242
|
end
|
275
243
|
|
276
244
|
|
277
|
-
# @private
|
245
|
+
# @api private
|
278
246
|
def xsd_type v
|
279
247
|
if v.respond_to? :to_rackful
|
280
248
|
v = v.to_rackful
|
@@ -289,7 +257,7 @@ How an object is serialized, depends:
|
|
289
257
|
' xs:type="xs:dateTime"'
|
290
258
|
elsif v.kind_of?( String ) && v.encoding == Encoding::BINARY
|
291
259
|
' xs:type="xs:base64Binary"'
|
292
|
-
elsif v.kind_of?( String )
|
260
|
+
elsif v.kind_of?( String )
|
293
261
|
' xs:type="xs:string"'
|
294
262
|
else
|
295
263
|
''
|
@@ -297,10 +265,10 @@ How an object is serialized, depends:
|
|
297
265
|
end
|
298
266
|
|
299
267
|
|
300
|
-
end # class XHTML
|
268
|
+
end # class Serializer::XHTML
|
301
269
|
|
302
270
|
|
303
|
-
class JSON < Serializer
|
271
|
+
class Serializer::JSON < Serializer
|
304
272
|
|
305
273
|
|
306
274
|
CONTENT_TYPES = [
|
@@ -309,31 +277,11 @@ class JSON < Serializer
|
|
309
277
|
]
|
310
278
|
|
311
279
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
How an object is serialized, depends:
|
316
|
-
|
317
|
-
* A *{Resource}* will be serialized by its own
|
318
|
-
{Resource#serializer serializer}.
|
319
|
-
* A *{Path}* will be serialized by a string, containing the relative path.
|
320
|
-
* An Object responding to *`#each_pair`* (i.e. something {Hash}-like) will
|
321
|
-
be represented as a JSON object.
|
322
|
-
* An Object responding to *`#each`* (i.e. something {Enumerable}) will
|
323
|
-
be represented as a JSON array.
|
324
|
-
* A *binary encoded {String}* (i.e. a blob} is represented by a JSON string,
|
325
|
-
containing the base64 encoded version of the data.
|
326
|
-
* A *{Time}* is represented by a string containing a dateTime as defined by
|
327
|
-
XMLSchema.
|
328
|
-
* On *all the rest,* method `#to_json` is invoked.
|
329
|
-
@overload each
|
330
|
-
@yieldparam json [String]
|
331
|
-
=end
|
280
|
+
# @yield [json]
|
281
|
+
# @yieldparam json [String]
|
332
282
|
def each thing = self.resource.to_rackful, &block
|
333
283
|
if thing.kind_of?( Resource ) && ! thing.equal?( self.resource )
|
334
|
-
thing.serializer( self.content_type ).each &block
|
335
|
-
elsif thing.kind_of?( Path )
|
336
|
-
yield thing.relative.to_json
|
284
|
+
thing.serializer( self.content_type ).each( &block )
|
337
285
|
elsif thing.respond_to? :each_pair
|
338
286
|
first = true
|
339
287
|
thing.each_pair do
|
@@ -362,37 +310,7 @@ How an object is serialized, depends:
|
|
362
310
|
end
|
363
311
|
|
364
312
|
|
365
|
-
|
366
|
-
r = ::JSON.parse(
|
367
|
-
input.read,
|
368
|
-
:symbolize_names => true
|
369
|
-
)
|
370
|
-
self.recursive_datetime_parser r
|
371
|
-
end
|
372
|
-
|
373
|
-
|
374
|
-
def self.recursive_datetime_parser p
|
375
|
-
if p.kind_of?(String)
|
376
|
-
begin
|
377
|
-
return Time.xmlschema(p)
|
378
|
-
rescue
|
379
|
-
end
|
380
|
-
elsif p.kind_of?(Hash)
|
381
|
-
p.keys.each do
|
382
|
-
|key|
|
383
|
-
p[key] = self.recursive_datetime_parser( p[key] )
|
384
|
-
end
|
385
|
-
elsif p.kind_of?(Array)
|
386
|
-
(0 ... p.size).each do
|
387
|
-
|i|
|
388
|
-
p[i] = self.recursive_datetime_parser( p[i] )
|
389
|
-
end
|
390
|
-
end
|
391
|
-
p
|
392
|
-
end
|
393
|
-
|
394
|
-
|
395
|
-
end # class HTTPStatus::JSON
|
313
|
+
end # class Serializer::JSON
|
396
314
|
|
397
315
|
|
398
316
|
end # module Rackful
|