resourceful 0.6.1 → 0.6.3

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