rackful 0.0.2 → 0.1.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.
- data/README.md +14 -2
- data/example/config.ru +19 -13
- data/example/config2.ru +41 -0
- data/lib/rackful/header_spoofing.rb +39 -32
- data/lib/rackful/method_spoofing.rb +56 -58
- data/lib/rackful/relative_location.rb +35 -21
- data/lib/rackful.rb +6 -934
- data/lib/rackful_http_status.rb +288 -0
- data/lib/rackful_path.rb +112 -0
- data/lib/rackful_request.rb +268 -0
- data/lib/rackful_resource.rb +454 -0
- data/lib/rackful_serializer.rb +318 -0
- data/lib/rackful_server.rb +124 -0
- data/rackful.gemspec +3 -1
- metadata +49 -52
@@ -0,0 +1,318 @@
|
|
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'
|
10
|
+
|
11
|
+
|
12
|
+
module Rackful
|
13
|
+
|
14
|
+
|
15
|
+
=begin markdown
|
16
|
+
Base class for all serializers.
|
17
|
+
|
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
|
+
@since 0.1.0
|
23
|
+
=end
|
24
|
+
class Serializer
|
25
|
+
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
attr_reader :resource, :content_type
|
29
|
+
|
30
|
+
# @since 0.1.0
|
31
|
+
def initialize resource, content_type
|
32
|
+
@resource, @content_type = resource, content_type
|
33
|
+
end
|
34
|
+
|
35
|
+
=begin markdown
|
36
|
+
Every serializer must implement this method.
|
37
|
+
@abstract
|
38
|
+
@since 0.1.0
|
39
|
+
=end
|
40
|
+
def each
|
41
|
+
raise HTTP500InternalServerError, "Class #{self.class} doesn't implement #each()."
|
42
|
+
end
|
43
|
+
|
44
|
+
=begin markdown
|
45
|
+
You don't have to include the `Content-Type` header, as this is done _for_ you.
|
46
|
+
|
47
|
+
This method is optional.
|
48
|
+
@!method headers()
|
49
|
+
@return [Hash, nil]
|
50
|
+
@abstract
|
51
|
+
@since 0.1.0
|
52
|
+
=end
|
53
|
+
|
54
|
+
|
55
|
+
=begin markdown
|
56
|
+
The content types this serializer can produce.
|
57
|
+
@!const CONTENT_TYPES
|
58
|
+
@return [(String)]
|
59
|
+
@abstract
|
60
|
+
@since 0.1.0
|
61
|
+
=end
|
62
|
+
|
63
|
+
end # class Serializer
|
64
|
+
|
65
|
+
|
66
|
+
=begin markdown
|
67
|
+
@since 0.1.0
|
68
|
+
=end
|
69
|
+
class XHTML < Serializer
|
70
|
+
|
71
|
+
# The content types served by this serializer.
|
72
|
+
# @see Serializer::CONTENT_TYPES
|
73
|
+
CONTENT_TYPES = [
|
74
|
+
'application/xhtml+xml; charset=UTF-8',
|
75
|
+
'text/html; charset=UTF-8',
|
76
|
+
'text/xml; charset=UTF-8',
|
77
|
+
'application/xml; charset=UTF-8'
|
78
|
+
]
|
79
|
+
|
80
|
+
|
81
|
+
# Turns a relative URI (starting with `/`) into a relative path (starting with `./`)
|
82
|
+
# @param path [Path]
|
83
|
+
# @return [String]
|
84
|
+
# @since 0.1.0
|
85
|
+
def htmlify path
|
86
|
+
@rackful_bp ||= Request.current.base_path # caching
|
87
|
+
length = @rackful_bp.length
|
88
|
+
if @rackful_bp == path[0, length]
|
89
|
+
'./' + path[length .. -1]
|
90
|
+
else
|
91
|
+
path.dup
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def each &block
|
97
|
+
request = Request.current
|
98
|
+
if /xml/ === self.content_type
|
99
|
+
yield <<EOS
|
100
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
101
|
+
EOS
|
102
|
+
end
|
103
|
+
yield <<EOS
|
104
|
+
<!DOCTYPE html>
|
105
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
106
|
+
<head>
|
107
|
+
EOS
|
108
|
+
unless request.path == request.content_path
|
109
|
+
yield <<EOS
|
110
|
+
<base href="#{request.base_path}"/>
|
111
|
+
EOS
|
112
|
+
end
|
113
|
+
unless '/' == request.path
|
114
|
+
yield <<EOS
|
115
|
+
<link rel="contents" href="#{File::dirname(request.path).to_path.slashify}"/>
|
116
|
+
EOS
|
117
|
+
end
|
118
|
+
yield header + '<div id="rackful_content">'
|
119
|
+
each_nested &block
|
120
|
+
yield '</div>' + footer
|
121
|
+
end
|
122
|
+
|
123
|
+
# Look at the source code!
|
124
|
+
def header
|
125
|
+
"<title>#{ Rack::Utils.escape_html(resource.title) }</title></head><body>"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Look at the source code!
|
129
|
+
def footer
|
130
|
+
'<div class="rackful_powered">Powered by <a href="http://github.com/pieterb/Rackful">Rackful</a></div></body></html>'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Serialize almost any kind of Ruby object to XHTML.
|
134
|
+
def each_nested p = self.resource.to_rackful, &block
|
135
|
+
# p = (args.size > 0) ? args[0] : self.resource.to_rackful
|
136
|
+
if p.kind_of?( Path )
|
137
|
+
yield "<a href=\"#{self.htmlify(p)}\">" +
|
138
|
+
Rack::Utils.escape_html( File::basename(p.unslashify).to_path.unescape ) +
|
139
|
+
'</a>'
|
140
|
+
elsif p.kind_of?( Resource ) && ! p.equal?( self.resource )
|
141
|
+
p.serializer( self.content_type ).each_nested &block
|
142
|
+
# elsif p.kind_of?( Hash )
|
143
|
+
# yield '<dl class="rackful_object">'
|
144
|
+
# p.each_pair do
|
145
|
+
# |key, value|
|
146
|
+
# yield '<dt>' + key.to_s.split('_').join(' ').escape_html +
|
147
|
+
# "</dt><dd class=\"rackful_object_#{key.to_s.escape_html}\"#{self.xsd_type(value)}>"
|
148
|
+
# self.each_nested value, &block
|
149
|
+
# yield "</dd>\n"
|
150
|
+
# end
|
151
|
+
# yield '</dl>'
|
152
|
+
elsif p.kind_of?( Enumerable ) and p.respond_to?( :each_pair ) and
|
153
|
+
p.all? { |r, s| r.kind_of?( Path ) }
|
154
|
+
yield '<dl class="rackful-resources">'
|
155
|
+
p.each_pair do
|
156
|
+
|path, child|
|
157
|
+
yield '<dt>'
|
158
|
+
self.each_nested path, &block
|
159
|
+
yield '</dt><dd>'
|
160
|
+
self.each_nested child, &block
|
161
|
+
yield "</dd>\n"
|
162
|
+
end
|
163
|
+
yield '</dl>'
|
164
|
+
elsif p.respond_to?( :each_pair )
|
165
|
+
yield '<dl class="rackful-object">'
|
166
|
+
p.each_pair do
|
167
|
+
|path, child|
|
168
|
+
yield '<dt>'
|
169
|
+
self.each_nested path, &block
|
170
|
+
yield '</dt><dd>'
|
171
|
+
self.each_nested child, &block
|
172
|
+
yield "</dd>\n"
|
173
|
+
end
|
174
|
+
yield '</dl>'
|
175
|
+
elsif p.kind_of?( Enumerable ) and ( q = p.first ) and (
|
176
|
+
q.respond_to?(:keys) && ( keys = q.keys ) &&
|
177
|
+
p.all? { |r| r.respond_to?(:keys) && r.keys == keys }
|
178
|
+
)
|
179
|
+
yield '<table class="rackful-objects"><thead><tr>' +
|
180
|
+
keys.collect {
|
181
|
+
|column|
|
182
|
+
'<th>' +
|
183
|
+
Rack::Utils.escape_html( column.to_s.split('_').join(' ') ) +
|
184
|
+
"</th>\n"
|
185
|
+
}.join + '</tr></thead><tbody>'
|
186
|
+
p.each do
|
187
|
+
|h|
|
188
|
+
yield '<tr>'
|
189
|
+
h.each_pair do
|
190
|
+
|key, value|
|
191
|
+
yield "<td class=\"rackful-objects-#{Rack::Utils.escape_html( key.to_s )}\"#{self.xsd_type(value)}>"
|
192
|
+
self.each_nested value, &block
|
193
|
+
yield "</td>\n"
|
194
|
+
end
|
195
|
+
yield '</tr>'
|
196
|
+
end
|
197
|
+
yield "</tbody></table>"
|
198
|
+
elsif p.kind_of?( Enumerable )
|
199
|
+
yield '<ul class="rackful-array">'
|
200
|
+
p.each do
|
201
|
+
|value|
|
202
|
+
yield "<li#{self.xsd_type(value)}>"
|
203
|
+
self.each_nested value, &block
|
204
|
+
yield "</li>\n"
|
205
|
+
end
|
206
|
+
yield '</ul>'
|
207
|
+
elsif p.kind_of?( Time )
|
208
|
+
yield p.utc.xmlschema
|
209
|
+
elsif p.kind_of?( String ) && p.encoding == Encoding::BINARY
|
210
|
+
yield Base64.encode64(p).chomp
|
211
|
+
else
|
212
|
+
yield Rack::Utils.escape_html( p.to_s )
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# @private
|
218
|
+
def xsd_type v
|
219
|
+
if v.respond_to? :to_rackful
|
220
|
+
v = v.to_rackful
|
221
|
+
end
|
222
|
+
if [nil, true, false].include? v
|
223
|
+
' xs:type="xs:boolean" xs:nil="true"'
|
224
|
+
elsif v.kind_of? Integer
|
225
|
+
' xs:type="xs:integer"'
|
226
|
+
elsif v.kind_of? Numeric
|
227
|
+
' xs:type="xs:decimal"'
|
228
|
+
elsif v.kind_of? Time
|
229
|
+
' xs:type="xs:dateTime"'
|
230
|
+
elsif v.kind_of?( String ) && v.encoding == Encoding::BINARY
|
231
|
+
' xs:type="xs:base64Binary"'
|
232
|
+
elsif v.kind_of?( String ) && !v.kind_of?( Path )
|
233
|
+
' xs:type="xs:string"'
|
234
|
+
else
|
235
|
+
''
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
end # class XHTML
|
241
|
+
|
242
|
+
|
243
|
+
class JSON < Serializer
|
244
|
+
|
245
|
+
|
246
|
+
CONTENT_TYPES = [
|
247
|
+
'application/json',
|
248
|
+
'application/x-json'
|
249
|
+
]
|
250
|
+
|
251
|
+
|
252
|
+
=begin markdown
|
253
|
+
@yield [json]
|
254
|
+
@yieldparam json [String]
|
255
|
+
=end
|
256
|
+
def each thing = self.resource.to_rackful, &block
|
257
|
+
if thing.kind_of?( Resource ) && ! thing.equal?( self.resource )
|
258
|
+
thing.serializer( self.content_type ).each &block
|
259
|
+
elsif thing.respond_to? :each_pair
|
260
|
+
first = true
|
261
|
+
thing.each_pair do
|
262
|
+
|k, v|
|
263
|
+
yield( ( first ? "{\n" : ",\n" ) + k.to_s.to_json + ":" )
|
264
|
+
first = false
|
265
|
+
self.each v, &block
|
266
|
+
end
|
267
|
+
yield( first ? "{}" : "\n}" )
|
268
|
+
elsif thing.respond_to? :each
|
269
|
+
first = true
|
270
|
+
thing.each do
|
271
|
+
|v|
|
272
|
+
yield( first ? "[\n" : ",\n" )
|
273
|
+
first = false
|
274
|
+
self.each v, &block
|
275
|
+
end
|
276
|
+
yield( first ? "[]" : "\n]" )
|
277
|
+
elsif thing.kind_of?( String ) && thing.encoding == Encoding::BINARY
|
278
|
+
yield Base64.encode64(thing).chomp.to_json
|
279
|
+
elsif thing.kind_of?( Time )
|
280
|
+
yield thing.utc.xmlschema.to_json
|
281
|
+
else
|
282
|
+
yield thing.to_json
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
def self.parse input
|
288
|
+
r = ::JSON.parse(
|
289
|
+
input.read,
|
290
|
+
:symbolize_names => true
|
291
|
+
)
|
292
|
+
self.recursive_datetime_parser r
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.recursive_datetime_parser p
|
296
|
+
if p.kind_of?(String)
|
297
|
+
begin
|
298
|
+
return Time.xmlschema(p)
|
299
|
+
rescue
|
300
|
+
end
|
301
|
+
elsif p.kind_of?(Hash)
|
302
|
+
p.keys.each do
|
303
|
+
|key|
|
304
|
+
p[key] = self.recursive_datetime_parser( p[key] )
|
305
|
+
end
|
306
|
+
elsif p.kind_of?(Array)
|
307
|
+
(0 ... p.size).each do
|
308
|
+
|i|
|
309
|
+
p[i] = self.recursive_datetime_parser( p[i] )
|
310
|
+
end
|
311
|
+
end
|
312
|
+
p
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
end # class HTTPStatus::JSON
|
317
|
+
|
318
|
+
end # module Rackful
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Required for parsing:
|
2
|
+
#require 'forwardable' # Used to be for ResourceFactoryWrapper.
|
3
|
+
|
4
|
+
# Required for running:
|
5
|
+
|
6
|
+
module Rackful
|
7
|
+
|
8
|
+
=begin markdown
|
9
|
+
Rack compliant server class for implementing RESTful web services.
|
10
|
+
@since 0.0.1
|
11
|
+
=end
|
12
|
+
class Server
|
13
|
+
|
14
|
+
|
15
|
+
=begin markdown
|
16
|
+
An object responding thread safely to method `#[]`.
|
17
|
+
|
18
|
+
A {Server} has no knowledge, and makes no presumptions, about your URI namespace.
|
19
|
+
It requires a _Resource Factory_ which produces {Resource Resources} given
|
20
|
+
a certain absolute path.
|
21
|
+
|
22
|
+
The Resource Factory you provide need only implement one method, with signature
|
23
|
+
`Resource #[]( String path )`.
|
24
|
+
This method will be called with a URI-encoded path string, and must return a
|
25
|
+
{Resource}, or `nil` if there's no resource at the given path.
|
26
|
+
|
27
|
+
For example, if a Rackful client
|
28
|
+
tries to access a resource with URI {http://example.com/your/resource http://example.com/some/resource},
|
29
|
+
then your Resource Factory can expect to be called like this:
|
30
|
+
|
31
|
+
resource = resource_factory[ '/your/resource' ]
|
32
|
+
|
33
|
+
If there's no resource at the given path, but you'd still like to respond to
|
34
|
+
`POST` or `PUT` requests to this path, you must return an
|
35
|
+
{Resource#empty? empty resource}.
|
36
|
+
@return [#[]]
|
37
|
+
@see #initialize
|
38
|
+
@since 0.0.1
|
39
|
+
=end
|
40
|
+
attr_reader :resource_factory
|
41
|
+
|
42
|
+
|
43
|
+
=begin markdown
|
44
|
+
{include:Server#resource_factory}
|
45
|
+
@since 0.0.1
|
46
|
+
=end
|
47
|
+
def initialize(resource_factory)
|
48
|
+
super()
|
49
|
+
@resource_factory = resource_factory
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
=begin markdown
|
54
|
+
As required by the Rack specification.
|
55
|
+
|
56
|
+
For thread safety, this method clones `self`, which handles the request in
|
57
|
+
{#call!}. A similar approach is taken by the Sinatra library.
|
58
|
+
@return [Array<(status_code, response_headers, response_body)>]
|
59
|
+
@since 0.0.1
|
60
|
+
=end
|
61
|
+
def call(p_env)
|
62
|
+
start = Time.now
|
63
|
+
retval = dup.call! p_env
|
64
|
+
#$stderr.puts( 'Duration: ' + ( Time.now - start ).to_s )
|
65
|
+
retval
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
=begin markdown
|
70
|
+
@return [Array<(status_code, response_headers, response_body)>]
|
71
|
+
@since 0.0.1
|
72
|
+
=end
|
73
|
+
def call!(p_env)
|
74
|
+
request = Request.new( self.resource_factory, p_env )
|
75
|
+
# See also Request::current():
|
76
|
+
Thread.current[:rackful_request] = request
|
77
|
+
response = Rack::Response.new
|
78
|
+
begin
|
79
|
+
raise HTTP404NotFound \
|
80
|
+
unless resource = self.resource_factory[Path.new(request.path)]
|
81
|
+
unless resource.path == request.path
|
82
|
+
response.header['Content-Location'] = resource.path
|
83
|
+
request.content_path = resource.path
|
84
|
+
end
|
85
|
+
request.assert_if_headers resource
|
86
|
+
if %w{HEAD GET OPTIONS PUT DELETE}.include?( request.request_method )
|
87
|
+
resource.__send__( :"http_#{request.request_method}", request, response )
|
88
|
+
else
|
89
|
+
resource.http_method request, response
|
90
|
+
end
|
91
|
+
rescue HTTPStatus => e
|
92
|
+
# Already handled by HTTPStatus#initialize:
|
93
|
+
#raise if $DEBUG && 500 <= e.status
|
94
|
+
bct = e.class.best_content_type request.accept, false
|
95
|
+
serializer = e.serializer(bct)
|
96
|
+
response = Rack::Response.new serializer, e.status, e.headers
|
97
|
+
ensure
|
98
|
+
# The next line fixes a small peculiarity in RFC2616: the response body of
|
99
|
+
# a `HEAD` request _must_ be empty, even for responses outside 2xx.
|
100
|
+
if request.head?
|
101
|
+
response.body = []
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if 201 == response.status &&
|
105
|
+
( location = response['Location'] ) &&
|
106
|
+
( new_resource = request.resource_factory[location] ) &&
|
107
|
+
! new_resource.empty? \
|
108
|
+
or ( (200...300) === response.status ||
|
109
|
+
304 == response.status ) &&
|
110
|
+
! response['Location'] &&
|
111
|
+
( new_resource = request.resource_factory[request.path] ) &&
|
112
|
+
! new_resource.empty?
|
113
|
+
response.headers.merge! new_resource.default_headers
|
114
|
+
end
|
115
|
+
r = response.finish
|
116
|
+
$stderr.puts r.inspect
|
117
|
+
r
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end # class Server
|
122
|
+
|
123
|
+
|
124
|
+
end # module Rackful
|
data/rackful.gemspec
CHANGED
@@ -2,12 +2,14 @@ Gem::Specification.new do |s|
|
|
2
2
|
|
3
3
|
# Required properties:
|
4
4
|
s.name = 'rackful'
|
5
|
-
s.version = '0.0
|
5
|
+
s.version = '0.1.0'
|
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
|
+
|
12
|
+
This version is NOT backward compatible with versions 0.0.x.
|
11
13
|
EOS
|
12
14
|
s.files = Dir[ '{example/*,lib/**/*}' ] +
|
13
15
|
%w( rackful.gemspec README.md LICENSE.md mkdoc.sh )
|
metadata
CHANGED
@@ -1,86 +1,83 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: rackful
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
version: 0.0.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Pieter van Beek
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-08-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: rack
|
22
|
-
|
23
|
-
|
24
|
-
requirements:
|
25
|
-
- -
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
|
28
|
-
- 1
|
29
|
-
- 4
|
30
|
-
version: "1.4"
|
16
|
+
requirement: &70130076580680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.4'
|
31
22
|
type: :runtime
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70130076580680
|
25
|
+
description: ! 'Rackful provides a minimal interface for developing ReSTful web services
|
26
|
+
with
|
27
|
+
|
28
|
+
Rack and Ruby. Instead of writing HTTP method handlers, you''ll implement
|
29
|
+
|
36
30
|
resource objects, which expose their state at URLs.
|
37
31
|
|
32
|
+
|
33
|
+
This version is NOT backward compatible with versions 0.0.x.
|
34
|
+
|
35
|
+
'
|
38
36
|
email: rackful@djinnit.com
|
39
37
|
executables: []
|
40
|
-
|
41
38
|
extensions: []
|
42
|
-
|
43
39
|
extra_rdoc_files: []
|
44
|
-
|
45
|
-
files:
|
40
|
+
files:
|
46
41
|
- example/config.ru
|
42
|
+
- example/config2.ru
|
47
43
|
- lib/rackful/header_spoofing.rb
|
48
44
|
- lib/rackful/method_spoofing.rb
|
49
45
|
- lib/rackful/relative_location.rb
|
50
46
|
- lib/rackful.rb
|
47
|
+
- lib/rackful_http_status.rb
|
48
|
+
- lib/rackful_path.rb
|
49
|
+
- lib/rackful_request.rb
|
50
|
+
- lib/rackful_resource.rb
|
51
|
+
- lib/rackful_serializer.rb
|
52
|
+
- lib/rackful_server.rb
|
51
53
|
- rackful.gemspec
|
52
54
|
- README.md
|
53
55
|
- LICENSE.md
|
54
56
|
- mkdoc.sh
|
55
|
-
has_rdoc: true
|
56
57
|
homepage: http://pieterb.github.com/Rackful/
|
57
|
-
licenses:
|
58
|
+
licenses:
|
58
59
|
- Apache License 2.0
|
59
60
|
post_install_message:
|
60
61
|
rdoc_options: []
|
61
|
-
|
62
|
-
require_paths:
|
62
|
+
require_paths:
|
63
63
|
- lib
|
64
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
requirements:
|
73
|
-
- -
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
|
76
|
-
- 0
|
77
|
-
version: "0"
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
78
76
|
requirements: []
|
79
|
-
|
80
77
|
rubyforge_project:
|
81
|
-
rubygems_version: 1.
|
78
|
+
rubygems_version: 1.8.10
|
82
79
|
signing_key:
|
83
80
|
specification_version: 3
|
84
81
|
summary: Library for building ReSTful web services with Rack
|
85
82
|
test_files: []
|
86
|
-
|
83
|
+
has_rdoc:
|