stickler 2.0.0a → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.bnsignore +14 -0
  2. data/.gitignore +17 -0
  3. data/HISTORY.asciidoc +20 -0
  4. data/README.asciidoc +126 -0
  5. data/Rakefile +22 -3
  6. data/bin/stickler +50 -0
  7. data/bin/stickler-passenger-config +112 -0
  8. data/bin/stickler-server +109 -0
  9. data/examples/config.ru +6 -3
  10. data/examples/gemcutter_repo.ru +2 -0
  11. data/examples/index_repo.ru +2 -0
  12. data/examples/local_repo.ru +6 -3
  13. data/examples/mirror_repo.ru +2 -0
  14. data/examples/not_found.ru +2 -0
  15. data/lib/stickler.rb +12 -0
  16. data/lib/stickler/client.rb +47 -0
  17. data/lib/stickler/client/config.rb +35 -0
  18. data/lib/stickler/client/config_file.rb +58 -0
  19. data/lib/stickler/client/mirror.rb +61 -0
  20. data/lib/stickler/client/push.rb +50 -0
  21. data/lib/stickler/client/yank.rb +51 -0
  22. data/lib/stickler/logable.rb +35 -0
  23. data/lib/stickler/middleware/gemcutter.rb +5 -0
  24. data/lib/stickler/middleware/helpers.rb +32 -0
  25. data/lib/stickler/middleware/index.rb +30 -4
  26. data/lib/stickler/middleware/mirror.rb +8 -3
  27. data/lib/stickler/middleware/not_found.rb +4 -2
  28. data/lib/stickler/paths.rb +53 -0
  29. data/lib/stickler/repository/local.rb +12 -12
  30. data/lib/stickler/repository/mirror.rb +13 -6
  31. data/lib/stickler/repository/null.rb +1 -0
  32. data/lib/stickler/repository/remote.rb +10 -4
  33. data/lib/stickler/repository/rubygems_authenticator.rb +32 -0
  34. data/lib/stickler/server.rb +34 -0
  35. data/lib/stickler/server/public/css/blueprint/LICENSE +22 -0
  36. data/lib/stickler/server/public/css/blueprint/ie.css +35 -0
  37. data/lib/stickler/server/public/css/blueprint/screen.css +266 -0
  38. data/lib/stickler/server/public/css/style.css +19 -0
  39. data/lib/stickler/server/public/images/apple-touch-icon.png +0 -0
  40. data/lib/stickler/server/public/images/favicon.ico +0 -0
  41. data/lib/stickler/server/public/js/modernizr-1.5.min.js +28 -0
  42. data/lib/stickler/server/views/index.erb +35 -0
  43. data/lib/stickler/server/views/layout.erb +42 -0
  44. data/lib/stickler/spec_lite.rb +16 -6
  45. data/lib/stickler/version.rb +1 -1
  46. data/man/asciidoc.conf +25 -0
  47. data/man/stickler-passenger-config.asciidoc +74 -0
  48. data/man/stickler-server.asciidoc +87 -0
  49. data/man/stickler.asciidoc +148 -0
  50. data/spec/middleware/common_gem_server_helpers.rb +4 -2
  51. data/spec/middleware/index_spec.rb +3 -3
  52. data/spec/middleware/legacy_gem_server_behavior.rb +0 -2
  53. data/spec/middleware/local_spec.rb +3 -3
  54. data/spec/middleware/modern_gem_server_behavior.rb +2 -0
  55. data/spec/paths_spec.rb +13 -0
  56. data/spec/spec_lite_spec.rb +14 -0
  57. data/tasks/man.rake +19 -0
  58. metadata +183 -56
  59. data/HISTORY.rdoc +0 -12
  60. data/README.rdoc +0 -88
  61. data/lib/stickler/web.rb +0 -19
  62. data/stickler.gemspec +0 -60
  63. data/views/index.erb +0 -19
  64. data/views/layout.erb +0 -39
@@ -45,6 +45,38 @@ module Stickler::Middleware
45
45
  [ specs_by_repo.values ].flatten.sort
46
46
  end
47
47
 
