rackful 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9219f743c670f4a149826fcf581a97e7c08251d6
4
- data.tar.gz: 10ec5c1eb9c59f3d7adda6ed9be9518773311ba9
3
+ metadata.gz: b3ca6f8649b19811edf97934421a559a7737c0e4
4
+ data.tar.gz: 7ca6cbcbd870d257c2e05de94ff612b43802bbf7
5
5
  SHA512:
6
- metadata.gz: f3d112684686175bc973cd240cce7bf899684fe115184ea94b0cd4797d49aa45a1b939983f5af12f9cc3702a03eb541030023319205996bae5b951711e2fd783
7
- data.tar.gz: a44bd266844173251e1c0a4ac35c9b7a3be32a77bfd4e369773cb6b49cecc47867e766e04cc8d0bf3eb0f2cbe28779dda4a7b52823b9104ce9e585ad0fec342b
6
+ metadata.gz: ebe348706302e4f09e1cc9670135f87ab8092b397514fa049164cd0e6a9880d3418c1c1f456d9e8a54aac9f650b297bc0ff76a8ea10275eeafd2d28e4a49315a
7
+ data.tar.gz: dc046d625255efb54558810c9069ee89856963fac75b1672b7b3877dc5ff4647ffdd3937d086d509479b91ccecffc6e67111582021458f88bd02d1e401a39d31
data/example/config.ru CHANGED
@@ -1,24 +1,26 @@
1
1
  # Load core functionality:
2
2
  require 'rackful'
3
3
 
4
+
4
5
  # Load extra middlewares: ({Rackful::MethodOverride}, {Rackful::HeaderSpoofing})
5
6
  require 'rackful/middleware'
6
-
7
7
  require 'digest/md5'
8
8
 
9
+
9
10
  # The class of the object we're going to serve:
10
- class Root < Rackful::Resource
11
- attr_reader :to_rackful
11
+ class MyResource
12
+ include Rackful::Resource
13
+ attr_accessor :to_rackful
14
+
12
15
  def initialize
13
- super( 'http://localhost:9292/hallo_wereld' )
16
+ self.uri = '/hello_world'
14
17
  @to_rackful = {
15
18
  :a => 'Hello',
16
19
  :b => Time.now,
17
- :c => URI('http://www.example.com/some/path') }
18
- end
19
- def do_PUT request, response
20
- @to_rackful = self.parser(request).parse
20
+ :c => URI('http://www.example.com/some/path')
21
+ }
21
22
  end
23
+
22
24
  def get_etag
23
25
  '"' + Digest::MD5.new.update(to_rackful.inspect).to_s + '"'
24
26
  end
@@ -27,12 +29,11 @@ class Root < Rackful::Resource
27
29
  add_parser Rackful::Parser::XHTML
28
30
  add_parser Rackful::Parser::JSON
29
31
  end
30
- $root_resource = nil
31
32
 
32
33
  use Rack::Reloader
33
34
  use Rackful::MethodOverride
34
35
  use Rackful::HeaderSpoofing
35
36
 
36
37
  run Rackful::Server.new { |uri|
37
- $root_resource ||= Root.new
38
+ $root_resource ||= MyResource.new
38
39
  }
data/lib/rackful.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'uri'
1
+ # encoding: utf-8
2
+
3
+ # External requirements:
2
4
  require 'nokogiri'
3
5
  require 'rack'
4
6
  require 'rack/utils'
@@ -6,10 +8,12 @@ require 'base64'
6
8
  require 'time'
7
9
  require 'json'
8
10
 
9
- require 'rackful/uri.rb'
10
- require 'rackful/request.rb'
11
- require 'rackful/serializer.rb'
11
+ # Internal “core” files, in alphabetic order:
12
+ require 'rackful/global.rb'
13
+ require 'rackful/httpstatus.rb'
12
14
  require 'rackful/parser.rb'
15
+ require 'rackful/request.rb'
13
16
  require 'rackful/resource.rb'
14
- require 'rackful/httpstatus.rb'
17
+ require 'rackful/serializer.rb'
15
18
  require 'rackful/server.rb'
19
+ require 'rackful/uri.rb'
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # This file is required by all other files.
3
+
4
+ # Required for parsing:
5
+
6
+ # Required for running:
7
+
8
+ # @todo Documentation
9
+ module Rackful
10
+ module StatusCodes
11
+ end
12
+ end
@@ -1,63 +1,58 @@
1
1
  # encoding: utf-8
2
+
2
3
  # Required for parsing:
4
+ require 'rackful/global.rb'
3
5
  require 'rackful/resource.rb'
