Skip to content

Instantly share code, notes, and snippets.

@jamis
Created May 15, 2009
Embed
What would you like to do?
Creates a stand-alone ruby script that bundles all dependencies
#!/usr/bin/env ruby
# ------------------------------------------------------------------------
# bundle.rb
# author: Jamis Buck <jamis@37signals.com>
# usage: ruby bundle.rb <config.yml>
#
# Takes a configuration file in YAML format and writes a single ruby script
# that includes all the dependencies you specify. For example:
#
# ---
# name: mapper-script.rb
# files:
# - bin
# - lib
# - vendor/rails/activesupport
# - vendor/rails/activeresource
# ignore:
# - *.swp
# start: bin/map
#
# Using the above configuration file, bundle.rb will create a single ruby
# script, named "mapper-script.rb", that contains within it all the files
# under the bin, lib, vendor/rails/activesupport, and
# vendor/rails/activeresource directories, except for *.swp files. When
# the script is invoked, it will execute the script in bin/map.
#
# This is not intended to be a general-purpose "ruby-to-executable"
# builder. Rather, it is intended to be used where an environment requires
# only a single, self-contained ruby script (e.g., Amazon Elastic
# MapReduce).
require 'yaml'
require 'zlib'
def ignore?(path, config)
Array(config['ignore']).any? do |pattern|
File.fnmatch(pattern, path, File::FNM_DOTMATCH)
end
end
def parse_path(fs, path)
path.split("/").inject(fs) { |node, part| node[part] ||= {} }
end
def import_directory(fs, directory, config)
import_entries(parse_path(fs, directory), directory, config)
end
def import_entries(root, directory, config)
contents = Dir.entries(directory).reject { |i| i == "." || i == ".." }
contents.each do |entry|
full_path = File.join(directory, entry)
next if ignore?(full_path, config)
if File.directory?(full_path)
root[entry] ||= {}
import_entries(root[entry], full_path, config)
else
root[entry] = File.read(full_path)
end
end
end
def import_file(fs, file, config)
dir, base = File.split(file)
start = parse_path(fs, dir)
start[base] = File.read(file)
end
def import_pattern(fs, pattern, config)
Dir.glob(pattern).each do |item|
next if ignore?(item, config)
import_item(fs, item, config)
end
end
def import_item(fs, item, config)
if File.directory?(item)
import_directory(fs, item, config)
elsif File.file?(item)
import_file(fs, item, config)
else
import_pattern(fs, item, config)
end
end
config_file = ARGV.shift or abort "please specify the config file"
config = YAML.load_file(config_file)
abort "you must specify a 'start' entry in the config" unless config['start']
file_system = {}
config["files"].each { |item| import_item(file_system, item, config) }
data = [Zlib::Deflate.deflate(Marshal.dump(file_system))].pack("m*")
File.open(config['name'] || "a.rb", "w") do |out|
out.puts(DATA.read)
parts = config['start'].split("/")
out.puts "start_file = #{config['start'].inspect}.split('/').inject(FILE_SYSTEM) { |node, entry| node[entry] }"
out.puts "eval(start_file, binding, #{config['start'].inspect})"
out.puts "__END__"
out.puts data
end
__END__
#!/usr/bin/env ruby
require 'zlib'
FILE_SYSTEM = Marshal.load(Zlib::Inflate.inflate(DATA.read.unpack("m*").first))
HOME_PATH = Dir.pwd
TOP_LEVEL = binding
module InMemoryLoading
def self.included(base)
base.send :alias_method, :require_without_in_memory_loading, :require
base.send :alias_method, :require, :require_with_in_memory_loading
end
FEATURE_EXTENSIONS = [".rb", ""]
def require_with_in_memory_loading(string, *args)
return false if FEATURE_EXTENSIONS.any? { |ext| $".include?(string + ext) }
$LOAD_PATH.each do |path|
next unless File.expand_path(path) =~ /^#{HOME_PATH}\/(.*)/
path = $1
full = File.join(path, string)
dir, base = File.split(full)
parts = dir.split("/")
folder = parts.inject(FILE_SYSTEM) { |node, entry| node && node[entry] }
if folder
FEATURE_EXTENSIONS.each do |ext|
name = base + ext
if folder[name]
$".push(string + ext)
eval(folder[name], TOP_LEVEL, full)
return true
end
end
end
end
require_without_in_memory_loading(string, *args)
end
end
Kernel.send(:include, InMemoryLoading)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment