pezra-resourceful 0.6.0 → 0.7.0

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.0"
21
+ VERSION = "0.7.0"
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|
@@ -78,43 +93,61 @@ module Resourceful
78
93
  self.class.new(@raw_fields.dup)
79
94
  end
80
95
 
81
-
82
96
  # Class to handle the details of each type of field.
83
- class HeaderFieldDef
97
+ class FieldDesc
84
98
  include Comparable
85
- include OptionsInterpretation
86
-
99
+
87
100
  ##
88
101
  attr_reader :name
89
-
102
+
103
+ # Create a new header field descriptor.
104
+ #
105
+ # @param [String] name The canonical name of this field.
106
+ #
107
+ # @param [Hash] options hash containing extra information about
108
+ # this header fields. Valid keys are:
109
+ #
110
+ # `:multivalued`
111
+ # `:multivalue`
112
+ # `:repeatable`
113
+ # : Values of this field are comma separated list of values.
114
+ # (n#VALUE per HTTP spec.) Default: false
115
+ #
116
+ # `:hop_by_hop`
117
+ # : True if the header is a hop-by-hop header. Default: false
118
+ #
119
+ # `:modifiable`
120
+ # : False if the header should not be modified by intermediates or caches. Default: true
121
+ #
90
122
  def initialize(name, options = {})
91
123
  @name = name
92
- extract_opts(options) do |opts|
93
- @repeatable = opts.extract(:repeatable, :default => false)
94
- @hop_by_hop = opts.extract(:hop_by_hop, :default => false)
95
- @modifiable = opts.extract(:modifiable, :default => true)
96
- end
124
+ options = Options.for(options).validate(:repeatable, :hop_by_hop, :modifiable)
125
+
126
+ @repeatable = options.getopt([:repeatable, :multivalue, :multivalued]) || false
127
+ @hop_by_hop = options.getopt(:hop_by_hop) || false
128
+ @modifiable = options.getopt(:modifiable, true)
97
129
  end
98
-
130
+
99
131
  def repeatable?
100
132
  @repeatable
101
133
  end
102
-
134
+ alias multivalued? repeatable?
135
+
103
136
  def hop_by_hop?
104
137
  @hop_by_hop
105
138
  end
106
-
139
+
107
140
  def modifiable?
108
141
  @modifiable
109
142
  end
110
-
143
+
111
144
  def get_from(raw_fields_hash)
112
145
  raw_fields_hash[name]
113
146
  end
114
-
147
+
115
148
  def set_to(value, raw_fields_hash)
116
- raw_fields_hash[name] = if repeatable?
117
- Array(value)
149
+ raw_fields_hash[name] = if multivalued?
150
+ Array(value).map{|v| v.split(/,\s*/)}.flatten
118
151
  elsif value.kind_of?(Array)
119
152
  raise ArgumentError, "#{name} field may only have one value" if value.size > 1
120
153
  value.first
@@ -122,86 +155,140 @@ module Resourceful
122
155
  value
123
156
  end
124
157
  end
125
-
158
+
126
159
  def exists_in?(raw_fields_hash)
127
160
  raw_fields_hash.has_key?(name)
128
161
  end
129
-
162
+
130
163
  def <=>(another)
131
164
  name <=> another.name
132
165
  end
133
-
166
+
134
167
  def ==(another)
135
- name_pattern === another.name
168
+ name_pattern === another.to_s
136
169
  end
137
170
  alias eql? ==
138
-
171
+
139
172
  def ===(another)
140
- if another.kind_of?(HeaderFieldDef)
173
+ if another.kind_of?(FieldDesc)
141
174
  self == another
142
175
  else
143
176
  name_pattern === another
144
177
  end
145
178
  end
146
-
179
+
147
180
  def name_pattern
148
- Regexp.new('^' + name.gsub('-', '[_-]') + '$', Regexp::IGNORECASE)
181
+ @name_pattern ||= Regexp.new('^' + name.gsub('-', '[_-]') + '$', Regexp::IGNORECASE)
149
182
  end
150
-
183
+
151
184
  def methodized_name
152
- name.downcase.gsub('-', '_')
185
+ @methodized_name ||= name.downcase.gsub('-', '_')
153
186
  end
