stickler 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +15 -0
  2. data/CONTRIBUTING.md +5 -4
  3. data/HISTORY.md +16 -9
  4. data/LICENSE +1 -1
  5. data/Manifest.txt +34 -20
  6. data/README.md +128 -0
  7. data/Rakefile +10 -9
  8. data/bin/stickler +9 -6
  9. data/bin/stickler-passenger-config +8 -8
  10. data/bin/stickler-server +12 -12
  11. data/examples/as_middleware.ru +14 -0
  12. data/examples/auth_repo.ru +1 -1
  13. data/examples/gemcutter_repo.ru +1 -1
  14. data/examples/local_repo.ru +1 -1
  15. data/lib/stickler.rb +3 -1
  16. data/lib/stickler/client.rb +2 -1
  17. data/lib/stickler/client/delete.rb +1 -1
  18. data/lib/stickler/client/latest-version.rb +40 -0
  19. data/lib/stickler/client/mirror.rb +47 -15
  20. data/lib/stickler/client/push.rb +1 -1
  21. data/lib/stickler/client/unyank.rb +1 -1
  22. data/lib/stickler/client/yank.rb +1 -1
  23. data/lib/stickler/gem_container.rb +40 -0
  24. data/lib/stickler/gemfile_lock_parser.rb +47 -0
  25. data/lib/stickler/middleware.rb +1 -0
  26. data/lib/stickler/middleware/server.rb +37 -0
  27. data/lib/stickler/repository/api.rb +16 -0
  28. data/lib/stickler/repository/index.rb +0 -3
  29. data/lib/stickler/repository/local.rb +6 -8
  30. data/lib/stickler/repository/remote.rb +29 -7
  31. data/lib/stickler/server.rb +2 -6
  32. data/man/stickler-passenger-config.1 +2 -22
  33. data/man/stickler-server.1 +9 -99
  34. data/man/stickler.1 +15 -173
  35. data/man/stickler.1.ronn +6 -0
  36. data/tasks/default.rake +16 -18
  37. data/tasks/man.rake +7 -0
  38. data/tasks/this.rb +5 -5
  39. data/test/data/Gemfile.lock.example +56 -0
  40. data/{spec → test}/data/gemcutter/gems/foo-1.0.0.gem +0 -0
  41. data/{spec → test}/data/gems/bar-1.0.0.gem +0 -0
  42. data/{spec → test}/data/gems/baz-3.1.4-java.gem +0 -0
  43. data/{spec → test}/data/gems/baz-3.1.4.gem +0 -0
  44. data/{spec → test}/data/gems/foo-1.0.0.gem +0 -0
  45. data/{spec → test}/data/gems/foo-2.0.0a.gem +0 -0
  46. data/test/data/specifications/bar-1.0.0.gemspec +31 -0
  47. data/test/data/specifications/baz-3.1.4-java.gemspec +32 -0
  48. data/test/data/specifications/baz-3.1.4.gemspec +31 -0
  49. data/test/data/specifications/foo-1.0.0.gemspec +31 -0
  50. data/test/data/specifications/foo-2.0.0a.gemspec +32 -0
  51. data/test/index_test_helpers.rb +75 -0
  52. data/test/middleware/test_local.rb +75 -0
  53. data/test/middleware/test_not_found.rb +26 -0
  54. data/test/repository/test_api.rb +49 -0
  55. data/test/repository/test_api_behavior.rb +208 -0
  56. data/test/repository/test_index.rb +48 -0
  57. data/test/repository/test_local.rb +59 -0
  58. data/test/repository/test_null.rb +15 -0
  59. data/test/repository/test_remote.rb +26 -0
  60. data/test/repository/test_remote_authenticated.rb +39 -0
  61. data/test/stickler_test_server.rb +35 -0
  62. data/test/test_gemfile_lock_parser.rb +28 -0
  63. data/test/test_paths.rb +22 -0
  64. data/test/test_spec_lite.rb +90 -0
  65. data/test/test_stickler.rb +49 -0
  66. metadata +58 -85
  67. data/README.rdoc +0 -156
  68. data/spec/index_spec_helpers.rb +0 -73
  69. data/spec/middleware/local_spec.rb +0 -72
  70. data/spec/middleware/not_found_spec.rb +0 -25
  71. data/spec/paths_spec.rb +0 -11
  72. data/spec/repository/api_behavior.rb +0 -192
  73. data/spec/repository/api_spec.rb +0 -37
  74. data/spec/repository/index_spec.rb +0 -46
  75. data/spec/repository/local_spec.rb +0 -49
  76. data/spec/repository/null_spec.rb +0 -14
  77. data/spec/repository/remote_spec.rb +0 -86
  78. data/spec/spec.opts +0 -2
  79. data/spec/spec_helper.rb +0 -24
  80. data/spec/spec_lite_spec.rb +0 -96
