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.
@@ -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