stickler 0.1.1 → 2.0.0a

Sign up to get free protection for your applications and to get access to all the features.
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