@@ -0,0 +1,14 @@
1
+ #-----------------------------------------------------------------------
2
+ #-*- vim: set ft=ruby: -*-
3
+ #
4
+ # Example rackup file for using stickler as middleware in a larger application
5
+ #-----------------------------------------------------------------------
6
+ $:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
7
+
8
+ require 'stickler'
9
+
10
+ root = File.expand_path( File.join( File.dirname( __FILE__ ), *%w[ .. test data ]))
11
+
12
+ puts root
13
+ use ::Stickler::Middleware::Server, :stickler_root => root
14
+ run ::Sinatra::Base
@@ -7,7 +7,7 @@ $:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
7
7
 
8
8
  require 'stickler'
9
9
 
10
- tmp = File.expand_path( File.join( File.dirname( __FILE__ ), "..", "spec", "data" ) )
10
+ tmp = File.expand_path( File.join( File.dirname( __FILE__ ), "..", "test", "tmp" ) )
11
11
 
12
12
  use Rack::Auth::Basic, 'Secure Stickler' do |u,p|
13
13
  (u == "stickler") and (p == "secret")
@@ -12,7 +12,7 @@ $:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
12
12
 
13
13
  require 'stickler'
14
14
 
15
- gem_dir = File.expand_path( "../spec/tmp", File.dirname( __FILE__ ) )
15
+ gem_dir = File.expand_path( "../test/tmp", File.dirname( __FILE__ ) )
16
16
 
17
17
  use ::Stickler::Middleware::Compression
18
18
  use ::Stickler::Middleware::Gemcutter, :repo_root => gem_dir
@@ -9,7 +9,7 @@ $:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )
9
9
  require 'stickler/middleware/compression'
10
10
  require 'stickler/middleware/local'
11
11
 
12
- gem_dir = File.expand_path( File.join( File.dirname( __FILE__ ), *%w[ .. spec data ]))
12
+ gem_dir = File.expand_path( File.join( File.dirname( __FILE__ ), *%w[ .. test data ]))
13
13
 
14
14
  puts gem_dir
15
15
  use ::Stickler::Middleware::Compression
@@ -1,6 +1,6 @@
1
1
  module Stickler
2
2
  # The Current Version of the library
3
- VERSION = "2.3.0"
3
+ VERSION = "2.4.0"
4
4
  end
5
5
  require 'sinatra/base'
6
6
 
@@ -8,6 +8,8 @@ require 'stickler/logable'
8
8
  require 'stickler/error'
9
9
  require 'stickler/paths'
10
10
  require 'stickler/spec_lite'
11
+ require 'stickler/gem_container'
12
+ require 'stickler/gemfile_lock_parser'
11
13
 
12
14
  require 'stickler/repository'
13
15
  require 'stickler/middleware'
@@ -30,7 +30,7 @@ module Stickler
30
30
  def parse( argv )
31
31
  opts = Trollop::with_standard_exception_handling( parser ) do
32
32
  o = parser.parse( argv )
33
- yield parser if block_given?
33
+ yield( parser, o ) if block_given?
34
34
  return o
