Last active
November 27, 2021 07:36
-
-
Save lygaret/8ead3a465573557e7c4d5386119776f1 to your computer and use it in GitHub Desktop.
override `Kernel.require` to track loaded files, and allow reloading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pp' | |
class Reloader | |
def initialize | |
# normalized path => mtime | |
@mtimecache = Hash.new | |
# normalized path => load path | |
# this will only lookup once, storing _nil_ in the cache if not found | |
@pathcache = Hash.new do |cache, path| | |
cache[path] = search_load_path(path) | |
end | |
end | |
def inspect | |
"<Reloader: #{@mtimecache.count} entries>" | |
end | |
# @see Kernel.require | |
def track(path) | |
if (path = @pathcache[path]) | |
@mtimecache[path] = File.mtime(path) | |
end | |
end | |
def reload(*paths, force: false) | |
# with no given paths, just use everything we've tracked | |
paths = @mtimecache.keys if paths.empty? | |
paths.each do |path| | |
# for each path, normalize from the load path | |
if (path = @pathcache[path]) | |
# and if we're either forcing the reload or it's been changed | |
if force or (File.mtime(path) > @mtimecache[path]) | |
# delete from the list of loaded modules, and rerequire | |
$".delete path | |
require path | |
end | |
end | |
end | |
end | |
# dump the current autoreload state | |
def dump | |
puts "path => mtime:" | |
pp @mtimecache | |
puts "require => path:" | |
pp @pathcache | |
return nil | |
end | |
# reset the path cache, in the case where we grabbed the wrong thing | |
def reset! | |
@pathcache.clear | |
end | |
private | |
# search the load path for the given requireable path | |
def search_load_path(path) | |
# if the path is absolute, it's already found, just look for extensions | |
if path =~ /^\// | |
options = Dir.glob("#{path}{.rb,.so,}") | |
# if the path is relative, we're globbing for from the cwd | |
elsif path =~ /^\.\.?\// # relative path | |
dirpath = File.expand_path(path) | |
options = Dir.glob("#{dirpath}{.rb,.so,}") | |
# otherwise it's a bare word, so it must be on the load path | |
else | |
options = $LOAD_PATH.lazy.flat_map do |dir| | |
dirpath = File.join(dir, path) | |
Dir.glob("#{dirpath}{.rb,.so,}") | |
end | |
end | |
# return the first thing we found that's actually a file | |
options.select { |o| File.file?(o) }.first | |
end | |
end | |
# monkeypatch the kernel | |
module Kernel | |
alias original_require require | |
def reloader | |
@reloader ||= Reloader.new | |
end | |
def require(path) | |
reloader.track(path) | |
original_require(path) | |
end | |
def reload! | |
reloader.reload | |
return | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment