class Puppet::Pops::Loader::ModuleLoaders::AbstractPathBasedModuleLoader
Attributes
The name of the module, or nil, if this is a global “component”, or “any module” if set to the `NAMESPACE_WILDCARD` (*)
The path to the location of the module/component - semantics determined by subclass
A Module Loader has a private loader, it is lazily obtained on request to provide the visibility for entities contained in the module. Since a ModuleLoader also represents an environment and it is created a different way, this loader can be set explicitly by the loaders bootstrap logic.
@api private
A map of type to smart-paths that help with minimizing the number of paths to scan
Public Class Methods
Initialize a kind of ModuleLoader for one module @param parent_loader [Loader] loader with higher priority @param loaders [Loaders] the container for this loader @param module_name [String] the name of the module (non qualified name), may be nil for a global “component” @param path [String] the path to the root of the module (semantics defined by subclass) @param loader_name [String] a name that is used for human identification (useful when module_name is nil)
# File lib/puppet/pops/loader/module_loaders.rb 128 def initialize(parent_loader, loaders, module_name, path, loader_name, loadables) 129 super(parent_loader, loader_name, loaders.environment) 130 131 raise ArgumentError, 'path based loader cannot be instantiated without a path' if path.nil? || path.empty? 132 133 @module_name = module_name 134 @path = path 135 @smart_paths = LoaderPaths::SmartPaths.new(self) 136 @loaders = loaders 137 @loadables = loadables 138 unless (loadables - LOADABLE_KINDS).empty? 139 #TRANSLATORS 'loadables' is a variable containing loadable modules and should not be translated 140 raise ArgumentError, _('given loadables are not of supported loadable kind') 141 end 142 loaders.add_loader_by_name(self) 143 end
Public Instance Methods
Abstract method that subclasses override to return an array of paths that may be associated with the resolved path.
@param resolved_path [String] a path, without extension, resolved by a smart path against the loader's root (if it has one) @return [Array<String>]
# File lib/puppet/pops/loader/module_loaders.rb 321 def candidate_paths(resolved_path) 322 raise NotImplementedError.new 323 end
# File lib/puppet/pops/loader/module_loaders.rb 149 def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) 150 global = global? 151 if name_authority == Pcore::RUNTIME_NAME_AUTHORITY 152 smart_paths.effective_paths(type).each do |sp| 153 relative_paths(sp).each do |rp| 154 tp = sp.typed_name(type, name_authority, rp, global ? nil : @module_name) 155 next unless sp.valid_name?(tp) 156 begin 157 load_typed(tp) unless block_given? && !block.yield(tp) 158 rescue StandardError => e 159 if error_collector.nil? 160 Puppet.warn_once(:unloadable_entity, tp.to_s, e.message) 161 else 162 err = Puppet::DataTypes::Error.new( 163 Issues::LOADER_FAILURE.format(:type => type), 164 'PUPPET_LOADER_FAILURE', 165 { 'original_error' => e.message }, 166 Issues::LOADER_FAILURE.issue_code) 167 error_collector << err unless error_collector.include?(err) 168 end 169 end 170 end 171 end 172 end 173 super 174 end
Abstract method that subclasses override to answer if the given relative path exists, and if so returns that path
@param resolved_path [String] a path resolved by a smart path against the loader's root (if it has one) @return [String, nil] the found path or nil if no such path was found
# File lib/puppet/pops/loader/module_loaders.rb 312 def existing_path(resolved_path) 313 raise NotImplementedError.new 314 end
Finds typed/named entity in this module @param typed_name [TypedName] the type/name to find @return [Loader::NamedEntry, nil found/created entry, or nil if not found
# File lib/puppet/pops/loader/module_loaders.rb 180 def find(typed_name) 181 # This loader is tailored to only find entries in the current runtime 182 return nil unless typed_name.name_authority == Pcore::RUNTIME_NAME_AUTHORITY 183 184 # Assume it is a global name, and that all parts of the name should be used when looking up 185 name_parts = typed_name.name_parts 186 187 # Certain types and names can be disqualified up front 188 if name_parts.size > 1 189 # The name is in a name space. 190 191 # Then entity cannot possible be in this module unless the name starts with the module name. 192 # Note: 193 # * If "module" represents a "global component", the module_name is nil and cannot match which is 194 # ok since such a "module" cannot have namespaced content). 195 # * If this loader is allowed to have namespaced content, the module_name can be set to NAMESPACE_WILDCARD `*` 196 # 197 return nil unless name_parts[0] == module_name || module_name == NAMESPACE_WILDCARD 198 else 199 # The name is in the global name space. 200 201 case typed_name.type 202 when :function, :resource_type, :resource_type_pp 203 # Can be defined in module using a global name. No action required 204 205 when :plan 206 if !global? 207 # Global name must be the name of the module 208 return nil unless name_parts[0] == module_name 209 210 # Look for the special 'init' plan. 211 origin, smart_path = find_existing_path(init_plan_name) 212 return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin) 213 end 214 215 when :task 216 if !global? 217 # Global name must be the name of the module 218 return nil unless name_parts[0] == module_name 219 220 # Look for the special 'init' Task 221 origin, smart_path = find_existing_path(init_task_name) 222 return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin) 223 end 224 225 when :type 226 if !global? 227 # Global name must be the name of the module 228 unless name_parts[0] == module_name || module_name == NAMESPACE_WILDCARD 229 # Check for ruby defined data type in global namespace before giving up 230 origin, smart_path = find_existing_path(typed_name) 231 return smart_path.is_a?(LoaderPaths::DataTypePath) ? instantiate(smart_path, typed_name, origin) : nil 232 end 233 234 # Look for the special 'init_typeset' TypeSet 235 origin, smart_path = find_existing_path(init_typeset_name) 236 return nil if smart_path.nil? 237 238 value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) 239 if value.is_a?(Types::PTypeSetType) 240 # cache the entry and return it 241 return set_entry(typed_name, value, origin) 242 end 243 244 # TRANSLATORS 'TypeSet' should not be translated 245 raise ArgumentError, _("The code loaded from %{origin} does not define the TypeSet '%{module_name}'") % 246 { origin: origin, module_name: name_parts[0].capitalize } 247 end 248 else 249 # anything else cannot possibly be in this module 250 # TODO: should not be allowed anyway... may have to revisit this decision 251 return nil 252 end 253 end 254 255 # Get the paths that actually exist in this module (they are lazily processed once and cached). 256 # The result is an array (that may be empty). 257 # Find the file to instantiate, and instantiate the entity if file is found 258 origin, smart_path = find_existing_path(typed_name) 259 return instantiate(smart_path, typed_name, origin) unless smart_path.nil? 260 261 return nil unless typed_name.type == :type && typed_name.qualified? 262 263 # Search for TypeSet using parent name 264 ts_name = typed_name.parent 265 while ts_name 266 # Do not traverse parents here. This search must be confined to this loader 267 tse = get_entry(ts_name) 268 tse = find(ts_name) if tse.nil? || tse.value.nil? 269 if tse && (ts = tse.value).is_a?(Types::PTypeSetType) 270 # The TypeSet might be unresolved at this point. If so, it must be resolved using 271 # this loader. That in turn, adds all contained types to this loader. 272 ts.resolve(self) 273 te = get_entry(typed_name) 274 return te unless te.nil? 275 end 276 ts_name = ts_name.parent 277 end 278 nil 279 end
Abstract method that subclasses override to produce the content of the effective path. It should either succeed and return a String or fail with an exception.
@param effective_path [String] a path as resolved by a smart path @return [String] the content of the file
# File lib/puppet/pops/loader/module_loaders.rb 331 def get_contents(effective_path) 332 raise NotImplementedError.new 333 end
Abstract method that subclasses override to produce a source reference String used to identify the system resource (resource in the URI sense).
@param relative_path [String] a path relative to the module's root @return [String] a reference to the source file (in file system, zip file, or elsewhere).
# File lib/puppet/pops/loader/module_loaders.rb 341 def get_source_ref(relative_path) 342 raise NotImplementedError.new 343 end
Answers the question if this loader represents a global component (true for resource type loader and environment loader)
@return [Boolean] `true` if this loader represents a global component
# File lib/puppet/pops/loader/module_loaders.rb 349 def global? 350 module_name.nil? || module_name == NAMESPACE_WILDCARD || module_name == ENVIRONMENT 351 end
# File lib/puppet/pops/loader/module_loaders.rb 281 def instantiate(smart_path, typed_name, origin) 282 if origin.is_a?(Array) 283 value = smart_path.instantiator.create(self, typed_name, origin) 284 else 285 value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) 286 end 287 # cache the entry and return it 288 set_entry(typed_name, value, origin) 289 end
Answers `true` if the loader used by this instance is rooted beneath 'lib'. This is typically true for the the system_loader. It will have a path relative to the parent of 'puppet' instead of the parent of 'lib/puppet' since the 'lib' directory of puppet is renamed during install. This is significant for loaders that load ruby code.
@return [Boolean] a boolean answering if the loader is rooted beneath 'lib'.
# File lib/puppet/pops/loader/module_loaders.rb 359 def lib_root? 360 false 361 end
# File lib/puppet/pops/loader/module_loaders.rb 145 def loadables 146 @loadables 147 end
Abstract method that subclasses override that checks if it is meaningful to search using a generic smart path. This optimization is performed to not be tricked into searching an empty directory over and over again. The implementation may perform a deep search for file content other than directories and cache this in and index. It is guaranteed that a call to meaningful_to_search? takes place before checking any other path with relative_path_exists?.
This optimization exists because many modules have been created from a template and they have empty directories for functions, types, etc. (It is also the place to create a cached index of the content).
@param smart_path [String] a path relative to the module's root @return [Boolean] true if there is content in the directory appointed by the relative path
# File lib/puppet/pops/loader/module_loaders.rb 303 def meaningful_to_search?(smart_path) 304 raise NotImplementedError.new 305 end
Produces the private loader for the module. If this module is not already resolved, this will trigger resolution
# File lib/puppet/pops/loader/module_loaders.rb 365 def private_loader 366 # The system loader has a nil module_name and it does not have a private_loader as there are no functions 367 # that can only by called by puppet runtime - if so, it acts as the private loader directly. 368 @private_loader ||= (global? ? self : @loaders.private_loader_for_module(module_name)) 369 end
Return all paths that matches the given smart path. The returned paths are relative to the `#generic_path` of the given smart path.
@param smart_path [SmartPath] the path to find relative paths for @return [Array<String>] found paths
# File lib/puppet/pops/loader/module_loaders.rb 376 def relative_paths(smart_path) 377 raise NotImplementedError.new 378 end
Private Instance Methods
Find an existing path or paths for the given `typed_name`. Return `nil` if no path is found @param typed_name [TypedName] the `typed_name` to find a path for @return [Array,nil] `nil`or a two element array where the first element is an effective path or array of paths
(depending on the `SmartPath`) and the second element is the `SmartPath` that produced the effective path or paths. A path is a String
# File lib/puppet/pops/loader/module_loaders.rb 404 def find_existing_path(typed_name) 405 is_global = global? 406 smart_paths.effective_paths(typed_name.type).each do |sp| 407 next unless sp.valid_name?(typed_name) 408 origin = sp.effective_path(typed_name, is_global ? 0 : 1) 409 unless origin.nil? 410 if sp.fuzzy_matching? 411 # If there are multiple *specific* paths for the file, find 412 # whichever ones exist. Otherwise, find all paths that *might* be 413 # related to origin 414 if origin.is_a?(Array) 415 origins = origin.map { |ori| existing_path(ori) }.compact 416 return [origins, sp] unless origins.empty? 417 else 418 origins = candidate_paths(origin) 419 return [origins, sp] unless origins.empty? 420 end 421 else 422 existing = existing_path(origin) 423 return [origin, sp] unless existing.nil? 424 end 425 end 426 end 427 nil 428 end
@return [TypedName] the fake typed name that maps to the path of an init.pp file that represents
a plan named after the module
# File lib/puppet/pops/loader/module_loaders.rb 395 def init_plan_name 396 @init_plan_name ||= TypedName.new(:plan, "#{module_name}::init") 397 end
@return [TypedName] the fake typed name that maps to the path of an init[arbitrary extension]
file that represents a task named after the module
# File lib/puppet/pops/loader/module_loaders.rb 389 def init_task_name 390 @init_task_name ||= TypedName.new(:task, "#{module_name}::init") 391 end
@return [TypedName] the fake typed name that maps to the init_typeset path for this module
# File lib/puppet/pops/loader/module_loaders.rb 383 def init_typeset_name 384 @init_typeset_name ||= TypedName.new(:type, "#{module_name}::init_typeset") 385 end