35
35
  end
36
36
  return opts
@@ -49,3 +49,4 @@ require 'stickler/client/mirror'
49
49
  require 'stickler/client/push'
50
50
  require 'stickler/client/unyank'
51
51
  require 'stickler/client/yank'
52
+ require 'stickler/client/latest-version'
@@ -22,7 +22,7 @@ _
22
22
 
23
23
  def parse( argv )
24
24
  gem_name = nil
25
- opts = super( argv ) do |p|
25
+ opts = super( argv ) do |p,o|
26
26
  raise Trollop::CommandlineError, "At least one gem is required to delete" if p.leftovers.empty?
27
27
  gem_name = p.leftovers.shift
28
28
  end
@@ -0,0 +1,40 @@
1
+ module Stickler
2
+ class Client
3
+ class LatestVersion < Stickler::Client
4
+ def self.banner
5
+ <<-_
6
+ Prints the latest version of a gem
7
+
8
+ Usage: stickler latest-version gem-name
9
+
10
+ Options:
11
+ _
12
+ end
13
+
14
+ def parse( argv )
15
+ gem_name = nil
16
+ opts = super( argv ) do |p,o|
17
+ raise Trollop::CommandlineError, "At lest one gem-name is required" if p.leftovers.empty?
18
+ gem_name = p.leftovers.shift
19
+ end
20
+ opts[:gem_name] = gem_name
21
+ return opts
22
+ end
23
+
24
+ def run
25
+ opts = parse( self.argv )
26
+ repo = remote_repo_for( opts )
27
+ match = repo.latest_specs_list.find do |name, version, platform|
28
+ name == opts[:gem_name]
29
+ end
30
+ if match then
31
+ $stdout.puts match[1]
32
+ else
33
+ $stdout.puts "Gem #{opts[:gem_name]} not found in remote repository"
34
+ end
35
+ rescue Stickler::Repository::Error => e
36
+ $stdout.puts "ERROR: #{e.message}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -5,9 +5,11 @@ module Stickler
5
5
  def self.banner
6
6
  <<-_
7
7
  Pull a specific version of a gem from an upstream gem server
8
- and store it in a stickler server.
8
+ and store it in a stickler server. Either a specific version
9
+ must be specificied, or a Gemfile.lock must be used.
9
10
 
10
11
  Usage: stickler mirror [options] --gem-version x.y.z gem
12
+ stickler mirror [options] Gemfile.lock
11
13
 
12
14
  Options:
13
15
  _
@@ -17,19 +19,26 @@ _
17
19
  unless @parser then
18
20
  @parser = super
19
21
  @parser.opt( :upstream, "The upstream gem server from which to pull", :type => :string, :default => Client.config.upstream )
20
- @parser.opt( :gem_version, "The version of the gem to yank (required)", :type => :string, :required => true )
21
- @parser.opt( :platform, "The platform of the gem to yank", :type => :string, :default => ::Gem::Platform::RUBY )
22
+ @parser.opt( :gem_version, "The version of the gem to mirror", :type => :string)
23
+ @parser.opt( :platform, "The platform of the gem to mirror", :type => :string, :default => ::Gem::Platform::RUBY )
22
24
  end
23
25
  return @parser
24
26
  end
25
27
 
26
28
  def parse( argv )
27
- gem_name = nil
28
- opts = super( argv ) do |p|
29
- raise Trollop::CommandlineError, "At least one gem is required to mirror" if p.leftovers.empty?
30
- gem_name = p.leftovers.shift
29
+ gem_name = nil
30
+ gemfile_lock = nil
31
+ opts = super( argv ) do |p, o|
32
+ raise Trollop::CommandlineError, "A Gemfile.lock or a gem name is required to mirror" if p.leftovers.empty?
33
+ if o[:gem_version] then
34
+ gem_name = p.leftovers.shift
35
+ else
36
+ gemfile_lock = p.leftovers.shift
37
+ raise Trollop::CommandlineError, "#{lock} must be readable" unless File.readable?( gemfile_lock )
38
+ end
31
39
  end
