rackful 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ ...