Module: Modulation

Defined in:
lib/modulation/core.rb,
lib/modulation.rb,
lib/modulation/paths.rb,
lib/modulation/builder.rb,
lib/modulation/version.rb,
lib/modulation/module_mixin.rb

Overview

Implements main Modulation functionality

Defined Under Namespace

Modules: Builder, ModuleMixin, Paths

Constant Summary collapse

DIR =
File.dirname(__FILE__)
GEM_REQUIRE_ERROR_MESSAGE =
<<~MSG
  Can't import from a gem that doesn't depend on Modulation. Please use `require` instead of `import`.
MSG
VERSION =
'0.25'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loaded_modulesHash (readonly)

Returns hash of loaded modules, mapping absolute paths to modules

Returns:

  • (Hash)

    hash of loaded modules, mapping absolute paths to modules


11
12
13
# File 'lib/modulation/core.rb', line 11

def loaded_modules
  @loaded_modules
end

Class Method Details

.add_module_constants(mod, target, *symbols) ⇒ void

This method returns an undefined value.

Adds all or part of a module's constants to a target object If no symbols are given, all constants are added

Parameters:

  • mod (Module)

    imported module

  • target (Object)

    object to add constants to

  • symbols (Array<Symbol>)

    list of constants to add


100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/modulation/core.rb', line 100

def add_module_constants(mod, target, *symbols)
  exported = mod.__module_info[:exported_symbols]
  unless symbols.empty?
    not_exported = symbols.select { |s| s =~ /^[A-Z]/ } - exported
    unless not_exported.empty?
      raise NameError, "symbol #{not_exported.first.inspect} not exported"
    end
    exported = exported & symbols
  end
  mod.singleton_class.constants(false).each do |sym|
    next unless exported.include?(sym)
    target.const_set(sym, mod.singleton_class.const_get(sym))
  end
end

.add_module_methods(mod, target, *symbols) ⇒ void

This method returns an undefined value.

Adds all or part of a module's methods to a target object If no symbols are given, all methods are added

Parameters:

  • mod (Module)

    imported module

  • target (Object)

    object to add methods to

  • symbols (Array<Symbol>)

    list of methods to add


80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/modulation/core.rb', line 80

def add_module_methods(mod, target, *symbols)
  methods = mod.singleton_class.instance_methods(false)
  unless symbols.empty?
    not_exported = symbols.select { |s| s =~ /^[a-z]/ } - methods
    unless not_exported.empty?
      raise NameError, "symbol #{not_exported.first.inspect} not exported"
    end
    methods = methods & symbols
  end
  methods.each do |sym|
    target.send(:define_method, sym, &mod.method(sym))
  end
end

.create_module_from_file(path) ⇒ Module

Creates a new module from a source file

Parameters:

  • path (String)

    source file name

Returns:


130
131
132
133
134
# File 'lib/modulation/core.rb', line 130

def create_module_from_file(path)
  Builder.make(location: path)
rescue StandardError => e
  raise_error(e)
end

.define_auto_import_const_missing_method(receiver, auto_import_hash) ⇒ void

This method returns an undefined value.

Defines a const_missing method used for auto-importing on a given object

Parameters:

  • receiver (Object)

    object to receive the const_missing method call

  • auto_import_hash (Hash)

    a hash mapping constant names to a source file and a caller location


120
121
122
123
124
125
# File 'lib/modulation/core.rb', line 120

def define_auto_import_const_missing_method(receiver, auto_import_hash)
  receiver.singleton_class.define_method(:const_missing) do |sym|
    (path, caller_location) = auto_import_hash[sym]
    path ? const_set(sym, import(path, caller_location)) : super(sym)
  end
end

.full_backtrace!Object

Show full backtrace for errors occuring while loading a module. Normally Modulation will remove stack frames occurring inside the modulation.rb code in order to make backtraces more readable when debugging.


21
22
23
# File 'lib/modulation/core.rb', line 21

def full_backtrace!
  @full_backtrace = true
end

.import(path, caller_location = caller(1..1).first) ⇒ Module

Imports a module from a file If the module is already loaded, returns the loaded module.

