class Puppet::Pops::Loader::ModuleLoaders::AbstractPathBasedModuleLoader

Attributes

module_name[R]

The name of the module, or nil, if this is a global “component”, or “any module” if set to the `NAMESPACE_WILDCARD` (*)

path[R]

The path to the location of the module/component - semantics determined by subclass

private_loader[W]

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

smart_paths[R]

A map of type to smart-paths that help with minimizing the number of paths to scan

Public Class Methods

new(parent_loader, loaders, module_name, path, loader_name, loadables) click to toggle source

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)

Calls superclass method
    # 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

candidate_paths(resolved_path) click to toggle source

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
discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) click to toggle source
Calls superclass method
    # 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
existing_path(resolved_path) click to toggle source

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
find(typed_name) click to toggle source

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
get_contents(effective_path) click to toggle source

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
get_source_ref(relative_path) click to toggle source

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
global?() click to toggle source

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
instantiate(smart_path, typed_name, origin) click to toggle source
    # 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
lib_root?() click to toggle source

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
loadables() click to toggle source
    # File lib/puppet/pops/loader/module_loaders.rb
145 def loadables
146   @loadables
147 end
meaningful_to_search?(smart_path) click to toggle source

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
private_loader() click to toggle source

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
relative_paths(smart_path) click to toggle source

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_existing_path(typed_name) click to toggle source

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
init_plan_name() click to toggle source

@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
init_task_name() click to toggle source

@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
init_typeset_name() click to toggle source

@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