rackful 0.2.0 → 0.2.1

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