module AnonymousLoader
Loads Ruby source under anonymous modules to avoid top-level namespace collisions.
Constants
- PathRequest
Internal normalized resolver request. @api private
- VERSION
- VersionRequest
Internal load-path version lookup request. @api private
Public Class Methods
Evaluate one or more Ruby files inside a fresh anonymous module.
The source’s natural constant nesting is preserved inside the anonymous module. For example, a file declaring ‘module Auth; module Sanitizer; end` becomes `anonymous_namespace::Auth::Sanitizer`, not `Object::Auth`.
@param files [String, Array<String>] absolute paths, or paths relative to root @param root [String, nil] optional root for relative paths @return [Module] anonymous namespace containing the evaluated source
# File lib/anonymous_loader.rb 33 def load(files:, root: nil, **_options) 34 namespace = Module.new 35 Array(files).each do |file| 36 path = expand_file(file, root) 37 namespace.module_eval(File.read(path), path, 1) 38 end 39 namespace 40 end
Resolve and anonymously load a single file.
@param options [Hash] resolver options @option options [String, nil] :path explicit file path @option options [String, nil] :gem_name gem name used for RubyGems lookup @option options [String, nil] :require_path path under lib, also used for $LOAD_PATH lookup @option options [String, Gem::Requirement, nil] :version_requirement accepted gem version @option options [String, nil] :version_file file next to require_path’s load root that declares VERSION @return [Module] anonymous namespace containing the evaluated source
# File lib/anonymous_loader.rb 51 def load_path(**options) 52 load(files: resolve_path(**options)) 53 end
Resolve a file from an explicit path, RubyGems metadata, or $LOAD_PATH.
RubyGems metadata is preferred when available. The $LOAD_PATH fallback supports Bundler standalone setups where files are loadable but the dependency is absent from Gem.loaded_specs and GEM_PATH.
@param options [Hash] resolver options @option options [String, nil] :path explicit file path @option options [String, nil] :gem_name gem name used for RubyGems lookup @option options [String, nil] :require_path path under lib, also used for $LOAD_PATH lookup @option options [String, Gem::Requirement, nil] :version_requirement accepted gem version @option options [String, nil] :version_file file next to require_path’s load root that declares VERSION @return [String] absolute path to the resolved file
# File lib/anonymous_loader.rb 68 def resolve_path(**options) 69 request = path_request(options) 70 resolved = explicit_path_for(request) || resolve_from_gemspec(request) || resolve_from_load_path(request) 71 return resolved if resolved 72 73 raise FileNotFoundError, unresolved_message(request) 74 end
Private Class Methods
# File lib/anonymous_loader.rb 105 def absolute_path?(path) 106 path.start_with?(File::SEPARATOR) || !!(File::ALT_SEPARATOR && path.start_with?(File::ALT_SEPARATOR)) 107 end
# File lib/anonymous_loader.rb 196 def existing_version_file(request) 197 return nil unless request.version_file 198 199 version_path = load_path_version_file(request) 200 version_path if File.file?(version_path) 201 end
# File lib/anonymous_loader.rb 98 def expand_file(file, root) 99 path = (root && !absolute_path?(file)) ? File.expand_path(file, root) : File.expand_path(file) 100 raise FileNotFoundError, "file not found: #{path}" unless File.file?(path) 101 102 path 103 end
# File lib/anonymous_loader.rb 89 def explicit_path_for(request) 90 path = request.path 91 expand_file(path, nil) if path 92 end
# File lib/anonymous_loader.rb 135 def find_spec(gem_name, requirement) 136 if requirement 137 Gem::Specification.find_by_name(gem_name, requirement) 138 else 139 Gem::Specification.find_by_name(gem_name) 140 end 141 rescue Gem::MissingSpecError 142 nil 143 end
# File lib/anonymous_loader.rb 207 def gem_version_for(version_path) 208 version_string = version_string_for(version_path) 209 Gem::Version.new(version_string) if version_string 210 end
# File lib/anonymous_loader.rb 114 def gemspec_candidate(request) 115 return nil unless gemspec_request_complete?(request) 116 117 spec = matching_spec(request.gem_name, request.requirement) 118 spec && File.join(spec.full_gem_path, "lib", request.require_path) 119 end
# File lib/anonymous_loader.rb 121 def gemspec_request_complete?(request) 122 request.gem_name && request.require_path 123 end
# File lib/anonymous_loader.rb 163 def load_path_candidates(require_path) 164 Gem.find_files(require_path).select { |candidate| File.file?(candidate) } 165 end
# File lib/anonymous_loader.rb 149 def load_path_candidates_for(request) 150 require_path = request.require_path 151 return [] unless require_path 152 153 load_path_candidates(require_path) 154 end
# File lib/anonymous_loader.rb 220 def load_path_root(request) 221 candidate_dir = File.dirname(request.candidate) 222 require_depth = request.require_path.split("/").length - 1 223 require_depth.times { candidate_dir = File.dirname(candidate_dir) } 224 candidate_dir 225 end
# File lib/anonymous_loader.rb 203 def load_path_version_file(request) 204 File.expand_path(request.version_file, load_path_root(request)) 205 end
# File lib/anonymous_loader.rb 175 def load_path_versions(candidates, request) 176 candidates.each_with_object([]) do |candidate, versions| 177 version = version_for_load_path_candidate(VersionRequest.new(candidate, request.require_path, request.version_file)) 178 versions << [candidate, version] if version 179 end 180 end
# File lib/anonymous_loader.rb 125 def matching_spec(gem_name, requirement) 126 spec = Gem.loaded_specs[gem_name] 127 spec = find_spec(gem_name, requirement) if !spec || spec_version_mismatch?(spec, requirement) 128 spec 129 end
# File lib/anonymous_loader.rb 78 def path_request(options) 79 version_requirement = options[:version_requirement] 80 PathRequest.new( 81 options[:path], 82 options[:gem_name], 83 options[:require_path], 84 version_requirement && Gem::Requirement.create(version_requirement), 85 options[:version_file] 86 ) 87 end
# File lib/anonymous_loader.rb 182 def raise_load_path_version_mismatch(request, mismatched_versions) 183 return nil if mismatched_versions.empty? 184 185 raise VersionMismatchError, 186 "#{request.require_path.inspect} did not satisfy #{request.requirement}; found #{mismatched_versions.uniq.join(", ")}" 187 end
# File lib/anonymous_loader.rb 109 def resolve_from_gemspec(request) 110 candidate = gemspec_candidate(request) 111 candidate if candidate && File.file?(candidate) 112 end
# File lib/anonymous_loader.rb 145 def resolve_from_load_path(request) 146 resolve_load_path_candidates(load_path_candidates_for(request), request) 147 end
# File lib/anonymous_loader.rb 156 def resolve_load_path_candidates(candidates, request) 157 return nil if candidates.empty? 158 return candidates.first unless request.requirement 159 160 resolve_versioned_load_path(candidates, request) 161 end
# File lib/anonymous_loader.rb 167 def resolve_versioned_load_path(candidates, request) 168 versions = load_path_versions(candidates, request) 169 match = versions.find { |pair| request.requirement.satisfied_by?(pair.last) } 170 return match.first if match 171 172 raise_load_path_version_mismatch(request, versions.map(&:last)) 173 end
# File lib/anonymous_loader.rb 131 def spec_version_mismatch?(spec, requirement) 132 requirement && !requirement.satisfied_by?(spec.version) 133 end
# File lib/anonymous_loader.rb 94 def unresolved_message(request) 95 "could not resolve #{request.require_path.inspect} from #{request.gem_name || "load path"}" 96 end
# File lib/anonymous_loader.rb 189 def version_for_load_path_candidate(request) 190 version_path = existing_version_file(request) 191 gem_version_for(version_path) if version_path 192 rescue ArgumentError 193 nil 194 end
# File lib/anonymous_loader.rb 212 def version_string_for(version_path) 213 source = File.read(version_path) 214 # This fallback only runs when RubyGems metadata is unavailable; the 215 # supported version file contract is a simple string VERSION assignment. 216 match = source.match(/VERSION\s*=\s*(["'])([^"']+)\1/) 217 match[2] if match 218 end