4
6
  require 'rackful/serializer.rb'
5
7
 
8
+ # Required for running:
6
9
 
7
10
  module Rackful
8
11
 
9
- =begin markdown
10
- Exception which represents an HTTP Status response.
11
- @abstract
12
- =end
13
- class HTTPStatus < RuntimeError
12
+ # Groups together class {HTTPStatus} and its many subclasses into one namespace.
13
+ #
14
+ # For code brevity and legibility, this module is included in {Rackful},
15
+ # {Resource}, {Serializer}, and {Parser}. So class
16
+ # {HTTP404NotFound Rackful::StatusCodes::HTTP404NotFound}
17
+ # can also be addressed as {HTTP404NotFound} in any of those contexts.
18
+ module StatusCodes
14
19
 
20
+ # Exception which represents an HTTP Status response.
21
+ # @abstract
22
+ class HTTPStatus < RuntimeError
15
23
 
16
- class Resource < ::Rackful::Resource
17
- class XHTML < Serializer::XHTML
24
+ include Resource
25
+
26
+ class XHTML < Serializer::XHTML
18
27
 
19
28
 
20
- def header
21
- retval = super
22
- retval += "<h1>HTTP/1.1 #{Rack::Utils.escape_html(resource.title)}</h1>\n"
23
- unless resource.message.empty?
24
- retval += "<div id=\"rackful-description\">#{resource.message}</div>\n"
25
- end
26
- retval
29
+ def header
30
+ retval = super
31
+ retval += "<h1>HTTP/1.1 #{Rack::Utils.escape_html(resource.title)}</h1>\n"
32
+ unless resource.message.empty?
33
+ retval += "<div id=\"rackful-description\">#{resource.message}</div>\n"
27
34
  end
35
+ retval
36
+ end
28
37
 
29
38
 
30
- def headers; self.resource.headers; end
31
-
32
-
33
- end # class Rackful::HTTPStatus::XHTML
34
-
35
-
36
- add_serializer XHTML, 1.0
37
-
38
- extend Forwardable
39
- def_delegators :@http_status_object, :headers, :to_rackful, :title, :message
40
-
39
+ def headers; self.resource.headers; end
41
40
 
42
- def initialize(uri, http_status_object)
43
- super(uri)
44
- @http_status_object = http_status_object
45
- end
46
41
 
42
+ end # class Rackful::StatusCodes::HTTPStatus::XHTML
47
43
 
48
- end # class Rackful::HTTPStatus::Resource
49
44
 
45
+ add_serializer XHTML, 1.0
46
+ add_serializer Serializer::JSON, 0.5
50
47
 
51
48
  attr_reader :status, :headers, :to_rackful
52
49
 
53
50
 
54
- =begin markdown
55
- @param status [Symbol, Integer] e.g. `404` or `:not_found`
56
- @param message [String] XHTML
57
- @param info [ { Symbol => Object, String => String } ]
58
- * **Objects** indexed by **Symbols** are returned in the response body.
59
- * **Strings** indexed by **Strings** are returned as response headers.
60
- =end
51
+ # @param status [Symbol, Integer] e.g. `404` or `:not_found`
52
+ # @param message [String] XHTML
53
+ # @param info [ { Symbol => Object, String => String } ]
54
+ # * **Objects** indexed by **Symbols** are returned in the response body.
55
+ # * **Strings** indexed by **Strings** are returned as response headers.
61
56
  def initialize status, message = nil, info = {}
62
57
  @status = Rack::Utils.status_code status
63
58
  raise "Wrong status: #{status}" if 0 === @status
@@ -86,24 +81,20 @@ class HTTPStatus < RuntimeError
86
81
  end
87
82
  super message
88
83
  end
89
-
90
- def serializer request
91
- Resource.new( request.url, self ).serializer( request )
92
- end
93
84
 
85
+
86
+ # @api private
94
87
  def title
95
88
  "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
96
89
  end
97
90
 
98
91
 
99
92
 
100
- end # class Rackful::HTTPStatus
93
+ end # class Rackful::StatusCodes::HTTPStatus
101
94
 
102
95
 
103
- =begin markdown
104
- @abstract Base class for HTTP status codes with only a simple text message, or
105
- no message at all.
106
- =end
96
+ # @abstract Base class for HTTP status codes with only a simple text message, or
97
+ # no message at all.
107
98
  class HTTPSimpleStatus < HTTPStatus
108
99
 
109
100
  def initialize message = nil
@@ -117,9 +108,12 @@ end
117
108
 
118
109
  class HTTP201Created < HTTPStatus
119
110
 