32
- opts[:gem_name] = gem_name
40
+ opts[:gem_name] = gem_name
41
+ opts[:gemfile_lock] = gemfile_lock
33
42
  return opts
34
43
  end
35
44
 
@@ -41,24 +50,47 @@ _
41
50
  Stickler::Repository::RemoteMirror.new( opts[:server], :debug => opts[:debug] )
42
51
  end
43
52
 
44
- def run
45
- opts = parse( self.argv )
46
- repo = remote_repo_for( opts )
47
- spec = Stickler::SpecLite.new( opts[:gem_name], opts[:gem_version], opts[:platform] )
48
- upstream_host = Addressable::URI.parse( opts[:upstream] ).host
53
+ def spec_list( opts )
54
+ if opts[:gem_name] then
55
+ return [Stickler::SpecLite.new( opts[:gem_name], opts[:gem_version], opts[:platform] )]
56
+ end
57
+
58
+ if opts[:gemfile_lock] then
59
+ parser = Stickler::GemfileLockParser.new( opts[:gemfile_lock] )
60
+ return parser.gem_dependencies
61
+ end
62
+ raise Sticker::Error, "No gem name, or gemfile lock... no idea what to do"
63
+ end
49
64
 
65
+ def mirror_one_spec( repo, spec, upstream_host )
50
66
  $stdout.write "Asking #{repo.uri} to mirror #{spec.full_name} from #{upstream_host} : "
51
67
  $stdout.flush
52
68
 
53
69
  resp = repo.mirror( spec, upstream_host )
54
-
55
70
  $stdout.puts "OK -> #{repo.uri.join(resp.headers['Location'])}"
71
+
72
+ rescue Stickler::Repository::Error => e
73
+ $stdout.puts "ERROR: #{e.message}"
74
+ rescue StandardError => e
75
+ $stdout.puts e.backtrace.join("\n")
76
+ $stdout.puts "ERROR -> #{e.message}"
77
+ end
78
+
79
+ def run
80
+ opts = parse( self.argv )
81
+ repo = remote_repo_for( opts )
82
+ specs = spec_list( opts )
83
+ upstream_host = Addressable::URI.parse( opts[:upstream] ).host
84
+
85
+ specs.each do |spec|
86
+ mirror_one_spec( repo, spec, upstream_host )
87
+ end
56
88
  rescue Stickler::Repository::Error => e
57
89
  $stdout.puts "ERROR: #{e.message}"
58
90
  rescue StandardError => e
59
91
  puts e.backtrace.join("\n")
60
92
  $stdout.puts "ERROR -> #{e.message}"
61
- end
93
+ end
62
94
  end
63
95
  end
64
96
  end
@@ -14,7 +14,7 @@ _
14
14
 
15
15
  def parse( argv )
16
16
  gemfiles = []
17
- opts = super do |p|
17
+ opts = super do |p,o|
18
18
  raise Trollop::CommandlineError, "At least one file is required to push" if p.leftovers.empty?
19
19
  p.leftovers.each do |gemfile|
20
20
  raise Trollop::CommandlineError, "#{gemfile} must be readable" unless File.readable?( gemfile )
@@ -22,7 +22,7 @@ _
22
22
 
23
23
  def parse( argv )
24
24
  gem_name = nil
25
- opts = super( argv ) do |p|
25
+ opts = super( argv ) do |p,o|
26
26
  raise Trollop::CommandlineError, "At least one gem is required to unyank" if p.leftovers.empty?
27
27
  gem_name = p.leftovers.shift
28
28
  end
@@ -23,7 +23,7 @@ _
23
23
 
24
24
  def parse( argv )
25
25
  gem_name = nil
26
- opts = super( argv ) do |p|
26
+ opts = super( argv ) do |p,o|
27
27
  raise Trollop::CommandlineError, "At least one gem is required to yank" if p.leftovers.empty?
