stickler 0.1.1 → 2.0.0a
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +5 -2
- data/Rakefile +31 -0
- data/examples/config.ru +9 -0
- data/examples/gemcutter_repo.ru +19 -0
- data/examples/index_repo.ru +15 -0
- data/examples/local_repo.ru +14 -0
- data/examples/mirror_repo.ru +16 -0
- data/examples/not_found.ru +8 -0
- data/lib/stickler/error.rb +3 -0
- data/lib/stickler/middleware/compression.rb +30 -0
- data/lib/stickler/middleware/gemcutter.rb +62 -0
- data/lib/stickler/middleware/helpers.rb +84 -0
- data/lib/stickler/middleware/index.rb +137 -0
- data/lib/stickler/middleware/local.rb +38 -0
- data/lib/stickler/middleware/mirror.rb +60 -0
- data/lib/stickler/middleware/not_found.rb +62 -0
- data/lib/stickler/middleware.rb +4 -0
- data/lib/stickler/repository/api.rb +167 -0
- data/lib/stickler/repository/index.rb +97 -0
- data/lib/stickler/repository/local.rb +251 -0
- data/lib/stickler/repository/mirror.rb +48 -0
- data/lib/stickler/repository/null.rb +58 -0
- data/lib/stickler/repository/remote.rb +235 -0
- data/lib/stickler/repository.rb +7 -499
- data/lib/stickler/spec_lite.rb +60 -14
- data/lib/stickler/version.rb +6 -6
- data/lib/stickler/web.rb +19 -0
- data/spec/data/gems/bar-1.0.0.gem +0 -0
- data/spec/data/gems/foo-1.0.0.gem +0 -0
- data/spec/data/specifications/bar-1.0.0.gemspec +31 -0
- data/spec/data/specifications/foo-1.0.0.gemspec +31 -0
- data/spec/middleware/common_gem_server_helpers.rb +67 -0
- data/spec/middleware/index_spec.rb +26 -0
- data/spec/middleware/legacy_gem_server_behavior.rb +33 -0
- data/spec/middleware/local_spec.rb +25 -0
- data/spec/middleware/modern_gem_server_behavior.rb +20 -0
- data/spec/middleware/not_found_spec.rb +25 -0
- data/spec/repository/api_behavior.rb +162 -0
- data/spec/repository/api_spec.rb +38 -0
- data/spec/repository/index_spec.rb +32 -0
- data/spec/repository/local_spec.rb +36 -0
- data/spec/repository/null_spec.rb +17 -0
- data/spec/repository/remote_spec.rb +49 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +15 -3
- data/spec/spec_lite_spec.rb +62 -0
- data/stickler.gemspec +60 -0
- data/views/index.erb +19 -0
- data/views/layout.erb +39 -0
- metadata +93 -63
- data/COPYING +0 -339
- data/bin/stickler +0 -58
- data/data/stickler.yml +0 -14
- data/gemspec.rb +0 -62
- data/lib/stickler/cli.rb +0 -302
- data/lib/stickler/configuration.rb +0 -74
- data/lib/stickler/console.rb +0 -72
- data/lib/stickler/paths.rb +0 -62
- data/lib/stickler/source.rb +0 -75
- data/lib/stickler/source_group.rb +0 -365
- data/lib/stickler.rb +0 -19
- data/spec/configuration_spec.rb +0 -68
- data/spec/paths_spec.rb +0 -25
- data/spec/repository_spec.rb +0 -55
- data/spec/version_spec.rb +0 -17
- data/tasks/announce.rake +0 -39
- data/tasks/config.rb +0 -107
- data/tasks/distribution.rake +0 -45
- data/tasks/documentation.rake +0 -31
- data/tasks/rspec.rake +0 -29
- data/tasks/rubyforge.rake +0 -51
- data/tasks/utils.rb +0 -80
data/HISTORY.rdoc
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
= stickler Changelog
|
2
|
+
== Version 0.1.2
|
2
3
|
|
3
|
-
|
4
|
+
* fix compatibility with gems 1.3.1
|
5
|
+
|
6
|
+
== Version 0.1.1
|
4
7
|
* 1 minor bugfix
|
5
8
|
* remove unnecessary require 'progressbar'
|
6
9
|
|
7
|
-
|
10
|
+
== Version 0.1.0
|
8
11
|
* {Managing a Gem Repository with Stickler}[http://copiousfreetime.org/articles/2008/10/09/managing-a-gem-repository-with-stickler.html]
|
9
12
|
* Initial public release
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'bones'
|
3
|
+
rescue LoadError
|
4
|
+
abort '### Please install the "bones" gem ###'
|
5
|
+
end
|
6
|
+
|
7
|
+
task :default => 'spec:run'
|
8
|
+
task 'gem:release' => 'spec:run'
|
9
|
+
|
10
|
+
Bones {
|
11
|
+
name 'stickler'
|
12
|
+
authors 'Jeremy Hinegardner'
|
13
|
+
email 'jeremy@hinegardner.org'
|
14
|
+
url 'http://rubygems.org/gems/stickler'
|
15
|
+
|
16
|
+
ruby_opts %w[-W0 -rubygems]
|
17
|
+
readme_file 'README.rdoc'
|
18
|
+
ignore_file '.gitignore'
|
19
|
+
history_file 'HISTORY.rdoc'
|
20
|
+
rubyforge.name 'copiousfreetime'
|
21
|
+
|
22
|
+
spec.opts << "--color" << "--format specdoc"
|
23
|
+
|
24
|
+
depend_on 'sinatra', '~> 1.0.0'
|
25
|
+
depend_on 'addressable', '~> 2.1.2'
|
26
|
+
depend_on 'resourceful', '~> 1.0.1'
|
27
|
+
|
28
|
+
depend_on 'bones' , '~> 3.4.6', :development => true
|
29
|
+
depend_on 'rack-test' , '~> 0.5.4', :development => true
|
30
|
+
depend_on 'bones-extras', '~> 1.2.4', :development => true
|
31
|
+
}
|
data/examples/config.ru
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Example rackup file for an entire stickler stack
|
3
|
+
#-----------------------------------------------------------------------
|
4
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
5
|
+
|
6
|
+
require 'stickler/web'
|
7
|
+
|
8
|
+
use Stickler::Web
|
9
|
+
run Sinatra::Base
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# An Example remote repository that implements all the methods that are
|
3
|
+
# required to satisfy being talked to by a Respository::Remote client.
|
4
|
+
# This means it needs to speak:
|
5
|
+
# - the gem cutter api
|
6
|
+
# - the modern gem server ai
|
7
|
+
#
|
8
|
+
#-----------------------------------------------------------------------
|
9
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
10
|
+
|
11
|
+
require 'stickler/middleware/gemcutter'
|
12
|
+
require 'stickler/middleware/compression'
|
13
|
+
|
14
|
+
gem_dir = File.join( File.expand_path( File.dirname( __FILE__ ) ), "..", "spec", "tmp" )
|
15
|
+
|
16
|
+
use ::Stickler::Middleware::Compression
|
17
|
+
use ::Stickler::Middleware::Gemcutter, :repo_root => gem_dir
|
18
|
+
run ::Sinatra::Base
|
19
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Example rackup file for serving up a null repository. This really
|
3
|
+
# would never be used in the wild, but it shows the basics of what
|
4
|
+
# is required to setup a stickler webstack
|
5
|
+
#-----------------------------------------------------------------------
|
6
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
7
|
+
|
8
|
+
require 'stickler/middleware/compression'
|
9
|
+
require 'stickler/middleware/index'
|
10
|
+
|
11
|
+
gem_dir = File.join( File.expand_path( File.dirname( __FILE__ ) ), "tmp" )
|
12
|
+
|
13
|
+
use ::Stickler::Middleware::Compression
|
14
|
+
use ::Stickler::Middleware::Index, :repo_root => gem_dir
|
15
|
+
run ::Sinatra::Base
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Example rackup file for serving up a single repository. This repository
|
3
|
+
# will respond to index and gem requests.
|
4
|
+
#-----------------------------------------------------------------------
|
5
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
6
|
+
|
7
|
+
require 'stickler/middleware/compression'
|
8
|
+
require 'stickler/middleware/repo_local'
|
9
|
+
|
10
|
+
gem_dir = File.join( File.expand_path( File.dirname( __FILE__ ) ), "data" )
|
11
|
+
|
12
|
+
use ::Stickler::Middleware::Compression
|
13
|
+
use ::Stickler::Middleware::RepoLocal, :repo_root => gem_dir
|
14
|
+
run ::Sinatra::Base
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Example repository for serving up a Mirror repository. This repo
|
3
|
+
# will respond to mirroring requests and mirror gems from a remote
|
4
|
+
# repository locally.
|
5
|
+
#-----------------------------------------------------------------------
|
6
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
7
|
+
|
8
|
+
require 'stickler/middleware/compression'
|
9
|
+
require 'stickler/middleware/mirror'
|
10
|
+
|
11
|
+
gem_dir = File.join( File.expand_path( File.dirname( __FILE__ ) ), "tmp" )
|
12
|
+
|
13
|
+
use ::Stickler::Middleware::Compression
|
14
|
+
use ::Stickler::Middleware::Mirror, :repo_root => gem_dir
|
15
|
+
run ::Sinatra::Base
|
16
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#-----------------------------------------------------------------------
|
2
|
+
# Serve up the not found page for everything
|
3
|
+
#-----------------------------------------------------------------------
|
4
|
+
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
|
5
|
+
|
6
|
+
require 'stickler/middleware/not_found'
|
7
|
+
|
8
|
+
run ::Stickler::Middleware::NotFound.new
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'stickler/middleware'
|
3
|
+
module Stickler::Middleware
|
4
|
+
class Compression
|
5
|
+
def initialize( app )
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call( env )
|
10
|
+
status, headers, body = @app.call( env )
|
11
|
+
return [ status, headers, body ] unless status == 200
|
12
|
+
|
13
|
+
headers = ::Rack::Utils::HeaderHash.new( headers )
|
14
|
+
stream = body
|
15
|
+
|
16
|
+
if compress_method = env['stickler.compression'] then
|
17
|
+
headers.delete('Content-Length')
|
18
|
+
case compress_method
|
19
|
+
when :gzip
|
20
|
+
headers['Content-Type'] = 'application/x-gzip'
|
21
|
+
stream = Gem.gzip( body.first )
|
22
|
+
when :deflate
|
23
|
+
headers['Content-Type'] = 'application/x-deflate'
|
24
|
+
stream = Gem.deflate( body.first )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return [ status, headers, stream ]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'stickler/middleware'
|
3
|
+
require 'stickler/middleware/local'
|
4
|
+
require 'stickler/repository/local'
|
5
|
+
|
6
|
+
module Stickler::Middleware
|
7
|
+
#
|
8
|
+
# A rack middleware for implementing the gemcutter api
|
9
|
+
#
|
10
|
+
# == Options
|
11
|
+
#
|
12
|
+
# <b>:serve_indexes</b>:: the same as the Index middleware
|
13
|
+
#
|
14
|
+
# <b>:repo_root</b>:: the same as the Local middleware
|
15
|
+
#
|
16
|
+
# The <b>:repo_root</b> option is required.
|
17
|
+
#
|
18
|
+
# == Usage
|
19
|
+
#
|
20
|
+
# use Stickler::Middleware::Gemcutter, :repo_root => '/path/to/repository'
|
21
|
+
#
|
22
|
+
# use Stickler::Middleware::Gemcutter, :repo_root => '/path/to/repository',
|
23
|
+
# :serve_indexes => true
|
24
|
+
#
|
25
|
+
#
|
26
|
+
class Gemcutter < ::Stickler::Middleware::Local
|
27
|
+
|
28
|
+
def initialize( app = nil, options = {} )
|
29
|
+
super( app, options )
|
30
|
+
end
|
31
|
+
|
32
|
+
# gemcutter push
|
33
|
+
post '/api/v1/gems' do
|
34
|
+
begin
|
35
|
+
spec = @repo.add( request.body )
|
36
|
+
return spec.to_s
|
37
|
+
rescue Stickler::Repository::Error => e
|
38
|
+
error( 500, "Error adding gem to repo: #{e}" )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# gemcutter yank
|
43
|
+
delete '/api/v1/gems/yank' do
|
44
|
+
spec = Stickler::SpecLite.new( params[:gem_name], params[:version] )
|
45
|
+
if s = @repo.yank( spec ) then
|
46
|
+
return "Yanked #{s.to_s}"
|
47
|
+
else
|
48
|
+
error( 503, "Did not Yank #{spec.to_s}" )
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# direct delete
|
53
|
+
delete %r{\A/gems/((.*?)-([0-9.]+)(-.*?)?)\.gem\Z} do
|
54
|
+
full_name, name, version, platform = *params[:captures]
|
55
|
+
spec = Stickler::SpecLite.new( name, version, platform )
|
56
|
+
@repo.delete( spec )
|
57
|
+
return "deleted gem #{spec.full_name}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'stickler/middleware'
|
2
|
+
module Stickler::Middleware
|
3
|
+
module Helpers
|
4
|
+
#
|
5
|
+
# set what, if any kind of compression to use on the response This is a Gem
|
6
|
+
# server specific type compressions, as it does not set the http headers and
|
7
|
+
# such in the same manner as normal compressed HTTP responses
|
8
|
+
#
|
9
|
+
# compression may be set to one of the following, all others will be
|
10
|
+
# ignored.
|
11
|
+
#
|
12
|
+
# <b>:gzip</b>:: use Gem.gzip
|
13
|
+
# <b>:deflate</b>:: use Gem.deflate
|
14
|
+
# <b>nil</b>:: no compression
|
15
|
+
#
|
16
|
+
module Compression
|
17
|
+
def compression=( type ) env['stickler.compression'] = type end
|
18
|
+
def compression() env['stickler.compression'] end
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Manage the contents of the <tt>stickler.specs</tt> environment variable.
|
23
|
+
# It is used as as communcation method between the various types of
|
24
|
+
# middlewares managing gem repositories. The Index server will use the
|
25
|
+
# values in this variable in generating the responses to gem index requests
|
26
|
+
#
|
27
|
+
# env['stickler.specs'] is a Hash itself, the key being the return value of
|
28
|
+
# +root_dir+ from the Class it is included in, the value for each key is
|
29
|
+
# the Array of SpecLite's.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
module Specs
|
33
|
+
#
|
34
|
+
# The specs by repository
|
35
|
+
#
|
36
|
+
def specs_by_repo
|
37
|
+
env['stickler.specs'] ||= Hash.new{ |h,k| h[k] = Array.new }
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# return the flattened array of all the values in
|
42
|
+
# <tt>#specs_by_repo</tt>
|
43
|
+
#
|
44
|
+
def specs
|
45
|
+
[ specs_by_repo.values ].flatten.sort
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Append spec or array of specs to the current list of specs for this key.
|
50
|
+
#
|
51
|
+
def append_spec( key, spec_or_array_of_specs )
|
52
|
+
if Array === spec_or_array_of_specs then
|
53
|
+
specs_by_repo[key].concat( spec_or_array_of_specs )
|
54
|
+
else
|
55
|
+
specs_by_repo[key] << spec_or_array_of_specs
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Automatically append the specs from the included class into the specs
|
61
|
+
# environment variable.
|
62
|
+
#
|
63
|
+
# The Class that includes this module and wants to use +append_specs+
|
64
|
+
# MUST have a +repo+ method. The +repo+ method must +respond_to+ both
|
65
|
+
# +root_dir+ and +specs+.
|
66
|
+
#
|
67
|
+
def append_specs
|
68
|
+
append_spec( self.repo.root_dir, self.repo.specs )
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Automatically append the latest_specs from the included class into the
|
73
|
+
# specs environment variable.
|
74
|
+
#
|
75
|
+
# The Class that includes this module and wants to use +append_specs+ MUST
|
76
|
+
# have a +repo+ method. The +repo+ method must +respond_to+ both
|
77
|
+
# +root_dir+ and +specs+.
|
78
|
+
#
|
79
|
+
def append_latest_specs
|
80
|
+
append_spec( self.repo.root_dir, self.repo.latest_specs )
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'stickler/middleware'
|
3
|
+
require 'stickler/middleware/helpers'
|
4
|
+
require 'stickler/repository/null'
|
5
|
+
|
6
|
+
module Stickler::Middleware
|
7
|
+
# Index is a Rack middleware that passes all requests through except for those
|
8
|
+
# matching these two urls:
|
9
|
+
#
|
10
|
+
# <b>/specs.#{Gem.marhsal_version}.gz</b>:: The [ name, version, platform ] index
|
11
|
+
# of <b>all<b> the gems in the
|
12
|
+
# entire repository
|
13
|
+
#
|
14
|
+
# <b>/latest_specs.#{Gem.marshal_version}.gz</b>:: The [ name, version, # platform ] index
|
15
|
+
# of the <b>most recent</b> version of each
|
16
|
+
# gem in the repository.
|
17
|
+
#
|
18
|
+
# For these 2 urls, it respond reponds with the summation of all the specs
|
19
|
+
# that are in <tt>env['stickler.specs']</tt>. If there are no specs in that
|
20
|
+
# environment variable, then it returns with an empty index.
|
21
|
+
#
|
22
|
+
# == Options
|
23
|
+
#
|
24
|
+
# This class is also the base class for all the other GemServer type
|
25
|
+
# middlewares, so there is an optional behavior to NOT respond to the index
|
26
|
+
# url requests and just append the spec, or latest_specs to
|
27
|
+
# env['stickler.specs'] instead of serving the values out of there.
|
28
|
+
#
|
29
|
+
# <b>:serve_indexes</b>:: +true+ or +false+ it defaults to +true+. This
|
30
|
+
# option is used when Index is used in a stack
|
31
|
+
# with other Index derived middlewares. In this
|
32
|
+
# case, all of the Index derived middlewares
|
33
|
+
# should set <b>:serve_indexes => false</b> except
|
34
|
+
# for the bottom one. It should set
|
35
|
+
# <b>:serve_indexes => true</b>. This allows all
|
36
|
+
# the Index derived middlewares to cooperatively
|
37
|
+
# respond to the <b>/specs</b> and
|
38
|
+
# </b>/latests_specs</b> urls.
|
39
|
+
#
|
40
|
+
# == Usage
|
41
|
+
#
|
42
|
+
# use Stickler::Middleware::Index, :serve_indexes => true
|
43
|
+
# use Stickler::Middleware::Index, :serve_indexes => false
|
44
|
+
#
|
45
|
+
class Index < ::Sinatra::Base
|
46
|
+
include Stickler::Middleware::Helpers::Compression
|
47
|
+
include Stickler::Middleware::Helpers::Specs
|
48
|
+
|
49
|
+
# The respository of the Index is a Repository::Null
|
50
|
+
attr_reader :repo
|
51
|
+
|
52
|
+
def initialize( app, opts = {} )
|
53
|
+
@app = app
|
54
|
+
@repo = ::Stickler::Repository::Null.new
|
55
|
+
@serve_indexes = opts.has_key?( :serve_indexes ) ? opts[:serve_indexes] : true
|
56
|
+
super( app )
|
57
|
+
end
|
58
|
+
|
59
|
+
before do
|
60
|
+
response["Date"] = @repo.last_modified_time.rfc2822
|
61
|
+
cache_control( 'no-cache' )
|
62
|
+
end
|
63
|
+
|
64
|
+
get '/' do
|
65
|
+
if @serve_indexes then
|
66
|
+
erb :index
|
67
|
+
else
|
68
|
+
not_found
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Respond to the requests for the <b>all gems</b> index
|
74
|
+
#
|
75
|
+
get %r{\A/specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
|
76
|
+
append_specs
|
77
|
+
serve_indexes( with_compression )
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Respond to the requests for the <b>latest gems</b> index
|
82
|
+
#
|
83
|
+
get %r{\A/latest_specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
|
84
|
+
append_latest_specs
|
85
|
+
serve_indexes( with_compression )
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Serve the indexes up as the response if @serve_indexes is true. Otherwise
|
90
|
+
# return false
|
91
|
+
#
|
92
|
+
def serve_indexes( with_compression = :none )
|
93
|
+
if @serve_indexes then
|
94
|
+
self.compression = to_compression_flag( with_compression )
|
95
|
+
return marshalled_specs( specs )
|
96
|
+
else
|
97
|
+
pass
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Actually serve up the gem. This is really only used by the child classes.
|
103
|
+
# an Index instance will never have any gems to return.
|
104
|
+
#
|
105
|
+
get %r{\A/gems/(.*?)-([0-9.]+)(-.*?)?\.gem\Z} do
|
106
|
+
name, version, platform = *params[:captures]
|
107
|
+
spec = Stickler::SpecLite.new( name, version, platform )
|
108
|
+
full_path = @repo.full_path_to_gem( spec )
|
109
|
+
if full_path then
|
110
|
+
content_type 'application/x-tar'
|
111
|
+
send_file( full_path )
|
112
|
+
else
|
113
|
+
pass
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Convert to the array format used by gem servers
|
119
|
+
# everywhere
|
120
|
+
#
|
121
|
+
def marshalled_specs( spec_a )
|
122
|
+
marshal( spec_a.collect { |s| s.to_rubygems_a } )
|
123
|
+
end
|
124
|
+
|
125
|
+
def marshal( data )
|
126
|
+
content_type 'application/octet-stream'
|
127
|
+
::Marshal.dump( data )
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_compression_flag( with_compression )
|
131
|
+
return with_compression if [ :gzip, :deflate, :none ].include?( with_compression )
|
132
|
+
return :gzip if with_compression =~ /\.gz\Z/i
|
133
|
+
return :deflate if with_compression =~ /\.(Z|rz)\Z/i
|
134
|
+
return :none
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'stickler/middleware'
|
3
|
+
require 'stickler/middleware/index'
|
4
|
+
require 'stickler/repository/local'
|
5
|
+
|
6
|
+
module Stickler::Middleware
|
7
|
+
#
|
8
|
+
# A Sinatra middleware that implements the HTTP portions of a Modern gem server.
|
9
|
+
# It sits on top of a Repository::Local and serves up the gems in it.
|
10
|
+
#
|
11
|
+
# It utilizies a Stickler::Repository::Local, and the :repo_root option
|
12
|
+
# is passed directly to it.
|
13
|
+
#
|
14
|
+
# == Options
|
15
|
+
#
|
16
|
+
# <b>:serve_indexes</b>:: the same as the Index middleware
|
17
|
+
#
|
18
|
+
# <b>:repo_root</b>:: The path that is to be the root of the
|
19
|
+
# Repository instance managed by this server.
|
20
|
+
#
|
21
|
+
# The <b>:repo_root</b> option is required.
|
22
|
+
#
|
23
|
+
# == Usage
|
24
|
+
#
|
25
|
+
# use Stickler::Middleware::Local, :repo_root => '/path/to/repository'
|
26
|
+
#
|
27
|
+
# use Stickler::Middleware::Local, :repo_root => '/path/to/repository',
|
28
|
+
# :serve_indexes => true
|
29
|
+
#
|
30
|
+
class Local < Index
|
31
|
+
def initialize( app = nil, opts = {} )
|
32
|
+
super( app, opts )
|
33
|
+
# overwrite the repo that is set in the parent
|
34
|
+
@repo = ::Stickler::Repository::Local.new( opts[:repo_root] )
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'stickler/middleware/index'
|
3
|
+
require 'stickler/repository/mirror'
|
4
|
+
|
5
|
+
module Stickler::Middleware
|
6
|
+
#
|
7
|
+
# A Mirror server keeps gems from one or more upstream gem servers in local
|
8
|
+
# repositories.
|
9
|
+
#
|
10
|
+
# == Options
|
11
|
+
#
|
12
|
+
# <b>:serve_indexes</b>:: the same as the Index middleware
|
13
|
+
#
|
14
|
+
# <b>:repo_root</b>:: The path that is to be the root of the
|
15
|
+
# Repository instance managed by this server.
|
16
|
+
#
|
17
|
+
# The <b>:repo_root</b> option is required.
|
18
|
+
#
|
19
|
+
# == Usage
|
20
|
+
#
|
21
|
+
# use Stickler::Middleware::Mirror, :repo_root => '/path/to/repository'
|
22
|
+
#
|
23
|
+
# use Stickler::Middleware::Mirror, :repo_root => '/path/to/repository',
|
24
|
+
# :serve_indexes => true
|
25
|
+
#
|
26
|
+
class Mirror < ::Stickler::Middleware::Index
|
27
|
+
|
28
|
+
def initialize( app, options = {} )
|
29
|
+
super( app )
|
30
|
+
@repo = ::Stickler::Repository::Mirror.new( options[:repo_root] )
|
31
|
+
end
|
32
|
+
|
33
|
+
def manage( params )
|
34
|
+
host = params[:source]
|
35
|
+
spec = Stickler::SpecLite.new( params[:name], params[:version], params[:platform] )
|
36
|
+
|
37
|
+
begin
|
38
|
+
if spec = @repo.mirror( host , spec ) then
|
39
|
+
status 201
|
40
|
+
response["Location"] = "/gems/#{spec.file_name}"
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
not_found "Unable to find gem [#{spec.full_name}] at source #{host}"
|
44
|
+
end
|
45
|
+
rescue ::Stickler::Repository::Mirror::Error => e
|
46
|
+
error( 409, e.message )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
post '/:source/:name/:version/:platform' do
|
51
|
+
manage( params )
|
52
|
+
end
|
53
|
+
|
54
|
+
post '/:source/:name/:version' do
|
55
|
+
manage( params )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'stickler/middleware'
|
2
|
+
module Stickler::Middleware
|
3
|
+
#
|
4
|
+
# Idea completely taken from rack-contrib
|
5
|
+
#
|
6
|
+
class NotFound
|
7
|
+
def initialize
|
8
|
+
@body = <<-_
|
9
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
10
|
+
<!DOCTYPE html html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
11
|
+
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
12
|
+
|
13
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
14
|
+
<head>
|
15
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
|
16
|
+
<style type="text/css" media="screen">
|
17
|
+
* { margin: 0; padding: 0; border: 0; outline: 0; }
|
18
|
+
div.clear { clear: both; }
|
19
|
+
body { background: #eeeeee; margin: 0; padding: 0; }
|
20
|
+
#wrap { width: 1000px; margin: 0 auto; padding: 30px 50px 20px 50px;
|
21
|
+
background: #fff; border-left: 1px solid #DDD;
|
22
|
+
border-right: 1px solid #DDD; }
|
23
|
+
#header { margin: 0 auto 25px auto; }
|
24
|
+
h1 { margin: 0; font-size: 36px; color: #981919; text-align: center; }
|
25
|
+
h2 { margin: 0; font-size: 22px; color: #333333; }
|
26
|
+
table.gem { width: 980px; text-align: left; font-size: 12px;
|
27
|
+
color: #666666; padding: 0; border-spacing: 0;
|
28
|
+
border: 1px solid #EEEEEE; border-bottom: 0;
|
29
|
+
border-left: 0;
|
30
|
+
clear:both;}
|
31
|
+
table.gem tr th { padding: 2px 10px; font-weight: bold;
|
32
|
+
font-size: 22px; background: #f7f7f7; text-align: center;
|
33
|
+
border-left: 1px solid #eeeeee;
|
34
|
+
border-bottom: 1px solid #eeeeee; }
|
35
|
+
table.gem tr td { padding: 2px 20px 2px 10px;
|
36
|
+
border-bottom: 1px solid #eeeeee;
|
37
|
+
border-left: 1px solid #eeeeee; }
|
38
|
+
|
39
|
+
</style>
|
40
|
+
<title>Stickler - Not Found</title>
|
41
|
+
</head>
|
42
|
+
<body>
|
43
|
+
<div id="wrap">
|
44
|
+
<div id="header">
|
45
|
+
<h1>Nothing Found</h1>
|
46
|
+
</div>
|
47
|
+
<h2>Try <a href="/">Over Here</a></h2>
|
48
|
+
</div>
|
49
|
+
</body>
|
50
|
+
</html>
|
51
|
+
_
|
52
|
+
@size = @body.size.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def call( env )
|
56
|
+
[ 404,
|
57
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => @size },
|
58
|
+
[ @body ]
|
59
|
+
]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|