rackful 0.1.2 → 0.1.3

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.
@@ -16,59 +16,54 @@ module Rackful
16
16
  Base class for all serializers.
17
17
 
18
18
  The default serializers defined in this library ({Rackful::XHTML} and {Rackful::JSON})
19
- depend on the availability of method {Rackful::Resource#to_rackful.}
19
+ depend on the availability of method {Rackful::Resource#to_rackful}.
20
20
  @abstract Subclasses must implement method `#each` end define constant
21
21
  `CONTENT_TYPES`
22
- @since 0.1.0
23
22
  =end
24
23
  class Serializer
25
24
 
25
+
26
26
  include Enumerable
27
27
 
28
+
28
29
  attr_reader :resource, :content_type
29
30
 
30
- # @since 0.1.0
31
+
31
32
  def initialize resource, content_type
32
33
  @resource, @content_type = resource, content_type
33
34
  end
34
35
 
36
+
35
37
  =begin markdown
36
38
  Every serializer must implement this method.
37
39
  @abstract
38
- @since 0.1.0
39
40
  @yield [data] the entity body
40
41
  =end
41
42
  def each
42
- raise HTTP500InternalServerError, "Class #{self.class} doesn't implement #each()."
43
+ raise "Class #{self.class} doesn't implement #each()."
43
44
  end
44
45
 
45
- =begin markdown
46
- You don't have to include the `Content-Type` header, as this is done _for_ you.
47
46
 
48
- This method is optional.
47
+ =begin markdown
49
48
  @!method headers()
50
- @return [Hash, nil]
51
- @abstract
52
- @since 0.1.0
53
- =end
49
+ Extra response headers that a serializer likes to return.
54
50
 
51
+ You don't have to include the `Content-Type` header, as this is done _for_ you.
55
52
 
56
- =begin markdown
57
- The content types this serializer can produce.
58
- @!const CONTENT_TYPES
59
- @return [(String)]
60
- @abstract
61
- @since 0.1.0
53
+ This method is optional.
54
+ @return [Hash, nil]
55
+ @abstract
62
56
  =end
63
57
 
58
+
64
59
  end # class Serializer
65
60
 
66
61
 
67
62
  =begin markdown
68
- @since 0.1.0
69
63
  =end
70
64
  class XHTML < Serializer
71
65
 
66
+
72
67
  # The content types served by this serializer.
73
68
  # @see Serializer::CONTENT_TYPES
74
69
  CONTENT_TYPES = [
@@ -79,21 +74,9 @@ class XHTML < Serializer
79
74
  ]
80
75
 
81
76
 
82
- # Turns a relative URI (starting with `/`) into a relative path (starting with `./`)
83
- # @param path [Path]
84
- # @return [String]
85
- # @since 0.1.0
86
- def htmlify path
87
- @rackful_bp ||= Request.current.base_path # caching
88
- length = @rackful_bp.length
89
- if @rackful_bp == path[0, length]
90
- './' + path[length .. -1]
91
- else
92
- path.dup
93
- end
94
- end
95
-
96
-
77
+ =begin
78
+ @yieldparam xhtml [String]
79
+ =end
97
80
  def each &block
98
81
  request = Request.current
99
82
  if /xml/ === self.content_type
@@ -105,15 +88,16 @@ EOS
105
88
  <!DOCTYPE html>
106
89
  <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
107
90
  <head>
91
+ <title>#{ Rack::Utils.escape_html(resource.title) }</title>
108
92
  EOS
109
- unless request.path == request.content_path
93
+ #~ unless request.path == request.content_path
110
94
  yield <<EOS
111
- <base href="#{request.base_path}"/>
95
+ <base href="#{request.base_path.relative request.path}"/>
112
96
  EOS
113
- end
97
+ #~ end
114
98
  unless '/' == request.path
115
99
  yield <<EOS
116
- <link rel="contents" href="#{File::dirname(request.path).to_path.slashify}"/>
100
+ <link rel="contents" href="#{'/' === request.content_path[-1] ? '../' : './' }"/>
117
101
  EOS
118
102
  end
119
103
  yield header + '<div id="rackful_content">'
@@ -121,61 +105,131 @@ EOS
121
105
  yield '</div>' + footer
122
106
  end
123
107
 
108
+
124
109
  # Look at the source code!
125
110
  def header
126
- "<title>#{ Rack::Utils.escape_html(resource.title) }</title></head><body>"
111
+ self.class.class_variable_defined?( :@@header ) && @@header ?
112
+ @@header.call( self ) :
113
+ "</head><body>"
127
114
  end
128
115
 
116
+
117
+ def self.header &block
118
+ @@header = block
119
+ self
120
+ end
121
+
122
+
129
123
  # Look at the source code!
130
124
  def footer
131
- '<div class="rackful_powered">Powered by <a href="http://github.com/pieterb/Rackful">Rackful</a></div></body></html>'
125
+ self.class.class_variable_defined?( :@@footer ) && @@footer ?
126
+ @@footer.call( self ) :
127
+ '<div class="rackful_powered">Powered by <a href="http://github.com/pieterb/Rackful">Rackful</a></div></body></html>'
132
128
  end
133
129
 
134
- # Serialize almost any kind of Ruby object to XHTML.
130
+
131
+ def self.footer &block
132
+ @@footer = block
133
+ self
134
+ end
135
+
136
+
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
135
158
  def each_nested p = self.resource.to_rackful, &block
136
- # p = (args.size > 0) ? args[0] : self.resource.to_rackful
137
- if p.kind_of?( Path )
138
- yield "<a href=\"#{self.htmlify(p)}\">" +
139
- Rack::Utils.escape_html( File::basename(p.unslashify).to_path.unescape ) +
140
- '</a>'
141
- elsif p.kind_of?( Resource ) && ! p.equal?( self.resource )
159
+
160
+ if p.kind_of?( Resource ) && ! p.equal?( self.resource )
142
161
  p.serializer( self.content_type ).each_nested &block
143
- # elsif p.kind_of?( Hash )
144
- # yield '<dl class="rackful_object">'
145
- # p.each_pair do
146
- # |key, value|
147
- # yield '<dt>' + key.to_s.split('_').join(' ').escape_html +
148
- # "</dt><dd class=\"rackful_object_#{key.to_s.escape_html}\"#{self.xsd_type(value)}>"
149
- # self.each_nested value, &block
150
- # yield "</dd>\n"
151
- # end
152
- # yield '</dl>'
153
- elsif p.kind_of?( Enumerable ) and p.respond_to?( :each_pair ) and
154
- p.all? { |r, s| r.kind_of?( Path ) }
155
- yield '<dl class="rackful-resources">'
156
- p.each_pair do
157
- |path, child|
158
- yield '<dt>'
159
- self.each_nested path, &block
160
- yield '</dt><dd>'
161
- self.each_nested child, &block
162
- yield "</dd>\n"
163
- end
164
- yield '</dl>'
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
+
165
216
  elsif p.respond_to?( :each_pair )
166
- yield '<dl class="rackful-object">'
217
+ yield '<br/><dl class="rackful-object">'
167
218
  p.each_pair do
168
- |path, child|
169
- yield '<dt>'
170
- self.each_nested path, &block
171
- yield '</dt><dd>'
172
- self.each_nested child, &block
219
+ |key, value|
220
+ yield "<dt#{self.xsd_type(key)}>"
221
+ self.each_nested key, &block
222
+ yield "</dt><dd#{self.xsd_type(value)}>"
223
+ self.each_nested value, &block
173
224
  yield "</dd>\n"
174
225
  end
175
226
  yield '</dl>'
176
- elsif p.kind_of?( Enumerable ) and ( q = p.first ) and (
177
- q.respond_to?(:keys) && ( keys = q.keys ) &&
178
- p.all? { |r| r.respond_to?(:keys) && r.keys == keys }
227
+
228
+ elsif p.kind_of?( Enumerable ) and
229
+ ( q = p.first ) and
230
+ (
231
+ q.respond_to?( :keys ) && ( keys = q.keys ) &&
232
+ p.all? { |r| r.respond_to?( :keys ) && r.keys == keys }
179
233
  )
180
234
  yield '<table class="rackful-objects"><thead><tr>' +
181
235
  keys.collect {
@@ -196,6 +250,7 @@ EOS
196
250
  yield '</tr>'
197
251
  end
198
252
  yield "</tbody></table>"
253
+
199
254
  elsif p.kind_of?( Enumerable )
200
255
  yield '<ul class="rackful-array">'
201
256
  p.each do
@@ -205,12 +260,16 @@ EOS
205
260
  yield "</li>\n"
206
261
  end
207
262
  yield '</ul>'
263
+
208
264
  elsif p.kind_of?( Time )
209
265
  yield p.utc.xmlschema
266
+
210
267
  elsif p.kind_of?( String ) && p.encoding == Encoding::BINARY
211
268
  yield Base64.encode64(p).chomp
269
+
212
270
  else
213
271
  yield Rack::Utils.escape_html( p.to_s )
272
+
214
273
  end
215
274
  end
216
275
 
@@ -251,12 +310,30 @@ class JSON < Serializer
251
310
 
252
311
 
253
312
  =begin markdown
254
- @yield [json]
255
- @yieldparam json [String]
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]
256
331
  =end
257
332
  def each thing = self.resource.to_rackful, &block
258
333
  if thing.kind_of?( Resource ) && ! thing.equal?( self.resource )
259
334
  thing.serializer( self.content_type ).each &block
335
+ elsif thing.kind_of?( Path )
336
+ yield thing.relative.to_json
260
337
  elsif thing.respond_to? :each_pair
261
338
  first = true
262
339
  thing.each_pair do
@@ -293,6 +370,7 @@ class JSON < Serializer
293
370
  self.recursive_datetime_parser r
294
371
  end
295
372
 
373
+
296
374
  def self.recursive_datetime_parser p
297
375
  if p.kind_of?(String)
298
376
  begin
@@ -316,4 +394,5 @@ class JSON < Serializer
316
394
 
317
395
  end # class HTTPStatus::JSON
318
396
 
397
+
319
398
  end # module Rackful
@@ -7,7 +7,6 @@ module Rackful
7
7
 
8
8
  =begin markdown
9
9
  Rack compliant server class for implementing RESTful web services.
10
- @since 0.0.1
11
10
  =end
12
11
  class Server
13
12
 
@@ -35,14 +34,12 @@ If there's no resource at the given path, but you'd still like to respond to
35
34
  {Resource#empty? empty resource}.
36
35
  @return [#[]]
37
36
  @see #initialize
38
- @since 0.0.1
39
37
  =end
40
38
  attr_reader :resource_factory
41
39
 
42
40
 
43
41
  =begin markdown
44
42
  @param resource_factory [#[]] see Server#resource_factory
45
- @since 0.0.1
46
43
  =end
47
44
  def initialize(resource_factory)
48
45
  super()
@@ -56,19 +53,17 @@ As required by the Rack specification.
56
53
  For thread safety, this method clones `self`, which handles the request in
57
54
  {#call!}. A similar approach is taken by the Sinatra library.
58
55
  @return [Array<(status_code, response_headers, response_body)>]
59
- @since 0.0.1
60
56
  =end
61
57
  def call(p_env)
62
58
  start = Time.now
63
59
  retval = dup.call! p_env
64
- #$stderr.puts( 'Duration: ' + ( Time.now - start ).to_s )
60
+ #$stderr.puts( p_env.inspect )
65
61
  retval
66
62
  end
67
63
 
68
64
 
69
65
  =begin markdown
70
66
  @return [Array<(status_code, response_headers, response_body)>]
71
- @since 0.0.1
72
67
  =end
73
68
  def call!(p_env)
74
69
  request = Request.new( self.resource_factory, p_env )
@@ -76,7 +71,7 @@ For thread safety, this method clones `self`, which handles the request in
76
71
  Thread.current[:rackful_request] = request
77
72
  response = Rack::Response.new
78
73
  begin
79
- raise HTTP404NotFound \
74
+ raise HTTP404NotFound, request.path \
80
75
  unless resource = self.resource_factory[Path.new(request.path)]
81
76
  unless resource.path == request.path
82
77
  response.header['Content-Location'] = resource.path
@@ -113,7 +108,7 @@ For thread safety, this method clones `self`, which handles the request in
113
108
  response.headers.merge! new_resource.default_headers
114
109
  end
115
110
  r = response.finish
116
- $stderr.puts r.inspect
111
+ #~ $stderr.puts r.inspect
117
112
  r
118
113
  end
119
114
 
data/rackful.gemspec CHANGED
@@ -2,16 +2,16 @@ Gem::Specification.new do |s|
2
2
 
3
3
  # Required properties:
4
4
  s.name = 'rackful'
5
- s.version = '0.1.2'
5
+ s.version = '0.1.3'
6
6
  s.summary = "Library for building ReSTful web services with Rack"
7
7
  s.description = <<EOS
8
8
  Rackful provides a minimal interface for developing ReSTful web services with
9
9
  Rack and Ruby. Instead of writing HTTP method handlers, you'll implement
10
10
  resource objects, which expose their state at URLs.
11
11
 
12
- This version is NOT backward compatible with versions 0.0.x.
12
+ This version is not backward compatible with v0.0.x.
13
13
  EOS
14
- s.files = Dir[ '{*.md,example/*,lib/**/*}' ] +
14
+ s.files = Dir[ '{*.md,example/*.ru,lib/**/*.rb}' ] +
15
15
  %w( rackful.gemspec mkdoc.sh )
16
16
 
17
17
  # Optional properties:
metadata CHANGED
@@ -1,88 +1,90 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rackful
3
- version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.1.2
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.3
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Pieter van Beek
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2012-10-11 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2012-10-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
16
15
  name: rack
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '1.4'
19
21
  none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "1.4"
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ none: false
28
+ prerelease: false
24
29
  type: :runtime
25
- version_requirements: *id001
26
- description: |
27
- Rackful provides a minimal interface for developing ReSTful web services with
28
- Rack and Ruby. Instead of writing HTTP method handlers, you'll implement
30
+ description: ! 'Rackful provides a minimal interface for developing ReSTful web services
31
+ with
32
+
33
+ Rack and Ruby. Instead of writing HTTP method handlers, you''ll implement
34
+
29
35
  resource objects, which expose their state at URLs.
30
-
31
- This version is NOT backward compatible with versions 0.0.x.
32
36
 
37
+
38
+ This version is not backward compatible with v0.0.x.
39
+
40
+ '
33
41
  email: rackful@djinnit.com
34
42
  executables: []
35
-
36
43
  extensions: []
37
-
38
44
  extra_rdoc_files: []
39
-
40
- files:
45
+ files:
41
46
  - RACKFUL.md
42
47
  - README.md
43
48
  - LICENSE.md
44
49
  - CHANGES.md
45
- - example/config2.ru
46
50
  - example/config.ru
47
- - lib/rackful_request.rb
48
- - lib/rackful_path.rb
49
- - lib/rackful/method_spoofing.rb
50
- - lib/rackful/header_spoofing.rb
51
- - lib/rackful/relative_location.rb
52
- - lib/rackful_http_status.rb
53
- - lib/rackful_server.rb
54
51
  - lib/rackful.rb
55
- - lib/rackful_resource.rb
56
- - lib/rackful_serializer.rb
52
+ - lib/rackful/resource.rb
53
+ - lib/rackful/path.rb
54
+ - lib/rackful/http_status.rb
55
+ - lib/rackful/serializer.rb
56
+ - lib/rackful/server.rb
57
+ - lib/rackful/request.rb
58
+ - lib/rackful/middleware.rb
59
+ - lib/rackful/middleware/method_spoofing.rb
60
+ - lib/rackful/middleware/header_spoofing.rb
61
+ - lib/rackful/middleware/relative_location.rb
57
62
  - rackful.gemspec
58
63
  - mkdoc.sh
59
64
  homepage: http://pieterb.github.com/Rackful/
60
- licenses:
65
+ licenses:
61
66
  - Apache License 2.0
62
- post_install_message:
67
+ post_install_message:
63
68
  rdoc_options: []
64
-
65
- require_paths:
69
+ require_paths:
66
70
  - lib
67
- required_ruby_version: !ruby/object:Gem::Requirement
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
68
76
  none: false
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- version: "0"
73
- required_rubygems_version: !ruby/object:Gem::Requirement
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
74
82
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- version: "0"
79
83
  requirements: []
80
-
81
- rubyforge_project:
84
+ rubyforge_project:
82
85
  rubygems_version: 1.8.24
83
- signing_key:
86
+ signing_key:
84
87
  specification_version: 3
85
88
  summary: Library for building ReSTful web services with Rack
86
89
  test_files: []
87
-
88
- has_rdoc:
90
+ ...