28
28
  gem_name = p.leftovers.shift
29
29
  end
@@ -0,0 +1,40 @@
1
+ module Stickler
2
+ #
3
+ # Wrap the class that opens the gem file and gives access to all the gem file
4
+ # internals. The class that implements this in rubygems itself changed, so we
5
+ # need to be backwards compatible with folks that are using older versions of
6
+ # rubygems.
7
+ #
8
+ class GemContainer
9
+ attr_reader :path
10
+ def initialize( gem_file_path )
11
+ @path = gem_file_path
12
+ @container = load_container( path )
13
+ end
14
+
15
+ def spec
16
+ @container.spec
17
+ end
18
+
19
+ private
20
+
21
+ # Rubygems transitions to using Gem::Package, so if we have that use it,
22
+ # otherwise fall back to the older method of using Gem::Format
23
+ begin
24
+ require 'rubygems/package'
25
+ def load_container( path )
26
+ Gem::Package.new( path )
27
+ end
28
+ rescue LoadError
29
+ puts "Unable to load 'rubygems/package' falling back to Gem::Format"
30
+ begin
31
+ require 'rubygems/format'
32
+ def load_container( path )
33
+ Gem::Format.from_file_by_path( path )
34
+ end
35
+ rescue LoadError
36
+ abort "FAilure to load rubygems/format"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ module Stickler
2
+ class GemfileLockParser
3
+ attr_reader :gem_dependencies
4
+
5
+
6
+ def initialize( path )
7
+ p = Pathname.new( path )
8
+ raise Stickler::Error, "#{path} does not exist" unless p.exist?
9
+ raise Stickler::Error, "#{path} is not readable" unless p.readable?
10
+ parse( p.read )
11
+ end
12
+
13
+ def depends_on?( name )
14
+ gem_dependencies.any?{ |spec| spec.name == name }
15
+ end
16
+
17
+ private
18
+
19
+ def parse( text )
20
+ parts = partition( text )
21
+ @gem_dependencies = parse_dependencies( parts['GEM'] )
22
+ end
23
+
24
+ def parse_dependencies( lines )
25
+ drop_until_specs( lines )
26
+ deps = []
27
+ lines.each do |line|
28
+ md = line.match( /\A\s{4}(\S+)\s+\(([\w\.]+)\)\Z/ )
29
+ next if md.nil?
30
+ deps << Stickler::SpecLite.new( md.captures[0], md.captures[1] )
31
+ end
32
+ return deps
33
+ end
34
+
35
+ def drop_until_specs( lines )
36
+ lines.drop_while{ |l| %w[ remote specs ].include?( l.strip.split(":").first ) }
37
+ end
38
+
39
+ def partition( text )
40
+ text.split("\n\n").each_with_object({}) { | p, h |
41
+ next if p.empty?
42
+ parts = p.split("\n").map(&:rstrip)
43
+ h[parts.first] = parts[1..-1]
44
+ }
45
+ end
46
+ end
47
+ end
@@ -9,3 +9,4 @@ require 'stickler/middleware/index'
9
9
  require 'stickler/middleware/local'
10
10
  require 'stickler/middleware/mirror'
11
11
  require 'stickler/middleware/not_found'