154
-
187
+
188
+ def constantized_name
189
+ @constantized_name ||= name.upcase.gsub('-', '_')
190
+ end
191
+
155
192
  alias to_s name
156
-
157
- def gen_setter(klass)
158
- klass.class_eval <<-RUBY
159
- def #{methodized_name}=(val) # def accept=(val)
160
- self['#{name}'] = val # self['Accept'] = val
161
- end # end
162
- RUBY
193
+
194
+ def accessor_module
195
+ @accessor_module ||= begin
196
+ Module.new.tap{|m| m.module_eval(<<-RUBY)}
197
+ #{constantized_name} = '#{name}'
198
+
199
+ def #{methodized_name} # def accept
200
+ self[#{constantized_name}] # self[ACCEPT]
201
+ end # end
202
+
203
+ def #{methodized_name}=(val) # def accept=(val)
204
+ self[#{constantized_name}] = val # self[ACCEPT] = val
205
+ end # end
206
+ RUBY
207
+ end
163
208
  end
164
209
 
165
- def gen_getter(klass)
166
- klass.class_eval <<-RUBY
167
- def #{methodized_name} # def accept
168
- self['#{name}'] # self['Accept']
169
- end # end
170
- RUBY
210
+ def hash
211
+ @name.hash
171
212
  end
172
-
173
- def gen_canonical_name_const(klass)
174
- const_name = name.upcase.gsub('-', '_')
175
213
 
176
- klass.const_set(const_name, name)
214
+ # Yields each commonly used lookup key for this header field.
215
+ def lookup_keys(&blk)
216
+ yield name
217
+ yield name.upcase
218
+ yield name.downcase
219
+ yield methodized_name
220
+ yield methodized_name.to_sym
221
+ yield constantized_name
222
+ yield constantized_name.to_sym
177
223
  end
178
- end
179
-
180
- @@header_field_defs = Set.new
181
-
224
+ end # FieldDesc
225
+
226
+ @@known_fields = Set.new
227
+ @@known_fields_lookup = Hash.new
228
+
229
+ # Declares a common header field. Header fields do not have to be
230
+ # defined this way but accessing them is easier, safer and faster
231
+ # if you do. Declaring a field does the following things:
232
+ #
233
+ # * defines accessor methods (e.g. `#content_type` and
234
+ # `#content_type=`) on `Header`
235
+ #
236
+ # * defines constant that can be used to reference there field
237
+ # name (e.g. `some_header[Header::CONTENT_TYPE]`)
238
+ #
239
+ # * includes the field in the appropriate *_fields groups (e.g. `Header.non_modifiable_fields`)
240
+ #
241
+ # * provides improved multiple value parsing
242
+ #
243
+ # Create a new header field descriptor.
244
+ #
245
+ # @param [String] name The canonical name of this field.
246
+ #
247
+ # @param [Hash] options hash containing extra information about
248
+ # this header fields. Valid keys are:
249
+ #
250
+ # `:multivalued`
251
+ # `:multivalue`
252
+ # `:repeatable`
253
+ # : Values of this field are comma separated list of values.
254
+ # (n#VALUE per HTTP spec.) Default: false
255
+ #
256
+ # `:hop_by_hop`
257
+ # : True if the header is a hop-by-hop header. Default: false
258
+ #
259
+ # `:modifiable`
260
+ # : False if the header should not be modified by intermediates or caches. Default: true
261
+ #
182
262
  def self.header_field(name, options = {})
183
- hfd = HeaderFieldDef.new(name, options)
184
-
185
- @@header_field_defs << hfd
186
-
187
- hfd.gen_getter(self)
188
- hfd.gen_setter(self)
189
- hfd.gen_canonical_name_const(self)
190
- end
263
+ hfd = FieldDesc.new(name, options)
264
+
265
+ @@known_fields << hfd
266
+ hfd.lookup_keys do |a_key|
267
+ @@known_fields_lookup[a_key] = hfd
268
+ end
191
269
 
192
- def self.hop_by_hop_headers
193
- @@header_field_defs.select{|hfd| hfd.hop_by_hop?}
270
+ include(hfd.accessor_module)
194
271
  end
195
-
196
- def self.non_modifiable_headers
197
- @@header_field_defs.reject{|hfd| hfd.repeatable?}
272
+
273
+ def self.hop_by_hop_fields
274
+ @@known_fields.select{|hfd| hfd.hop_by_hop?}
198
275
  end
199
276
 
200
- def field_def(name)
201
- @@header_field_defs.find{|hfd| hfd === name} ||
202
- HeaderFieldDef.new(name.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-'), :repeatable => true)
277
+ def self.non_modifiable_fields
278
+ @@known_fields.reject{|hfd| hfd.modifiable?}
203
279
  end
204
280
 
281
+ protected
282
+
283
+ # ---
284
+ #
285
+ # We have to fall back on a slow iteration to find the header
286
+ # field some times because field names are
287
+ def field_def(name)
288
+ @@known_fields_lookup[name] || # the fast way
289
+ @@known_fields.find{|hfd| hfd === name} || # the slow way
290
+ FieldDesc.new(name.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-'), :repeatable => true) # make up as we go
291
+ end
205
292
 
206
293
  header_field('Accept', :repeatable => true)
207
294
  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
 
@@ -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,29 +2,31 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{resourceful}
5
- s.version = "0.6.0"
5
+ s.version = "0.7.0"
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-08-14}
9
+ s.date = %q{2009-08-25}
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
+ s.has_rdoc = true
14
15
  s.homepage = %q{http://github.com/paul/resourceful}
15
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Resourceful", "--main", "README.markdown"]
16
17
  s.require_paths = ["lib"]
17
18
  s.rubyforge_project = %q{resourceful}
18
- s.rubygems_version = %q{1.3.5}
19
+ s.rubygems_version = %q{1.3.1}
19
20
  s.summary = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
20
21
 
21
22
  if s.respond_to? :specification_version then
22
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
- s.specification_version = 3
24
+ s.specification_version = 2
24
25
 
25
26
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
27
  s.add_runtime_dependency(%q<addressable>, [">= 2.1.0"])
27
28
  s.add_runtime_dependency(%q<httpauth>, [">= 0"])
29
+ s.add_runtime_dependency(%q<options>, [">= 2.2.0"])
28
30
  s.add_development_dependency(%q<thin>, [">= 0"])
29
31
  s.add_development_dependency(%q<yard>, [">= 0"])
30
32
  s.add_development_dependency(%q<sinatra>, [">= 0"])
@@ -32,6 +34,7 @@ Gem::Specification.new do |s|
32
34
  else
33
35
  s.add_dependency(%q<addressable>, [">= 2.1.0"])
34
36
  s.add_dependency(%q<httpauth>, [">= 0"])
37
+ s.add_dependency(%q<options>, [">= 2.2.0"])
35
38
  s.add_dependency(%q<thin>, [">= 0"])
36
39
  s.add_dependency(%q<yard>, [">= 0"])
37
40
  s.add_dependency(%q<sinatra>, [">= 0"])
@@ -40,6 +43,7 @@ Gem::Specification.new do |s|
40
43
  else
41
44
  s.add_dependency(%q<addressable>, [">= 2.1.0"])
42
45
  s.add_dependency(%q<httpauth>, [">= 0"])
46
+ s.add_dependency(%q<options>, [">= 2.2.0"])
43
47
  s.add_dependency(%q<thin>, [">= 0"])
44
48
  s.add_dependency(%q<yard>, [">= 0"])
45
49
  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: pezra-resourceful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
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-08-14 00:00:00 -07:00
12
+ date: 2009-08-25 00:00:00 -07: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,65 +89,65 @@ 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
140
- has_rdoc: false
149
+ - spec/spec_helper.rb
150
+ has_rdoc: true
141
151
  homepage: http://github.com/paul/resourceful
142
152
  licenses:
143
153
  post_install_message:
@@ -167,7 +177,7 @@ requirements: []
167
177
  rubyforge_project: resourceful
168
178
  rubygems_version: 1.3.5
169
179
  signing_key:
170
- specification_version: 3
180
+ specification_version: 2
171
181
  summary: An HTTP library for Ruby that takes advantage of everything HTTP has to offer.
172
182
  test_files: []
173
183
 
@@ -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