111
+ # @param locations [URI::Generic, String, Array<URI::Generic, String>]
120
112
  def initialize locations
121
113
  locations = [ locations ] unless locations.kind_of? Array
122
- locations = locations.collect { |l| URI(l) }
114
+ locations = locations.collect do |location|
115
+ location.kind_of?( URI::Generic ) ? location : URI(location).normalize
116
+ end
123
117
  if locations.size > 1
124
118
  super( 201, 'New resources were created:', :locations => locations )
125
119
  else
@@ -136,9 +130,10 @@ end
136
130
 
137
131
  class HTTP202Accepted < HTTPStatus
138
132
 
133
+ # @param location [URI::Generic, String]
139
134
  def initialize location = nil
140
135
  if location
141
- location = URI(location)
136
+ location = location.kind_of?( URI::Generic ) ? location : URI(location).normalize
142
137
  super(
143
138
  202, "The request body you sent has been accepted for processing.",
144
139
  :"Job status location:" => location, 'Location' => location
@@ -153,8 +148,9 @@ end
153
148
 
154
149
  class HTTP301MovedPermanently < HTTPStatus
155
150
 
151
+ # @param location [URI::Generic, String]
156
152
  def initialize location
157
- location = URI(location)
153
+ location = location.kind_of?( URI::Generic ) ? location : URI(location).normalize
158
154
  super( 301, '', :'New location:' => location, 'Location' => location )
159
155
  end
160
156
 
@@ -163,8 +159,9 @@ end
163
159
 
164
160
  class HTTP303SeeOther < HTTPStatus
165
161
 
162
+ # @param location [URI::Generic, String]
166
163
  def initialize location
167
- location = URI(location)
164
+ location = location.kind_of?( URI::Generic ) ? location : URI(location).normalize
168
165
  super( 303, '', :'See:' => location, 'Location' => location )
169
166
  end
170
167
 
@@ -182,8 +179,9 @@ end
182
179
 
183
180
  class HTTP307TemporaryRedirect < HTTPStatus
184
181
 
182
+ # @param location [URI::Generic, String]
185
183
  def initialize location
186
- location = URI(location)
184
+ location = location.kind_of?( URI::Generic ) ? location : URI(location).normalize
187
185
  super( 301, '', :'Current location:' => location, 'Location' => location )
188
186
  end
189
187
 
@@ -247,4 +245,5 @@ class HTTP501NotImplemented < HTTPSimpleStatus; end
247
245
 
248
246
  class HTTP503ServiceUnavailable < HTTPSimpleStatus; end
249
247
 
248
+ end # module Rackful::StatusCodes
250
249
  end # module Rackful
@@ -1,2 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ # Required for parsing:
4
+
5
+ # Required for running:
1
6
  require 'rackful/middleware/headerspoofing.rb'
2
7
  require 'rackful/middleware/methodoverride.rb'
@@ -1,9 +1,14 @@
1
1
  # encoding: utf-8
2
+
2
3
  # Required for parsing:
4
+
5
+ # This requirement is only made explicit in source files that aren’t
6
+ # included in the rackful “core”.
3
7
  require 'rackful'
4
8
 
5
9
  # Required for running:
6
10
 
11
+ module Rackful
7
12
 
8
13
  # Rack middleware that provides header spoofing.
9
14
  #
@@ -18,7 +23,7 @@ require 'rackful'
18
23
  # @example Using this middleware
19
24
  # require 'rackful/middleware/header_spoofing'
20
25
  # use Rackful::HeaderSpoofing
21
- class Rackful::HeaderSpoofing
26
+ class HeaderSpoofing
22
27
 
23
28
  def initialize app
24
29
  @app = app
@@ -47,3 +52,4 @@ def call env
47
52
  end
48
53
 
49
54
  end # Rackful::HeaderSpoofing
55
+ end # module Rackful
@@ -1,9 +1,14 @@
1
1
  # encoding: utf-8
2
+
2
3
  # Required for parsing:
4
+
5
+ # This requirement is only made explicit in source files that aren’t
6
+ # included in the rackful “core”.
3
7
  require 'rackful'
4
8
 
5
9
  # Required for running:
6
- require 'set'
10
+
11
+ module Rackful
7
12
 
8
13
 
9
14
  # Middleware that provides method spoofing, like {Rack::MethodOverride}.
@@ -47,7 +52,7 @@ require 'set'
47
52
  # @example Using this middleware
48
53
  # require 'rackful/middleware/method_override'
49
54
  # use Rackful::MethodOverride
50
- class Rackful::MethodOverride
55
+ class MethodOverride
51
56
 
52
57
  METHOD_OVERRIDE_PARAM_KEY = '_method'.freeze
53
58
 
@@ -134,3 +139,4 @@ class Rackful::MethodOverride
134
139
  end
135
140
 
136
141
  end # Rackful::MethodOverride
142
+ end # module Rackful
@@ -1,77 +1,80 @@
1
1
  # encoding: utf-8
2
2
 
3
+ # Required for parsing:
4
+ require 'rackful/global.rb'
3
5
 
4
- module Rackful
6
+ # Required for running:
5
7
 
8
+ module Rackful
6
9
 
7
- =begin markdown
8
- Base class for all parsers.
9
- @abstract Subclasses must implement method `#parse`, and define constant
10
- {MEDIA_TYPES} as an array of media types this parser accepts.
11
- @example Subclassing this class
12
- class MyTextParser < Rackful::Parser
13
- MEDIA_TYPES = [ 'text/plain' ]
14
- def parse
15
- # YOUR CODE HERE...
16
- end
17
- end
18
- =end
10
+ # Base class for all concrete parsers defined in this library. You’ll probably
11
+ # want to use this class as the base class for your own parsers, too.
12
+ #
13
+ # This class mixes in module `StatusCodes` for convenience, as explained in the
14
+ # {StatusCodes StatusCodes documentation}.
15
+ # @abstract Subclasses must implement classmethod `::parse` with signature
16
+ # `(void) parse( Request, Response, Resource )`
17
+ # explained in {Resource::ClassMethods::add_parser}
18
+ # @example Subclassing this class
19
+ # class MyTextParser < Rackful::Parser
20
+ # parses 'text/*', 'application/xhtml+xml'
21
+ # def self.parse request, response, resource
22
+ # # YOUR CODE HERE...
23
+ # end
24
+ # end
19
25
  class Parser
26
+ include StatusCodes
27
+ class << self
28
+ # @api private
29
+ def media_types
30
+ @media_types ||= begin
31
+ retval = rackful_parser_media_types
32
+ if superclass.respond_to?(:media_types)
33
+ retval += superclass.media_types
34
+ end
35
+ retval.uniq
36
+ end
37
+ end
38
+
39
+ # @overload parses( media_type, ... )
40
+ # @param media_type [String]
41
+ def parses *args
42
+ rackful_parser_media_types.unshift(
43
+ *( args.map { |mt| mt.to_s }.reverse )
44
+ )
45
+ rackful_parser_media_types.uniq!
46
+ self
47
+ end
48
+
49
+ private
50
+ def rackful_parser_media_types
51
+ @rackful_parser_media_types ||= []
52
+ end
53
+ end # class << self # Rackful::Parser
20
54
 
55
+ end # class Rackful::Parser
21
56
 
22
- =begin markdown
23
- An array of media type strings.
24
- @!parse MEDIA_TYPES = [ 'example/type1', 'example/type2' ]
25
- =end
26
-
27
-
28
- # @return [Request]
29
- attr_reader :request
30
- # @return [Resource]
31
- attr_reader :resource
32
-
33
-
34
- =begin markdown
35
- @param request [Request]
36
- @param resource [Resource]
37
- =end
38
- def initialize request, resource
39
- @request, @resource = request, resource
40
- end
41
-
42
-
43
- end # class Parser
44
-
45
-
46
- =begin markdown
47
- Parent class of all XML-parsing parsers.
48
- @abstract
49
- @since 0.2.0
50
- =end
57
+ # Parent class of all XML-parsing parsers.
58
+ # @abstract
59
+ # @since 0.2.0
51
60
  class Parser::DOM < Parser
52
61
 
62
+ # The media types parsed by this parser.
63
+ # @see Parser
64
+ parses 'text/xml', 'application/xml'
53
65
 
54
- =begin markdown
55
- The media types parsed by this parser.
56
- @see Parser
57
- =end
58
- MEDIA_TYPES = [
59
- 'text/xml',
60
- 'application/xml'
61
- ]
62
66
 
67
+ def self.parse request, response, resource
68
+ self.new(request).parse( response, resource )
69
+ end
63
70
 
64
- =begin markdown
65
- @return [Nokogiri::XML::Document]
66
- =end
71
+ # @return [Nokogiri::XML::Document]
67
72
  attr_reader :document
73
+ attr_reader :request
68
74
 
69
-
70
- =begin markdown
71
- @raise [HTTP400BadRequest] if the document is malformed.
72
- =end
73
- def initialize request, resource
74
- super request, resource
75
+ # @raise [HTTP400BadRequest] if the document is malformed.
76
+ def initialize request
77
+ @request = request
75
78
  # TODO Is ISO-8859-1 indeed the default encoding for XML documents? If so,
76
79
  # that fact must be documented and referenced.
77
80
  encoding = self.request.media_type_params['charset'] || 'ISO-8859-1'
@@ -88,31 +91,21 @@ The media types parsed by this parser.
88
91
  end
89
92
  raise( HTTP400BadRequest, $!.to_s ) unless @document.root
90
93
  end
94
+
95
+ end # class Rackful::Parser::DOM
91
96
 
92
97
 
93
- end # class Parser::DOM
94
98
 
95
99
 
96
- =begin markdown
97
- Parses XHTML as generated by {Serializer::XHTML}.
98
- =end
100
+ # Parses XHTML as generated by {Serializer::XHTML}.
99
101
  class Parser::XHTML < Parser::DOM
100
102
 
101
103
 
102
- =begin markdown
103
- The media types parsed by this parser.
104
- @see Parser
105
- =end
106
- MEDIA_TYPES = Parser::DOM::MEDIA_TYPES + [
107
- 'application/xhtml+xml',
108
- 'text/html'
109
- ]
104
+ parses 'application/xhtml+xml', 'text/html'
110
105
 
111
106
 
112
- =begin markdown
113
- @see Parser#parse
114
- =end
115
- def parse
107
+ # @see Parser#parse
108
+ def parse response, resource
116
109
  # Try to find the actual content:
117
110
  content = self.document.root.xpath(
118
111
  '//html:div[@id="rackful-content"]',
@@ -139,13 +132,11 @@ The media types parsed by this parser.
139
132
  end
140
133
  end
141
134
  # Parse the f*cking thing:
142
- self.parse_recursive content.first
135
+ resource.to_rackful = self.parse_recursive content.first
143
136
  end
144
137
 
145
138
 
146
- =begin markdown
147
- @api private
148
- =end
139
+ # @api private
149
140
  def parse_recursive node
150
141
 
151
142
  # A URI:
@@ -181,9 +172,7 @@ The media types parsed by this parser.
181
172
  end
182
173
 
183
174
 
184
- =begin markdown
185
- @api private
186
- =end
175
+ # @api private
187
176
  def parse_simple_type node, typename
188
177
  case typename
189
178
  when 'boolean'
@@ -208,9 +197,7 @@ The media types parsed by this parser.
208
197
  end
209
198
 
210
199
 
211
- =begin markdown
212
- @api private
213
- =end
200
+ # @api private
214
201
  def parse_object node
215
202
  current_property = nil
216
203
  r = {}
@@ -234,9 +221,7 @@ The media types parsed by this parser.
234
221
  end
235
222
 
236
223
 
237
- =begin markdown
238
- @api private
239
- =end
224
+ # @api private
240
225
  def parse_object_list node
241
226
  properties = node.xpath(
242
227
  'html:thead/html:tr/html:th',
@@ -273,22 +258,18 @@ end # class Parser::XHTML
273
258
  class Parser::JSON < Parser
274
259
 
275
260
 
276
- MEDIA_TYPES = [
277
- 'application/json',
278
- 'application/x-json'
279
- ]
261
+ parses 'application/json', 'application/x-json'
280
262
 
281
-
282
- def parse
263
+ def self.parse request, response, resource
283
264
  r = ::JSON.parse(
284
- self.request.env['rack.input'].read,
265
+ request.env['rack.input'].read,
285
266
  :symbolize_names => true
286
267
  )
287
- self.recursive_datetime_parser r
268
+ resource.to_rackful = self.recursive_datetime_parser r
288
269
  end
289
270
 
290
271
 
291
- def recursive_datetime_parser p
272
+ def self.recursive_datetime_parser p
292
273
  if p.kind_of?(String)
293
274
  begin
294
275
  return Time.xmlschema(p)
@@ -297,12 +278,12 @@ class Parser::JSON < Parser
297
278
  elsif p.kind_of?(Hash)
298
279
  p.keys.each do
299
280
  |key|
300
- p[key] = self.recursive_datetime_parser( p[key] )
281
+ p[key] = recursive_datetime_parser( p[key] )
301
282
  end
302
283
  elsif p.kind_of?(Array)
303
284
  (0 ... p.size).each do
304
285
  |i|
305
- p[i] = self.recursive_datetime_parser( p[i] )
286
+ p[i] = recursive_datetime_parser( p[i] )
306
287
  end
307
288
  end
308
289
  p
@@ -310,6 +291,4 @@ class Parser::JSON < Parser
310
291
 
311
292
 
312
293
  end # class Parser::JSON
313
-
314
-
315
294
  end # module Rackful