12
+ require 'stickler/middleware/server'
@@ -0,0 +1,37 @@
1
+ require 'pathname'
2
+ module Stickler::Middleware
3
+ # Server is the entire stickler stack as a single piece of middleware that
4
+ # may be used by other libraries that would like to include Stickler in their
5
+ # application.
6
+ class Server
7
+ attr_reader :stickler_root
8
+
9
+ def initialize( app, opts = {} )
10
+ @app = app
11
+ @stickler_root = Pathname.new( opts.fetch( :stickler_root ) ).realpath
12
+ @run = server_app
13
+ validate
14
+ end
15
+
16
+ def call( env )
17
+ @run.call( env )
18
+ end
19
+
20
+ def server_app
21
+ root = self.stickler_root
22
+ Rack::Builder.app( @app )do
23
+ use Stickler::Middleware::Compression
24
+ use Stickler::Middleware::Gemcutter, :serve_indexes => false, :repo_root => root.join( "gemcutter" )
25
+ use Stickler::Middleware::Mirror, :serve_indexes => false, :repo_root => root.join( "mirror" )
26
+ use Stickler::Middleware::Index, :serve_indexes => true
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def validate
33
+ raise ::Stickler::Error, "Stickler root directory '#{stickler_root}' must already exist" unless stickler_root.directory?
34
+ raise ::Stickler::Error, "Stickler root directory '#{stickler_root}' must be writable" unless stickler_root.writable?
35
+ end
36
+ end
37
+ end
@@ -109,6 +109,22 @@ module Stickler::Repository
109
109
  raise NotImplementedError, not_implemented_msg( :yank )
110
110
  end
111
111
 
112
+ #
113
+ # :call-seq:
114
+ # repo.unyank( spec ) -> true or nil
115
+ #
116
+ # "unyank" in the sense of undoing "yank"
117
+ #
118
+ # This means, put the gem matching +spec+ back into the index so that it
119
+ # will be found during searching.
120
+ #
121
+ # If the gem is sucessfully put back into the index then true is returned.
122
+ # Otherwise nil is returned
123
+ #
124
+ def unyank( spec )
125
+ raise NotImplementedError, not_implemented_msg( :unyank )
126
+ end
127
+
112
128
  #
113
129
  # :call-seq:
114
130
  # repo.get( spec ) -> bytes
@@ -11,9 +11,6 @@ module Stickler::Repository
11
11
  class Index
12
12
  class Error < ::Stickler::Repository::Error; end
13
13
 
14
- # The list of specs in the index
15
- attr_reader :specs
16
-
17
14
  # The directory the specs live
18
15
  attr_reader :spec_dir
19
16
 
@@ -2,7 +2,6 @@ require 'stickler/repository/index'
2
2
  require 'addressable/uri'
3
3
  require 'tempfile'
4
4
  require 'forwardable'
5
- require 'rubygems/format'
6
5
 
7
6
  module Stickler::Repository
8
7
  #
@@ -180,13 +179,14 @@ module Stickler::Repository
180
179
  #
181
180
  def add( io )
182
181
  # spooling to a temp file because Gem::Format.from_io() closes the io
183
- # stream it is sent. Why it does this, I do not know.
182
+ # stream it is sent. Why it does this, I do not know. This may be
183
+ # removed once we are no longer using older rubygems
184
184
  tempfile = Tempfile.new( "uploaded-gem.", temp_dir )
185
185
  tempfile.write( io.read )
186
186
  tempfile.rewind
187
187
 
188
- format = Gem::Format.from_file_by_path( tempfile.path )
189
- spec = Stickler::SpecLite.new( format.spec.name, format.spec.version, format.spec.platform )
188
+ container = Stickler::GemContainer.new( tempfile.path )
189
+ spec = Stickler::SpecLite.new( container.spec.name, container.spec.version, container.spec.platform )
190
190
  specs = search_for( spec )
191
191
 
192
192
  raise Error, "gem #{spec.full_name} already exists" unless specs.empty?
@@ -201,8 +201,6 @@ module Stickler::Repository
201
201
  # See Api#push
202
202
  #
203
203
  def push( path )
204
- # is this line needed? Never used.
205
- # spec = specification_from_gem_file( path )
206
204
  result = nil
207
205
  File.open( path ) do |io|
208
206
  result = add( io )
@@ -302,8 +300,8 @@ module Stickler::Repository
302
300
  end
303
301
 
304
302
  def specification_from_gem_file( path )
305
- format = Gem::Format.from_file_by_path( path )
306
- return format.spec
303
+ container = Stickler::GemContainer.new( path )
304
+ return container.spec
307
305
  end
308
306
 
309
307
  def speclite_from_specification( spec )