zeitwerk 2.7.1 → 2.7.3
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/lib/zeitwerk/core_ext/kernel.rb +3 -3
- data/lib/zeitwerk/core_ext/module.rb +6 -5
- data/lib/zeitwerk/cref/map.rb +159 -0
- data/lib/zeitwerk/cref.rb +17 -16
- data/lib/zeitwerk/error.rb +2 -0
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +4 -4
- data/lib/zeitwerk/inflector.rb +3 -3
- data/lib/zeitwerk/internal.rb +1 -0
- data/lib/zeitwerk/loader/callbacks.rb +19 -23
- data/lib/zeitwerk/loader/config.rb +37 -44
- data/lib/zeitwerk/loader/eager_load.rb +7 -7
- data/lib/zeitwerk/loader/helpers.rb +9 -10
- data/lib/zeitwerk/loader.rb +133 -95
- data/lib/zeitwerk/null_inflector.rb +1 -0
- data/lib/zeitwerk/real_mod_name.rb +7 -4
- data/lib/zeitwerk/registry/autoloads.rb +38 -0
- data/lib/zeitwerk/registry/explicit_namespaces.rb +61 -0
- data/lib/zeitwerk/registry/inceptions.rb +31 -0
- data/lib/zeitwerk/registry/loaders.rb +33 -0
- data/lib/zeitwerk/registry.rb +18 -96
- data/lib/zeitwerk/version.rb +2 -1
- data/lib/zeitwerk.rb +1 -2
- metadata +8 -7
- data/lib/zeitwerk/explicit_namespace.rb +0 -113
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1500c4f54c6ac7e64eef74c8702682764fa845533a5b16c67a48ee11781ef3e0
|
4
|
+
data.tar.gz: f7201576b8b59ab3786cd4bc6fd58f3cd3afa3d8dcc1e86477121000f06ac50a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab113d90c9a42ec5c75c6ebe68ab5a04395f1b33228de42de6952f2e673a329f30de85477977ec11d582df82f40c4dc5c08e8ba2305f46a6d07ac4e88c564887
|
7
|
+
data.tar.gz: 73fb9dc78a9a7148119b306f93a513f841e4888d421fbf178963bbfa1368a8c7c66cd8661a28e8d95d14f51dab16fb4ff886f23183ec13a74847c45c1850f30d
|
@@ -19,9 +19,9 @@ module Kernel
|
|
19
19
|
alias_method :zeitwerk_original_require, :require
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
#: (String) -> bool
|
23
23
|
def require(path)
|
24
|
-
if loader = Zeitwerk::Registry.
|
24
|
+
if loader = Zeitwerk::Registry.autoloads.registered?(path)
|
25
25
|
if path.end_with?(".rb")
|
26
26
|
required = zeitwerk_original_require(path)
|
27
27
|
loader.__on_file_autoloaded(path) if required
|
@@ -34,7 +34,7 @@ module Kernel
|
|
34
34
|
required = zeitwerk_original_require(path)
|
35
35
|
if required
|
36
36
|
abspath = $LOADED_FEATURES.last
|
37
|
-
if loader = Zeitwerk::Registry.
|
37
|
+
if loader = Zeitwerk::Registry.autoloads.registered?(abspath)
|
38
38
|
loader.__on_file_autoloaded(abspath)
|
39
39
|
end
|
40
40
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Zeitwerk::ConstAdded
|
3
|
+
module Zeitwerk::ConstAdded # :nodoc:
|
4
|
+
#: (Symbol) -> void
|
4
5
|
def const_added(cname)
|
5
|
-
if loader = Zeitwerk::
|
6
|
+
if loader = Zeitwerk::Registry.explicit_namespaces.loader_for(self, cname)
|
6
7
|
namespace = const_get(cname, false)
|
8
|
+
cref = Zeitwerk::Cref.new(self, cname)
|
7
9
|
|
8
10
|
unless namespace.is_a?(Module)
|
9
|
-
|
10
|
-
raise Zeitwerk::Error, "#{cref.path} is expected to be a namespace, should be a class or module (got #{namespace.class})"
|
11
|
+
raise Zeitwerk::Error, "#{cref} is expected to be a namespace, should be a class or module (got #{namespace.class})"
|
11
12
|
end
|
12
13
|
|
13
|
-
loader.
|
14
|
+
loader.__on_namespace_loaded(cref, namespace)
|
14
15
|
end
|
15
16
|
super
|
16
17
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Description of the structure
|
4
|
+
# ----------------------------
|
5
|
+
#
|
6
|
+
# This class emulates a hash table whose keys are of type Zeitwerk::Cref.
|
7
|
+
#
|
8
|
+
# It is a synchronized 2-level hash.
|
9
|
+
#
|
10
|
+
# The keys of the top one, stored in `@map`, are class and module objects, but
|
11
|
+
# their hash code is forced to be their object IDs because class and module
|
12
|
+
# objects may not be hashable (https://github.com/fxn/zeitwerk/issues/188).
|
13
|
+
#
|
14
|
+
# Then, each one of them stores a hash table with their constants and values.
|
15
|
+
# Constants are stored as symbols.
|
16
|
+
#
|
17
|
+
# For example, if we store values 0, 1, and 2 for the crefs that would
|
18
|
+
# correspond to `M::X`, `M::Y`, and `N::Z`, the map will look like this:
|
19
|
+
#
|
20
|
+
# { M => { X: 0, :Y => 1 }, N => { Z: 2 } }
|
21
|
+
#
|
22
|
+
# This structure is internal, so only the needed interface is implemented.
|
23
|
+
#
|
24
|
+
# Alternative approaches
|
25
|
+
# -----------------------
|
26
|
+
#
|
27
|
+
# 1. We could also use a 1-level hash whose keys are constant paths. In the
|
28
|
+
# example above it would be:
|
29
|
+
#
|
30
|
+
# { "M::X" => 0, "M::Y" => 1, "N::Z" => 2 }
|
31
|
+
#
|
32
|
+
# The gem used this approach for several years.
|
33
|
+
#
|
34
|
+
# 2. Write a custom `hash`/`eql?` in Zeitwerk::Cref. Hash code would be
|
35
|
+
#
|
36
|
+
# real_mod_hash(@mod) ^ @cname.hash
|
37
|
+
#
|
38
|
+
# where `real_mod_hash(@mod)` would actually be a call to the real `hash`
|
39
|
+
# method in Module. Like what we do for module names to bypass overrides.
|
40
|
+
#
|
41
|
+
# 3. Similar to 2, but use
|
42
|
+
#
|
43
|
+
# @mod.object_id ^ @cname.object_id
|
44
|
+
#
|
45
|
+
# as hash code instead.
|
46
|
+
#
|
47
|
+
# Benchamrks
|
48
|
+
# ----------
|
49
|
+
#
|
50
|
+
# Writing:
|
51
|
+
#
|
52
|
+
# map - baseline
|
53
|
+
# (3) - 1.74x slower
|
54
|
+
# (2) - 2.91x slower
|
55
|
+
# (1) - 3.87x slower
|
56
|
+
#
|
57
|
+
# Reading:
|
58
|
+
#
|
59
|
+
# map - baseline
|
60
|
+
# (3) - 1.99x slower
|
61
|
+
# (2) - 2.80x slower
|
62
|
+
# (1) - 3.48x slower
|
63
|
+
#
|
64
|
+
# Extra ball
|
65
|
+
# ----------
|
66
|
+
#
|
67
|
+
# In addition to that, the map is synchronized and provides `delete_mod_cname`,
|
68
|
+
# which is ad-hoc for the hot path in `const_added`, we do not need to create
|
69
|
+
# unnecessary cref objects for constants we do not manage (but we do not know in
|
70
|
+
# advance there).
|
71
|
+
|
72
|
+
#: [Value]
|
73
|
+
class Zeitwerk::Cref::Map # :nodoc: all
|
74
|
+
#: () -> void
|
75
|
+
def initialize
|
76
|
+
@map = {}
|
77
|
+
@map.compare_by_identity
|
78
|
+
@mutex = Mutex.new
|
79
|
+
end
|
80
|
+
|
81
|
+
#: (Zeitwerk::Cref, Value) -> Value
|
82
|
+
def []=(cref, value)
|
83
|
+
@mutex.synchronize do
|
84
|
+
cnames = (@map[cref.mod] ||= {})
|
85
|
+
cnames[cref.cname] = value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#: (Zeitwerk::Cref) -> Value?
|
90
|
+
def [](cref)
|
91
|
+
@mutex.synchronize do
|
92
|
+
@map[cref.mod]&.[](cref.cname)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#: (Zeitwerk::Cref, { () -> Value }) -> Value
|
97
|
+
def get_or_set(cref, &block)
|
98
|
+
@mutex.synchronize do
|
99
|
+
cnames = (@map[cref.mod] ||= {})
|
100
|
+
cnames.fetch(cref.cname) { cnames[cref.cname] = block.call }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#: (Zeitwerk::Cref) -> Value?
|
105
|
+
def delete(cref)
|
106
|
+
delete_mod_cname(cref.mod, cref.cname)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Ad-hoc for loader_for, called from const_added. That is a hot path, I prefer
|
110
|
+
# to not create a cref in every call, since that is global.
|
111
|
+
#
|
112
|
+
#: (Module, Symbol) -> Value?
|
113
|
+
def delete_mod_cname(mod, cname)
|
114
|
+
@mutex.synchronize do
|
115
|
+
if cnames = @map[mod]
|
116
|
+
value = cnames.delete(cname)
|
117
|
+
@map.delete(mod) if cnames.empty?
|
118
|
+
value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
#: (Value) -> void
|
124
|
+
def delete_by_value(value)
|
125
|
+
@mutex.synchronize do
|
126
|
+
@map.delete_if do |mod, cnames|
|
127
|
+
cnames.delete_if { _2 == value }
|
128
|
+
cnames.empty?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Order of yielded crefs is undefined.
|
134
|
+
#
|
135
|
+
#: () { (Zeitwerk::Cref) -> void } -> void
|
136
|
+
def each_key
|
137
|
+
@mutex.synchronize do
|
138
|
+
@map.each do |mod, cnames|
|
139
|
+
cnames.each_key do |cname|
|
140
|
+
yield Zeitwerk::Cref.new(mod, cname)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#: () -> void
|
147
|
+
def clear
|
148
|
+
@mutex.synchronize do
|
149
|
+
@map.clear
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#: () -> bool
|
154
|
+
def empty? # for tests
|
155
|
+
@mutex.synchronize do
|
156
|
+
@map.empty?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/lib/zeitwerk/cref.rb
CHANGED
@@ -2,66 +2,67 @@
|
|
2
2
|
|
3
3
|
# This private class encapsulates pairs (mod, cname).
|
4
4
|
#
|
5
|
-
# Objects represent the constant cname in the class or module object mod
|
6
|
-
# have API to manage them
|
5
|
+
# Objects represent the constant `cname` in the class or module object `mod`,
|
6
|
+
# and have API to manage them. Examples:
|
7
7
|
#
|
8
8
|
# cref.path
|
9
9
|
# cref.set(value)
|
10
10
|
# cref.get
|
11
11
|
#
|
12
|
-
# The constant may or may not exist in mod
|
12
|
+
# The constant may or may not exist in `mod`.
|
13
13
|
class Zeitwerk::Cref
|
14
|
+
require_relative "cref/map"
|
15
|
+
|
14
16
|
include Zeitwerk::RealModName
|
15
17
|
|
16
|
-
|
18
|
+
#: Module
|
17
19
|
attr_reader :mod
|
18
20
|
|
19
|
-
|
21
|
+
#: Symbol
|
20
22
|
attr_reader :cname
|
21
23
|
|
22
24
|
# The type of the first argument is Module because Class < Module, class
|
23
25
|
# objects are also valid.
|
24
26
|
#
|
25
|
-
|
27
|
+
#: (Module, Symbol) -> void
|
26
28
|
def initialize(mod, cname)
|
27
29
|
@mod = mod
|
28
30
|
@cname = cname
|
29
31
|
@path = nil
|
30
32
|
end
|
31
33
|
|
32
|
-
|
34
|
+
#: () -> String
|
33
35
|
def path
|
34
|
-
@path ||= Object
|
36
|
+
@path ||= Object == @mod ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}".freeze
|
35
37
|
end
|
38
|
+
alias to_s path
|
36
39
|
|
37
|
-
|
40
|
+
#: () -> String?
|
38
41
|
def autoload?
|
39
42
|
@mod.autoload?(@cname, false)
|
40
43
|
end
|
41
44
|
|
42
|
-
|
45
|
+
#: (String) -> nil
|
43
46
|
def autoload(abspath)
|
44
47
|
@mod.autoload(@cname, abspath)
|
45
48
|
end
|
46
49
|
|
47
|
-
|
50
|
+
#: () -> bool
|
48
51
|
def defined?
|
49
52
|
@mod.const_defined?(@cname, false)
|
50
53
|
end
|
51
54
|
|
52
|
-
|
55
|
+
#: (top) -> top
|
53
56
|
def set(value)
|
54
57
|
@mod.const_set(@cname, value)
|
55
58
|
end
|
56
59
|
|
57
|
-
|
58
|
-
# @sig () -> Object
|
60
|
+
#: () -> top ! NameError
|
59
61
|
def get
|
60
62
|
@mod.const_get(@cname, false)
|
61
63
|
end
|
62
64
|
|
63
|
-
|
64
|
-
# @sig () -> void
|
65
|
+
#: () -> void ! NameError
|
65
66
|
def remove
|
66
67
|
@mod.__send__(:remove_const, @cname)
|
67
68
|
end
|
data/lib/zeitwerk/error.rb
CHANGED
@@ -5,6 +5,7 @@ module Zeitwerk
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class ReloadingDisabledError < Error
|
8
|
+
#: () -> void
|
8
9
|
def initialize
|
9
10
|
super("can't reload, please call loader.enable_reloading before setup")
|
10
11
|
end
|
@@ -14,6 +15,7 @@ module Zeitwerk
|
|
14
15
|
end
|
15
16
|
|
16
17
|
class SetupRequired < Error
|
18
|
+
#: () -> void
|
17
19
|
def initialize
|
18
20
|
super("please, finish your configuration and call Zeitwerk::Loader#setup once all is ready")
|
19
21
|
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
class GemInflector < Inflector
|
5
|
-
|
5
|
+
#: (String) -> void
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
8
8
|
root_dir = File.dirname(root_file)
|
9
9
|
@version_file = File.join(root_dir, namespace, "version.rb")
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
#: (String, String) -> String
|
13
13
|
def camelize(basename, abspath)
|
14
14
|
abspath == @version_file ? "VERSION" : super
|
15
15
|
end
|
data/lib/zeitwerk/gem_loader.rb
CHANGED
@@ -10,12 +10,12 @@ module Zeitwerk
|
|
10
10
|
private_class_method :new
|
11
11
|
|
12
12
|
# @private
|
13
|
-
|
13
|
+
#: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::GemLoader
|
14
14
|
def self.__new(root_file, namespace:, warn_on_extra_files:)
|
15
15
|
new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
#: (String, namespace: Module, warn_on_extra_files: boolish) -> void
|
19
19
|
def initialize(root_file, namespace:, warn_on_extra_files:)
|
20
20
|
super()
|
21
21
|
|
@@ -30,7 +30,7 @@ module Zeitwerk
|
|
30
30
|
push_dir(@root_dir, namespace: namespace)
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
#: () -> void
|
34
34
|
def setup
|
35
35
|
warn_on_extra_files if @warn_on_extra_files
|
36
36
|
super
|
@@ -38,7 +38,7 @@ module Zeitwerk
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
|
41
|
+
#: () -> void
|
42
42
|
def warn_on_extra_files
|
43
43
|
expected_namespace_dir = @root_file.delete_suffix(".rb")
|
44
44
|
|
data/lib/zeitwerk/inflector.rb
CHANGED
@@ -11,7 +11,7 @@ module Zeitwerk
|
|
11
11
|
#
|
12
12
|
# Takes into account hard-coded mappings configured with `inflect`.
|
13
13
|
#
|
14
|
-
|
14
|
+
#: (String, String) -> String
|
15
15
|
def camelize(basename, _abspath)
|
16
16
|
overrides[basename] || basename.split('_').each(&:capitalize!).join
|
17
17
|
end
|
@@ -28,7 +28,7 @@ module Zeitwerk
|
|
28
28
|
# inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
|
29
29
|
# inflector.camelize("users_controller", abspath) # => "UsersController"
|
30
30
|
#
|
31
|
-
|
31
|
+
#: (Hash[String, String]) -> void
|
32
32
|
def inflect(inflections)
|
33
33
|
overrides.merge!(inflections)
|
34
34
|
end
|
@@ -38,7 +38,7 @@ module Zeitwerk
|
|
38
38
|
# Hard-coded basename to constant name user maps that override the default
|
39
39
|
# inflection logic.
|
40
40
|
#
|
41
|
-
|
41
|
+
#: () -> Hash[String, String]
|
42
42
|
def overrides
|
43
43
|
@overrides ||= {}
|
44
44
|
end
|
data/lib/zeitwerk/internal.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Zeitwerk::Loader::Callbacks
|
4
|
-
include Zeitwerk::RealModName
|
3
|
+
module Zeitwerk::Loader::Callbacks # :nodoc: all
|
5
4
|
extend Zeitwerk::Internal
|
6
5
|
|
7
6
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
8
7
|
#
|
9
|
-
|
10
|
-
# @sig (String) -> void
|
8
|
+
#: (String) -> void ! Zeitwerk::NameError
|
11
9
|
internal def on_file_autoloaded(file)
|
12
10
|
cref = autoloads.delete(file)
|
13
11
|
|
14
|
-
Zeitwerk::Registry.
|
12
|
+
Zeitwerk::Registry.autoloads.unregister(file)
|
15
13
|
|
16
14
|
if cref.defined?
|
17
|
-
log("constant #{cref
|
18
|
-
to_unload[
|
15
|
+
log("constant #{cref} loaded from file #{file}") if logger
|
16
|
+
to_unload[file] = cref if reloading_enabled?
|
19
17
|
run_on_load_callbacks(cref.path, cref.get, file) unless on_load_callbacks.empty?
|
20
18
|
else
|
21
|
-
msg = "expected file #{file} to define constant #{cref
|
19
|
+
msg = "expected file #{file} to define constant #{cref}, but didn't"
|
22
20
|
log(msg) if logger
|
23
21
|
|
24
22
|
# Ruby still keeps the autoload defined, but we remove it because the
|
@@ -28,7 +26,7 @@ module Zeitwerk::Loader::Callbacks
|
|
28
26
|
# Since the expected constant was not defined, there is nothing to unload.
|
29
27
|
# However, if the exception is rescued and reloading is enabled, we still
|
30
28
|
# need to deleted the file from $LOADED_FEATURES.
|
31
|
-
to_unload[
|
29
|
+
to_unload[file] = cref if reloading_enabled?
|
32
30
|
|
33
31
|
raise Zeitwerk::NameError.new(msg, cref.cname)
|
34
32
|
end
|
@@ -37,7 +35,7 @@ module Zeitwerk::Loader::Callbacks
|
|
37
35
|
# Invoked from our decorated Kernel#require when a managed directory is
|
38
36
|
# autoloaded.
|
39
37
|
#
|
40
|
-
|
38
|
+
#: (String) -> void
|
41
39
|
internal def on_dir_autoloaded(dir)
|
42
40
|
# Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
|
43
41
|
# we handle directories ourselves without going through Kernel#require, so
|
@@ -54,10 +52,9 @@ module Zeitwerk::Loader::Callbacks
|
|
54
52
|
dirs_autoload_monitor.synchronize do
|
55
53
|
if cref = autoloads.delete(dir)
|
56
54
|
implicit_namespace = cref.set(Module.new)
|
57
|
-
|
58
|
-
log("module #{cpath} autovivified from directory #{dir}") if logger
|
55
|
+
log("module #{cref} autovivified from directory #{dir}") if logger
|
59
56
|
|
60
|
-
to_unload[
|
57
|
+
to_unload[dir] = cref if reloading_enabled?
|
61
58
|
|
62
59
|
# We don't unregister `dir` in the registry because concurrent threads
|
63
60
|
# wouldn't find a loader associated to it in Kernel#require and would
|
@@ -65,21 +62,20 @@ module Zeitwerk::Loader::Callbacks
|
|
65
62
|
# these to be able to unregister later if eager loading.
|
66
63
|
autoloaded_dirs << dir
|
67
64
|
|
68
|
-
on_namespace_loaded(implicit_namespace)
|
65
|
+
on_namespace_loaded(cref, implicit_namespace)
|
69
66
|
|
70
|
-
run_on_load_callbacks(
|
67
|
+
run_on_load_callbacks(cref.path, implicit_namespace, dir) unless on_load_callbacks.empty?
|
71
68
|
end
|
72
69
|
end
|
73
70
|
end
|
74
71
|
|
75
|
-
# Invoked when a
|
76
|
-
#
|
77
|
-
#
|
72
|
+
# Invoked when a namespace is created, either from const_added or from module
|
73
|
+
# autovivification. If the namespace has matching subdirectories, we descend
|
74
|
+
# into them now.
|
78
75
|
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
if dirs = namespace_dirs.delete(real_mod_name(namespace))
|
76
|
+
#: (Zeitwerk::Cref, Module) -> void
|
77
|
+
internal def on_namespace_loaded(cref, namespace)
|
78
|
+
if dirs = namespace_dirs.delete(cref)
|
83
79
|
dirs.each do |dir|
|
84
80
|
define_autoloads_for_dir(dir, namespace)
|
85
81
|
end
|
@@ -88,7 +84,7 @@ module Zeitwerk::Loader::Callbacks
|
|
88
84
|
|
89
85
|
private
|
90
86
|
|
91
|
-
|
87
|
+
#: (String, top, String) -> void
|
92
88
|
def run_on_load_callbacks(cpath, value, abspath)
|
93
89
|
# Order matters. If present, run the most specific one.
|
94
90
|
callbacks = reloading_enabled? ? on_load_callbacks[cpath] : on_load_callbacks.delete(cpath)
|