rackful 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,240 +1,204 @@
1
- # Required for parsing:
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
- The default serializers defined in this library ({Rackful::XHTML} and {Rackful::JSON})
19
- depend on the availability of method {Rackful::Resource#to_rackful}.
20
- @abstract Subclasses must implement method `#each` end define constant
21
- `CONTENT_TYPES`
22
- =end
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
- =begin markdown
38
- Every serializer must implement this method.
39
- @abstract
40
- @yield [data] the entity body
41
- =end
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
- =begin markdown
48
- @!method headers()
49
- Extra response headers that a serializer likes to return.
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
- This method is optional.
54
- @return [Hash, nil]
55
- @abstract
56
- =end
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
- =begin markdown
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/xhtml+xml; charset=UTF-8',
71
- 'text/html; charset=UTF-8',
67
+ 'application/xml; charset=UTF-8',
72
68
  'text/xml; charset=UTF-8',
73
- 'application/xml; charset=UTF-8'
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
- request = Request.current
86
+ tmp = ''
87
+ # The XML header is only sent for XML media types:
82
88
  if /xml/ === self.content_type
83
- yield <<EOS
89
+ tmp += <<EOS
84
90
  <?xml version="1.0" encoding="UTF-8"?>
85
91
  EOS
86
92
  end
87
- yield <<EOS
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
- #~ unless request.path == request.content_path
94
- yield <<EOS
95
- <base href="#{request.base_path.relative request.path}"/>
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
- yield header + '<div id="rackful_content">'
104
- each_nested &block
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
- # Look at the source code!
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
- # Look at the source code!
130
+ # @api private
124
131
  def footer
125
132
  self.class.class_variable_defined?( :@@footer ) && @@footer ?
126
133
  @@footer.call( self ) :
127
- '<div class="rackful_powered">Powered by <a href="http://github.com/pieterb/Rackful">Rackful</a></div></body></html>'
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
- =begin markdown
138
- Serializes many kinds of objects to XHTML.
139
-
140
- How an object is serialized, depends:
141
-
142
- * A *{Resource}* will be serialized by its own
143
- {Resource#serializer serializer}.
144
- * A *{Path}* will be serialized as a hyperlink.
145
- * An Object responding to *`#each_pair`* (i.e. something {Hash}-like) will
146
- be represented by
147
- * a descriptive list, with
148
- * An Object responding to *`#each`* (i.e. something {Enumerable}) will
149
- be represented as a JSON array.
150
- * A *binary encoded {String}* (i.e. a blob} is represented by a JSON string,
151
- containing the base64 encoded version of the data.
152
- * A *{Time}* is represented by a string containing a dateTime as defined by
153
- XMLSchema.
154
- * On *all the rest,* method `#to_json` is invoked.
155
- @overload each_nested
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
- elsif p.kind_of?( Path )
164
- yield "<a href=\"#{p.relative}\">" +
165
- Rack::Utils.escape_html( Rack::Utils.unescape(
166
- File::basename(p.unslashify)
167
- ) ) + '</a>'
168
-
169
- # elsif p.kind_of?( Collection )
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 class="rackful-object">'
180
+ yield '<br/><dl>'
218
181
  p.each_pair do
219
182
  |key, value|
220
- yield "<dt#{self.xsd_type(key)}>"
221
- self.each_nested key, &block
222
- yield "</dt><dd#{self.xsd_type(value)}>"
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 class="rackful-objects"><thead><tr>' +
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 class=\"rackful-objects-#{Rack::Utils.escape_html( key.to_s )}\"#{self.xsd_type(value)}>"
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 class="rackful-array">'
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 ) && !v.kind_of?( Path )
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
- =begin markdown
313
- Serializes many kinds of objects to JSON.
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
- def self.parse input
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