ocran 1.3.15 → 1.3.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/ocran +2 -1314
- data/lib/ocran/build_constants.rb +16 -0
- data/lib/ocran/build_facade.rb +17 -0
- data/lib/ocran/build_helper.rb +105 -0
- data/lib/ocran/command_output.rb +22 -0
- data/lib/ocran/direction.rb +386 -0
- data/lib/ocran/file_path_set.rb +69 -0
- data/lib/ocran/gem_spec_queryable.rb +172 -0
- data/lib/ocran/host_config_helper.rb +37 -0
- data/lib/ocran/inno_setup_script_builder.rb +111 -0
- data/lib/ocran/launcher_batch_builder.rb +85 -0
- data/lib/ocran/library_detector.rb +61 -0
- data/lib/ocran/option.rb +273 -0
- data/lib/ocran/refine_pathname.rb +104 -0
- data/lib/ocran/runner.rb +105 -0
- data/lib/ocran/runtime_environment.rb +46 -0
- data/lib/ocran/stub_builder.rb +224 -0
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/windows_command_escaping.rb +15 -0
- data/lib/ocran.rb +3 -2
- data/share/ocran/edicon.exe +0 -0
- data/share/ocran/stub.exe +0 -0
- data/share/ocran/stubw.exe +0 -0
- metadata +23 -6
- /data/{share/ocran/empty-msys-2.0.dll → lib/ocran/empty_source} +0 -0
data/lib/ocran/option.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
class Option
|
6
|
+
load File.expand_path("refine_pathname.rb", __dir__) unless defined? RefinePathname
|
7
|
+
using RefinePathname
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@options = {
|
11
|
+
:add_all_core? => false,
|
12
|
+
:add_all_encoding? => true,
|
13
|
+
:argv => [],
|
14
|
+
:auto_detect_dlls? => true,
|
15
|
+
:chdir_before? => false,
|
16
|
+
:enable_compression? => true,
|
17
|
+
:enable_debug_extract? => false,
|
18
|
+
:enable_debug_mode? => false,
|
19
|
+
:extra_dlls => [],
|
20
|
+
:force_console? => false,
|
21
|
+
:force_windows? => false,
|
22
|
+
:gem_options => [],
|
23
|
+
:gemfile => nil,
|
24
|
+
:icon_filename => nil,
|
25
|
+
:inno_setup_script => nil,
|
26
|
+
:load_autoload? => true,
|
27
|
+
:output_override => nil,
|
28
|
+
:quiet? => false,
|
29
|
+
:rubyopt => nil,
|
30
|
+
:run_script? => true,
|
31
|
+
:script => nil,
|
32
|
+
:source_files => [],
|
33
|
+
:verbose? => false,
|
34
|
+
:warning? => true,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def usage
|
39
|
+
<<EOF
|
40
|
+
ocran [options] script.rb
|
41
|
+
|
42
|
+
Ocran options:
|
43
|
+
|
44
|
+
--help Display this information.
|
45
|
+
--quiet Suppress output while building executable.
|
46
|
+
--verbose Show extra output while building executable.
|
47
|
+
--version Display version number and exit.
|
48
|
+
|
49
|
+
Packaging options:
|
50
|
+
|
51
|
+
--dll dllname Include additional DLLs from the Ruby bindir.
|
52
|
+
--add-all-core Add all core ruby libraries to the executable.
|
53
|
+
--gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
|
54
|
+
--no-enc Exclude encoding support files
|
55
|
+
|
56
|
+
Gem content detection modes:
|
57
|
+
|
58
|
+
--gem-minimal[=gem1,..] Include only loaded scripts
|
59
|
+
--gem-guess=[gem1,...] Include loaded scripts & best guess (DEFAULT)
|
60
|
+
--gem-all[=gem1,..] Include all scripts & files
|
61
|
+
--gem-full[=gem1,..] Include EVERYTHING
|
62
|
+
--gem-spec[=gem1,..] Include files in gemspec (Does not work with Rubygems 1.7+)
|
63
|
+
|
64
|
+
minimal: loaded scripts
|
65
|
+
guess: loaded scripts and other files
|
66
|
+
all: loaded scripts, other scripts, other files (except extras)
|
67
|
+
full: Everything found in the gem directory
|
68
|
+
|
69
|
+
--[no-]gem-scripts[=..] Other script files than those loaded
|
70
|
+
--[no-]gem-files[=..] Other files (e.g. data files)
|
71
|
+
--[no-]gem-extras[=..] Extra files (README, etc.)
|
72
|
+
|
73
|
+
scripts: .rb/.rbw files
|
74
|
+
extras: C/C++ sources, object files, test, spec, README
|
75
|
+
files: all other files
|
76
|
+
|
77
|
+
Auto-detection options:
|
78
|
+
|
79
|
+
--no-dep-run Don't run script.rb to check for dependencies.
|
80
|
+
--no-autoload Don't load/include script.rb's autoloads.
|
81
|
+
--no-autodll Disable detection of runtime DLL dependencies.
|
82
|
+
|
83
|
+
Output options:
|
84
|
+
|
85
|
+
--output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
|
86
|
+
--no-lzma Disable LZMA compression of the executable.
|
87
|
+
--innosetup <file> Use given Inno Setup script (.iss) to create an installer.
|
88
|
+
|
89
|
+
Executable options:
|
90
|
+
|
91
|
+
--windows Force Windows application (rubyw.exe)
|
92
|
+
--console Force console application (ruby.exe)
|
93
|
+
--chdir-first When exe starts, change working directory to app dir.
|
94
|
+
--icon <ico> Replace icon with a custom one.
|
95
|
+
--rubyopt <str> Set the RUBYOPT environment variable when running the executable
|
96
|
+
--debug Executable will be verbose.
|
97
|
+
--debug-extract Executable will unpack to local dir and not delete after.
|
98
|
+
EOF
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse(argv)
|
102
|
+
while (arg = argv.shift)
|
103
|
+
case arg
|
104
|
+
when /\A--(no-)?lzma\z/
|
105
|
+
@options[:enable_compression?] = !$1
|
106
|
+
when "--no-dep-run"
|
107
|
+
@options[:run_script?] = false
|
108
|
+
when "--add-all-core"
|
109
|
+
@options[:add_all_core?] = true
|
110
|
+
when "--output"
|
111
|
+
path = argv.shift
|
112
|
+
@options[:output_override] = Pathname.new(path).expand_path if path
|
113
|
+
when "--dll"
|
114
|
+
path = argv.shift
|
115
|
+
@options[:extra_dlls] << path if path
|
116
|
+
when "--quiet"
|
117
|
+
@options[:quiet?] = true
|
118
|
+
when "--verbose"
|
119
|
+
@options[:verbose?] = true
|
120
|
+
when "--windows"
|
121
|
+
@options[:force_windows?] = true
|
122
|
+
when "--console"
|
123
|
+
@options[:force_console?] = true
|
124
|
+
when "--no-autoload"
|
125
|
+
@options[:load_autoload?] = false
|
126
|
+
when "--chdir-first"
|
127
|
+
@options[:chdir_before?] = true
|
128
|
+
when "--icon"
|
129
|
+
path = argv.shift
|
130
|
+
raise "Icon file #{path} not found" unless path && File.exist?(path)
|
131
|
+
@options[:icon_filename] = Pathname.new(path).expand_path
|
132
|
+
when "--rubyopt"
|
133
|
+
@options[:rubyopt] = argv.shift
|
134
|
+
when "--gemfile"
|
135
|
+
path = argv.shift
|
136
|
+
raise "Gemfile #{path} not found" unless path && File.exist?(path)
|
137
|
+
@options[:gemfile] = Pathname.new(path).expand_path
|
138
|
+
when "--innosetup"
|
139
|
+
path = argv.shift
|
140
|
+
raise "Inno Script #{path} not found" unless path && File.exist?(path)
|
141
|
+
@options[:inno_setup_script] = Pathname.new(path).expand_path
|
142
|
+
when "--no-autodll"
|
143
|
+
@options[:auto_detect_dlls?] = false
|
144
|
+
when "--version"
|
145
|
+
require_relative "version"
|
146
|
+
puts "Ocran #{VERSION}"
|
147
|
+
raise SystemExit
|
148
|
+
when "--no-warnings"
|
149
|
+
@options[:warning?] = false
|
150
|
+
when "--debug"
|
151
|
+
@options[:enable_debug_mode?] = true
|
152
|
+
when "--debug-extract"
|
153
|
+
@options[:enable_debug_extract?] = true
|
154
|
+
when "--"
|
155
|
+
@options[:argv] = argv.dup
|
156
|
+
argv.clear
|
157
|
+
break
|
158
|
+
when /\A--(no-)?enc\z/
|
159
|
+
@options[:add_all_encoding?] = !$1
|
160
|
+
when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
|
161
|
+
negate, group, list = $1, $2, $3
|
162
|
+
@options[:gem_options] << [negate, group.to_sym, list&.split(",")] if group
|
163
|
+
when "--help", /\A--./
|
164
|
+
puts usage
|
165
|
+
raise SystemExit
|
166
|
+
else
|
167
|
+
raise "#{arg} not found!" unless File.exist?(arg)
|
168
|
+
|
169
|
+
if File.directory?(arg)
|
170
|
+
raise "#{arg} is empty!" if Dir.empty?(arg)
|
171
|
+
# If a directory is passed, we want all files under that directory
|
172
|
+
@options[:source_files] += Pathname.new(arg).find.reject(&:directory?).map(&:expand_path)
|
173
|
+
else
|
174
|
+
@options[:source_files] << Pathname.new(arg).expand_path
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
raise "No script file specified" if source_files.empty?
|
180
|
+
|
181
|
+
@options[:script] = source_files.first
|
182
|
+
|
183
|
+
@options[:force_autoload?] = run_script? && load_autoload?
|
184
|
+
|
185
|
+
@options[:output_executable] =
|
186
|
+
if output_override
|
187
|
+
output_override
|
188
|
+
else
|
189
|
+
executable = script
|
190
|
+
# If debug mode is enabled, append "-debug" to the filename
|
191
|
+
executable = executable.append_to_filename("-debug") if enable_debug_mode?
|
192
|
+
# Build output files are created in the current directory
|
193
|
+
executable.basename.sub_ext(".exe").expand_path
|
194
|
+
end
|
195
|
+
|
196
|
+
@options[:use_inno_setup?] = !!inno_setup_script
|
197
|
+
|
198
|
+
@options[:verbose?] &&= !quiet?
|
199
|
+
|
200
|
+
@options[:windowed?] = (script.extname?(".rbw") || force_windows?) && !force_console?
|
201
|
+
|
202
|
+
if inno_setup_script
|
203
|
+
if enable_debug_extract?
|
204
|
+
raise "The --debug-extract option conflicts with use of Inno Setup"
|
205
|
+
end
|
206
|
+
|
207
|
+
if enable_compression?
|
208
|
+
raise "LZMA compression must be disabled (--no-lzma) when using Inno Setup"
|
209
|
+
end
|
210
|
+
|
211
|
+
unless chdir_before?
|
212
|
+
raise "Chdir-first mode must be enabled (--chdir-first) when using Inno Setup"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def add_all_core? = @options[__method__]
|
218
|
+
|
219
|
+
def add_all_encoding? = @options[__method__]
|
220
|
+
|
221
|
+
def argv = @options[__method__]
|
222
|
+
|
223
|
+
def auto_detect_dlls? = @options[__method__]
|
224
|
+
|
225
|
+
def chdir_before? = @options[__method__]
|
226
|
+
|
227
|
+
def enable_compression? = @options[__method__]
|
228
|
+
|
229
|
+
def enable_debug_extract? = @options[__method__]
|
230
|
+
|
231
|
+
def enable_debug_mode? = @options[__method__]
|
232
|
+
|
233
|
+
def extra_dlls = @options[__method__]
|
234
|
+
|
235
|
+
def force_autoload? = @options[__method__]
|
236
|
+
|
237
|
+
def force_console? = @options[__method__]
|
238
|
+
|
239
|
+
def force_windows? = @options[__method__]
|
240
|
+
|
241
|
+
def gem_options = @options[__method__]
|
242
|
+
|
243
|
+
def gemfile = @options[__method__]
|
244
|
+
|
245
|
+
def icon_filename = @options[__method__]
|
246
|
+
|
247
|
+
def inno_setup_script = @options[__method__]
|
248
|
+
|
249
|
+
def load_autoload? = @options[__method__]
|
250
|
+
|
251
|
+
def output_executable = @options[__method__]
|
252
|
+
|
253
|
+
def output_override = @options[__method__]
|
254
|
+
|
255
|
+
def quiet? = @options[__method__]
|
256
|
+
|
257
|
+
def rubyopt = @options[__method__]
|
258
|
+
|
259
|
+
def run_script? = @options[__method__]
|
260
|
+
|
261
|
+
def script = @options[__method__]
|
262
|
+
|
263
|
+
def source_files = @options[__method__]
|
264
|
+
|
265
|
+
def use_inno_setup? = @options[__method__]
|
266
|
+
|
267
|
+
def verbose? = @options[__method__]
|
268
|
+
|
269
|
+
def warning? = @options[__method__]
|
270
|
+
|
271
|
+
def windowed? = @options[__method__]
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
# The Pathname class in Ruby is modified to handle mixed path separators and
|
6
|
+
# to be case-insensitive.
|
7
|
+
module RefinePathname
|
8
|
+
refine Pathname do
|
9
|
+
def normalize_file_separator(s)
|
10
|
+
if File::ALT_SEPARATOR
|
11
|
+
s.tr(File::ALT_SEPARATOR, File::SEPARATOR)
|
12
|
+
else
|
13
|
+
s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
private :normalize_file_separator
|
17
|
+
|
18
|
+
# Compares two paths for equality based on the case sensitivity of the
|
19
|
+
# Ruby execution environment's file system.
|
20
|
+
# If the file system is case-insensitive, it performs a case-insensitive
|
21
|
+
# comparison. Otherwise, it performs a case-sensitive comparison.
|
22
|
+
def pathequal(a, b)
|
23
|
+
if File::FNM_SYSCASE.nonzero?
|
24
|
+
a.casecmp(b) == 0
|
25
|
+
else
|
26
|
+
a == b
|
27
|
+
end
|
28
|
+
end
|
29
|
+
private :pathequal
|
30
|
+
|
31
|
+
def to_posix
|
32
|
+
normalize_file_separator(to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks if two Pathname objects are equal, considering the file system's
|
36
|
+
# case sensitivity and path separators. Returns false if the other object is not
|
37
|
+
# an Pathname.
|
38
|
+
# This method enables the use of the `uniq` method on arrays of Pathname objects.
|
39
|
+
def eql?(other)
|
40
|
+
return false unless other.is_a?(Pathname)
|
41
|
+
|
42
|
+
a = normalize_file_separator(to_s)
|
43
|
+
b = normalize_file_separator(other.to_s)
|
44
|
+
pathequal(a, b)
|
45
|
+
end
|
46
|
+
|
47
|
+
alias == eql?
|
48
|
+
alias === eql?
|
49
|
+
|
50
|
+
# Calculates a normalized hash value for a pathname to ensure consistent
|
51
|
+
# hashing across different environments, particularly in Windows.
|
52
|
+
# This method first normalizes the path by:
|
53
|
+
# 1. Converting the file separator from the platform-specific separator
|
54
|
+
# to the common POSIX separator ('/') if necessary.
|
55
|
+
# 2. Converting the path to lowercase if the filesystem is case-insensitive.
|
56
|
+
# The normalized path string is then hashed, providing a stable hash value
|
57
|
+
# that is consistent with the behavior of eql? method, thus maintaining
|
58
|
+
# the integrity of hash-based data structures like Hash or Set.
|
59
|
+
#
|
60
|
+
# @return [Integer] A hash integer based on the normalized path.
|
61
|
+
def hash
|
62
|
+
path = if File::FNM_SYSCASE.nonzero?
|
63
|
+
to_s.downcase
|
64
|
+
else
|
65
|
+
to_s
|
66
|
+
end
|
67
|
+
normalize_file_separator(path).hash
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks if the current path is a sub path of the specified base_directory.
|
71
|
+
# Both paths must be either absolute paths or relative paths; otherwise, this
|
72
|
+
# method returns false.
|
73
|
+
def subpath?(base_directory)
|
74
|
+
s = relative_path_from(base_directory).each_filename.first
|
75
|
+
s != '.' && s != ".."
|
76
|
+
rescue ArgumentError
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# Appends the given suffix to the filename, preserving the file extension.
|
81
|
+
# If the filename has an extension, the suffix is inserted before the extension.
|
82
|
+
# If the filename does not have an extension, the suffix is appended to the end.
|
83
|
+
# This method handles both directory and file paths correctly.
|
84
|
+
#
|
85
|
+
# Examples:
|
86
|
+
# pathname = Pathname("path.to/foo.tar.gz")
|
87
|
+
# pathname.append_to_filename("_bar") # => #<Pathname:path.to/foo_bar.tar.gz>
|
88
|
+
#
|
89
|
+
# pathname = Pathname("path.to/foo")
|
90
|
+
# pathname.append_to_filename("_bar") # => #<Pathname:path.to/foo_bar>
|
91
|
+
#
|
92
|
+
def append_to_filename(suffix)
|
93
|
+
dirname + basename.sub(/(\.?[^.]+)?(\..*)?\z/, "\\1#{suffix}\\2")
|
94
|
+
end
|
95
|
+
|
96
|
+
# Checks if the file's extension matches the expected extension.
|
97
|
+
# The comparison is case-insensitive.
|
98
|
+
# Example usage: ocran_pathname.extname?(".exe")
|
99
|
+
def extname?(expected_ext)
|
100
|
+
extname.casecmp(expected_ext) == 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/ocran/runner.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
load File.expand_path("../ocran.rb", __dir__)
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
class Runner
|
6
|
+
load File.expand_path("command_output.rb", __dir__)
|
7
|
+
include CommandOutput
|
8
|
+
|
9
|
+
def fatal_error(statement)
|
10
|
+
error statement
|
11
|
+
exit false
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
load File.expand_path("runtime_environment.rb", __dir__)
|
16
|
+
@pre_env = RuntimeEnvironment.save
|
17
|
+
|
18
|
+
load File.expand_path("option.rb", __dir__)
|
19
|
+
@option = Option.new.tap do |opt|
|
20
|
+
opt.parse(ARGV)
|
21
|
+
rescue RuntimeError => e
|
22
|
+
# Capture RuntimeError during parsing and display an appropriate
|
23
|
+
# error message to the user. This error usually occurs from invalid
|
24
|
+
# option arguments.
|
25
|
+
fatal_error e.message
|
26
|
+
else
|
27
|
+
# Update ARGV with the parsed command line arguments to pass to
|
28
|
+
# the user's script. This ensures the script executes based on
|
29
|
+
# the user-specified arguments.
|
30
|
+
ARGV.replace(opt.argv)
|
31
|
+
end
|
32
|
+
|
33
|
+
Ocran.option = @option
|
34
|
+
|
35
|
+
@ignore_modules = ObjectSpace.each_object(Module).to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
at_exit do
|
40
|
+
if $!.nil? or $!.kind_of?(SystemExit)
|
41
|
+
build
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
exit unless @option.run_script?
|
47
|
+
say "Loading script to check dependencies"
|
48
|
+
$PROGRAM_NAME = @option.script.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
# Force loading autoloaded constants. Searches through all modules
|
52
|
+
# (and hence classes), and checks their constants for autoloaded
|
53
|
+
# ones, then attempts to load them.
|
54
|
+
def attempt_load_autoload(ignore_modules = [])
|
55
|
+
checked_modules = ignore_modules.inject({}) { |h, mod| h[mod] = true; h }
|
56
|
+
while ObjectSpace.each_object(Module).count { |mod|
|
57
|
+
next if checked_modules.include?(mod)
|
58
|
+
mod.constants.each do |const|
|
59
|
+
next unless mod.autoload?(const)
|
60
|
+
say "Attempting to trigger autoload of #{mod}::#{const}"
|
61
|
+
begin
|
62
|
+
mod.const_get(const)
|
63
|
+
rescue ScriptError, StandardError => e
|
64
|
+
# Some autoload constants may throw exceptions beyond the expected
|
65
|
+
# errors. This includes issues dependent on the system or execution
|
66
|
+
# environment, so it is preferable to ignore exceptions other than
|
67
|
+
# critical errors.
|
68
|
+
warning "#{mod}::#{const} loading failed: #{e.message}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
checked_modules[mod] = true
|
72
|
+
}.nonzero?
|
73
|
+
# Loops until all constants have been checked.
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def build
|
78
|
+
# If the script was run and autoload is enabled, attempt to autoload libraries.
|
79
|
+
if @option.force_autoload?
|
80
|
+
attempt_load_autoload(@ignore_modules)
|
81
|
+
end
|
82
|
+
|
83
|
+
@post_env = RuntimeEnvironment.save
|
84
|
+
# NOTE: From this point, $LOADED_FEATURES has been captured, so it is now
|
85
|
+
# safe to call require_relative.
|
86
|
+
|
87
|
+
ENV.replace(@pre_env.env)
|
88
|
+
|
89
|
+
# It might be useful to reset the current directory to the point where the
|
90
|
+
# command was launched, especially when implementing the builder object.
|
91
|
+
Dir.chdir(@pre_env.pwd)
|
92
|
+
|
93
|
+
require_relative "direction"
|
94
|
+
direction = Direction.new(@post_env, @pre_env, @option)
|
95
|
+
|
96
|
+
if @option.use_inno_setup?
|
97
|
+
direction.build_inno_setup_installer
|
98
|
+
else
|
99
|
+
direction.build_stab_exe
|
100
|
+
end
|
101
|
+
rescue RuntimeError => e
|
102
|
+
fatal_error e.message
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
load File.expand_path("refine_pathname.rb", __dir__) unless defined? RefinePathname
|
6
|
+
using RefinePathname
|
7
|
+
|
8
|
+
class RuntimeEnvironment
|
9
|
+
class << self
|
10
|
+
alias save new
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :env, :load_path, :loaded_features, :pwd
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@env = ENV.to_hash.freeze
|
17
|
+
@load_path = $LOAD_PATH.dup.freeze
|
18
|
+
@loaded_features = $LOADED_FEATURES.dup.freeze
|
19
|
+
@pwd = Dir.pwd.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
# Expands the given path using the working directory stored in this
|
23
|
+
# instance as the base. This method resolves relative paths to
|
24
|
+
# absolute paths, ensuring they are fully qualified based on the
|
25
|
+
# working directory stored within this instance.
|
26
|
+
def expand_path(path)
|
27
|
+
File.expand_path(path, @pwd)
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_load_path(path)
|
31
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
32
|
+
|
33
|
+
if path.absolute?
|
34
|
+
# For an absolute path feature, find the load path that contains the feature
|
35
|
+
# and determine the longest matching path (most specific path).
|
36
|
+
@load_path.select { |load_path| path.subpath?(expand_path(load_path)) }
|
37
|
+
.max_by { |load_path| expand_path(load_path).length }
|
38
|
+
else
|
39
|
+
# For a relative path feature, find the load path where the expanded feature exists
|
40
|
+
# and select the longest load path (most specific path).
|
41
|
+
@load_path.select { |load_path| path.expand_path(load_path).exist? }
|
42
|
+
.max_by { |load_path| load_path.length }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|