stickler 0.1.1 → 2.0.0a
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.
- 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
|