Parameters:

  • path (String)

    unqualified file name

  • caller_location (String) (defaults to: caller(1..1).first)

    caller location

Returns:

  • (Module)

    loaded module object


34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/modulation/core.rb', line 34

def import(path, caller_location = caller(1..1).first)
  abs_path = Paths.absolute_path(path, caller_location) ||
             Paths.lookup_gem_path(path)

  case abs_path
  when String
    @loaded_modules[abs_path] || create_module_from_file(abs_path)
  when :require_gem
    raise_error(LoadError.new(GEM_REQUIRE_ERROR_MESSAGE), caller)
  else
    raise_error(LoadError.new("Module not found: #{path}"), caller)
  end
end

.import_all(path, caller_location = caller(1..1).first) ⇒ Array

Imports all source files in given directory @ param path [String] relative directory path

Parameters:

  • caller_location (String) (defaults to: caller(1..1).first)

    caller location

Returns:

  • (Array)

    array of module objects


52
53
54
55
56
57
# File 'lib/modulation/core.rb', line 52

def import_all(path, caller_location = caller(1..1).first)
  abs_path = Paths.absolute_dir_path(path, caller_location)
  Dir["#{abs_path}/**/*.rb"].map do |fn|
    @loaded_modules[fn] || create_module_from_file(fn)
  end
end

.import_map(path, caller_location = caller(1..1).first) ⇒ Hash

Imports all source files in given directory, returning a hash mapping filenames to modules @ param path [String] relative directory path

Parameters:

  • caller_location (String) (defaults to: caller(1..1).first)

    caller location

Returns:

  • (Hash)

    hash mapping filenames to modules


64
65
66
67
68
69
70
71
72
# File 'lib/modulation/core.rb', line 64

def import_map(path, caller_location = caller(1..1).first)
  abs_path = Paths.absolute_dir_path(path, caller_location)
  Dir["#{abs_path}/**/*.rb"].each_with_object({}) do |fn, h|
    mod = @loaded_modules[fn] || create_module_from_file(fn)
    name = File.basename(fn) =~ /^(.+)\.rb$/ && $1
    name = yield name, mod if block_given?
    h[name] = mod
  end
end

.mock(path, mod, caller_location = caller(1..1).first) ⇒ void

This method returns an undefined value.

Maps the given path to the given mock module, restoring the previously loaded module (if any) after calling the given block

Parameters:

  • path (String)

    module path

  • mod (Module)

    module

  • caller_location (String) (defaults to: caller(1..1).first)

    caller location


173
174
175
176
177
178
179
180
# File 'lib/modulation/core.rb', line 173

def mock(path, mod, caller_location = caller(1..1).first)
  path = Paths.absolute_path(path, caller_location)
  old_module = @loaded_modules[path]
  @loaded_modules[path] = mod
  yield if block_given?
ensure
  @loaded_modules[path] = old_module if block_given?
end

.raise_error(error, backtrace = nil) ⇒ void

This method returns an undefined value.

(Re-)raises an error, potentially filtering its backtrace to remove stack frames occuring in Modulation code

Parameters:

  • error (Error)

    raised error

  • caller (Array)

    error backtrace


141
142
143
144
145
146
147
148
149
# File 'lib/modulation/core.rb', line 141

def raise_error(error, backtrace = nil)
  if backtrace
    unless @full_backtrace
      backtrace = backtrace.reject { |l| l =~ /^#{Modulation::DIR}/ }
    end
    error.set_backtrace(backtrace)
  end
  raise error
end

.reload(mod) ⇒ Module

Reloads the given module from its source file

Parameters:

  • mod (Module, String)

    module to reload

Returns:


154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/modulation/core.rb', line 154

def reload(mod)
  if mod.is_a?(String)
    path = mod
    mod = @loaded_modules[File.expand_path(mod)]
    raise "No module loaded from #{path}" unless mod
  end

  Builder.cleanup_module(mod)
  Builder.reload_module_code(mod)

  mod.tap { Builder.set_exported_symbols(mod, mod.__exported_symbols) }
end

.reset!Object

Resets the loaded modules hash


14
15
16
# File 'lib/modulation/core.rb', line 14

def reset!
  @loaded_modules = {}
end