resourceful 0.6.1 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,19 @@
1
+ Version 0.7.0
2
+ =============
3
+ * Multiple values in a single header field are treated the same as repeated header fields. (Peter Williams)
4
+
5
+ Compatibility issues
6
+ --------------------
7
+
8
+ * The semantics of the Resourceful::Header API have changed slightly.
9
+ Previously multiple values in a single header field
10
+ (e.g. `Accept: application/xml, application/json`) where treated
11
+ differently from multiple values occurring in multiple fields (e.g
12
+ `Accept: application/xml\n\rAccept: application/json`). This lead
13
+ to some unfortunate complexity when interacting with multi-valued
14
+ fields.
15
+
16
+
1
17
  Version 0.6.0
2
18
  =============
3
19
 
data/Manifest CHANGED
@@ -1,39 +1,40 @@
1
- lib/resourceful.rb
2
- lib/resourceful/net_http_adapter.rb
3
- lib/resourceful/options_interpretation.rb
4
- lib/resourceful/stubbed_resource_proxy.rb
5
- lib/resourceful/urlencoded_form_data.rb
6
- lib/resourceful/header.rb
7
- lib/resourceful/memcache_cache_manager.rb
8
- lib/resourceful/response.rb
9
- lib/resourceful/util.rb
1
+ History.txt
10
2
  lib/resourceful/abstract_form_data.rb
3
+ lib/resourceful/authentication_manager.rb
11
4
  lib/resourceful/cache_manager.rb
12
- lib/resourceful/request.rb
13
- lib/resourceful/resource.rb
14
5
  lib/resourceful/exceptions.rb
15
- lib/resourceful/multipart_form_data.rb
6
+ lib/resourceful/header.rb
16
7
  lib/resourceful/http_accessor.rb
17
- lib/resourceful/authentication_manager.rb
18
- History.txt
19
- resourceful.gemspec
20
- README.markdown
8
+ lib/resourceful/memcache_cache_manager.rb
9
+ lib/resourceful/multipart_form_data.rb
10
+ lib/resourceful/net_http_adapter.rb
11
+ lib/resourceful/request.rb
12
+ lib/resourceful/resource.rb
13
+ lib/resourceful/response.rb
14
+ lib/resourceful/stubbed_resource_proxy.rb
15
+ lib/resourceful/urlencoded_form_data.rb
16
+ lib/resourceful/util.rb
17
+ lib/resourceful.rb
18
+ Manifest
21
19
  MIT-LICENSE
22
20
  Rakefile
23
- Manifest
24
- spec/simple_sinatra_server_spec.rb
25
- spec/old_acceptance_specs.rb
26
- spec/acceptance_shared_specs.rb
27
- spec/spec_helper.rb
28
- spec/simple_sinatra_server.rb
21
+ README.markdown
22
+ resourceful.gemspec
29
23
  spec/acceptance/authorization_spec.rb
30
- spec/acceptance/header_spec.rb
31
- spec/acceptance/resource_spec.rb
32
24
  spec/acceptance/caching_spec.rb
25
+ spec/acceptance/header_spec.rb
33
26
  spec/acceptance/redirecting_spec.rb
34
- spec/resourceful/multipart_form_data_spec.rb
27
+ spec/acceptance/resource_spec.rb
28
+ spec/acceptance_shared_specs.rb
29
+ spec/caching_spec.rb
30
+ spec/old_acceptance_specs.rb
35
31
  spec/resourceful/header_spec.rb
32
+ spec/resourceful/http_accessor_spec.rb
33
+ spec/resourceful/multipart_form_data_spec.rb
36
34
  spec/resourceful/resource_spec.rb
35
+ spec/resourceful/response_spec.rb
37
36
  spec/resourceful/urlencoded_form_data_spec.rb
38
- spec/caching_spec.rb
37
+ spec/simple_sinatra_server.rb
38
+ spec/simple_sinatra_server_spec.rb
39
39
  spec/spec.opts
40
+ spec/spec_helper.rb
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  p.email = "psadauskas@gmail.com"
13
13
 
14
14
  p.ignore_pattern = ["pkg/*", "tmp/*"]
15
- p.dependencies = [['addressable', '>= 2.1.0'], 'httpauth']
15
+ p.dependencies = [['addressable', '>= 2.1.0'], 'httpauth', ['options', '>= 2.2.0']]
16
16
  p.development_dependencies = ['thin', 'yard', 'sinatra', 'rspec']
17
17
  p.retain_gemspec = true
18
18
  end
@@ -13,10 +13,11 @@ require 'resourceful/http_accessor'
13
13
  module Resourceful
14
14
  autoload :MultipartFormData, 'resourceful/multipart_form_data'
15
15
  autoload :UrlencodedFormData, 'resourceful/urlencoded_form_data'
16
+ autoload :StubbedResourceProxy, 'resourceful/stubbed_resource_proxy'
16
17
  end
17
18
 
18
19
  # Resourceful is a library that provides a high level HTTP interface.
19
20
  module Resourceful
20
- VERSION = "0.6.1"
21
+ VERSION = "0.6.3"
21
22
  RESOURCEFUL_USER_AGENT_TOKEN = "Resourceful/#{VERSION}(Ruby/#{RUBY_VERSION})"
22
23
  end
@@ -1,5 +1,6 @@
1
- require 'resourceful/options_interpretation'
1
+ require 'options'
2
2
  require 'set'
3
+ require 'facets/memoize'
3
4
 
