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[RW]

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
127 def initialize(parent_loader, loaders, module_name, path, loader_name, loadables)
128   super(parent_loader, loader_name, loaders.environment)
129 
130   raise ArgumentError, 'path based loader cannot be instantiated without a path' if path.nil? || path.empty?
131 
132   @module_name = module_name
133   @path = path
134   @smart_paths = LoaderPaths::SmartPaths.new(self)
135   @loaders = loaders
136   @loadables = loadables
137   unless (loadables - LOADABLE_KINDS).empty?
138     #TRANSLATORS 'loadables' is a variable containing loadable modules and should not be translated
139     raise ArgumentError, _('given loadables are not of supported loadable kind')
140   end
141   loaders.add_loader_by_name(self)
142 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
320 def candidate_paths(resolved_path)
321   raise NotImplementedError.new
322 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
148 def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block)
149   global = global?
150   if name_authority == Pcore::RUNTIME_NAME_AUTHORITY
151     smart_paths.effective_paths(type).each do |sp|
152       relative_paths(sp).each do |rp|
153         tp = sp.typed_name(type, name_authority, rp, global ? nil : @module_name)
154         next unless sp.valid_name?(tp)
155         begin
156           load_typed(tp) unless block_given? && !block.yield(tp)
157         rescue StandardError => e
158           if error_collector.nil?
159             Puppet.warn_once(:unloadable_entity, tp.to_s, e.message)
160           else
161             err = Puppet::DataTypes::Error.new(
162               Issues::LOADER_FAILURE.format(:type => type),
163               'PUPPET_LOADER_FAILURE',
164               { 'original_error' => e.message },
165               Issues::LOADER_FAILURE.issue_code)
166             error_collector << err unless error_collector.include?(err)
167           end
168         end
169       end
170     end
171   end
172   super
173 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
311 def existing_path(resolved_path)
312   raise NotImplementedError.new
313 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
179 def find(typed_name)
180   # This loader is tailored to only find entries in the current runtime
181   return nil unless typed_name.name_authority == Pcore::RUNTIME_NAME_AUTHORITY
182 
183   # Assume it is a global name, and that all parts of the name should be used when looking up
184   name_parts = typed_name.name_parts
185 
186   # Certain types and names can be disqualified up front
187   if name_parts.size > 1
188     # The name is in a name space.
189 
190     # Then entity cannot possible be in this module unless the name starts with the module name.
191     # Note:
192     # * If "module" represents a "global component", the module_name is nil and cannot match which is
193     #   ok since such a "module" cannot have namespaced content).
194     # * If this loader is allowed to have namespaced content, the module_name can be set to NAMESPACE_WILDCARD `*`
195     #
196     return nil unless name_parts[0] == module_name || module_name == NAMESPACE_WILDCARD
197   else
198     # The name is in the global name space.
199 
200     case typed_name.type
201     when :function, :resource_type, :resource_type_pp
202       # Can be defined in module using a global name. No action required
203 
204     when :plan
205       if !global?
206         # Global name must be the name of the module
207         return nil unless name_parts[0] == module_name
208 
209         # Look for the special 'init' plan.
210         origin, smart_path = find_existing_path(init_plan_name)
211         return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin)
212       end
213 
214     when :task
215       if !global?
216         # Global name must be the name of the module
217         return nil unless name_parts[0] == module_name
218 
219         # Look for the special 'init' Task
220         origin, smart_path = find_existing_path(init_task_name)
221         return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin)
222       end
223 
224     when :type
225       if !global?
226         # Global name must be the name of the module
227         unless name_parts[0] == module_name || module_name == NAMESPACE_WILDCARD
228           # Check for ruby defined data type in global namespace before giving up
229           origin, smart_path = find_existing_path(typed_name)
230           return smart_path.is_a?(LoaderPaths::DataTypePath) ? instantiate(smart_path, typed_name, origin) : nil
231         end
232 
233         # Look for the special 'init_typeset' TypeSet
234         origin, smart_path = find_existing_path(init_typeset_name)
235         return nil if smart_path.nil?
236 
237         value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin))
238         if value.is_a?(Types::PTypeSetType)
239           # cache the entry and return it
240           return set_entry(typed_name, value, origin)
241         end
242 
243         # TRANSLATORS 'TypeSet' should not be translated
244         raise ArgumentError, _("The code loaded from %{origin} does not define the TypeSet '%{module_name}'") %
245             { origin: origin, module_name: name_parts[0].capitalize }
246       end
247     else
248       # anything else cannot possibly be in this module
249       # TODO: should not be allowed anyway... may have to revisit this decision
250       return nil
251     end
252   end
253 
254   # Get the paths that actually exist in this module (they are lazily processed once and cached).
255   # The result is an array (that may be empty).
256   # Find the file to instantiate, and instantiate the entity if file is found
257   origin, smart_path = find_existing_path(typed_name)
258   return instantiate(smart_path, typed_name, origin) unless smart_path.nil?
259 
260   return nil unless typed_name.type == :type && typed_name.qualified?
261 
262   # Search for TypeSet using parent name
263   ts_name = typed_name.parent
264   while ts_name
265     # Do not traverse parents here. This search must be confined to this loader
266     tse = get_entry(ts_name)
267     tse = find(ts_name) if tse.nil? || tse.value.nil?
268     if tse && (ts = tse.value).is_a?(Types::PTypeSetType)
269       # The TypeSet might be unresolved at this point. If so, it must be resolved using
270       # this loader. That in turn, adds all contained types to this loader.
271       ts.resolve(self)
272       te = get_entry(typed_name)
273       return te unless te.nil?
274     end
275     ts_name = ts_name.parent
276   end
277   nil
278 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
330 def get_contents(effective_path)
331   raise NotImplementedError.new
332 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
340 def get_source_ref(relative_path)
341   raise NotImplementedError.new
342 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
348 def global?
349   module_name.nil? || module_name == NAMESPACE_WILDCARD || module_name == ENVIRONMENT
350 end
instantiate(smart_path, typed_name, origin) click to toggle source
    # File lib/puppet/pops/loader/module_loaders.rb
