This is a script that I use to make Ruby projects accessible in my $PATH
. It writes a shim in ~/bin
that executes the program with the right settings for rbenv and bundler. You can use it too for those cases where a simple symlink is not good enough. Obviously, this is not for you if you prefer RVM.
I’ve been using it for a few years already and I’m really quite pleased with it.
$ cat ~/bin/install-shim
#!/usr/bin/env ruby
require 'erb'
require 'optparse'
require 'pathname'
PROJECT_ROOT_FILES = %w(.ruby-version Gemfile .env)
$verbose = false
$force = false
$dry_run = false
optionparser = OptionParser.new do |parser|
parser.banner = "Usage: #{$0} [options] PATH"
parser.separator ''
parser.separator 'Install a shim in ~/bin for your Ruby project. Use this instead of a symlink if your script depends on bundler or rbenv.'
parser.separator ''
parser.on("-d", "--[no-]dry-run", "print shim to stdout; do not write to file") do |d|
$dry_run = d
end
parser.on("-f", "--[no-]force", "Overwrite shim if it already exists") do |f|
$force = f
end
parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
$verbose = v
end
parser.on_tail("-h", "--help", "Show this message") do
puts parser
exit
end
end
optionparser.parse!(ARGV)
if ARGV.empty?
$stderr.puts "PATH is missing."
$stderr.puts
abort optionparser.to_s
end
def info(*string)
$stderr.puts(*string) if $verbose
end
def detect_project_root(dir)
Pathname.new(dir).ascend do |path|
return path.to_s if PROJECT_ROOT_FILES.any? { |filename| File.exist?("#{path}/#{filename}") }
end
abort "Cannot find #{PROJECT_ROOT_FILES.join(' or ')} for executable."
end
class String
def lchomp(match)
if index(match) == 0
self[match.size..-1]
else
self.dup
end
end
end
TARGET_DIRS = ["#{ENV['HOME']}/bin/shims", "#{ENV['HOME']}/bin"]
def target_dir
TARGET_DIRS.detect { |dir| File.directory? dir }
end
# Set project_root, subdir, and basename
target = ARGV.first
full_path = File.absolute_path target
project_root = detect_project_root(full_path)
subdir = File.dirname(full_path).lchomp(project_root)
basename = File.basename(target)
info "project root = #{project_root}"
info "subdir = #{subdir}"
info "basename = #{basename}"
if project_root.start_with?(ENV['HOME'])
project_root = project_root.sub(ENV['HOME'], '$HOME')
info "normalized project root = #{project_root}"
end
# Generate the script
script = ERB.new(DATA.read).result(binding) # the call to #binding gives the template access to all vars
if $dry_run
puts script
exit 0
end
# Write the shim
shim = "#{target_dir}/#{basename}"
abort "File #{shim} already exists. Use --force to overwrite it." if File.exist?(shim) && !$force
File.write(shim, script)
`chmod +x #{shim}`
__END__
#!/bin/sh
# A very very stupid shim that seems to work fine for my projects
# The good thing about it is that it:
#
# 1. is stupidly simple
# 2. does not change the working directory!
# 3. does not change anything besides two ENV variables
dir=<%= project_root %>
export RBENV_DIR=$dir
export BUNDLE_GEMFILE=$dir/Gemfile
$dir<%= subdir %>/$(basename "$0") "$@"