4
5
  # Represents the header fields of an HTTP message. To access a field
5
6
  # you can use `#[]` and `#[]=`. For example, to get the content type
@@ -53,11 +54,25 @@ module Resourceful
53
54
  def has_key?(k)
54
55
  field_def(k).exists_in?(@raw_fields)
55
56
  end
57
+ alias has_field? has_key?
56
58
 
57
59
  def each(&blk)
58
60
  @raw_fields.each(&blk)
59
61
  end
60
- alias each_field each
62
+
63
+ # Iterates through the fields with values provided as message
64
+ # ready strings.
65
+ def each_field(&blk)
66
+ each do |k,v|
67
+ str_v = if field_def(k).multivalued?
68
+ v.join(', ')
69
+ else
70
+ v.to_s
71
+ end
72
+
73
+ yield k, str_v
74
+ end
75
+ end
61
76
 
62
77
  def merge!(another)
63
78
  another.each do |k,v|
@@ -82,43 +97,61 @@ module Resourceful
82
97
  self.class.new(@raw_fields.dup)
83
98
  end
84
99
 
85
-
86
100
  # Class to handle the details of each type of field.
87
- class HeaderFieldDef
101
+ class FieldDesc
88
102
  include Comparable
89
- include OptionsInterpretation
90
-
103
+
91
104
  ##
92
105
  attr_reader :name
93
-
106
+
107
+ # Create a new header field descriptor.
108
+ #
109
+ # @param [String] name The canonical name of this field.
110
+ #
111
+ # @param [Hash] options hash containing extra information about
112
+ # this header fields. Valid keys are:
113
+ #
114
+ # `:multivalued`
115
+ # `:multivalue`
116
+ # `:repeatable`
117
+ # : Values of this field are comma separated list of values.
118
+ # (n#VALUE per HTTP spec.) Default: false
119
+ #
120
+ # `:hop_by_hop`
121
+ # : True if the header is a hop-by-hop header. Default: false
122
+ #
123
+ # `:modifiable`
124
+ # : False if the header should not be modified by intermediates or caches. Default: true
125
+ #
94
126
  def initialize(name, options = {})
95
127
  @name = name
96
- extract_opts(options) do |opts|
97
- @repeatable = opts.extract(:repeatable, :default => false)
98
- @hop_by_hop = opts.extract(:hop_by_hop, :default => false)
99
- @modifiable = opts.extract(:modifiable, :default => true)
100
- end
128
+ options = Options.for(options).validate(:repeatable, :hop_by_hop, :modifiable)
129
+
130
+ @repeatable = options.getopt([:repeatable, :multivalue, :multivalued]) || false
131
+ @hop_by_hop = options.getopt(:hop_by_hop) || false
132
+ @modifiable = options.getopt(:modifiable, true)
101
133
  end
102
-
134
+
103
135
  def repeatable?
104
136
  @repeatable
105
137
  end
106
-
138
+ alias multivalued? repeatable?
139
+
107
140
  def hop_by_hop?
108
141
  @hop_by_hop
109
142
  end
110
-
143
+
111
144
  def modifiable?
112
145
  @modifiable
113
146
  end
114
-
147
+
115
148
  def get_from(raw_fields_hash)
116
149
  raw_fields_hash[name]
117
150
  end
118
-
151
+
119
152
  def set_to(value, raw_fields_hash)
120
- raw_fields_hash[name] = if repeatable?
121
- Array(value)
153
+ raw_fields_hash[name] = if multivalued?
154
+ Array(value).map{|v| v.split(/,\s*/)}.flatten
122
155
  elsif value.kind_of?(Array)
123
156
  raise ArgumentError, "#{name} field may only have one value" if value.size > 1
124
157
  value.first
@@ -126,86 +159,140 @@ module Resourceful
126
159
  value
127
160
  end
128
161
  end
129
-
162
+
130
163
  def exists_in?(raw_fields_hash)
131
164
  raw_fields_hash.has_key?(name)
132
165
  end
133
-
166
+
134
167
  def <=>(another)
135
168
  name <=> another.name
136
169
  end
137
-
170
+
138
171
  def ==(another)
139
- name_pattern === another.name
172
+ name_pattern === another.to_s
140
173
  end
141
174
  alias eql? ==
142
-
175
+
143
176
  def ===(another)
144
- if another.kind_of?(HeaderFieldDef)
177
+ if another.kind_of?(FieldDesc)
145
178
  self == another
146
179
  else
147
180
  name_pattern === another
148
181
  end
149
182
  end
150
-
183
+
151
184
  def name_pattern
152
- Regexp.new('^' + name.gsub('-', '[_-]') + '$', Regexp::IGNORECASE)
185
+ @name_pattern || @name_pattern = Regexp.new('^' + name.gsub('-', '[_-]') + '$', Regexp::IGNORECASE)
153
186
  end
154
-
187
+
155
188
  def methodized_name
156
- name.downcase.gsub('-', '_')
189
+ @methodized_name ||= name.downcase.gsub('-', '_')
157
190
  end
158
-
191
+
192
+ def constantized_name
193
+ @constantized_name ||= name.upcase.gsub('-', '_')
194
+ end
195
+
159
196
  alias to_s name
