Permalink
Browse files

Add the ability to use a Gemfile.lock for mirroring

Closes #13
  • Loading branch information...
1 parent f420074 commit b31021a28f815579fd17b0f76cab39ec27708a79 @copiousfreetime committed Oct 15, 2013
View
@@ -2,21 +2,22 @@
pkg
log
doc
-coverage
+coverage/
*.swp
*.swo
.*.swp
announcement.txt
-spec/tmp
version.txt
*.pid
tmp
*.html
-*.gemspec
+stickler.gemspec
work/
scratch/
*.css
-Gemfile*
+Gemfile
+Gemfile.lock
.rvmrc
-spec/data/mirror
-spec/data/gemcutter
+test/data/mirror
+test/data/gemcutter
+test/tmp
View
@@ -9,6 +9,7 @@ module Stickler
require 'stickler/paths'
require 'stickler/spec_lite'
require 'stickler/gem_container'
+require 'stickler/gemfile_lock_parser'
require 'stickler/repository'
require 'stickler/middleware'
View
@@ -30,7 +30,7 @@ def parser
def parse( argv )
opts = Trollop::with_standard_exception_handling( parser ) do
o = parser.parse( argv )
- yield parser if block_given?
+ yield( parser, o ) if block_given?
return o
end
return opts
@@ -5,9 +5,11 @@ class Mirror < Stickler::Client
def self.banner
<<-_
Pull a specific version of a gem from an upstream gem server
-and store it in a stickler server.
+and store it in a stickler server. Either a specific version
+must be specificied, or a Gemfile.lock must be used.
Usage: stickler mirror [options] --gem-version x.y.z gem
+ stickler mirror [options] Gemfile.lock
Options:
_
@@ -17,19 +19,26 @@ def parser
unless @parser then
@parser = super
@parser.opt( :upstream, "The upstream gem server from which to pull", :type => :string, :default => Client.config.upstream )
- @parser.opt( :gem_version, "The version of the gem to yank (required)", :type => :string, :required => true )
- @parser.opt( :platform, "The platform of the gem to yank", :type => :string, :default => ::Gem::Platform::RUBY )
+ @parser.opt( :gem_version, "The version of the gem to mirror", :type => :string)
+ @parser.opt( :platform, "The platform of the gem to mirror", :type => :string, :default => ::Gem::Platform::RUBY )
end
return @parser
end
def parse( argv )
- gem_name = nil
- opts = super( argv ) do |p|
- raise Trollop::CommandlineError, "At least one gem is required to mirror" if p.leftovers.empty?
- gem_name = p.leftovers.shift
+ gem_name = nil
+ gemfile_lock = nil
+ opts = super( argv ) do |p, o|
+ raise Trollop::CommandlineError, "A Gemfile.lock or a gem name is required to mirror" if p.leftovers.empty?
+ if o[:gem_version] then
+ gem_name = p.leftovers.shift
+ else
+ gemfile_lock = p.leftovers.shift
+ raise Trollop::CommandlineError, "#{lock} must be readable" unless File.readable?( gemfile_lock )
+ end
end
- opts[:gem_name] = gem_name
+ opts[:gem_name] = gem_name
+ opts[:gemfile_lock] = gemfile_lock
return opts
end
@@ -41,24 +50,47 @@ def remote_repo_for( opts )
Stickler::Repository::RemoteMirror.new( opts[:server], :debug => opts[:debug] )
end
- def run
- opts = parse( self.argv )
- repo = remote_repo_for( opts )
- spec = Stickler::SpecLite.new( opts[:gem_name], opts[:gem_version], opts[:platform] )
- upstream_host = Addressable::URI.parse( opts[:upstream] ).host
+ def spec_list( opts )
+ if opts[:gem_name] then
+ return [Stickler::SpecLite.new( opts[:gem_name], opts[:gem_version], opts[:platform] )]
+ end
+
+ if opts[:gemfile_lock] then
+ parser = Stickler::GemfileLockParser.new( opts[:gemfile_lock] )
+ return parser.gem_dependencies
+ end
+ raise Sticker::Error, "No gem name, or gemfile lock... no idea what to do"
+ end
+ def mirror_one_spec( repo, spec, upstream_host )
$stdout.write "Asking #{repo.uri} to mirror #{spec.full_name} from #{upstream_host} : "
$stdout.flush
resp = repo.mirror( spec, upstream_host )
-
$stdout.puts "OK -> #{repo.uri.join(resp.headers['Location'])}"
+
+ rescue Stickler::Repository::Error => e
+ $stdout.puts "ERROR: #{e.message}"
+ rescue StandardError => e
+ $stdout.puts e.backtrace.join("\n")
+ $stdout.puts "ERROR -> #{e.message}"
+ end
+
+ def run
+ opts = parse( self.argv )
+ repo = remote_repo_for( opts )
+ specs = spec_list( opts )
+ upstream_host = Addressable::URI.parse( opts[:upstream] ).host
+
+ specs.each do |spec|
+ mirror_one_spec( repo, spec, upstream_host )
+ end
rescue Stickler::Repository::Error => e
$stdout.puts "ERROR: #{e.message}"
rescue StandardError => e
puts e.backtrace.join("\n")
$stdout.puts "ERROR -> #{e.message}"
- end
+ end
end
end
end
@@ -0,0 +1,47 @@
+module Stickler
+ class GemfileLockParser
+ attr_reader :gem_dependencies
+
+
+ def initialize( path )
+ p = Pathname.new( path )
+ raise Stickler::Error, "#{path} does not exist" unless p.exist?
+ raise Stickler::Error, "#{path} is not readable" unless p.readable?
+ parse( p.read )
+ end
+
+ def depends_on?( name )
+ gem_dependencies.any?{ |spec| spec.name == name }
+ end
+
+ private
+
+ def parse( text )
+ parts = partition( text )
+ @gem_dependencies = parse_dependencies( parts['GEM'] )
+ end
+
+ def parse_dependencies( lines )
+ drop_until_specs( lines )
+ deps = []
+ lines.each do |line|
+ md = line.match( /\A\s{4}(\S+)\s+\(([\w\.]+)\)\Z/ )
+ next if md.nil?
+ deps << Stickler::SpecLite.new( md.captures[0], md.captures[1] )
+ end
+ return deps
+ end
+
+ def drop_until_specs( lines )
+ lines.drop_while{ |l| %w[ remote specs ].include?( l.strip.split(":").first ) }
+ end
+
+ def partition( text )
+ text.split("\n\n").each_with_object({}) { | p, h |
+ next if p.empty?
+ parts = p.split("\n").map(&:rstrip)
+ h[parts.first] = parts[1..-1]
+ }
+ end
+ end
+end
@@ -0,0 +1,56 @@
+PATH
+ remote: .
+ specs:
+ stickler (2.3.0)
+ addressable (~> 2.3)
+ excon (~> 0.27.3)
+ logging (~> 1.8.1)
+ sinatra (~> 1.4)
+ trollop (~> 2.0)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ addressable (2.3.5)
+ builder (3.2.2)
+ excon (0.27.6)
+ hpricot (0.8.6)
+ json (1.8.0)
+ little-plugger (1.1.3)
+ logging (1.8.1)
+ little-plugger (>= 1.1.3)
+ multi_json (>= 1.3.6)
+ minitest (5.0.8)
+ multi_json (1.8.2)
+ mustache (0.99.4)
+ rack (1.5.2)
+ rack-protection (1.5.0)
+ rack
+ rack-test (0.6.2)
+ rack (>= 1.0)
+ rake (10.1.0)
+ rdiscount (2.1.7)
+ rdoc (4.0.1)
+ json (~> 1.4)
+ ronn (0.7.3)
+ hpricot (>= 0.8.2)
+ mustache (>= 0.7.0)
+ rdiscount (>= 1.5.8)
+ sinatra (1.4.3)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ tilt (1.4.1)
+ trollop (2.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ builder (~> 3.2)
+ minitest (~> 5.0)
+ rack-test (~> 0.6.2)
+ rake (~> 10.1)
+ rdoc (~> 4.0)
+ ronn (~> 0.7.3)
+ stickler!
@@ -0,0 +1,28 @@
+require 'test_stickler'
+require 'stickler/gemfile_lock_parser'
+
+module Stickler
+ class TestGemfileLockParser < Test
+ def setup
+ @lockfile = File.join( test_dir, 'data', 'Gemfile.lock.example' )
+ @parser = ::Stickler::GemfileLockParser.new( @lockfile )
+ end
+
+ def test_raises_exception_when_file_does_not_exist
+ assert_raises( ::Stickler::Error ) {
+ ::Stickler::GemfileLockParser.new( File.join( test_dir, 'data', "Gemfile.dne" ) )
+ }
+ end
+
+ def test_parse_gem_dependencies
+ names = %w[ addressable builder excon hpricot json little-plugger logging
+ minitest multi_json mustache rack rack-protection rack-test rake
+ rdiscount rdoc ronn sinatra tilt trollop ]
+ names.each do |n|
+ assert @parser.depends_on?( n ), "fails to depend on #{n}"
+ end
+
+ assert_equal names.size, @parser.gem_dependencies.size
+ end
+ end
+end

0 comments on commit b31021a

Please sign in to comment.