Skip to content

Instantly share code, notes, and snippets.

@lygaret
Last active November 27, 2021 07:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lygaret/8ead3a465573557e7c4d5386119776f1 to your computer and use it in GitHub Desktop.
Save lygaret/8ead3a465573557e7c4d5386119776f1 to your computer and use it in GitHub Desktop.
override `Kernel.require` to track loaded files, and allow reloading
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