160
-
161
- def gen_setter(klass)
162
- klass.class_eval <<-RUBY
163
- def #{methodized_name}=(val) # def accept=(val)
164
- self['#{name}'] = val # self['Accept'] = val
165
- end # end
166
- RUBY
197
+
198
+ def accessor_module
199
+ @accessor_module ||= begin
200
+ Module.new.tap{|m| m.module_eval(<<-RUBY)}
201
+ #{constantized_name} = '#{name}'
202
+
203
+ def #{methodized_name} # def accept
204
+ self[#{constantized_name}] # self[ACCEPT]
205
+ end # end
206
+
207
+ def #{methodized_name}=(val) # def accept=(val)
208
+ self[#{constantized_name}] = val # self[ACCEPT] = val
209
+ end # end
210
+ RUBY
211
+ end
167
212
  end
168
213
 
169
- def gen_getter(klass)
170
- klass.class_eval <<-RUBY
171
- def #{methodized_name} # def accept
172
- self['#{name}'] # self['Accept']
173
- end # end
174
- RUBY
214
+ def hash
215
+ @name.hash
175
216
  end
176
-
177
- def gen_canonical_name_const(klass)
178
- const_name = name.upcase.gsub('-', '_')
179
217
 
180
- klass.const_set(const_name, name)
218
+ # Yields each commonly used lookup key for this header field.
219
+ def lookup_keys(&blk)
220
+ yield name
221
+ yield name.upcase
222
+ yield name.downcase
223
+ yield methodized_name
224
+ yield methodized_name.to_sym
225
+ yield constantized_name
226
+ yield constantized_name.to_sym
181
227
  end
182
- end
183
-
184
- @@header_field_defs = Set.new
185
-
228
+ end # FieldDesc
229
+
230
+ @@known_fields = Set.new
231
+ @@known_fields_lookup = Hash.new
232
+
233
+ # Declares a common header field. Header fields do not have to be
234
+ # defined this way but accessing them is easier, safer and faster
235
+ # if you do. Declaring a field does the following things:
236
+ #
237
+ # * defines accessor methods (e.g. `#content_type` and
238
+ # `#content_type=`) on `Header`
239
+ #
240
+ # * defines constant that can be used to reference there field
241
+ # name (e.g. `some_header[Header::CONTENT_TYPE]`)
242
+ #
243
+ # * includes the field in the appropriate *_fields groups (e.g. `Header.non_modifiable_fields`)
244
+ #
245
+ # * provides improved multiple value parsing
246
+ #
247
+ # Create a new header field descriptor.
248
+ #
249
+ # @param [String] name The canonical name of this field.
250
+ #
251
+ # @param [Hash] options hash containing extra information about
252
+ # this header fields. Valid keys are:
253
+ #
254
+ # `:multivalued`
255
+ # `:multivalue`
256
+ # `:repeatable`
257
+ # : Values of this field are comma separated list of values.
258
+ # (n#VALUE per HTTP spec.) Default: false
259
+ #
260
+ # `:hop_by_hop`
261
+ # : True if the header is a hop-by-hop header. Default: false
262
+ #
263
+ # `:modifiable`
264
+ # : False if the header should not be modified by intermediates or caches. Default: true
265
+ #
186
266
  def self.header_field(name, options = {})
187
- hfd = HeaderFieldDef.new(name, options)
188
-
189
- @@header_field_defs << hfd
190
-
191
- hfd.gen_getter(self)
192
- hfd.gen_setter(self)
193
- hfd.gen_canonical_name_const(self)
194
- end
267
+ hfd = FieldDesc.new(name, options)
268
+
269
+ @@known_fields << hfd
270
+ hfd.lookup_keys do |a_key|
271
+ @@known_fields_lookup[a_key] = hfd
272
+ end
195
273
 
196
- def self.hop_by_hop_headers
197
- @@header_field_defs.select{|hfd| hfd.hop_by_hop?}
274
+ include(hfd.accessor_module)
198
275
  end
199
-
200
- def self.non_modifiable_headers
201
- @@header_field_defs.reject{|hfd| hfd.repeatable?}
276
+
277
+ def self.hop_by_hop_fields
278
+ @@known_fields.select{|hfd| hfd.hop_by_hop?}
202
279
  end
203
280
 
204
- def field_def(name)
205
- @@header_field_defs.find{|hfd| hfd === name} ||
206
- HeaderFieldDef.new(name.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-'), :repeatable => true)
281
+ def self.non_modifiable_fields
282
+ @@known_fields.reject{|hfd| hfd.modifiable?}
207
283
  end
208
284
 
285
+ protected
286
+
287
+ # ---
288
+ #
289
+ # We have to fall back on a slow iteration to find the header
290
+ # field some times because field names are
291
+ def field_def(name)
292
+ @@known_fields_lookup[name] || # the fast way
293
+ @@known_fields.find{|hfd| hfd === name} || # the slow way
294
+ FieldDesc.new(name.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-'), :repeatable => true) # make up as we go
295
+ end
209
296
 
210
297
  header_field('Accept', :repeatable => true)
211
298
  header_field('Accept-Charset', :repeatable => true)
@@ -1,10 +1,10 @@
1
1
  require 'net/http'
2
2
 
3
- require 'resourceful/options_interpretation'
4
3
  require 'resourceful/authentication_manager'
5
4
  require 'resourceful/cache_manager'
6
5
  require 'resourceful/resource'
7
- require 'resourceful/stubbed_resource_proxy'
6
+
7
+ require 'options'
8
8
 
9
9
  module Resourceful
10
10
  # This is an imitation Logger used when no real logger is
@@ -28,8 +28,6 @@ module Resourceful
28
28
  # provided by the Resourceful library. Conceptually this object
29
29
  # acts a collection of all the resources available via HTTP.
30
30
  class HttpAccessor
31
- include OptionsInterpretation
32
-
33
31
  # A logger object to which messages about the activities of this
34
32
  # object will be written. This should be an object that responds
35
33
  # to +#info(message)+ and +#debug(message)+.
@@ -67,18 +65,19 @@ module Resourceful
67
65
  #
68
66
  #
69
67
  def initialize(options = {})
68
+ options = Options.for(options).validate(:logger, :user_agent, :cache_manager, :authenticator, :authenticators, :http_adapter)
69
+
70
70
  @user_agent_tokens = [RESOURCEFUL_USER_AGENT_TOKEN]
71
71
  @auth_manager = AuthenticationManager.new()
72
72
 
73
- extract_opts(options) do |opts|
74
- @user_agent_tokens.push(*opts.extract(:user_agent, :default => []) {|ua| [ua].flatten})
75
-
76
- self.logger = opts.extract(:logger, :default => BitBucketLogger.new)
77
- @cache_manager = opts.extract(:cache_manager, :default => NullCacheManager.new)
78
- @http_adapter = opts.extract(:http_adapter, :default => lambda{NetHttpAdapter.new})
79
-
80
- opts.extract(:authenticator, :required => false).tap{|a| add_authenticator(a) if a}
81
- opts.extract(:authenticators, :default => []).each { |a| add_authenticator(a) }
73
+
74
+ @user_agent_tokens.push(*Array(options.getopt(:user_agent)).flatten.reverse)
75
+ self.logger = options.getopt(:logger) || BitBucketLogger.new
76
+ @cache_manager = options.getopt(:cache_manager) || NullCacheManager.new
77
+ @http_adapter = options.getopt(:http_adapter) || NetHttpAdapter.new
78
+
79
+ Array(options.getopt([:authenticator, :authenticators])).flatten.each do |an_authenticator|
80
+ add_authenticator(an_authenticator)
82
81
  end
83
82
  end
84
83
 
@@ -123,6 +123,7 @@ module Resourceful
123
123
  logger.info("Authentication Required. Retrying with auth info")
124
124
  accessor.auth_manager.associate_auth_info(response)
125
125
  add_credentials!
126
+ @body.rewind if @body # Its a stringIO, and we already fed it to the adapter once, so rewind it when we try again
126
127
  response = fetch_response
127
128
  end
128
129
 
@@ -23,11 +23,10 @@ module Resourceful
23
23
  #
24
24
  # @return true|false
25
25
  def expired?
26
- if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
27
- max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
28
- return true if current_age > max_age
29
- elsif header['Expires']
30
- return true if Time.httpdate(header['Expires']) < Time.now
26
+ if header.cache_control and m_age_str = header.cache_control.find{|cc| /^max-age=/ === cc}
27
+ return current_age > m_age_str[/\d+/].to_i
28
+ elsif header.expires
29
+ return Time.httpdate(header.expires) < Time.now
31
30
  end
32
31
 
33
32
  false
@@ -38,10 +37,8 @@ module Resourceful
38
37
  # @return true|false
39
38
  def stale?
40
39
  return true if expired?
41
- if header['Cache-Control']
42
- return true if header['Cache-Control'].include?('must-revalidate')
43
- return true if header['Cache-Control'].include?('no-cache')
44
- end
40
+ return false unless header.has_field?(Header::CACHE_CONTROL)
41
+ return true if header.cache_control.any?{|cc| /must-revalidate|no-cache/ === cc}
45
42
 
46
43
  false
47
44
  end
@@ -75,8 +72,8 @@ module Resourceful
75
72
 
76
73
  # Algorithm taken from RCF2616#13.2.3
77
74
  def current_age
78
- age_value = header['Age'] || 0
79
- date_value = Time.httpdate(header['Date'])
75
+ age_value = header.age.to_i
76
+ date_value = Time.httpdate(header.date)
80
77
  now = Time.now
81
78
 
82
79
  apparent_age = [0, response_time - date_value].max
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{resourceful}
5
- s.version = "0.6.1"
5
+ s.version = "0.6.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Paul Sadauskas"]
9
- s.date = %q{2009-09-15}
9
+ s.date = %q{2009-09-29}
10
10
  s.description = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
11
11
  s.email = %q{psadauskas@gmail.com}
12
- s.extra_rdoc_files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/abstract_form_data.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "README.markdown"]
13
- s.files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/abstract_form_data.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "History.txt", "resourceful.gemspec", "README.markdown", "MIT-LICENSE", "Rakefile", "Manifest", "spec/simple_sinatra_server_spec.rb", "spec/old_acceptance_specs.rb", "spec/acceptance_shared_specs.rb", "spec/spec_helper.rb", "spec/simple_sinatra_server.rb", "spec/acceptance/authorization_spec.rb", "spec/acceptance/header_spec.rb", "spec/acceptance/resource_spec.rb", "spec/acceptance/caching_spec.rb", "spec/acceptance/redirecting_spec.rb", "spec/resourceful/multipart_form_data_spec.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/urlencoded_form_data_spec.rb", "spec/caching_spec.rb", "spec/spec.opts"]
12
+ s.extra_rdoc_files = ["lib/resourceful/abstract_form_data.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/response.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/util.rb", "lib/resourceful.rb", "README.markdown"]
13
+ s.files = ["History.txt", "lib/resourceful/abstract_form_data.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/response.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/util.rb", "lib/resourceful.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.markdown", "resourceful.gemspec", "spec/acceptance/authorization_spec.rb", "spec/acceptance/caching_spec.rb", "spec/acceptance/header_spec.rb", "spec/acceptance/redirecting_spec.rb", "spec/acceptance/resource_spec.rb", "spec/acceptance_shared_specs.rb", "spec/caching_spec.rb", "spec/old_acceptance_specs.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/http_accessor_spec.rb", "spec/resourceful/multipart_form_data_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/response_spec.rb", "spec/resourceful/urlencoded_form_data_spec.rb", "spec/simple_sinatra_server.rb", "spec/simple_sinatra_server_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
14
14
  s.homepage = %q{http://github.com/paul/resourceful}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Resourceful", "--main", "README.markdown"]
16
16
  s.require_paths = ["lib"]
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
26
  s.add_runtime_dependency(%q<addressable>, [">= 2.1.0"])
27
27
  s.add_runtime_dependency(%q<httpauth>, [">= 0"])
28
+ s.add_runtime_dependency(%q<options>, [">= 2.2.0"])
28
29
  s.add_development_dependency(%q<thin>, [">= 0"])
29
30
  s.add_development_dependency(%q<yard>, [">= 0"])
30
31
  s.add_development_dependency(%q<sinatra>, [">= 0"])
@@ -32,6 +33,7 @@ Gem::Specification.new do |s|
32
33
  else
33
34
  s.add_dependency(%q<addressable>, [">= 2.1.0"])
34
35
  s.add_dependency(%q<httpauth>, [">= 0"])
36
+ s.add_dependency(%q<options>, [">= 2.2.0"])
35
37
  s.add_dependency(%q<thin>, [">= 0"])
36
38
  s.add_dependency(%q<yard>, [">= 0"])
37
39
  s.add_dependency(%q<sinatra>, [">= 0"])
@@ -40,6 +42,7 @@ Gem::Specification.new do |s|
40
42
  else
41
43
  s.add_dependency(%q<addressable>, [">= 2.1.0"])
42
44
  s.add_dependency(%q<httpauth>, [">= 0"])
45
+ s.add_dependency(%q<options>, [">= 2.2.0"])
43
46
  s.add_dependency(%q<thin>, [">= 0"])
44
47
  s.add_dependency(%q<yard>, [">= 0"])
45
48
  s.add_dependency(%q<sinatra>, [">= 0"])
@@ -54,7 +54,7 @@ describe "Caching" do
54
54
 
55
55
  resp = @resource.get
56
56
  resp.headers['X-Updateme'].should == ["bar"]
57
- resp.headers['Cache-Control'].should == ["private,max-age=0"]
57
+ resp.headers['Cache-Control'].should == ["private", "max-age=0"]
58
58
  end
59
59
 
60
60
  end
@@ -1,8 +1,153 @@
1
1
  require File.dirname(__FILE__) + "/../spec_helper.rb"
2
2
 
3
+ module Resourceful
4
+ describe Header do
5
+ def self.should_support_header(name)
6
+ const_name = name.upcase.gsub('-', '_')
7
+ meth_name = name.downcase.gsub('-', '_')
3
8
 
4
- describe Resourceful::Header do
5
- it "should have constants for header names" do
6
- Resourceful::Header::CONTENT_TYPE.should == 'Content-Type'
9
+ eval <<-RUBY
10
+ it "should have constant `#{const_name}` for header `#{name}`" do
11
+ Resourceful::Header::#{const_name}.should == '#{name}'
12
+ end
13
+
14
+ it "should have accessor method `#{meth_name}` for header `#{name}`" do
15
+ Resourceful::Header.new.should respond_to(:#{meth_name})
16
+ end
17
+
18
+ RUBY
19
+ end
20
+
21
+ should_support_header('Accept')
22
+ should_support_header('Accept-Charset')
23
+ should_support_header('Accept-Encoding')
24
+ should_support_header('Accept-Language')
25
+ should_support_header('Accept-Ranges')
26
+ should_support_header('Age')
27
+ should_support_header('Allow')
28
+ should_support_header('Authorization')
29
+ should_support_header('Cache-Control')
30
+ should_support_header('Connection')
31
+ should_support_header('Content-Encoding')
32
+ should_support_header('Content-Language')
33
+ should_support_header('Content-Length')
34
+ should_support_header('Content-Location')
35
+ should_support_header('Content-MD5')
36
+ should_support_header('Content-Range')
37
+ should_support_header('Content-Type')
38
+ should_support_header('Date')
39
+ should_support_header('ETag')
40
+ should_support_header('Expect')
41
+ should_support_header('Expires')
42
+ should_support_header('From')
43
+ should_support_header('Host')
44
+ should_support_header('If-Match')
45
+ should_support_header('If-Modified-Since')
46
+ should_support_header('If-None-Match')
47
+ should_support_header('If-Range')
48
+ should_support_header('If-Unmodified-Since')
49
+ should_support_header('Keep-Alive')
50
+ should_support_header('Last-Modified')
51
+ should_support_header('Location')
52
+ should_support_header('Max-Forwards')
53
+ should_support_header('Pragma')
54
+ should_support_header('Proxy-Authenticate')
55
+ should_support_header('Proxy-Authorization')
56
+ should_support_header('Range')
57
+ should_support_header('Referer')
58
+ should_support_header('Retry-After')
59
+ should_support_header('Server')
60
+ should_support_header('TE')
61
+ should_support_header('Trailer')
62
+ should_support_header('Transfer-Encoding')
63
+ should_support_header('Upgrade')
64
+ should_support_header('User-Agent')
65
+ should_support_header('Vary')
66
+ should_support_header('Via')
67
+ should_support_header('Warning')
68
+ should_support_header('WWW-Authenticate')
69
+
70
+
71
+ it "should be instantiatable w/ single valued header fields" do
72
+ Header.new('Host' => 'foo.example').
73
+ host.should eql('foo.example')
74
+ end
75
+
76
+ it "should gracefully handle repeated values for single valued header fields" do
77
+ lambda {
78
+ Header.new('Host' => ['foo.example', 'bar.example'])
79
+ }.should raise_error(ArgumentError, 'Host field may only have one value')
80
+ end
81
+
82
+ it "should provide #each_fields to iterate through all header fields and values as strings" do
83
+ field_names = []
84
+ Header.new('Accept' => "this", :content_type => "that", 'pragma' => 'test').each_field do |fname, _|
85
+ field_names << fname
86
+ end
87
+
88
+ field_names.should include('Accept')
89
+ field_names.should include('Content-Type')
90
+ field_names.should include('Pragma')
91
+ field_names.should have(3).items
92
+ end
93
+
94
+ it "should provide #to_hash as a way to dump the header fields" do
95
+ Header.new('Accept' => "this", :content_type => "that", 'date' => 'today').to_hash.tap do |h|
96
+ h.should have_pair('Accept', ['this'])
97
+ h.should have_pair('Content-Type', 'that')
98
+ h.should have_pair('Date', 'today')
99
+ end
100
+ end
101
+
102
+ it "should provide a list of hop-by-hop fields" do
103
+ Header.header_field('X-Hop-By-Hop-Header', :hop_by_hop => true)
104
+ Header.hop_by_hop_fields.should include('X-Hop-By-Hop-Header')
105
+ end
106
+
107
+ it "should provide a list of not modified fields" do
108
+ Header.header_field('X-Dont-Modify-Me', :modifiable => false)
109
+ Header.non_modifiable_fields.should include('X-Dont-Modify-Me')
110
+ end
111
+
112
+ describe "multi-valued fields" do
113
+ it "should be instantiatable w/ repeated multi-valued header fields" do
114
+ Header.new('Accept' => ['application/foo', 'application/bar']).
115
+ accept.should eql(['application/foo', 'application/bar'])
116
+ end
117
+
118
+ it "should be instantiatable w/ repeated multi-valued header fields w/ multiple values" do
119
+ Header.new('Accept' => ['application/foo, application/bar', 'text/plain']).
120
+ accept.should eql(['application/foo', 'application/bar', 'text/plain'])
121
+ end
122
+
123
+ it "should be instantiatable w/ multi-valued header fields w/ multiple values" do
124
+ Header.new('Accept' => 'application/foo, application/bar').
125
+ accept.should eql(['application/foo', 'application/bar'])
126
+ end
127
+
128
+ it "should be instantiatable w/ multi-valued header fields w/ one value" do
129
+ Header.new('Accept' => 'application/foo').
130
+ accept.should eql(['application/foo'])
131
+ end
132
+
133
+ it "should provide values to #each_field as a comma separated string" do
134
+ Header.new('Accept' => ['this', 'that']).each_field do |fname, fval|
135
+ fval.should == 'this, that'
136
+ end
137
+ end
138
+
139
+ it "should provide #each as a way to iterate through fields as w/ higher level values" do
140
+ Header.new('Accept' => ['this', 'that']).each do |fname, fval|
141
+ fval.should == ['this', 'that']
142
+ end
143
+ end
144
+ end
145
+
146
+ Spec::Matchers.define :have_pair do |name, value|
147
+ match do |header_hash|
148
+ header_hash.has_key?(name)
149
+ header_hash[name] == value
150
+ end
151
+ end
7
152
  end
8
153
  end
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ require "resourceful/http_accessor"
4
+
5
+ module Resourceful
6
+ describe HttpAccessor do
7
+ describe "instantiation" do
8
+ it "should accept logger option" do
9
+ test_logger = stub('logger', :debug => false)
10
+ ha = HttpAccessor.new(:logger => test_logger)
11
+ ha.logger.should equal(test_logger)
12
+ end
13
+
14
+ it "should accept array user_agent option" do
15
+ ha = HttpAccessor.new(:user_agent => ['foo/3.2', 'bar/1.0'])
16
+ ha.user_agent_string.should match(/^foo\/3.2 bar\/1.0 Resourceful/)
17
+ end
18
+
19
+ it "should accept string user_agent option" do
20
+ ha = HttpAccessor.new(:user_agent => 'foo')
21
+ ha.user_agent_string.should match(/^foo Resourceful/)
22
+ end
23
+
24
+ it "should accept cache_manager option" do
25
+ test_cache_manager = stub('cache_manager', :debug => false)
26
+ ha = HttpAccessor.new(:cache_manager => test_cache_manager)
27
+ ha.cache_manager.should equal(test_cache_manager)
28
+ end
29
+
30
+ it "should accept http_adapter option" do
31
+ test_http_adapter = stub('http_adapter', :debug => false)
32
+ ha = HttpAccessor.new(:http_adapter => test_http_adapter)
33
+ ha.http_adapter.should equal(test_http_adapter)
34
+ end
35
+
36
+ it "should accept authenticator option" do
37
+ test_authenticator = stub('authenticator', :debug => false)
38
+ ha = HttpAccessor.new(:authenticator => test_authenticator)
39
+ # cannot really be tested safely so we just rely on the fact that the option was accepted
40
+ end
41
+
42
+ it "should accept authenticators option" do
43
+ test_authenticator1 = stub('authenticator1', :debug => false)
44
+ test_authenticator2 = stub('authenticator2', :debug => false)
45
+ ha = HttpAccessor.new(:authenticator => [test_authenticator1, test_authenticator2])
46
+ # cannot really be tested safely so we just rely on the fact that the option was accepted
47
+ end
48
+
49
+ it "should reject unrecognized options" do
50
+ lambda {
51
+ HttpAccessor.new(:not_a_valid_option => "this")
52
+ }.should raise_error(ArgumentError)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ require 'resourceful/response'
4
+ require 'resourceful/header'
5
+
6
+ module Resourceful
7
+ describe Response do
8
+
9
+ it "should know when it is expired" do
10
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=2', 'Date' => (Time.now - 2).httpdate), nil)
11
+ resp.request_time = Time.now
12
+
13
+ resp.expired?.should be_true
14
+ end
15
+
16
+ it "should know when it is not expired" do
17
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => Time.now.httpdate), nil)
18
+ resp.request_time = Time.now
19
+
20
+ resp.expired?.should be_false
21
+ end
22
+
23
+ it "know when it is stale due to expiration" do
24
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => (Time.now - 2).httpdate), nil)
25
+ resp.request_time = Time.now
26
+
27
+ resp.stale?.should be_true
28
+ end
29
+
30
+ it "know when it is stale due to no-cache" do
31
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'no-cache', 'Date' => Time.now.httpdate), nil)
32
+ resp.request_time = Time.now
33
+
34
+ resp.stale?.should be_true
35
+ end
36
+
37
+ it "know when it is stale due to must-revalidate" do
38
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'must-revalidate', 'Date' => Time.now.httpdate), nil)
39
+ resp.request_time = Time.now
40
+
41
+ resp.stale?.should be_true
42
+ end
43
+
44
+ it "know when it is not stale" do
45
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => Time.now.httpdate), nil)
46
+ resp.request_time = Time.now
47
+
48
+ resp.stale?.should be_false
49
+ end
50
+ end
51
+ end
@@ -2,10 +2,12 @@ require 'rubygems'
2
2
  require 'spec'
