-
Notifications
You must be signed in to change notification settings - Fork 141
Add reload command to re-load files loaded via require #1157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ahogappa
wants to merge
4
commits into
ruby:master
Choose a base branch
from
ahogappa:add-reloadable-require
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+489
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module IRB | ||
| # :stopdoc: | ||
|
|
||
| module Command | ||
| class Reload < Base | ||
| category "IRB" | ||
| description "Reload files that were loaded via require in IRB session." | ||
|
|
||
| def execute(_arg) | ||
| unless reloadable_require_available? | ||
| warn "The reload command requires IRB.conf[:RELOADABLE_REQUIRE] = true and Ruby::Box (Ruby 4.0+) with RUBY_BOX=1 environment variable." | ||
| return | ||
| end | ||
|
|
||
| files = IRB.conf[:__RELOADABLE_FILES__] | ||
| if files.empty? | ||
| puts "No files to reload. Use require to load files first." | ||
| return | ||
| end | ||
|
|
||
| files.each { |path| reload_file(path) } | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def reloadable_require_available? | ||
| IRB.conf[:RELOADABLE_REQUIRE] && defined?(Ruby::Box) && Ruby::Box.enabled? | ||
| end | ||
|
|
||
| def reload_file(path) | ||
| $LOADED_FEATURES.delete(path) | ||
| load path | ||
| $LOADED_FEATURES << path | ||
| puts "Reloaded: #{path}" | ||
| rescue LoadError => e | ||
| warn "Failed to reload #{path}: #{e.message}" | ||
| rescue SyntaxError => e | ||
| warn "Syntax error in #{path}: #{e.message}" | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # :startdoc: | ||
| end |
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| if !defined?(Ruby::Box) || !Ruby::Box.enabled? | ||
| raise "ReloadableRequire requires Ruby::Box to be enabled" | ||
| end | ||
|
|
||
| module IRB | ||
| # Provides reload-aware require functionality for IRB. | ||
| # | ||
| # Limitations: | ||
| # - Native extensions cannot be reloaded (load doesn't support them) | ||
| # - Files loaded via box.require are not tracked | ||
| # - Constant redefinition warnings will appear on reload (uses load internally) | ||
| # - Context mode 5 (running IRB inside a Ruby::Box) is not supported | ||
| # | ||
| # This feature requires Ruby::Box (Ruby 4.0+). | ||
|
|
||
| class << self | ||
| def track_and_load_files(source, current_box) | ||
| before = source.dup | ||
| result = yield | ||
| new_files = source - before | ||
|
|
||
| return result if new_files.empty? | ||
|
|
||
| ruby_files, native_extensions = new_files.partition { |path| path.end_with?('.rb') } | ||
|
|
||
| native_extensions.each { |path| current_box.require(path) } | ||
|
|
||
| IRB.conf[:__RELOADABLE_FILES__].merge(ruby_files) | ||
|
|
||
| main_loaded_features = current_box.eval('$LOADED_FEATURES') | ||
| main_loaded_features.concat(ruby_files - main_loaded_features) | ||
| ruby_files.each { |path| current_box.load(path) } | ||
|
|
||
| result | ||
| end | ||
| end | ||
|
|
||
| Ruby::Box.class_eval do | ||
| alias_method :__irb_original_require__, :require | ||
| alias_method :__irb_original_require_relative__, :require_relative | ||
|
|
||
| def __irb_reloadable_require__(feature) | ||
| unless IRB.conf[:__AUTOLOAD_FILES__].include?(feature) | ||
| return __irb_original_require__(feature) | ||
| end | ||
|
|
||
| IRB.conf[:__AUTOLOAD_FILES__].delete(feature) | ||
| IRB.track_and_load_files($LOADED_FEATURES, Ruby::Box.main) { __irb_original_require__(feature) } | ||
| end | ||
|
|
||
| def __irb_reloadable_require_relative__(feature) | ||
| __irb_original_require_relative__(feature) | ||
| end | ||
| end | ||
|
|
||
| module ReloadableRequire | ||
| class << self | ||
| def extended(base) | ||
| apply_autoload_hook | ||
| end | ||
|
|
||
| def apply_autoload_hook | ||
| Ruby::Box.class_eval do | ||
| alias_method :require, :__irb_reloadable_require__ | ||
| alias_method :require_relative, :__irb_reloadable_require_relative__ | ||
| end | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def reloadable_require_internal(absolute_path, caller_box) | ||
| return false if caller_box.eval('$LOADED_FEATURES').include?(absolute_path) | ||
|
|
||
| box = Ruby::Box.new | ||
| load_path = caller_box.eval('$LOAD_PATH') | ||
| # Copy $LOAD_PATH to the box so it can resolve dependencies. | ||
| box.eval("$LOAD_PATH.concat(#{load_path})") | ||
|
|
||
| IRB.track_and_load_files(box.eval('$LOADED_FEATURES'), caller_box) { box.__irb_original_require__(absolute_path) } | ||
| end | ||
|
|
||
| def require(feature) | ||
| caller_loc = caller_locations(1, 1).first | ||
| current_box = Ruby::Box.main | ||
| resolved = current_box.eval("$LOAD_PATH.resolve_feature_path(#{feature.dump})") | ||
|
|
||
| # Fallback for calls outside IRB prompt | ||
| if caller_loc.path != "(irb)" || !resolved || resolved[0] != :rb | ||
| return current_box.require(feature) | ||
| end | ||
|
|
||
| reloadable_require_internal(resolved[1], current_box) | ||
| end | ||
|
|
||
| def require_relative(feature) | ||
| caller_loc = caller_locations(1, 1).first | ||
| current_box = Ruby::Box.main | ||
|
|
||
| # Fallback for calls outside IRB prompt | ||
| if caller_loc.path != "(irb)" | ||
| file_path = caller_loc.absolute_path || caller_loc.path | ||
| return current_box.eval("eval('Kernel.require_relative(#{feature.dump})', nil, #{file_path.dump}, #{caller_loc.lineno})") | ||
| end | ||
|
|
||
| absolute_path = resolve_require_relative_path(feature) | ||
|
|
||
| if !absolute_path.end_with?('.rb') || !File.exist?(absolute_path) | ||
| file_path = File.join(Dir.pwd, "(irb)") | ||
| return current_box.eval("eval('Kernel.require_relative(#{feature.dump})', nil, #{file_path.dump}, 1)") | ||
| end | ||
|
|
||
| reloadable_require_internal(absolute_path, current_box) | ||
| end | ||
|
|
||
| def resolve_require_relative_path(feature) | ||
| absolute_path = File.expand_path(feature, Dir.pwd) | ||
| return absolute_path unless File.extname(absolute_path).empty? | ||
|
|
||
| dlext = RbConfig::CONFIG['DLEXT'] | ||
| ['.rb', ".#{dlext}"].each do |ext| | ||
| candidate = absolute_path + ext | ||
| return candidate if File.exist?(candidate) | ||
| end | ||
|
|
||
| absolute_path | ||
| end | ||
|
|
||
| def autoload(const, feature) | ||
| IRB.conf[:__AUTOLOAD_FILES__] << feature | ||
| Ruby::Box.main.eval("Kernel.autoload(:#{const}, #{feature.dump})") | ||
| end | ||
| end | ||
| end |
This file contains hidden or 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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command name
reloadmight be confused with Rails console'sreload!. Consider alternatives likereload_requiresorrefresh.