48
+ #
49
+ # return the specs as a hash of lists, keyedy by gemname
50
+ #
51
+ def specs_by_name
52
+ specs_grouped_by_name( specs )
53
+ end
54
+
55
+ #
56
+ # Return all the specs as a hash of specs_by_name. The keys
57
+ # in this case are the first character of the gem name
58
+ #
59
+ def specs_by_first_upcase_char
60
+ by_char = Hash.new{ |h,k| h[k] = Array.new }
61
+ specs.each do |spec|
62
+ by_char[spec.name[0...1].upcase] << spec
63
+ end
64
+
65
+ by_char.keys.each { |k| by_char[k] = specs_grouped_by_name(by_char[k]) }
66
+ return by_char
67
+ end
68
+
69
+ #
70
+ # Given a list of specs, this will group them by name
71
+ #
72
+ def specs_grouped_by_name( list )
73
+ by_name = Hash.new{ |h,k| h[k] = Array.new }
74
+ list.each do |spec|
75
+ by_name[spec.name.downcase] << spec
76
+ end
77
+ return by_name
78
+ end
79
+
48
80
  #
49
81
  # Append spec or array of specs to the current list of specs for this key.
50
82
  #
@@ -2,6 +2,9 @@ require 'sinatra'
2
2
  require 'stickler/middleware'
3
3
  require 'stickler/middleware/helpers'
4
4
  require 'stickler/repository/null'
5
+ require 'stickler/spec_lite'
6
+ require 'stickler/logable'
7
+ require 'stickler/paths'
5
8
 
6
9
  module Stickler::Middleware
7
10
  # Index is a Rack middleware that passes all requests through except for those
@@ -45,10 +48,17 @@ module Stickler::Middleware
45
48
  class Index < ::Sinatra::Base
46
49
  include Stickler::Middleware::Helpers::Compression
47
50
  include Stickler::Middleware::Helpers::Specs
51
+ include Stickler::Logable
48
52
 
49
53
  # The respository of the Index is a Repository::Null
50
54
  attr_reader :repo
51
55
 
56
+ server_path = Stickler::Paths.lib_path( "stickler", "server" )
57
+
58
+ set :views, File.join( server_path, "views" )
59
+ set :public, File.join( server_path, "public" )
60
+ set :static, true
61
+
52
62
  def initialize( app, opts = {} )
53
63
  @app = app
54
64
  @repo = ::Stickler::Repository::Null.new
@@ -62,10 +72,11 @@ module Stickler::Middleware
62
72
  end
63
73
 
64
74
  get '/' do
75
+ append_specs
65
76
  if @serve_indexes then
66
77
  erb :index
67
78
  else
68
- not_found
79
+ pass
69
80
  end
70
81
  end
71
82
 
@@ -106,9 +117,24 @@ module Stickler::Middleware
106
117
  name, version, platform = *params[:captures]
107
118
  spec = Stickler::SpecLite.new( name, version, platform )
108
119
  full_path = @repo.full_path_to_gem( spec )