3
3
  require 'pp'
4
4
 
5
- $LOAD_PATH << File.join(File.dirname(__FILE__), "..", "lib")
5
+ __DIR__ = File.dirname(__FILE__)
6
+
7
+ $LOAD_PATH << File.join(__DIR__, "..", "lib")
6
8
  require 'resourceful'
7
9
 
8
- $LOAD_PATH << File.dirname(__FILE__) # ./spec
10
+ $LOAD_PATH << __DIR__ # ./spec
9
11
 
10
12
  # Spawn the server in another process
11
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resourceful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Sadauskas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-15 00:00:00 -06:00
12
+ date: 2009-09-29 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -32,6 +32,16 @@ dependencies:
32
32
  - !ruby/object:Gem::Version
33
33
  version: "0"
34
34
  version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: options
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.2.0
44
+ version:
35
45
  - !ruby/object:Gem::Dependency
36
46
  name: thin
37
47
  type: :development
@@ -79,64 +89,64 @@ executables: []
79
89
  extensions: []
80
90
 
81
91
  extra_rdoc_files:
82
- - lib/resourceful.rb
83
- - lib/resourceful/net_http_adapter.rb
84
- - lib/resourceful/options_interpretation.rb
85
- - lib/resourceful/stubbed_resource_proxy.rb
86
- - lib/resourceful/urlencoded_form_data.rb
87
- - lib/resourceful/header.rb
88
- - lib/resourceful/memcache_cache_manager.rb
89
- - lib/resourceful/response.rb
90
- - lib/resourceful/util.rb
91
92
  - lib/resourceful/abstract_form_data.rb
