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

load(files:, root: nil, **_options) click to toggle source

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
load_path(**options) click to toggle source

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_path(**options) click to toggle source

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

absolute_path?(path) click to toggle source
    # 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
existing_version_file(request) click to toggle source
    # 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
expand_file(file, root) click to toggle source
    # 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
explicit_path_for(request) click to toggle source
   # File lib/anonymous_loader.rb
89 def explicit_path_for(request)
90   path = request.path
91   expand_file(path, nil) if path
92 end
find_spec(gem_name, requirement) click to toggle source
    # 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
gem_version_for(version_path) click to toggle source
    # 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
gemspec_candidate(request) click to toggle source
    # 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
gemspec_request_complete?(request) click to toggle source
    # File lib/anonymous_loader.rb
121 def gemspec_request_complete?(request)
122   request.gem_name && request.require_path
123 end
load_path_candidates(require_path) click to toggle source
    # 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
load_path_candidates_for(request) click to toggle source
    # 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
load_path_root(request) click to toggle source
    # 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
load_path_version_file(request) click to toggle source
    # 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
load_path_versions(candidates, request) click to toggle source
    # 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
matching_spec(gem_name, requirement) click to toggle source
    # 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
path_request(options) click to toggle source
   # 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
raise_load_path_version_mismatch(request, mismatched_versions) click to toggle source
    # 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
resolve_from_gemspec(request) click to toggle source
    # 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
resolve_from_load_path(request) click to toggle source
    # 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
resolve_load_path_candidates(candidates, request) click to toggle source
    # 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
resolve_versioned_load_path(candidates, request) click to toggle source
    # 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
spec_version_mismatch?(spec, requirement) click to toggle source
    # File lib/anonymous_loader.rb
131 def spec_version_mismatch?(spec, requirement)
132   requirement && !requirement.satisfied_by?(spec.version)
133 end
unresolved_message(request) click to toggle source
   # 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
version_for_load_path_candidate(request) click to toggle source
    # 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
version_string_for(version_path) click to toggle source
    # 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