stickler 2.3.0 → 2.4.0

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 (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 )