stickler 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/.gitignore +1 -0
  2. data/HISTORY.asciidoc +17 -8
  3. data/LICENSE +13 -54
  4. data/README.asciidoc +29 -5
  5. data/Rakefile +11 -10
  6. data/TODO.asciidoc +6 -0
  7. data/bin/stickler +5 -2
  8. data/examples/fetch-a-gem +6 -0
  9. data/examples/gemcutter_repo.ru +1 -1
  10. data/lib/stickler.rb +1 -1
  11. data/lib/stickler/client.rb +1 -0
  12. data/lib/stickler/client/list.rb +30 -0
  13. data/lib/stickler/client/mirror.rb +5 -5
  14. data/lib/stickler/client/push.rb +1 -1
  15. data/lib/stickler/middleware/gemcutter.rb +2 -2
  16. data/lib/stickler/middleware/helpers.rb +37 -42
  17. data/lib/stickler/middleware/index.rb +51 -19
  18. data/lib/stickler/repository/index.rb +46 -9
  19. data/lib/stickler/repository/local.rb +75 -20
  20. data/lib/stickler/repository/null.rb +0 -2
  21. data/lib/stickler/repository/remote.rb +81 -64
  22. data/lib/stickler/repository/rubygems_authenticator.rb +3 -8
  23. data/lib/stickler/server.rb +1 -1
  24. data/lib/stickler/spec_lite.rb +13 -10
  25. data/lib/stickler/version.rb +2 -2
  26. data/man/stickler.asciidoc +3 -1
  27. data/spec/data/gems/foo-2.0.0a.gem +0 -0
  28. data/spec/index_spec_helpers.rb +71 -0
  29. data/spec/middleware/local_spec.rb +58 -10
  30. data/spec/middleware/not_found_spec.rb +1 -0
  31. data/spec/repository/index_spec.rb +15 -0
  32. data/spec/repository/local_spec.rb +20 -5
  33. data/spec/repository/remote_spec.rb +2 -3
  34. data/spec/spec.opts +1 -1
  35. data/spec/spec_helper.rb +8 -6
  36. data/spec/spec_lite_spec.rb +19 -6
  37. data/tasks/man.rake +1 -1
  38. metadata +74 -40
  39. data/spec/middleware/common_gem_server_helpers.rb +0 -69
  40. data/spec/middleware/index_spec.rb +0 -26
  41. data/spec/middleware/legacy_gem_server_behavior.rb +0 -31
  42. data/spec/middleware/modern_gem_server_behavior.rb +0 -22
@@ -7,27 +7,33 @@ require 'stickler/logable'
7
7
  require 'stickler/paths'
8
8
 
9
9
  module Stickler::Middleware
10
- # Index is a Rack middleware that passes all requests through except for those
11
- # matching these two urls:
10
+ # Index is a Rack middleware that passes all requests through except for the
11
+ # following urls:
12
12
  #
13
13
  # <b>/specs.#{Gem.marhsal_version}.gz</b>:: The [ name, version, platform ] index
14
14
  # of <b>all<b> the gems in the
15
15
  # entire repository
16
16
  #
17
- # <b>/latest_specs.#{Gem.marshal_version}.gz</b>:: The [ name, version, # platform ] index
17
+ # <b>/latest_specs.#{Gem.marshal_version}.gz</b>:: The [ name, version, platform ] index
18
18
  # of the <b>most recent</b> version of each
19
19
  # gem in the repository.
20
20
  #
21
- # For these 2 urls, it respond reponds with the summation of all the specs
22
- # that are in <tt>env['stickler.specs']</tt>. If there are no specs in that
23
- # environment variable, then it returns with an empty index.
21
+ # <b>/prerelease_specs.#{Gem.marshal-version}.gz</b>:: The [ name, version, platform ] index
22
+ # of the <b>prerelease</b> versions of the
23
+ # prerelease gems in the repository
24
+ #
25
+ # <b>/quick/Marshal.#{Gem.marshal_version}/*.gemspec.rz</b>:: The gemspec of each gem
26
+ #
27
+ # <b>/gems/*.gem</b>:: The actual .gem file to serve
28
+ #
29
+ # For the <b>specs</b> urls, it responds with the summation of all the specs
30
+ # that are in all the repositories served by stickler
24
31
  #
25
32
  # == Options
26
33
  #
27
34
  # This class is also the base class for all the other GemServer type
