module Origen::Loader::ModuleConstMissing

This is inspired by Rails’ ActiveSupport::Dependencies module.

Public Class Methods

append_features(base) click to toggle source
Calls superclass method
# File lib/origen/loader.rb, line 212
def self.append_features(base)
  base.class_eval do
    # Emulate #exclude via an ivar
    return if defined?(@_const_missing) && @_const_missing

    @_const_missing = instance_method(:const_missing)
    remove_method(:const_missing)
  end
  super
end
exclude_from(base) click to toggle source
# File lib/origen/loader.rb, line 223
def self.exclude_from(base)
  base.class_eval do
    define_method :const_missing, @_const_missing
    @_const_missing = nil
  end
end

Public Instance Methods

_load_const(file, name, altname = nil) click to toggle source

@api_private

# File lib/origen/loader.rb, line 374
def _load_const(file, name, altname = nil)
  load file
  if defined?(@@pre_loading_controller)
    return if @@pre_loading_controller
  end
  @_checking_name = altname || name
  const = eval(altname || name)
  @_checking_name = nil
  if const
    Origen::Loader.record_const(altname || name)
    return const
  end
  msg ||= "uninitialized constant #{name} (expected it to be defined in: #{file})"
  _raise_uninitialized_constant_error(name, msg)
end
_raise_uninitialized_constant_error(name, msg = nil) click to toggle source

@api private

# File lib/origen/loader.rb, line 391
def _raise_uninitialized_constant_error(name, msg = nil)
  msg ||= "uninitialized constant #{name}"
  name_error = NameError.new(msg, name)
  name_error.set_backtrace(caller.reject { |l| l =~ /^#{__FILE__}/ })
  fail name_error
end
_sub_derivatives_from_end(new_val, dirs) click to toggle source

@api private Substitutes a single occurrence of ‘derivatives’ in the given dirs array, starting from the end of it and replacing it with the given new value. Returns true if a substitution is made, else false.

# File lib/origen/loader.rb, line 234
def _sub_derivatives_from_end(new_val, dirs)
  subbed = false
  size = dirs.size - 1
  dirs.reverse_each.with_index do |val, i|
    if val == 'derivatives'
      dirs[size - i] = new_val
      subbed = true
      break
    end
  end
  dirs if subbed
end
const_missing(name) click to toggle source

Allows classes and modules to be defined in app/blocks and app/lib without needing to require them and in the case of app/blocks to use a custom directory structure.

The first time a reference is made to a class or module name it will trigger this hook, and we then work out what the file name should be and require it.

# File lib/origen/loader.rb, line 252
def const_missing(name)
  if Origen.in_app_workspace?
    if self == Object
      name = name.to_s
    else
      name = "#{self}::#{name}"
    end
    return nil if @_checking_name == name

    names = name.split('::')
    namespace = names.shift
    if app = Origen::Application.from_namespace(namespace)
      # First we are going to check for a match in the app/blocks directory, this needs to be handled
      # specially since it follows a non-std structure, e.g. use of derivatives/ and sub_blocks/ folders
      # for organization without having them as part of the class name-spacing
      altname = nil
      dirs = [app.root, 'app', 'blocks']
      names.each_with_index do |name, i|
        dirs << 'derivatives' unless i == 0
        dirs << name.underscore
      end

      # Is this a reference to a model?
      if File.exist?(f = File.join(*dirs, 'model.rb'))
        model = _load_const(f, name)
        # Also load the model's controller if it exists
        if File.exist?(f = File.join(*dirs, 'controller.rb'))
          controller = _load_const(f, name + 'Controller')
        end
        return model
      end

      # Is this a reference to a controller?
      if dirs.last.to_s =~ /_controller$/
        controller_reference = true
        dirs << dirs.pop.sub(/_controller$/, '')
        if File.exist?(f = File.join(*dirs, 'controller.rb'))
          return _load_const(f, name)
        end
      end

      # Is this a reference to a sub-block model or controller that is nested within a block?
      dirs_ = dirs.dup
      while dirs_ = _sub_derivatives_from_end('sub_blocks', dirs_)
        if controller_reference
          if File.exist?(f = File.join(*dirs_, 'controller.rb'))
            return _load_const(f, name)
          end
        elsif File.exist?(f = File.join(*dirs_, 'model.rb'))
          model = _load_const(f, name)
          # Also load the model's controller if it exists
          if File.exist?(f = File.join(*dirs_, 'controller.rb'))
            controller = _load_const(f, name + 'Controller')
          end
          return model
        end
      end

      # Is this a reference to a module that has been added to a model or controller?
      # In this case dirs contains something like:
      #    [..., "my_model", "derivatives", "my_module"]
      #    [..., "my_model_controller", "derivatives", "my_module"]
      # So let's try by transforming these into:
      #    [..., "my_model", "model"] + "my_module.rb"
      #    [..., "my_model", "controller"] + "my_module.rb"
      filename = dirs.pop + '.rb'
      dirs.pop # Lose 'derivatives'
      if dirs.last.to_s =~ /_controller$/
        dirs << dirs.pop.sub(/_controller$/, '')
        dirs << 'controller'
      else
        dirs << 'model'
      end
      if File.exist?(f = File.join(*dirs, filename))
        return _load_const(f, name)
      end

      # Now that we have established that it is not a reference to a block (which has a non-std code
      # organization structure), we can now check for a match in the app/lib directory following std
      # Ruby code organization conventions
      until names.empty?
        path = File.join(*names.map(&:underscore)) + '.rb'

        f = File.join(app.root, 'app', 'lib', namespace.underscore, path)
        if File.exist?(f)
          model = _load_const(f, name, altname)
          # Try and reference the controller to load it too, though don't raise an error if it
          # doesn't exist
          @@pre_loading_controller = true
          eval "#{altname || name}Controller"
          return model
        # If a folder exists that is named after this constant, then assume it is an otherwise
        # undeclared namespace module and declare it now
        elsif File.exist?(f.sub('.rb', ''))
          return const_set path.sub('.rb', '').camelcase, Module.new
        end

        # Don't waste time looking up the namespace hierarchy for the controller, if it exists it
        # should be within the exact same namespace as the model
        return nil if defined?(@@pre_loading_controller) && @@pre_loading_controller

        # Remove the highest level namespace and then search again in the parent namespace
        if discarded_namespace = names.delete_at(-2)
          altname ||= name
          altname = altname.sub("#{discarded_namespace}::", '')
        else
          names.pop
        end
      end

      _raise_uninitialized_constant_error(name)
    else
      _raise_uninitialized_constant_error(name)
    end
  else
    _raise_uninitialized_constant_error(name)
  end
ensure
  @@pre_loading_controller = false
end