stickler 2.0.0a → 2.0.1

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 (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;}