109
- if full_path then
110
- content_type 'application/x-tar'
111
- send_file( full_path )
120
+ if full_path and File.exist?( full_path )then
121
+ send_file( full_path, :type => "application/x-tar" )
122
+ else
123
+ pass
124
+ end
125
+ end
126
+
127
+ #
128
+ # Serve up a gemspec. This is really only used by the child classes.
129
+ # an Index instance will never have any gemspecs to return
130
+ #
131
+ get %r{\A/quick/Marshal.#{Gem.marshal_version}/(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz\Z} do
132
+ name, version, platform, with_compression = *params[:captures]
133
+ spec = Stickler::SpecLite.new( name, version, platform )
134
+ full_path = @repo.full_path_to_specification( spec )
135
+ if full_path and File.exist?( full_path ) then
136
+ self.compression = :deflate # always compressed
137
+ marshal( eval( IO.read( full_path ) ) )
112
138
  else
113
139
  pass
114
140
  end
@@ -36,14 +36,20 @@ module Stickler::Middleware
36
36
 
37
37
  begin
38
38
  if spec = @repo.mirror( host , spec ) then
39
+ logger.info("Mirrored #{spec.file_name}")
39
40
  status 201
40
41
  response["Location"] = "/gems/#{spec.file_name}"
41
42
  nil
42
43
  else
44
+ logger.info( "Unable to find #{spec.full_name} at #{host}" )
43
45
  not_found "Unable to find gem [#{spec.full_name}] at source #{host}"
44
46
  end
45
- rescue ::Stickler::Repository::Mirror::Error => e
46
- error( 409, e.message )
47
+ rescue ::Stickler::Repository::Mirror::ConflictError => ce
48
+ logger.error( ce.message )
49
+ error( 409, ce.message )
50
+ rescue ::Stickler::Repository::Mirror::NotFoundError => nfe
51
+ logger.error( nfe.message )
52
+ not_found nfe.message
47
53
  end
48
54
  end
49
55
 
@@ -57,4 +63,3 @@ module Stickler::Middleware
57
63
  end
58
64
  end
59
65
 
60
-
@@ -1,10 +1,12 @@
1
1
  require 'stickler/middleware'
2
2
  module Stickler::Middleware
3
3
  #
4
- # Idea completely taken from rack-contrib
4
+ # Idea completely taken from rack-contrib, it can function as a middleware
5
+ # also, and in that case, completely swallows all requests and returns the
6
+ # 4040 page.
5
7
  #
6
8
  class NotFound
7
- def initialize
9
+ def initialize( app = nil )
8
10
  @body = <<-_
9
11
  <?xml version="1.0" encoding="UTF-8"?>
10
12
  <!DOCTYPE html html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
@@ -0,0 +1,53 @@
1
+ #--
2
+ # Copyright (c) 2010 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ #
6
+ module Stickler
7
+ #
8
+ # Access to various paths inside the project programatically
9
+ #
10
+ module Paths
11
+ #
12
+ # :call-seq:
13
+ # Stickler::Paths.root_dir -> String
14
+ #
15
+ # Returns The full expanded path of the parent directory of +lib+
16
+ # going up the path from the current file. A trailing File::SEPARATOR
17
+ # is guaranteed.
18
+ #
19
+ def self.root_dir
20
+ @root_dir ||=(
21
+ path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
22
+ lib_index = path_parts.rindex("lib")
23
+ @root_dir = path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
24
+ )
25
+ end
26
+
27
+ #
28
+ # :call-seq:
29
+ # Stickler::Paths.lib_path( *args ) -> String
30
+ #
31
+ # Returns The full expanded path of the +lib+ directory below
32
+ # _root_dir_. All parameters passed in are joined onto the
33
+ # result. A trailing File::SEPARATOR is guaranteed if
34
+ # _args_ are *not* present.
35
+ #
36
+ def self.lib_path(*args)
37
+ self.sub_path("lib", *args)
38
+ end
39
+
40
+ #
41
+ # :call-seq:
42
+ # Stickler::Paths.sub_path( sub, *args ) -> String
43
+ #
44
+ # Returns the full expanded path of the +sub+ directory below _root_dir. All
45
+ # _arg_ parameters passed in are joined onto the result. A trailing
46
+ # File::SEPARATOR is guaranteed if _args_ are *not* present.
47
+ #
48
+ def self.sub_path(sub,*args)
49
+ sp = ::File.join(root_dir, sub) + File::SEPARATOR
50
+ sp = ::File.join(sp, *args) if args
51
+ end
52
+ end
53
+ end
@@ -179,6 +179,18 @@ module Stickler::Repository
179
179
  File.join( gems_dir, spec.file_name )
180
180
  end
181
181
 
182
+ def full_path_to_specification( spec )
183
+ File.join( specifications_dir, spec.spec_file_name )
184
+ end
185
+
186
+ def gem_file_exist?( spec )
187
+ File.exist?( full_path_to_gem( spec ) )
188
+ end
189
+
190
+ def specification_file_exist?( spec )
191
+ File.exist?( full_path_to_specification( spec ) )
192
+ end
193
+
182
194
 
183
195
  private
184
196
 
@@ -188,10 +200,6 @@ module Stickler::Repository
188
200
  end
189
201
  end
190
202
 
191
- def full_path_to_specification( spec )
192
- File.join( specifications_dir, spec.spec_file_name )
193
- end
194
-
195
203
  def install( spec, io )
196
204
  install_gem( spec, io )
197
205
  install_specification( spec )
@@ -231,14 +239,6 @@ module Stickler::Repository
231
239
  return true if File.unlink( path ) > 0
232
240
  end
233
241
 
234
- def gem_file_exist?( spec )
235
- File.exist?( full_path_to_gem( spec ) )
236
- end
237
-
238
- def specification_file_exist?( spec )
239
- File.exist?( full_path_to_specification( spec ) )
240
- end
241
-
242
242
  def specification_from_gem_file( path )
243
243
  format = Gem::Format.from_file_by_path( path )
244
244
  return format.spec
@@ -11,17 +11,22 @@ module Stickler::Repository
11
11
  # and store in the Local instance
12
12
  #
13
13
  class Mirror
14
- class Error < ::Stickler::Repository::Error ; end
14
+ class ConflictError < ::Stickler::Repository::Error ; end
15
+ class NotFoundError < ::Stickler::Repository::Error ; end
15
16
 
16
17
  extend Forwardable
17
18
 
19
+ include Stickler::Logable
20
+
18
21
  def initialize( root_dir )
19
- @root_dir = root_dir
20
- @local_repo = ::Stickler::Repository::Local.new( @root_dir )
22
+ @local_repo = ::Stickler::Repository::Local.new( root_dir )
21
23
  @remote_repos = {}
22
24
  end
23
25
  def_delegators :@local_repo, :uri, :gems_uri, :uri_for_gem, :search_for,
24
- :push, :delete, :get, :open
26
+ :push, :delete, :get, :open,
27
+ :specs, :latest_specs, :root_dir,
28
+ :last_modified_time, :full_path_to_gem,
29
+ :full_path_to_specification
25
30
 
26
31
  #
27
32
  # :call-seq:
@@ -32,17 +37,19 @@ module Stickler::Repository
32
37
  #
33
38
  def mirror( host, spec )
34
39
  specs = @local_repo.search_for( spec )
35
- raise Error, "gem #{spec.full_name} already exists" unless specs.empty?
40
+ raise ConflictError, "gem #{spec.full_name} already exists" unless specs.empty?
36
41
 
37
42
  repo = remote_repo_for( host )
38
43
  repo.open( spec ) do |io|
39
44
  @local_repo.add( io )
40
45
  end
46
+ raise NotFoundError, "Unable to find gem #{spec.full_name} on #{host}" unless @local_repo.gem_file_exist?( spec )
47
+ logger.info( "Downloaded #{spec.full_name} from #{host} and adding to repo" )
41
48
  return spec
42
49
  end
43
50
 
44
51
  def remote_repo_for( host )
45
- @remote_repos[host] ||= ::Stickler::Repository::Remote.new( host )
52
+ @remote_repos[host] ||= ::Stickler::Repository::Remote.new( host, :debug => true )
46
53
  end
47
54
  end
48
55
  end
@@ -32,6 +32,7 @@ module Stickler::Repository
32
32
  alias :open :nilish
33
33
  alias :uri_for_gem :nilish
34
34
  alias :full_path_to_gem :nilish
35
+ alias :full_path_to_specification :nilish
35
36
 
36
37
  def last_modified_time
37
38
  Time.now
@@ -1,6 +1,7 @@
1
1
  require 'resourceful'
2
2
  require 'stickler/repository'
3
3
  require 'stickler/repository/api'
4
+ require 'stickler/repository/rubygems_authenticator'
4
5
  require 'stringio'
5
6
 
6
7
  module ::Stickler::Repository
@@ -13,10 +14,15 @@ module ::Stickler::Repository
13
14
  # the http client
14
15
  attr_reader :http
15
16
 
16
- def initialize( repo_uri )
17
+ def initialize( repo_uri, options = {} )
18
+ options[:authenticator] ||= Stickler::Repository::RubygemsAuthenticator.new
19
+ options[:cache_manager] ||= Resourceful::InMemoryCacheManager.new
20
+ if options.delete(:debug) then
21
+ options[:logger] ||= Resourceful::StdOutLogger.new
22
+ end
23
+
17
24
  @uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
18
- @http = Resourceful::HttpAccessor.new( :cache_manager => Resourceful::InMemoryCacheManager.new )
19
- # :logger => Resourceful::StdOutLogger.new )
25
+ @http = Resourceful::HttpAccessor.new( options )
20
26
  @specs_list = nil
21
27
  end
22
28
 
@@ -164,7 +170,7 @@ module ::Stickler::Repository
164
170
  end
165
171
 
166
172
  def push_resource
167
- @push_resource ||= @http.resource( push_uri, { 'Content-Type', 'application/octet-stream' } )
173
+ @push_resource ||= @http.resource( push_uri, { 'Content-Type' => 'application/octet-stream' } )
168
174
  end
169
175
 
170
176
  def yank_uri
@@ -0,0 +1,32 @@
1
+ require 'addressable/uri'
2
+
3
+ module Stickler::Repository
4
+ #
5
+ # When talking to rubygems itself, the rubygems_api key is required.
6
+ # This authenticator is injected automatically if the host you are
7
+ # talking to matches the rubygems host
8
+ #
9
+ class RubygemsAuthenticator
10
+ def self.rubygems_uri
11
+ @rubygems_uri ||= Addressable::URI.parse( "https://rubygems.org" )
12
+ end
13
+
14
+ def credentials
15
+ Gem.configuration.rubygems_api_key
16
+ end
17
+
18
+ def rubygems_uri
19
+ self.class.rubygems_uri
20
+ end
21
+
22
+ def can_handle?( request )
23
+ request_uri = Addressable::URI.parse(request.uri)
24
+ return (request_uri.host == rubygems_uri.host ) &&
25
+ (request_uri.scheme == rubygems_uri.scheme)
26
+ end
27
+
28
+ def add_credentials_to(request)
29
+ request.header['Authorization'] = credentials
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ require 'stickler/error'
2
+ require 'stickler/middleware/compression'
3
+ require 'stickler/middleware/gemcutter'
4
+ require 'stickler/middleware/mirror'
5
+ require 'stickler/middleware/index'
6
+ require 'stickler/middleware/not_found'
7
+ require 'rack/commonlogger'
8
+
9
+ module Stickler
10
+ class Server
11
+
12
+ # The directory holding all the repositories
13
+ attr_reader :stickler_root
14
+
15
+ def initialize( stickler_root )
16
+ @stickler_root = File.expand_path( stickler_root )
17
+ raise ::Stickler::Error, "Stickler root directory '#{@stickler_root}' must already exist" unless File.directory?( @stickler_root )
18
+ raise ::Stickler::Error, "Stickler root directory '#{@stickler_root}' must be writable" unless File.writable?( @stickler_root )
19
+ end
20
+
21
+ def app
22
+ root = self.stickler_root
23
+ Rack::Builder.new do
24
+ use Rack::CommonLogger
25
+ use Stickler::Middleware::Compression
26
+ use Stickler::Middleware::Gemcutter, :serve_indexes => false, :repo_root => File.join( root, "gemcutter" )
27
+ use Stickler::Middleware::Mirror, :serve_indexes => false, :repo_root => File.join( root, "mirror" )
28
+ use Stickler::Middleware::Index, :serve_indexes => true
29
+ use Stickler::Middleware::NotFound
30
+ run Sinatra::Base
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2007 - 2010 blueprintcss.org
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ /* -----------------------------------------------------------------------
2
+
3
+
4
+ Blueprint CSS Framework 0.9
5
+ http://blueprintcss.org
6
+
7
+ * Copyright (c) 2007-Present. See LICENSE for more info.
8
+ * See README for instructions on how to use Blueprint.
9
+ * For credits and origins, see AUTHORS.
10
+ * This is a compressed file. See the sources in the 'src' directory.
11
+
12
+ ----------------------------------------------------------------------- */
13
+
14
+ /* ie.css */
15
+ body {text-align:center;}
16
+ .container {text-align:left;}
17
+ * html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;}
18
+ * html legend {margin:0px -8px 16px 0;padding:0;}
19
+ sup {vertical-align:text-top;}
20
+ sub {vertical-align:text-bottom;}
21
+ html>body p code {*white-space:normal;}
22
+ hr {margin:-8px auto 11px;}
23
+ img {-ms-interpolation-mode:bicubic;}
24
+ .clearfix, .container {display:inline-block;}
25
+ * html .clearfix, * html .container {height:1%;}
26
+ fieldset {padding-top:0;}
27
+ textarea {overflow:auto;}
28
+ input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
29
+ input.text:focus, input.title:focus {border-color:#666;}
30
+ input.text, input.title, textarea, select {margin:0.5em 0;}
31
+ input.checkbox, input.radio {position:relative;top:.25em;}
32
+ form.inline div, form.inline p {vertical-align:middle;}
33
+ form.inline label {position:relative;top:-0.25em;}
34
+ form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;}
35
+ button, input.button {position:relative;top:0.25em;}