Skip to content

Instantly share code, notes, and snippets.

@minad minad/packages.rb
Created Oct 30, 2013

Embed
What would you like to do?
Package system similar to Python in Ruby.
require 'forwardable'
module Packages
class Package
module Mixin
extend Forwardable
def_delegators :@__PACKAGE__, :import, :bind
attr_reader :__PACKAGE__
def method_added(name)
__PACKAGE__.bind_method(name, __PACKAGE__, name)
end
end
attr_reader :bundle, :file, :path, :module, :dependencies
def initialize(bundle, file, path)
@bundle, @file, @path, @dependencies = bundle, file, path, {}
body = File.read(file)
@module = Module.new { [self, Mixin].each {|m| extend m } }
Packages.const_set("P#{Bundle.unique_id}_#{const_name(path)}", @module)
@module.instance_variable_set(:@__PACKAGE__, self)
%i(import bind __PACKAGE__).each {|m| bind_method(m, self, m) }
TOPLEVEL_BINDING.eval("using #{@module.name}\nmodule #{@module.name}\n#{body}\nend", file, 2)
end
def import(*paths)
hash = Hash === paths.last ? paths.pop : {}
(paths.map {|p| [p, File.basename(p)] } + hash.to_a).each do |path, name|
bind_const(const_name(name), dep(relative_path(path)).module)
end
end
def bind(path, *imports)
src = dep(relative_path(path))
if imports.empty?
methods = src.module.instance_methods - Mixin.instance_methods
consts = src.module.constants - Mixin.constants
else
hash = Hash === imports.last ? imports.pop : {}
all = imports + hash.to_a
methods = all.select {|n| src.module.respond_to?(n) }
consts = all - methods
end
consts.each {|srcname, name| bind_const(name || srcname, src.module.const_get(srcname)) }
methods.each {|srcname, name| bind_method(name || srcname, src, srcname) }
end
def bind_const(name, value)
raise "'#{name}' is already defined" if @module.const_defined?(name)
@module.const_set(name, value)
end
def bind_method(name, src, srcname)
raise "'#{name}' is already defined" if self != src && @module.respond_to?(name)
@module.module_eval <<-end_eval, __FILE__, __LINE__
def #{name}(*a, &b); #{src.module.name}.#{srcname}(*a, &b); end if #{src.module.name} != self
refine(Object) { def #{name}(*a, &b); #{src.module.name}.#{srcname}(*a, &b); end }
end_eval
end
private
def dep(path)
@dependencies[path] ||= bundle[path]
end
def relative_path(rel)
rel =~ /\A(\.+)\// ? path.sub(/(\A|\/)([^\/]+)(\/[^\/]+){#{$1.size-1}}\Z/, "\\1#{$'}") : rel
end
def const_name(path)
path.gsub(/(?:_|\A|(\/))([a-z]+)/) { ($1 ? '_' : '') << $2.capitalize }
end
end
class Bundle
include Enumerable
extend Forwardable
def_delegators :@loaded, :each
@unique_id = 0
def self.unique_id
@unique_id += 1
end
attr_reader :path
def initialize(path)
@path, @loaded, @loading = path, {}, {}
end
def unload(path)
if pkg = @loaded.delete(path)
Packages.remove_const(pkg.module.name.sub(/\A\w+::/, ''))
end
end
def [](path)
if pkg = @loaded[path]
pkg
elsif @loading.include?(path)
raise LoadError, "Circular dependency detected for '#{path}'"
else
begin
@loading[path] = nil
@loaded[path] = Package.new(self, File.join(@path, path) << '.rb', path)
rescue Exception
unload(path)
raise
ensure
@loading.delete(path)
end
end
end
end
end
@minad

This comment has been minimized.

Copy link
Owner Author

commented Oct 30, 2013

load an init.rb package as follows

require './packages.rb'
__BUNDLE__ = Packages::Bundle.new('test')
__BUNDLE__['init']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.