bootsnap 1.4.6 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -0
- data/README.md +45 -14
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +229 -65
- data/ext/bootsnap/extconf.rb +19 -14
- data/lib/bootsnap.rb +90 -15
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +2 -2
- data/lib/bootsnap/compile_cache/iseq.rb +21 -7
- data/lib/bootsnap/compile_cache/yaml.rb +109 -40
- data/lib/bootsnap/load_path_cache.rb +3 -16
- data/lib/bootsnap/load_path_cache/cache.rb +23 -6
- data/lib/bootsnap/load_path_cache/change_observer.rb +1 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +16 -4
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +3 -3
- data/lib/bootsnap/load_path_cache/path.rb +2 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +17 -9
- data/lib/bootsnap/setup.rb +1 -36
- data/lib/bootsnap/version.rb +1 -1
- metadata +16 -30
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -9
- data/README.jp.md +0 -231
- data/Rakefile +0 -13
- data/bin/ci +0 -10
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -46
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
- data/shipit.rubygems.yml +0 -0
@@ -34,9 +34,9 @@ module Bootsnap
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.supported?
|
37
|
-
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
37
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
38
38
|
RUBY_ENGINE == 'ruby' &&
|
39
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
39
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
40
40
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
41
41
|
end
|
42
42
|
end
|
@@ -15,7 +15,7 @@ module Bootsnap
|
|
15
15
|
raise(Uncompilable, 'syntax error')
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.storage_to_output(binary)
|
18
|
+
def self.storage_to_output(binary, _args)
|
19
19
|
RubyVM::InstructionSequence.load_from_binary(binary)
|
20
20
|
rescue RuntimeError => e
|
21
21
|
if e.message == 'broken binary format'
|
@@ -26,7 +26,24 @@ module Bootsnap
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def self.
|
29
|
+
def self.fetch(path, cache_dir: ISeq.cache_dir)
|
30
|
+
Bootsnap::CompileCache::Native.fetch(
|
31
|
+
cache_dir,
|
32
|
+
path.to_s,
|
33
|
+
Bootsnap::CompileCache::ISeq,
|
34
|
+
nil,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.precompile(path, cache_dir: ISeq.cache_dir)
|
39
|
+
Bootsnap::CompileCache::Native.precompile(
|
40
|
+
cache_dir,
|
41
|
+
path.to_s,
|
42
|
+
Bootsnap::CompileCache::ISeq,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.input_to_output(_data, _kwargs)
|
30
47
|
nil # ruby handles this
|
31
48
|
end
|
32
49
|
|
@@ -35,11 +52,7 @@ module Bootsnap
|
|
35
52
|
# Having coverage enabled prevents iseq dumping/loading.
|
36
53
|
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
37
54
|
|
38
|
-
Bootsnap::CompileCache::
|
39
|
-
Bootsnap::CompileCache::ISeq.cache_dir,
|
40
|
-
path.to_s,
|
41
|
-
Bootsnap::CompileCache::ISeq
|
42
|
-
)
|
55
|
+
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
43
56
|
rescue Errno::EACCES
|
44
57
|
Bootsnap::CompileCache.permission_error(path)
|
45
58
|
rescue RuntimeError => e
|
@@ -60,6 +73,7 @@ module Bootsnap
|
|
60
73
|
crc = Zlib.crc32(option.inspect)
|
61
74
|
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
62
75
|
end
|
76
|
+
compile_option_updated
|
63
77
|
|
64
78
|
def self.install!(cache_dir)
|
65
79
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
@@ -5,58 +5,127 @@ module Bootsnap
|
|
5
5
|
module CompileCache
|
6
6
|
module YAML
|
7
7
|
class << self
|
8
|
-
attr_accessor(:msgpack_factory)
|
9
|
-
end
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# NoMethodError is unexpected types; RangeError is Bignums
|
19
|
-
Marshal.dump(obj)
|
20
|
-
end
|
10
|
+
def input_to_storage(contents, _)
|
11
|
+
obj = strict_load(contents)
|
12
|
+
msgpack_factory.dump(obj)
|
13
|
+
rescue NoMethodError, RangeError
|
14
|
+
# The object included things that we can't serialize
|
15
|
+
raise(Uncompilable)
|
16
|
+
end
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Marshal.load(data)
|
28
|
-
else
|
29
|
-
msgpack_factory.unpacker.feed(data).read
|
18
|
+
def storage_to_output(data, kwargs)
|
19
|
+
if kwargs && kwargs.key?(:symbolize_names)
|
20
|
+
kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
|
21
|
+
end
|
22
|
+
msgpack_factory.load(data, kwargs)
|
30
23
|
end
|
31
|
-
end
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
def input_to_output(data, kwargs)
|
26
|
+
::YAML.load(data, **(kwargs || {}))
|
27
|
+
end
|
36
28
|
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
def strict_load(payload, *args)
|
30
|
+
ast = ::YAML.parse(payload)
|
31
|
+
return ast unless ast
|
32
|
+
strict_visitor.create(*args).visit(ast)
|
33
|
+
end
|
34
|
+
ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
36
|
+
def precompile(path, cache_dir: YAML.cache_dir)
|
37
|
+
Bootsnap::CompileCache::Native.precompile(
|
38
|
+
cache_dir,
|
39
|
+
path.to_s,
|
40
|
+
Bootsnap::CompileCache::YAML,
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def install!(cache_dir)
|
45
|
+
self.cache_dir = cache_dir
|
46
|
+
init!
|
47
|
+
::YAML.singleton_class.prepend(Patch)
|
48
|
+
end
|
49
|
+
|
50
|
+
def init!
|
51
|
+
require('yaml')
|
52
|
+
require('msgpack')
|
53
|
+
require('date')
|
54
|
+
|
55
|
+
# MessagePack serializes symbols as strings by default.
|
56
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
57
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
58
|
+
factory = MessagePack::Factory.new
|
59
|
+
factory.register_type(0x00, Symbol)
|
60
|
+
|
61
|
+
if defined? MessagePack::Timestamp
|
62
|
+
factory.register_type(
|
63
|
+
MessagePack::Timestamp::TYPE, # or just -1
|
64
|
+
Time,
|
65
|
+
packer: MessagePack::Time::Packer,
|
66
|
+
unpacker: MessagePack::Time::Unpacker
|
67
|
+
)
|
68
|
+
|
69
|
+
marshal_fallback = {
|
70
|
+
packer: ->(value) { Marshal.dump(value) },
|
71
|
+
unpacker: ->(payload) { Marshal.load(payload) },
|
72
|
+
}
|
73
|
+
{
|
74
|
+
Date => 0x01,
|
75
|
+
Regexp => 0x02,
|
76
|
+
}.each do |type, code|
|
77
|
+
factory.register_type(code, type, marshal_fallback)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
self.msgpack_factory = factory
|
82
|
+
|
83
|
+
self.supported_options = []
|
84
|
+
params = ::YAML.method(:load).parameters
|
85
|
+
if params.include?([:key, :symbolize_names])
|
86
|
+
self.supported_options << :symbolize_names
|
87
|
+
end
|
88
|
+
if params.include?([:key, :freeze])
|
89
|
+
if factory.load(factory.dump('yaml'), freeze: true).frozen?
|
90
|
+
self.supported_options << :freeze
|
91
|
+
end
|
92
|
+
end
|
93
|
+
self.supported_options.freeze
|
94
|
+
end
|
95
|
+
|
96
|
+
def strict_visitor
|
97
|
+
self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
|
98
|
+
def visit(target)
|
99
|
+
if target.tag
|
100
|
+
raise Uncompilable, "YAML tags are not supported: #{target.tag}"
|
101
|
+
end
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module Patch
|
109
|
+
def load_file(path, *args)
|
110
|
+
return super if args.size > 1
|
111
|
+
if kwargs = args.first
|
112
|
+
return super unless kwargs.is_a?(Hash)
|
113
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
114
|
+
end
|
47
115
|
|
48
|
-
klass = class << ::YAML; self; end
|
49
|
-
klass.send(:define_method, :load_file) do |path|
|
50
116
|
begin
|
51
|
-
Bootsnap::CompileCache::Native.fetch(
|
52
|
-
cache_dir,
|
53
|
-
path,
|
54
|
-
Bootsnap::CompileCache::YAML
|
117
|
+
::Bootsnap::CompileCache::Native.fetch(
|
118
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
119
|
+
File.realpath(path),
|
120
|
+
::Bootsnap::CompileCache::YAML,
|
121
|
+
kwargs,
|
55
122
|
)
|
56
123
|
rescue Errno::EACCES
|
57
|
-
Bootsnap::CompileCache.permission_error(path)
|
124
|
+
::Bootsnap::CompileCache.permission_error(path)
|
58
125
|
end
|
59
126
|
end
|
127
|
+
|
128
|
+
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
60
129
|
end
|
61
130
|
end
|
62
131
|
end
|
@@ -28,10 +28,9 @@ module Bootsnap
|
|
28
28
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
29
29
|
|
30
30
|
class << self
|
31
|
-
attr_reader(:load_path_cache, :
|
32
|
-
:loaded_features_index, :realpath_cache)
|
31
|
+
attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
|
33
32
|
|
34
|
-
def setup(cache_path:, development_mode
|
33
|
+
def setup(cache_path:, development_mode:)
|
35
34
|
unless supported?
|
36
35
|
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
37
36
|
return
|
@@ -45,23 +44,11 @@ module Bootsnap
|
|
45
44
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
46
45
|
require_relative('load_path_cache/core_ext/kernel_require')
|
47
46
|
require_relative('load_path_cache/core_ext/loaded_features')
|
48
|
-
|
49
|
-
if active_support
|
50
|
-
# this should happen after setting up the initial cache because it
|
51
|
-
# loads a lot of code. It's better to do after +require+ is optimized.
|
52
|
-
require('active_support/dependencies')
|
53
|
-
@autoload_paths_cache = Cache.new(
|
54
|
-
store,
|
55
|
-
::ActiveSupport::Dependencies.autoload_paths,
|
56
|
-
development_mode: development_mode
|
57
|
-
)
|
58
|
-
require_relative('load_path_cache/core_ext/active_support')
|
59
|
-
end
|
60
47
|
end
|
61
48
|
|
62
49
|
def supported?
|
63
50
|
RUBY_ENGINE == 'ruby' &&
|
64
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/
|
51
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
|
65
52
|
end
|
66
53
|
end
|
67
54
|
end
|
@@ -10,8 +10,8 @@ module Bootsnap
|
|
10
10
|
def initialize(store, path_obj, development_mode: false)
|
11
11
|
@development_mode = development_mode
|
12
12
|
@store = store
|
13
|
-
@mutex =
|
14
|
-
@path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
|
15
15
|
@has_relative_paths = nil
|
16
16
|
reinitialize
|
17
17
|
end
|
@@ -46,7 +46,7 @@ module Bootsnap
|
|
46
46
|
# loadpath.
|
47
47
|
def find(feature)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
-
feature = feature.to_s
|
49
|
+
feature = feature.to_s.freeze
|
50
50
|
return feature if absolute_path?(feature)
|
51
51
|
return expand_path(feature) if feature.start_with?('./')
|
52
52
|
@mutex.synchronize do
|
@@ -194,9 +194,26 @@ module Bootsnap
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
|
197
|
+
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
|
198
|
+
if s.respond_to?(:-@)
|
199
|
+
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= '2.7'
|
200
|
+
def try_index(f)
|
201
|
+
if (p = @index[f])
|
202
|
+
-(File.join(p, f).freeze)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
else
|
206
|
+
def try_index(f)
|
207
|
+
if (p = @index[f])
|
208
|
+
-File.join(p, f).untaint
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
def try_index(f)
|
214
|
+
if (p = @index[f])
|
215
|
+
File.join(p, f)
|
216
|
+
end
|
200
217
|
end
|
201
218
|
end
|
202
219
|
|
@@ -26,7 +26,7 @@ module Bootsnap
|
|
26
26
|
super
|
27
27
|
end
|
28
28
|
|
29
|
-
# uniq! keeps the first
|
29
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
30
30
|
# order, preserving the effective load path
|
31
31
|
def uniq!(*args)
|
32
32
|
ret = super
|
@@ -38,7 +38,11 @@ module Kernel
|
|
38
38
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
39
39
|
false
|
40
40
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
41
|
-
|
41
|
+
fallback = true
|
42
|
+
ensure
|
43
|
+
if fallback
|
44
|
+
require_with_bootsnap_lfi(path)
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
48
|
alias_method(:require_relative_without_bootsnap, :require_relative)
|
@@ -56,7 +60,7 @@ module Kernel
|
|
56
60
|
end
|
57
61
|
|
58
62
|
# load also allows relative paths from pwd even when not in $:
|
59
|
-
if File.exist?(relative = File.expand_path(path))
|
63
|
+
if File.exist?(relative = File.expand_path(path).freeze)
|
60
64
|
return load_without_bootsnap(relative, wrap)
|
61
65
|
end
|
62
66
|
|
@@ -67,7 +71,11 @@ module Kernel
|
|
67
71
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
68
72
|
false
|
69
73
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
70
|
-
|
74
|
+
fallback = true
|
75
|
+
ensure
|
76
|
+
if fallback
|
77
|
+
load_without_bootsnap(path, wrap)
|
78
|
+
end
|
71
79
|
end
|
72
80
|
end
|
73
81
|
|
@@ -88,6 +96,10 @@ class Module
|
|
88
96
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
89
97
|
false
|
90
98
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
91
|
-
|
99
|
+
fallback = true
|
100
|
+
ensure
|
101
|
+
if fallback
|
102
|
+
autoload_without_bootsnap(const, path)
|
103
|
+
end
|
92
104
|
end
|
93
105
|
end
|
@@ -26,7 +26,7 @@ module Bootsnap
|
|
26
26
|
class LoadedFeaturesIndex
|
27
27
|
def initialize
|
28
28
|
@lfi = {}
|
29
|
-
@mutex =
|
29
|
+
@mutex = Mutex.new
|
30
30
|
|
31
31
|
# In theory the user could mutate $LOADED_FEATURES and invalidate our
|
32
32
|
# cache. If this ever comes up in practice — or if you, the
|
@@ -99,7 +99,7 @@ module Bootsnap
|
|
99
99
|
altname = if extension_elidable?(short)
|
100
100
|
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
101
101
|
strip_extension_if_elidable(short)
|
102
|
-
elsif long && (ext = File.extname(long))
|
102
|
+
elsif long && (ext = File.extname(long.freeze))
|
103
103
|
# We already know the extension of the actual file this
|
104
104
|
# resolves to, so put that back on.
|
105
105
|
short + ext
|
@@ -129,7 +129,7 @@ module Bootsnap
|
|
129
129
|
# to name files in a way that assumes otherwise.
|
130
130
|
# (E.g. It's unlikely that someone will know that their code
|
131
131
|
# will _never_ run on MacOS, and therefore think they can get away
|
132
|
-
# with
|
132
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
133
133
|
#
|
134
134
|
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
135
135
|
def extension_elidable?(f)
|
@@ -21,7 +21,7 @@ module Bootsnap
|
|
21
21
|
attr_reader(:path)
|
22
22
|
|
23
23
|
def initialize(path)
|
24
|
-
@path = path.to_s
|
24
|
+
@path = path.to_s.freeze
|
25
25
|
end
|
26
26
|
|
27
27
|
# True if the path exists, but represents a non-directory object
|
@@ -60,7 +60,7 @@ module Bootsnap
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def expanded_path
|
63
|
-
File.expand_path(path)
|
63
|
+
File.expand_path(path).freeze
|
64
64
|
end
|
65
65
|
|
66
66
|
private
|
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
|
|
5
5
|
module Bootsnap
|
6
6
|
module LoadPathCache
|
7
7
|
module PathScanner
|
8
|
-
ALL_FILES = "/{,**/*/**/}*"
|
9
8
|
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
10
9
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
11
10
|
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
|
@@ -16,34 +15,59 @@ module Bootsnap
|
|
16
15
|
''
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
requirables << relative_path
|
18
|
+
class << self
|
19
|
+
def call(path)
|
20
|
+
path = File.expand_path(path.to_s).freeze
|
21
|
+
return [[], []] unless File.directory?(path)
|
22
|
+
|
23
|
+
# If the bundle path is a descendent of this path, we do additional
|
24
|
+
# checks to prevent recursing into the bundle path as we recurse
|
25
|
+
# through this path. We don't want to scan the bundle path because
|
26
|
+
# anything useful in it will be present on other load path items.
|
27
|
+
#
|
28
|
+
# This can happen if, for example, the user adds '.' to the load path,
|
29
|
+
# and the bundle path is '.bundle'.
|
30
|
+
contains_bundle_path = BUNDLE_PATH.start_with?(path)
|
31
|
+
|
32
|
+
dirs = []
|
33
|
+
requirables = []
|
34
|
+
walk(path, nil) do |relative_path, absolute_path, is_directory|
|
35
|
+
if is_directory
|
36
|
+
dirs << os_path(relative_path)
|
37
|
+
!contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
|
38
|
+
elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
|
39
|
+
requirables << os_path(relative_path)
|
40
|
+
end
|
43
41
|
end
|
42
|
+
[requirables, dirs]
|
44
43
|
end
|
45
44
|
|
46
|
-
|
45
|
+
def walk(absolute_dir_path, relative_dir_path, &block)
|
46
|
+
Dir.foreach(absolute_dir_path) do |name|
|
47
|
+
next if name.start_with?('.')
|
48
|
+
relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
|
49
|
+
|
50
|
+
absolute_path = "#{absolute_dir_path}/#{name}"
|
51
|
+
if File.directory?(absolute_path)
|
52
|
+
if yield relative_path, absolute_path, true
|
53
|
+
walk(absolute_path, relative_path, &block)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
yield relative_path, absolute_path, false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if RUBY_VERSION >= '3.1'
|
62
|
+
def os_path(path)
|
63
|
+
path.freeze
|
64
|
+
end
|
65
|
+
else
|
66
|
+
def os_path(path)
|
67
|
+
path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
|
68
|
+
path.freeze
|
69
|
+
end
|
70
|
+
end
|
47
71
|
end
|
48
72
|
end
|
49
73
|
end
|