93
+ - lib/resourceful/authentication_manager.rb
92
94
  - lib/resourceful/cache_manager.rb
93
- - lib/resourceful/request.rb
94
- - lib/resourceful/resource.rb
95
95
  - lib/resourceful/exceptions.rb
96
- - lib/resourceful/multipart_form_data.rb
96
+ - lib/resourceful/header.rb
97
97
  - lib/resourceful/http_accessor.rb
98
- - lib/resourceful/authentication_manager.rb
99
- - README.markdown
100
- files:
101
- - lib/resourceful.rb
98
+ - lib/resourceful/memcache_cache_manager.rb
99
+ - lib/resourceful/multipart_form_data.rb
102
100
  - lib/resourceful/net_http_adapter.rb
103
- - lib/resourceful/options_interpretation.rb
101
+ - lib/resourceful/request.rb
102
+ - lib/resourceful/resource.rb
103
+ - lib/resourceful/response.rb
104
104
  - lib/resourceful/stubbed_resource_proxy.rb
105
105
  - lib/resourceful/urlencoded_form_data.rb
106
- - lib/resourceful/header.rb
107
- - lib/resourceful/memcache_cache_manager.rb
108
- - lib/resourceful/response.rb
109
106
  - lib/resourceful/util.rb
107
+ - lib/resourceful.rb
108
+ - README.markdown
109
+ files:
110
+ - History.txt
110
111
  - lib/resourceful/abstract_form_data.rb
