-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathbase_loader.rb
178 lines (157 loc) · 4.97 KB
/
base_loader.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# frozen_string_literal: true
module Puppet::Pops
module Loader
# BaseLoader
# ===
# An abstract implementation of Loader
#
# A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching".
#
# @api private
#
class BaseLoader < Loader
# The parent loader
attr_reader :parent
def initialize(parent_loader, loader_name, environment)
super(loader_name, environment)
@parent = parent_loader # the higher priority loader to consult
@named_values = {} # hash name => NamedEntry
@last_result = nil # the value of the last name (optimization)
end
def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block)
result = []
@named_values.each_pair do |key, entry|
result << key unless entry.nil? || entry.value.nil? || key.type != type || (block_given? && !yield(key))
end
result.concat(parent.discover(type, error_collector, name_authority, &block))
result.uniq!
result
end
# @api public
#
def load_typed(typed_name)
# The check for "last queried name" is an optimization when a module searches. First it checks up its parent
# chain, then itself, and then delegates to modules it depends on.
# These modules are typically parented by the same
# loader as the one initiating the search. It is inefficient to again try to search the same loader for
# the same name.
synchronize do
if @last_result.nil? || typed_name != @last_result.typed_name
@last_result = internal_load(typed_name)
else
@last_result
end
end
end
# @api public
#
def loaded_entry(typed_name, check_dependencies = false)
synchronize do
if @named_values.has_key?(typed_name)
@named_values[typed_name]
elsif parent
parent.loaded_entry(typed_name, check_dependencies)
else
nil
end
end
end
# This method is final (subclasses should not override it)
#
# @api private
#
def get_entry(typed_name)
@named_values[typed_name]
end
# @api private
#
def set_entry(typed_name, value, origin = nil)
synchronize do
# It is never ok to redefine in the very same loader unless redefining a 'not found'
entry = @named_values[typed_name]
if entry
fail_redefine(entry) unless entry.value.nil?
end
# Check if new entry shadows existing entry and fail
# (unless special loader allows shadowing)
if typed_name.type == :type && !allow_shadowing?
entry = loaded_entry(typed_name)
if entry
fail_redefine(entry) unless entry.value.nil? # || entry.value == value
end
end
@last_result = Loader::NamedEntry.new(typed_name, value, origin)
@named_values[typed_name] = @last_result
end
end
# @api private
#
def add_entry(type, name, value, origin)
set_entry(TypedName.new(type, name), value, origin)
end
# @api private
#
def remove_entry(typed_name)
synchronize do
unless @named_values.delete(typed_name).nil?
@last_result = nil unless @last_result.nil? || typed_name != @last_result.typed_name
end
end
end
# Promotes an already created entry (typically from another loader) to this loader
#
# @api private
#
def promote_entry(named_entry)
synchronize do
typed_name = named_entry.typed_name
entry = @named_values[typed_name]
if entry then fail_redefine(entry); end
@named_values[typed_name] = named_entry
end
end
protected
def allow_shadowing?
false
end
private
def fail_redefine(entry)
origin_info = entry.origin ? _("Originally set %{original}.") % { original: origin_label(entry.origin) } : _("Set at unknown location")
raise ArgumentError, _("Attempt to redefine entity '%{name}'. %{origin_info}") % { name: entry.typed_name, origin_info: origin_info }
end
# TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI?
#
def origin_label(origin)
if origin && origin.is_a?(URI)
format_uri(origin)
elsif origin.respond_to?(:uri)
format_uri(origin.uri)
else
origin
end
end
def format_uri(uri)
(uri.scheme == 'puppet' ? 'by ' : 'at ') + uri.to_s.sub(/^puppet:/, '')
end
# loads in priority order:
# 1. already loaded here
# 2. load from parent
# 3. find it here
# 4. give up
#
def internal_load(typed_name)
# avoid calling get_entry by looking it up
te = @named_values[typed_name]
return te unless te.nil? || te.value.nil?
te = parent.load_typed(typed_name)
return te unless te.nil? || te.value.nil?
# Under some circumstances, the call to the parent loader will have resulted in files being
# parsed that in turn contained references to the requested entity and hence, caused a
# recursive call into this loader. This means that the entry might be present now, so a new
# check must be made.
te = @named_values[typed_name]
te.nil? || te.value.nil? ? find(typed_name) : te
end
end
end
end