Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Rich Meyers September 26, 2011
file 109 lines (96 sloc) 2.913 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
#!/usr/bin/env ruby
# The `rpg-parse-gemfile` program reads a Bundler Gemfile and writes a
# list of packages on standard output suitable for feeding to `rpg-install`.
#
# Usage
# -----
#
# Given a project with a Gemfile, it is possible to install its dependencies
# by running:
#
# rpg install $(rpg parse-gemfile /path/to/Gemfile)
#
# Caveats
# -------
#
# At this time only top-level gem statements are actually supported.
# All options are ignored. All groups other than the top-level group are
# ignored. All other statements are ignored.
#
# Bundler uses dependency declarations in all groups when resolving
# dependencies, even in groups that are not being installed.
# See http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/,
# section "Consistency". This may render a Gemfile unresolvable if, for
# example, the test group specifies conflicting or broken dependencies, even
# for users who don't want to install the test group. rpg ignores all groups
# that are not being installed during dependency resolution. As a consequence,
# rpg and Bundler may install different sets of packages from the same Gemfile.
USAGE = <<BANNER
Usage: rpg-parse-gemfile [PATH]
Convert Bundler Gemfile to a list of packages suitable for installation.

Reads a Gemfile located at specified PATH, or standard input, and writes
a list of packages suitable for feeding to rpg-install on standard output.

Try: rpg install $(rpg parse-gemfile /path/to/Gemfile)
BANNER

if ARGV.include?('--help') || ARGV.length > 1 || ARGV.empty? && STDIN.tty?
  puts USAGE
  exit 2
end

class GemfileParser
  def initialize
    @packages = []
  end
  
  def parse(gemfile_text, file=nil)
    eval(gemfile_text, nil, file)
  end
  
  def method_missing(name, *args)
    #puts "Ignoring #{name}"
  end
  
  def gem(name, *args)
    unless args.first.is_a?(Hash)
      version = args.shift
    end
    options = args.shift || {}
    @packages << {
      :name => name, :version => version, :options => options,
      :group => @in_group,
    }
  end
  
  def group(name)
    if @in_group
      raise ArgumentError, 'Nested groups are not supported'
    end
    @in_group = name
    yield
    @in_group = nil
  end
  
  def print
    @packages.each do |package|
      next if package[:group]
      version_op, version_value = canonicalize_version(package[:version])
      puts "#{package[:name]} #{version_op} #{version_value}"
    end
  end
  
  def canonicalize_version(version)
    if version.nil?
      return ['>=', '0']
    end
    unless version =~ /^([>=~]*)(\d.+)$/
      raise ArgumentError, "Invalid version specification: #{version}"
    end
    op, value = $1, $2
    if op.empty?
      op = '='
    end
    return [op, value]
  end
end

if (file = ARGV.shift) && file != '-'
  text = File.read(file)
else
  file = '<stdin>'
  text = STDIN.read
end

parser = GemfileParser.new
parser.parse(text, file)
parser.print
Something went wrong with that request. Please try again.