28
35
  # middlewares, so there is an optional behavior to NOT respond to the index
29
- # url requests and just append the spec, or latest_specs to
30
- # env['stickler.specs'] instead of serving the values out of there.
36
+ # url requests.
31
37
  #
32
38
  # <b>:serve_indexes</b>:: +true+ or +false+ it defaults to +true+. This
33
39
  # option is used when Index is used in a stack
@@ -72,7 +78,6 @@ module Stickler::Middleware
72
78
  end
73
79
 
74
80
  get '/' do
75
- append_specs
76
81
  if @serve_indexes then
77
82
  erb :index
78
83
  else
@@ -83,24 +88,29 @@ module Stickler::Middleware
83
88
  #
84
89
  # Respond to the requests for the <b>all gems</b> index
85
90
  #
86
- get %r{\A/specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
87
- append_specs
88
- serve_indexes( with_compression )
91
+ get %r{\A/specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
92
+ serve_indexes( released_specs, compression )
89
93
  end
90
94
 
91
95
  #
92
96
  # Respond to the requests for the <b>latest gems</b> index
93
97
  #
94
- get %r{\A/latest_specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
95
- append_latest_specs
96
- serve_indexes( with_compression )
98
+ get %r{\A/latest_specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
99
+ serve_indexes( latest_specs, compression)
100
+ end
101
+
102
+ #
103
+ # Respond to the request for the <b>pre release gems</b> index
104
+ #
105
+ get %r{\A/prerelease_specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
106
+ serve_indexes( prerelease_specs, compression )
97
107
  end
98
108
 
99
109
  #
100
110
  # Serve the indexes up as the response if @serve_indexes is true. Otherwise
101
111
  # return false
102
112
  #
103
- def serve_indexes( with_compression = :none )
113
+ def serve_indexes( specs, with_compression = :none )
104
114
  if @serve_indexes then
105
115
  self.compression = to_compression_flag( with_compression )
106
116
  return marshalled_specs( specs )
@@ -128,8 +138,10 @@ module Stickler::Middleware
128
138
  # Serve up a gemspec. This is really only used by the child classes.
129
139
  # an Index instance will never have any gemspecs to return
130
140
  #
131
- get %r{\A/quick/Marshal.#{Gem.marshal_version}/(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz\Z} do
132
- name, version, platform, with_compression = *params[:captures]
141
+ # To support pre-releases the a-z has been added to the version
142
+ #
143
+ get %r{\A/quick/Marshal.#{Gem.marshal_version}/(.*?)-([0-9.]+[0-9a-z.]*)(-.*?)?\.gemspec\.rz\Z} do
144
+ name, version, platform = *params[:captures]
133
145
  spec = Stickler::SpecLite.new( name, version, platform )
134
146
  full_path = @repo.full_path_to_specification( spec )
135
147
  if full_path and File.exist?( full_path ) then
@@ -145,7 +157,27 @@ module Stickler::Middleware
145
157
  # everywhere
146
158
  #
147
159
  def marshalled_specs( spec_a )
148
- marshal( spec_a.collect { |s| s.to_rubygems_a } )
160
+ a = spec_a.collect { |s| s.to_rubygems_a }
161
+ marshal( optimize_specs( a ) )
162
+ end
163
+
164
+ #
165
+ # Optimize the specs marshalling by using identical objects as much as possible. this
166
+ # is take directly from RubyGems source code. See rubygems/indexer.rb
167
+ # #compact_specs
168
+ #
169
+ def optimize_specs( specs )
170
+ names = {}
171
+ versions = {}
172
+ platforms = {}
173
+
174
+ specs.collect do |(name, version, platform)|
175
+ names[name] = name unless names.include?( name )
176
+ versions[version] = version unless versions.include?( version )
177
+ platforms[platform] = platform unless platforms.include?( platform )
178
+
179
+ [ names[name], versions[version], platforms[platform] ]
180
+ end
149
181
  end
150
182
 
151
183
  def marshal( data )
@@ -18,11 +18,18 @@ module Stickler::Repository
18
18
  # The directory the specs live
19
19
  attr_reader :spec_dir
20
20
 
21
- #
21
+ # The last time the repository directory was modified
22
+ attr_reader :last_modified_time
23
+
24
+ # The number of entries in the spec directory
25
+ attr_reader :last_entry_count
22
26
 
23
27
  def initialize( spec_dir )
24
- @specs = []
25
- @spec_dir = spec_dir
28
+ @specs = []
29
+ @spec_dir = spec_dir
30
+ @last_modified_time = nil
31
+ @last_entry_count = nil
32
+ @spec_glob = File.join( @spec_dir, "*.gemspec" )
26
33
  load_specs
27
34
  end
28
35
 
@@ -31,9 +38,14 @@ module Stickler::Repository
31
38
  return @specs
32
39
  end
33
40
 
41
+ #
42
+ # return all the latest specs in the repository, do not include pre-release
43
+ # gems
44
+ #
34
45
  def latest_specs
35
46
  latest = {}
36
47
  specs.each do |s|
48
+ next if s.prerelease?
37
49
  if old_spec = latest[s.name] then
38
50
  if old_spec.version < s.version then
39
51
  latest[s.name] = s
@@ -45,25 +57,49 @@ module Stickler::Repository
45
57
  latest.values
46
58
  end
47
59
 
60
+ #
61
+ # return just the list of pre-release specs
62
+ #
63
+ def prerelease_specs
64
+ specs.select { |s| s.prerelease? }
65
+ end
66
+
67
+ #
68
+ # return just the list of release specs
69
+ #
70
+ def released_specs
71
+ specs.select { |s| not s.prerelease? }
72
+ end
73
+
48
74
  def load_specs
49
75
  load_specs_in_dir( self.spec_dir )
50
76
  end
51
77
 
52
78
  def reload_necessary?
53
- return true
54
- # return true unless @last_modified_time
55
- # current_modified_time = File.stat( self.spec_dir ).mtime
56
- # return (current_modified_time > @last_modified_time )
79
+ return true unless @last_modified_time
80
+ return true unless @last_entry_count
81
+ return true if (self.current_modified_time > @last_modified_time )
82
+ return true if (self.current_entry_count != @last_entry_count )
83
+ return false
57
84
  end
58
85
 
59
- def last_modified_time
86
+ def current_modified_time
60
87
  File.stat( self.spec_dir ).mtime
61
88
  end
62
89
 
90
+ def current_entry_count
91
+ Dir.glob( @spec_glob ).size
92
+ end
93
+
63
94
  def spec_dir=( d )
64
95
  raise Error, "#{d} is not a directory" unless File.directory?( d )
65
96
  @spec_dir = d
66
- @last_modified_time = File.stat( d ).mtime
97
+ update_reload_conditions
98
+ end
99
+
100
+ def update_reload_conditions
101
+ @last_modified_time = self.current_modified_time
102
+ @last_entry_count = self.current_entry_count
67
103
  end
68
104
 
69
105
  def load_specs_in_dir( spec_dir )
@@ -75,6 +111,7 @@ module Stickler::Repository
75
111
  next unless entry =~ /\.gemspec\Z/
76
112
  add_spec_from_file( File.join( spec_dir, entry ) )
77
113
  end
114
+ update_reload_conditions
78
115
  end
79
116
 
80
117
  def add_spec_from_file( path )
@@ -1,9 +1,11 @@
1
1
  require 'stickler/spec_lite'
2
+ require 'stickler/logable'
2
3
  require 'stickler/repository'
3
4
  require 'stickler/repository/api'
4
5
  require 'stickler/repository/index'
5
6
  require 'addressable/uri'
6
7
  require 'tempfile'
8
+ require 'forwardable'
7
9
 
8
10
  module Stickler::Repository
9
11
  #
@@ -18,6 +20,10 @@ module Stickler::Repository
18
20
  #
19
21
  class Local
20
22
  class Error < ::Stickler::Repository::Error; end
23
+ include Stickler::Logable
24
+
25
+ # The name to give to this repository
26
+ attr_reader :name
21
27
 
22
28
  # the root directory of the repository
23
29
  attr_reader :root_dir
@@ -34,13 +40,73 @@ module Stickler::Repository
34
40
  # the index of the repository
35
41
  attr_reader :index
36
42
 
37
- def initialize( root_dir )
43
+ # mutex for synchronizing local instance allocations
44
+ @mutex = Mutex.new
45
+
46
+ # The list of repos keyd by root_dir
47
+ @repos = Hash.new
48
+
49
+ #
50
+ # Return the list of all the Local repositories
51
+ #
52
+ def self.repos
53
+ return @repos
54
+ end
55
+
56
+ #
57
+ # Purge the list of local repos that are known. This is mainly used in
58
+ # testing.
59
+ #
60
+ def self.purge
61
+ @mutex.synchronize { @repos.clear }
62
+ end
63
+
64
+ #
65
+ # :call-seq:
66
+ # Local.new( '/tmp/repo' )
67
+ # Local.new( '/tmp/repo', "Temporary Repo" )
68
+ #
69
+ # Create a new Local repository. Local repository instances
70
+ # are shared if the +root_dir+ is the same. That is
71
+ #
72
+ # repo1 = Local.new( '/foo/bar' )
73
+ # repo2 = Local.new( '/foo/bar' )
74
+ #
75
+ # repo1 and repo2 will be references to the sname object
76
+ #
77
+ # If a new is called for an already existing repo, and the +name+
78
+ # of the repo is not nil, and different than the existing repo
79
+ # an exception is raised.
80
+ #
81
+ def self.new( root_dir, name = nil )
82
+ repo = nil
83
+ @mutex.synchronize do
84
+ local_key = File.expand_path( root_dir ) + File::SEPARATOR
85
+ repo = @repos[local_key]
86
+ if repo.nil? then
87
+ repo = super( local_key, name )
88
+ @repos[local_key] = repo
89
+ else
90
+ if name and (repo.name != name) then
91
+ raise Error, "A repository already exists for #{root_dir} with name has the name #{repo.name} which conflicts with the given name #{name}"
92
+ end
93
+ end
94
+ end
95
+ repo
96
+ end
97
+
98
+ def initialize( root_dir, name = nil )
38
99
  @root_dir = File.expand_path( root_dir ) + File::SEPARATOR
100
+ @name = name || @root_dir
39
101
  @gems_dir = File.join( @root_dir, 'gems/' )
40
102
  @specifications_dir = File.join( @root_dir, 'specifications/' )
41
103
  @temp_dir = File.join( @root_dir, "tmp/" )
42
- @index = ::Stickler::Repository::Index.new( @specifications_dir )
104
+
105
+ # setup the dirs before doing the index because the @specifications_dir
106
+ # may not exist yet.
43
107
  setup_dirs
108
+
109
+ @index = ::Stickler::Repository::Index.new( @specifications_dir )
44
110
  end
45
111
 
46
112
  #
@@ -66,25 +132,14 @@ module Stickler::Repository
66
132
  end
67
133
 
68
134
  #
69
- # A list of all the specs in the repo
135
+ # Forward some calls directly to the index
70
136
  #
71
- def specs
72
- @index.specs
73
- end
74
-
75
- #
76
- # A list of just the latests specs in the repo
77
- #
78
- def latest_specs
79
- @index.latest_specs
80
- end
81
-
82
- #
83
- # The last time this index was modified
84
- #
85
- def last_modified_time
86
- @index.last_modified_time
87
- end
137
+ extend Forwardable
138
+ def_delegators :@index, :specs,
139
+ :released_specs,
140
+ :latest_specs,
141
+ :prerelease_specs,
142
+ :last_modified_time
88
143
 
89
144
  #
90
145
  # See Api#search_for
@@ -45,8 +45,6 @@ module Stickler::Repository
45
45
  alias :latest_specs :empty_array
46
46
  alias :search_for :empty_array
47
47
 
48
- def
49
-
50
48
  def specs
51
49
  Array.new
52
50
  end
@@ -1,5 +1,6 @@
1
- require 'resourceful'
1
+ require 'excon'
2
2
  require 'stickler/repository'
3
+ require 'stickler/version'
3
4
  require 'stickler/repository/api'
4
5
  require 'stickler/repository/rubygems_authenticator'
5
6
  require 'stringio'
@@ -11,19 +12,13 @@ module ::Stickler::Repository
11
12
  # cutter api (push/yank/unyank). The legacy gem server api is not utilized.
12
13
  #
13
14
  class Remote
14
- # the http client
15
- attr_reader :http
16
15
 
17
- def initialize( repo_uri, options = {} )
18
- options[:authenticator] ||= Stickler::Repository::RubygemsAuthenticator.new
19
- options[:cache_manager] ||= Resourceful::InMemoryCacheManager.new
20
- if options.delete(:debug) then
21
- options[:logger] ||= Resourceful::StdOutLogger.new
22
- end
16
+ attr_reader :authenticator
23
17
 
24
- @uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
25
- @http = Resourceful::HttpAccessor.new( options )
26
- @specs_list = nil
18
+ def initialize( repo_uri, options = {} )
19
+ @authenticator = options[:authenticator] || Stickler::Repository::RubygemsAuthenticator.new
20
+ @uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
21
+ @specs_list = nil
27
22
  end
28
23
 
29
24
  #
@@ -80,13 +75,11 @@ module ::Stickler::Repository
80
75
  def push( path )
81
76
  spec = speclite_from_gem_file( path )
82
77
  raise Stickler::Repository::Error, "gem #{spec.full_name} already exists in remote repository" if remote_gem_file_exist?( spec )
83
- begin
84
- resp = push_resource.post( IO.read( path ) )
85
- rescue Resourceful::UnsuccessfulHttpRequestError => e
86
- msg = "Failure pushing #{path} to remote repository : response code => #{e.http_response.code}, response message => '#{e.http_response.body}'"
87
- raise Stickler::Repository::Error, msg
88
- end
78
+ resp = resource_request( push_resource, :body => IO.read( path ) )
89
79
  return spec
80
+ rescue Excon::Errors::Error => e
81
+ msg = "Failure pushing #{path} to remote repository : response code => #{e.response.status}, response message => '#{e.response.body}'"
82
+ raise Stickler::Repository::Error, msg
90
83
  end
91
84
 
92
85
  #
@@ -94,15 +87,11 @@ module ::Stickler::Repository
94
87
  #
95
88
  def yank( spec )
96
89
  return nil unless remote_gem_file_exist?( spec )
97
- begin
98
- form_data = Resourceful::UrlencodedFormData.new
99
- form_data.add( "gem_name", spec.name )
100
- form_data.add( "version", spec.version.to_s )
101
- yank_resource.request( :delete, form_data.read, {'Content-Type' => form_data.content_type } )
102
- return full_uri_to_gem( spec )
103
- rescue Resourceful::UnsuccessfulHttpRequestError => e
104
- raise Stickler::Repository::Error, "Failure yanking: #{e.inspect}"
105
- end
90
+ query = { :gem_name => spec.name, :version => spec.version.to_s }
91
+ resp = resource_request( yank_resource, :query => query )
92
+ return full_uri_to_gem( spec )
93
+ rescue Excon::Errors::Error => e
94
+ raise Stickler::Repository::Error, "Failure yanking: #{e.inspect}"
106
95
  end
107
96
 
108
97
  #
@@ -110,12 +99,10 @@ module ::Stickler::Repository
110
99
  #
111
100
  def delete( spec )
112
101
  return false unless remote_gem_file_exist?( spec )
113
- begin
114
- gem_resource( spec ).delete
115
- return true
116
- rescue Resourceful::UnsuccessfulHttpRequestError => e
117
- return false
118
- end
102
+ resource_request( gem_resource( spec ), :method => :delete )
103
+ return true
104
+ rescue Excon::Errors::Error => e
105
+ return false
119
106
  end
120
107
 
121
108
  #
@@ -123,22 +110,21 @@ module ::Stickler::Repository
123
110
  #
124
111
  def open( spec, &block )
125
112
  return nil unless remote_gem_file_exist?( spec )
126
- begin
127
- data = download_resource( gem_resource( spec ) )
128
- io = StringIO.new( data , "rb" )
129
- if block_given? then
130
- begin
131
- yield io
132
- ensure
133
- io.close
134
- end
135
- else
136
- return io
113
+ data = download_resource( gem_resource( spec ) )
114
+ io = StringIO.new( data , "rb" )
115
+ if block_given? then
116
+ begin
117
+ yield io
118
+ ensure
119
+ io.close
137
120
  end
138
- rescue Resourceful::UnsuccessfulHttpRequestError => e
139
- return nil
121
+ else
122
+ return io
140
123
  end
141
124
  nil
125
+ rescue Excon::Errors::Error => e
126
+ $stderr.puts e.inspect
127
+ return nil
142
128
  end
143
129
 
144
130
  private
@@ -162,7 +148,7 @@ module ::Stickler::Repository
162
148
  end
163
149
 
164
150
  def specs_list_resource
165
- @specs_list_resource ||= @http.resource( specs_list_uri )
151
+ @specs_list_resource ||= Excon.new( specs_list_uri.to_s, :method => :get, :expects => [200] )
166
152
  end
167
153
 
168
154
  def push_uri
@@ -170,7 +156,11 @@ module ::Stickler::Repository
170
156
  end
171
157
 
172
158
  def push_resource
173
- @push_resource ||= @http.resource( push_uri, { 'Content-Type' => 'application/octet-stream' } )
159
+ unless @push_resource then
160
+ params = { :method => :post, :headers => { 'Content-Type' => 'application/octet-stream' }, :expects => [ 201, 200 ] }
161
+ @push_resource = Excon.new( push_uri.to_s, params )
162
+ end
163
+ return @push_resource
174
164
  end
175
165
 
176
166
  def yank_uri
@@ -178,11 +168,17 @@ module ::Stickler::Repository
178
168
  end
179
169
 
180
170
  def yank_resource
181
- @yank_resource ||= @http.resource( yank_uri )
171
+ unless @yank_resource then
172
+ params = { :method => :delete,
173
+ :headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
174
+ :expects => [200] }
175
+ @yank_resource = Excon.new( yank_uri.to_s, params )
176
+ end
177
+ return @yank_resource
182
178
  end
183
179
 
184
180
  def gem_resource( spec )
185
- @http.resource( full_uri_to_gem( spec ) )
181
+ Excon.new( full_uri_to_gem( spec ), :method => :get, :expects => [200] )
186
182
  end
187
183
 
188
184
  def download_specs_list
@@ -198,15 +194,14 @@ module ::Stickler::Repository
198
194
  end
199
195
 
200
196
  def download_uri( uri )
201
- download_resource( http.resource( uri ) )
197
+ download_resource( Excon.new( uri ) )
202
198
  end
203
199
 
204
200
  def download_resource( resource )
205
- begin
206
- resource.get.body
207
- rescue Resourceful::UnsuccessfulHttpRequestError => e
208
- return false
209
- end
201
+ resource_request( resource, :method => :get, :expects => [200] ).body
202
+ rescue Excon::Errors::Error => e
203
+ puts e.inspect
204
+ return false
210
205
  end
211
206
 
212
207
  def remote_gem_file_exist?( spec )
@@ -215,14 +210,10 @@ module ::Stickler::Repository
215
210
  end
216
211
 
217
212
  def remote_uri_exist?( uri )
218
- begin
219
- # FIXME: bug in Resourceful that uses cached HEAD responses
220
- # to satisfy later GET requests.
221
- rc = http.resource( uri ).head( 'Cache-Control' => 'no-store' ).successful?
222
- return rc
223
- rescue Resourceful::UnsuccessfulHttpRequestError => e
224
- return false
225
- end
213
+ rc = resource_request( Excon.new( uri.to_s ), :method => :head, :expects => [200] )
214
+ return true
215
+ rescue Excon::Errors::Error => e
216
+ return false
226
217
  end
227
218
 
228
219
  def speclite_from_gem_file( path )
@@ -237,5 +228,31 @@ module ::Stickler::Repository
237
228
  def speclite_from_specification( spec )
238
229
  Stickler::SpecLite.new( spec.name, spec.version.to_s, spec.platform )
239
230
  end
231
+
232
+ def resource_request( resource, params = {} )
233
+ trys = 0
234
+ begin
235
+ resource.connection[:headers]['User-Agent'] = "Stickler Client v#{Stickler::VERSION}"
236
+ resource.connection[:headers].delete('Authorization')
237
+ if authenticator.handles?( resource.connection[:scheme], resource.connection[:host] ) then
238
+ resource.connection[:headers]['Authorization'] = authenticator.credentials
239
+ end
240
+ trys += 1
241
+ #puts "Making request #{resource.connection.inspect} with extra params #{params.inspect}"
242
+ resource.request( params )
243
+ rescue Excon::Errors::MovedPermanently, Excon::Errors::Found,
244
+ Excon::Errors::SeeOther, Excon::Errors::TemporaryRedirect => redirect
245
+ # follow a redirect, it is only allowable to follow redirects from a GET or
246
+ # HEAD request. Only follow a few times though.
247
+ raise redirect unless [ :get, :head ].include?( redirect.request[:method] )
248
+ raise redirect if trys > 5
249
+ #puts "Redirecting to #{redirect.response.headers['Location']}"
250
+ resource = Excon::Connection.new( redirect.response.headers['Location'],
251
+ { :headers => resource.connection[:headers],
252
+ :query => resource.connection[:headers],
253
+ :method => resource.connection[:method] } )
254
+ retry
255
+ end
256
+ end
240
257
  end
241
258
  end