112
+ - lib/resourceful/authentication_manager.rb
111
113
  - lib/resourceful/cache_manager.rb
112
- - lib/resourceful/request.rb
113
- - lib/resourceful/resource.rb
114
114
  - lib/resourceful/exceptions.rb
115
- - lib/resourceful/multipart_form_data.rb
115
+ - lib/resourceful/header.rb
116
116
  - lib/resourceful/http_accessor.rb
117
- - lib/resourceful/authentication_manager.rb
118
- - History.txt
119
- - resourceful.gemspec
120
- - README.markdown
117
+ - lib/resourceful/memcache_cache_manager.rb
118
+ - lib/resourceful/multipart_form_data.rb
119
+ - lib/resourceful/net_http_adapter.rb
120
+ - lib/resourceful/request.rb
121
+ - lib/resourceful/resource.rb
122
+ - lib/resourceful/response.rb
123
+ - lib/resourceful/stubbed_resource_proxy.rb
124
+ - lib/resourceful/urlencoded_form_data.rb
125
+ - lib/resourceful/util.rb
126
+ - lib/resourceful.rb
127
+ - Manifest
121
128
  - MIT-LICENSE
122
129
  - Rakefile
123
- - Manifest
124
- - spec/simple_sinatra_server_spec.rb
125
- - spec/old_acceptance_specs.rb
126
- - spec/acceptance_shared_specs.rb
127
- - spec/spec_helper.rb
128
- - spec/simple_sinatra_server.rb
130
+ - README.markdown
131
+ - resourceful.gemspec
129
132
  - spec/acceptance/authorization_spec.rb