280 def instantiate(smart_path, typed_name, origin)
281   if origin.is_a?(Array)
282     value = smart_path.instantiator.create(self, typed_name, origin)
283   else
284     value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin))
285   end
286   # cache the entry and return it
287   set_entry(typed_name, value, origin)
288 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
358 def lib_root?
359   false
360 end
loadables() click to toggle source
    # File lib/puppet/pops/loader/module_loaders.rb
144 def loadables
145   @loadables
146 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
302 def meaningful_to_search?(smart_path)
303   raise NotImplementedError.new
304 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
375 def relative_paths(smart_path)
376   raise NotImplementedError.new
377 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
403 def find_existing_path(typed_name)
404   is_global = global?
405   smart_paths.effective_paths(typed_name.type).each do |sp|
406     next unless sp.valid_name?(typed_name)
407     origin = sp.effective_path(typed_name, is_global ? 0 : 1)
408     unless origin.nil?
409       if sp.fuzzy_matching?
410         # If there are multiple *specific* paths for the file, find
411         # whichever ones exist. Otherwise, find all paths that *might* be
412         # related to origin
413         if origin.is_a?(Array)
414           origins = origin.map { |ori| existing_path(ori) }.compact
415           return [origins, sp] unless origins.empty?
416         else
417           origins = candidate_paths(origin)
418           return [origins, sp] unless origins.empty?
419         end
420       else
421         existing = existing_path(origin)
422         return [origin, sp] unless existing.nil?
423       end
424     end
425   end
426   nil
427 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
394 def init_plan_name
395   @init_plan_name ||= TypedName.new(:plan, "#{module_name}::init")
396 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
388 def init_task_name
389   @init_task_name ||= TypedName.new(:task, "#{module_name}::init")
390 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
382 def init_typeset_name
383   @init_typeset_name ||= TypedName.new(:type, "#{module_name}::init_typeset")
384 end