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.
Files changed (72) hide show
  1. data/HISTORY.rdoc +5 -2
  2. data/Rakefile +31 -0
  3. data/examples/config.ru +9 -0
  4. data/examples/gemcutter_repo.ru +19 -0
  5. data/examples/index_repo.ru +15 -0
  6. data/examples/local_repo.ru +14 -0
  7. data/examples/mirror_repo.ru +16 -0
  8. data/examples/not_found.ru +8 -0
  9. data/lib/stickler/error.rb +3 -0
  10. data/lib/stickler/middleware/compression.rb +30 -0
  11. data/lib/stickler/middleware/gemcutter.rb +62 -0
  12. data/lib/stickler/middleware/helpers.rb +84 -0
  13. data/lib/stickler/middleware/index.rb +137 -0
  14. data/lib/stickler/middleware/local.rb +38 -0
  15. data/lib/stickler/middleware/mirror.rb +60 -0
  16. data/lib/stickler/middleware/not_found.rb +62 -0
  17. data/lib/stickler/middleware.rb +4 -0
  18. data/lib/stickler/repository/api.rb +167 -0
  19. data/lib/stickler/repository/index.rb +97 -0
  20. data/lib/stickler/repository/local.rb +251 -0
  21. data/lib/stickler/repository/mirror.rb +48 -0
  22. data/lib/stickler/repository/null.rb +58 -0
  23. data/lib/stickler/repository/remote.rb +235 -0
  24. data/lib/stickler/repository.rb +7 -499
  25. data/lib/stickler/spec_lite.rb +60 -14
  26. data/lib/stickler/version.rb +6 -6
  27. data/lib/stickler/web.rb +19 -0
  28. data/spec/data/gems/bar-1.0.0.gem +0 -0
  29. data/spec/data/gems/foo-1.0.0.gem +0 -0
  30. data/spec/data/specifications/bar-1.0.0.gemspec +31 -0
  31. data/spec/data/specifications/foo-1.0.0.gemspec +31 -0
  32. data/spec/middleware/common_gem_server_helpers.rb +67 -0
  33. data/spec/middleware/index_spec.rb +26 -0
  34. data/spec/middleware/legacy_gem_server_behavior.rb +33 -0
  35. data/spec/middleware/local_spec.rb +25 -0
  36. data/spec/middleware/modern_gem_server_behavior.rb +20 -0
  37. data/spec/middleware/not_found_spec.rb +25 -0
  38. data/spec/repository/api_behavior.rb +162 -0
  39. data/spec/repository/api_spec.rb +38 -0
  40. data/spec/repository/index_spec.rb +32 -0
  41. data/spec/repository/local_spec.rb +36 -0
  42. data/spec/repository/null_spec.rb +17 -0
  43. data/spec/repository/remote_spec.rb +49 -0
  44. data/spec/spec.opts +2 -0
  45. data/spec/spec_helper.rb +15 -3
  46. data/spec/spec_lite_spec.rb +62 -0
  47. data/stickler.gemspec +60 -0
  48. data/views/index.erb +19 -0
  49. data/views/layout.erb +39 -0
  50. metadata +93 -63
  51. data/COPYING +0 -339
  52. data/bin/stickler +0 -58
  53. data/data/stickler.yml +0 -14
  54. data/gemspec.rb +0 -62
  55. data/lib/stickler/cli.rb +0 -302
  56. data/lib/stickler/configuration.rb +0 -74
  57. data/lib/stickler/console.rb +0 -72
  58. data/lib/stickler/paths.rb +0 -62
  59. data/lib/stickler/source.rb +0 -75
  60. data/lib/stickler/source_group.rb +0 -365
  61. data/lib/stickler.rb +0 -19
  62. data/spec/configuration_spec.rb +0 -68
  63. data/spec/paths_spec.rb +0 -25
  64. data/spec/repository_spec.rb +0 -55
  65. data/spec/version_spec.rb +0 -17
  66. data/tasks/announce.rake +0 -39
  67. data/tasks/config.rb +0 -107
  68. data/tasks/distribution.rake +0 -45
  69. data/tasks/documentation.rake +0 -31
  70. data/tasks/rspec.rake +0 -29
  71. data/tasks/rubyforge.rake +0 -51
  72. 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
- === Version 0.1.1
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
- === Version 0.1.0
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
+ }
@@ -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,3 @@
1
+ module Stickler
2
+ class Error < ::StandardError ; end
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module Stickler
2
+ module Middleware
3
+ end
4
+ end