130
- - spec/acceptance/header_spec.rb
131
- - spec/acceptance/resource_spec.rb
132
133
  - spec/acceptance/caching_spec.rb
134
+ - spec/acceptance/header_spec.rb
133
135
  - spec/acceptance/redirecting_spec.rb
134
- - spec/resourceful/multipart_form_data_spec.rb
136
+ - spec/acceptance/resource_spec.rb
137
+ - spec/acceptance_shared_specs.rb
138
+ - spec/caching_spec.rb
139
+ - spec/old_acceptance_specs.rb
135
140
  - spec/resourceful/header_spec.rb
141
+ - spec/resourceful/http_accessor_spec.rb
142
+ - spec/resourceful/multipart_form_data_spec.rb
136
143
  - spec/resourceful/resource_spec.rb
144
+ - spec/resourceful/response_spec.rb
137
145
  - spec/resourceful/urlencoded_form_data_spec.rb
138
- - spec/caching_spec.rb
146
+ - spec/simple_sinatra_server.rb
147
+ - spec/simple_sinatra_server_spec.rb
139
148
  - spec/spec.opts
149
+ - spec/spec_helper.rb
140
150
  has_rdoc: true
141
151
  homepage: http://github.com/paul/resourceful
142
152
  licenses: []
@@ -1,72 +0,0 @@
1
- module Resourceful
2
- # Declarative way of interpreting options hashes
3
- #
4
- # include OptionsInterpretion
5
- # def my_method(opts = {})
6
- # extract_opts(opts) do |opts|
7
- # host = opts.extract(:host)
8
- # port = opts.extract(:port, :default => 80) {|p| Integer(p)}
9
- # end
10
- # end
11
- #
12
- module OptionsInterpretation
13
- # Interpret an options hash
14
- #
15
- # @param [Hash] opts
16
- # The options to interpret.
17
- #
18
- # @yield block that used to interpreter options hash
19
- #
20
- # @yieldparam [Resourceful::OptionsInterpretion::OptionsInterpreter] interpeter
21
- # An interpreter that can be used to extract option information from the options hash.
22
- def extract_opts(opts, &blk)
23
- opts = opts.clone
24
- yield OptionsInterpreter.new(opts)
25
-
26
- unless opts.empty?
27
- raise ArgumentError, "Unrecognized options: #{opts.keys.join(", ")}"
28
- end
29
-
30
- end
31
-
32
- class OptionsInterpreter
33
- def initialize(options_hash)
34
- @options_hash = options_hash
35
- end
36
-
37
- # Extract a particular option.
38
- #
39
- # @param [String] name
40
- # Name of option to extract
41
- # @param [Hash] interpreter_opts
42
- # ':default'
43
- # :: The default value, or an object that responds to #call
44
- # with the default value.
45
- # ':required'
46
- # :: Boolean indicating if this option is required. Default:
47
- # false if a default is provided; otherwise true.
48
- def extract(name, interpreter_opts = {}, &blk)
49
- option_required = !interpreter_opts.has_key?(:default)
50
- option_required = interpreter_opts[:required] if interpreter_opts.has_key?(:required)
51
-
52
- raise ArgumentError, "Required option #{name} not provided" if option_required && !@options_hash.has_key?(name)
53
- # We have the option we need
54
-
55
- orig_val = @options_hash.delete(name)
56
-
57
- if block_given?
58
- yield orig_val
59
-
60
- elsif orig_val
61
- orig_val
62
-
63
- elsif interpreter_opts[:default] && interpreter_opts[:default].respond_to?(:call)
64
- interpreter_opts[:default].call()
65
-
66
- elsif interpreter_opts[:default]
67
- interpreter_opts[:default]
68
- end
69
- end
70
- end
71
- end
72
- end