modulation 0.31 → 0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2fdd479aff209b4cf494c13fc4f7bce1ebc64c058ac6860c2dec80928b46b3b
4
- data.tar.gz: 3ffce99915f23b4d1444e50c2c216e4b695f00ffdc5c5d9ff6960beb03f8fb9e
3
+ metadata.gz: 598679c69845dd516cf7f19ff4ae9f60e254bf488abc1460cd08b121714b4cba
4
+ data.tar.gz: 6b962c6f2cea4a300918eea8ff0d142999e01a32d64e7e8d814d5d15cbfb0984
5
5
  SHA512:
6
- metadata.gz: 10e1497aa425b14ebabad8247f7898d45f67a255c0b88854c4eeacddaa5ffa36530425d51292f43f478e9043dc15e472a06930b9f9f9d4258e9d9651990eda6e
7
- data.tar.gz: d84f11b4c5b206e30462a75307d6399492781cf5009426103e176c48aad65222c6f938468da1453755487559ae08848dc7fd641378d2167fc88f050da0d51977
6
+ metadata.gz: 1d8618b49710007df4312cfaefdaacc6291a7ca926945dae9378a6cb77efb0cd54947db9fe1cd84cfc8e39e9295aa22ed12db34be953f6df8eea0a072b32e596
7
+ data.tar.gz: ed6a9627b2e6dd89e0c3751b59362bf5b78dffb334934028aa2a8895922b64b80d79101a35f9e0ad7882fd6515d71bae87f3d1560c2f8a5d8cbe9ee856e460a2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ 0.32 2019-09-03
2
+ ---------------
3
+
4
+ * Implement export_from_receiver
5
+ * Implement additive exports
6
+ * Refactor auto_import_map, add not_found option
7
+ * Fix backtrace for exports of missing symbols
8
+
1
9
  0.31 2019-08-28
2
10
  ---------------
3
11
 
data/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  [INSTALL](#installing-modulation) |
7
7
  [GUIDE](#organizing-your-code-with-modulation) |
8
+ [API](#api-reference) |
8
9
  [EXAMPLES](examples) |
9
10
  [RDOC](https://www.rubydoc.info/gems/modulation/)
10
11
 
@@ -27,24 +28,19 @@ a functional style, minimizing boilerplate code.
27
28
 
28
29
  ## Features
29
30
 
30
- - Provides complete isolation of each module: constant definitions in one file
31
- do not leak into another.
32
- - Enforces explicit exporting and importing of methods, classes, modules and
33
- constants.
34
- - Supports circular dependencies.
35
- - Supports [default exports](#default-exports) for modules exporting a single
31
+ - Complete isolation of each module.
32
+ - Explicit exporting and importing of methods and constants.
33
+ - Support for circular dependencies.
34
+ - Support for [default exports](#default-exports) for modules exporting a single
36
35
  class or value.
37
- - Modules can be [lazy loaded](#lazy-loading) to improve start up time and
38
- memory consumption.
39
- - Modules can be [reloaded](#reloading-modules) at runtime without breaking your
40
- code in wierd ways.
41
- - Allows [mocking of dependencies](#mocking-dependencies) for testing purposes.
36
+ - [Lazy Loading](#lazy-loading) improves start up time and memory consumption.
37
+ - [Hot module reloading](#reloading-modules)
38
+ - [Mocking of dependencies](#mocking-dependencies) for testing purposes.
42
39
  - Can be used to [write gems](#writing-gems-using-modulation).
43
- - Module dependencies can be [introspected](#dependency-introspection).
44
- - Facilitates [unit-testing](#unit-testing-modules) of private methods and
40
+ - [Dependency introspection](#dependency-introspection).
41
+ - Easier [unit-testing](#unit-testing-modules) of private methods and
45
42
  constants.
46
- - Can load all source files in directory [at once](#importing-all-source-files-in-a-directory).
47
- - Packs entire applications [into a single
43
+ - Pack entire applications [into a single
48
44
  file](#packing-applications-with-modulation).
49
45
 
50
46
  ## Rationale
@@ -65,6 +61,8 @@ issues:
65
61
  - There's no easy way to hide implementation-specific classes or methods. Yes,
66
62
  there's `#private`, `#private_constant` etc, but by default everything is
67
63
  `#public`!
64
+ - Extracting functionality is harder when modules are namespaced and
65
+ dependencies are implicit.
68
66
  - Writing reusable functional code requires wrapping it in modules using
69
67
  `class << self`, `def self.foo ...`, `extend self` or `include Singleton`
70
68
  (the pain of implementing singletons in Ruby has been
@@ -81,11 +79,10 @@ codebases is... not as elegant or painfree as I would expect from a
81
79
  first-class development environment. I also wanted to have a better solution
82
80
  for writing in a functional style.
83
81
 
84
- So I came up with Modulation, a small gem (less than 300 LOC) that takes a
85
- different approach to organizing Ruby code: any so-called global declarations
86
- are hidden unless explicitly exported, and the global namespace remains
87
- clutter-free. All dependencies between source files are explicit, visible, and
88
- easy to understand.
82
+ So I came up with Modulation, a small gem that takes a different approach to
83
+ organizing Ruby code: any so-called global declarations are hidden unless
84
+ explicitly exported, and the global namespace remains clutter-free. All
85
+ dependencies between source files are explicit, visible, and easy to understand.
89
86
 
90
87
  ## Installing Modulation
91
88
 
@@ -109,6 +106,9 @@ Each source file is evaluated in the context of a newly-created `Module`
109
106
  instance, with some additional methods for introspection and miscellaneous
110
107
  operations such as [hot reloading](#reloading-modules).
111
108
 
109
+ Modulation provides an alternative APIs for loading modules. Instead of using
110
+ `require` and `require_relative`, you use `import`, `import_map` and other APIs.
111
+
112
112
  ### Exporting declarations
113
113
 
114
114
  Any class, module or constant be exported using `#export`:
@@ -174,6 +174,17 @@ Any capitalized key will be interpreted as a const, otherwise it will be defined
174
174
  as a method. If the value is a symbol, Modulation will look for the
175
175
  corresponding method or const definition and will treat the key as an alias.
176
176
 
177
+ The `export` method can be called multiple times. Its behavior is additive:
178
+
179
+ ```ruby
180
+ # this:
181
+ export :foo, :bar
182
+
183
+ # is the same as this:
184
+ export :foo
185
+ export :bar
186
+ ```
187
+
177
188
  ### Importing declarations
178
189
 
179
190
  Declarations from another module can be imported using `#import`:
@@ -690,6 +701,44 @@ end
690
701
  ...
691
702
  ```
692
703
 
704
+ ## API Reference
705
+
706
+ This section will be expanded on in a future release.
707
+
708
+ #### `__module_info`
709
+
710
+ ### `__reload!`
711
+
712
+ #### `alias_method_once()`
713
+
714
+ #### `auto_import()`
715
+
716
+ #### `auto_import_map()`
717
+
718
+ #### `export()`
719
+
720
+ #### `export_default()`
721
+
722
+ #### `export_from_receiver()`
723
+
724
+ #### `extend_from()`
725
+
726
+ #### `import()`
727
+
728
+ #### `import_all()`
729
+
730
+ #### `import_map()`
731
+
732
+ #### `include_from()`
733
+
734
+ #### `Modulation.full_backtrace!`
735
+
736
+ #### `Modulation.reload()`
737
+
738
+ #### `MODULE`
739
+
740
+ #### `MODULE.__module_info`
741
+
693
742
  ## Why you should not use Modulation
694
743
 
695
744
  - Modulation is not production-ready.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative('exports')
4
- require_relative('default_export')
4
+ require_relative('export_default')
5
5
 
6
6
  module Modulation
7
7
  # Implements creation of module instances
@@ -56,16 +56,15 @@ module Modulation
56
56
  def load_module_code(mod, info)
57
57
  path = info[:location]
58
58
  mod.instance_eval(info[:source] || IO.read(path), path)
59
- mod.__post_load
60
59
  end
61
60
 
62
61
  def finalize_module_exports(info, mod)
63
62
  if (default = mod.__export_default_info)
64
- DefaultExport.set_module_default_value(
63
+ ExportDefault.set_module_default_value(
65
64
  default[:value], info, mod, default[:caller]
66
65
  )
67
66
  else
68
- Exports.set_exported_symbols(mod, mod.__exported_symbols)
67
+ Exports.perform_exports(mod)
69
68
  mod
70
69
  end
71
70
  end
@@ -80,7 +79,7 @@ module Modulation
80
79
 
81
80
  cleanup_module(mod)
82
81
  load_module_code(mod, mod.__module_info)
83
- Exports.set_exported_symbols(mod, mod.__exported_symbols)
82
+ Exports.perform_exports(mod)
84
83
  ensure
85
84
  Thread.current[:__current_module] = prev_module
86
85
  $VERBOSE = orig_verbose
@@ -97,8 +96,7 @@ module Modulation
97
96
  singleton.instance_methods(false).each(&undef_method)
98
97
  singleton.private_instance_methods(false).each(&undef_method)
99
98
 
100
- mod.__exported_symbols.clear
101
- mod.__reset_dependencies
99
+ mod.__before_reload
102
100
  end
103
101
 
104
102
  # Adds all or part of a module's methods to a target object
@@ -6,6 +6,8 @@ module Modulation
6
6
  require_relative './builder'
7
7
  require_relative './module_mixin'
8
8
 
9
+ RE_CONST = /^[A-Z]/.freeze
10
+
9
11
  class << self
10
12
  CALLER_RANGE = (1..1).freeze
11
13
 
@@ -38,7 +40,7 @@ module Modulation
38
40
 
39
41
  case abs_path
40
42
  when String
41
- @loaded_modules[abs_path] || create_module_from_file(abs_path)
43
+ @loaded_modules[abs_path] || create_module_from_file(abs_path, caller)
42
44
  when :require_gem
43
45
  raise_error(LoadError.new(GEM_REQUIRE_ERROR_MESSAGE), caller)
44
46
  else
@@ -53,7 +55,7 @@ module Modulation
53
55
  def import_all(path, caller_location = caller(CALLER_RANGE).first)
54
56
  abs_path = Paths.absolute_dir_path(path, caller_location)
55
57
  Dir["#{abs_path}/**/*.rb"].map do |fn|
56
- @loaded_modules[fn] || create_module_from_file(fn)
58
+ @loaded_modules[fn] || create_module_from_file(fn, caller)
57
59
  end
58
60
  end
59
61
 
@@ -67,8 +69,8 @@ module Modulation
67
69
  caller_location = caller(CALLER_RANGE).first)
68
70
  abs_path = Paths.absolute_dir_path(path, caller_location)
69
71
  use_symbols = options[:symbol_keys]
70
- Dir["#{abs_path}/**/*.rb"].each_with_object({}) do |fn, h|
71
- mod = @loaded_modules[fn] || create_module_from_file(fn)
72
+ Dir["#{abs_path}/*.rb"].each_with_object({}) do |fn, h|
73
+ mod = @loaded_modules[fn] || create_module_from_file(fn, caller)
72
74
  name = File.basename(fn) =~ /^(.+)\.rb$/ && Regexp.last_match(1)
73
75
  h[use_symbols ? name.to_sym : name] = mod
74
76
  end
@@ -79,21 +81,26 @@ module Modulation
79
81
  abs_path = Paths.absolute_dir_path(path, caller_location)
80
82
  Hash.new do |h, k|
81
83
  fn = Paths.check_path(File.join(abs_path, k.to_s))
82
- return nil unless fn
83
-
84
- mod = @loaded_modules[fn] || create_module_from_file(fn)
85
- k = yield k, mod if block_given?
86
- h[k] = mod
84
+ h[k] = find_auto_import_module(fn, path, options)
85
+ end
86
+ end
87
+
88
+ def find_auto_import_module(filename, path, options)
89
+ if filename
90
+ return @loaded_modules[filename] ||
91
+ create_module_from_file(filename, caller)
87
92
  end
93
+
94
+ return options[:not_found] if options.key?(:not_found)
95
+
96
+ raise "Module not found #{path}"
88
97
  end
89
98
 
90
99
  # Creates a new module from a source file
91
100
  # @param path [String] source file name
92
101
  # @return [Module] module
93
- def create_module_from_file(path)
94
- Builder.make(location: path)
95
- rescue StandardError => e
96
- raise_error(e)
102
+ def create_module_from_file(path, import_caller)
103
+ Builder.make(location: path, caller: import_caller)
97
104
  end
98
105
 
99
106
  # (Re-)raises an error, potentially filtering its backtrace to remove stack
@@ -104,7 +111,10 @@ module Modulation
104
111
  def raise_error(error, backtrace = nil)
105
112
  if backtrace
106
113
  unless @full_backtrace
107
- backtrace = backtrace.reject { |l| l =~ /^#{Modulation::DIR}/ }
114
+ i = 0
115
+ backtrace = backtrace.reject do |l|
116
+ (i += 1) > 1 && l =~ /^#{Modulation::DIR}/
117
+ end
108
118
  end
109
119
  error.set_backtrace(backtrace)
110
120
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Modulation
4
4
  # default export functionality
5
- module DefaultExport
5
+ module ExportDefault
6
6
  class << self
7
7
  # Returns exported value for a default export
8
8
  # If the given value is a symbol, returns the value of the corresponding
@@ -24,7 +24,7 @@ module Modulation
24
24
 
25
25
  def get_module_constant(mod, value)
26
26
  unless mod.singleton_class.constants(true).include?(value)
27
- Exports.raise_exported_symbol_not_found_error(value, mod, :const)
27
+ Exports.raise_exported_symbol_not_found_error(value, :const)
28
28
  end
29
29
 
30
30
  mod.singleton_class.const_get(value)
@@ -32,7 +32,7 @@ module Modulation
32
32
 
33
33
  def get_module_method(mod, value)
34
34
  unless mod.singleton_class.instance_methods(true).include?(value)
35
- Exports.raise_exported_symbol_not_found_error(value, mod, :method)
35
+ Exports.raise_exported_symbol_not_found_error(value, :method)
36
36
  end
37
37
 
38
38
  proc { |*args, &block| mod.send(value, *args, &block) }
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulation
4
+ # default export functionality
5
+ module ExportDefault
6
+ class << self
7
+ # Returns exported value for a default export
8
+ # If the given value is a symbol, returns the value of the corresponding
9
+ # constant. If the symbol refers to a method, returns a proc enveloping
10
+ # the method. Raises if symbol refers to non-existent constant or method.
11
+ # @param value [any] export_default value
12
+ # @param mod [Module] module
13
+ # @return [any] exported value
14
+ def transform_export_default_value(value, mod)
15
+ return value unless value.is_a?(Symbol)
16
+
17
+ case value
18
+ when /^[A-Z]/
19
+ get_module_constant(mod, value)
20
+ else
21
+ get_module_method(mod, value)
22
+ end
23
+ end
24
+
25
+ def get_module_constant(mod, value)
26
+ unless mod.singleton_class.constants(true).include?(value)
27
+ Exports.raise_exported_symbol_not_found_error(value, :const)
28
+ end
29
+
30
+ mod.singleton_class.const_get(value)
31
+ end
32
+
33
+ def get_module_method(mod, value)
34
+ unless mod.singleton_class.instance_methods(true).include?(value)
35
+ Exports.raise_exported_symbol_not_found_error(value, :method)
36
+ end
37
+
38
+ proc { |*args, &block| mod.send(value, *args, &block) }
39
+ end
40
+
41
+ # Error message to be displayed when trying to set a singleton value as
42
+ # default export
43
+ DEFAULT_VALUE_ERROR_MSG =
44
+ 'Default export cannot be boolean, numeric, or symbol'
45
+
46
+ # Sets the default value for a module using export_default
47
+ # @param value [any] default value
48
+ # @param info [Hash] module info
49
+ # @param mod [Module] module
50
+ # @return [any] default value
51
+ def set_module_default_value(value, info, mod, caller)
52
+ value = transform_export_default_value(value, mod)
53
+ case value
54
+ when nil, true, false, Numeric, Symbol
55
+ raise(TypeError, DEFAULT_VALUE_ERROR_MSG, caller)
56
+ end
57
+ set_reload_info(value, mod.__module_info)
58
+ Modulation.loaded_modules[info[:location]] = value
59
+ end
60
+
61
+ # Adds methods for module_info and reloading to a value exported as
62
+ # default
63
+ # @param value [any] export_default value
64
+ # @param info [Hash] module info
65
+ # @return [void]
66
+ def set_reload_info(value, info)
67
+ value.define_singleton_method(:__module_info) { info }
68
+ value.define_singleton_method(:__reload!) do
69
+ Modulation::Builder.make(info)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulation
4
+ # Functionality related to export from receiver
5
+ module ExportFromReceiver
6
+ class << self
7
+ def from_const(mod, name)
8
+ receiver = mod.singleton_class.const_get(name)
9
+
10
+ methods = create_forwarding_methods(mod, receiver)
11
+ consts = copy_constants(mod, receiver)
12
+ methods + consts
13
+ end
14
+
15
+ # @return [Array] list of receiver methods
16
+ def create_forwarding_methods(mod, receiver)
17
+ receiver_methods(receiver).each do |m|
18
+ mod.singleton_class.define_method(m) do |*args, &block|
19
+ receiver.send(m, *args, &block)
20
+ end
21
+ end
22
+ end
23
+
24
+ def receiver_methods(receiver)
25
+ ignored_klass = case receiver
26
+ when Class, Module then receiver.class
27
+ else Object
28
+ end
29
+
30
+ methods = receiver.methods.select { |m| m !~ /^__/ }
31
+ methods - ignored_klass.instance_methods
32
+ end
33
+
34
+ # @return [Array] list of receiver constants
35
+ def copy_constants(mod, receiver)
36
+ receiver.constants(false).each do |c|
37
+ mod.singleton_class.const_set(c, receiver.const_get(c))
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,10 +1,96 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './export_from_receiver'
4
+
3
5
  module Modulation
4
- # Export functionality
6
+ # Functionality related to symbol export
5
7
  module Exports
6
8
  class << self
7
- # Marks all non-exported methods as private
9
+ # Performs the exporting of symbols after the module has loaded
10
+ def perform_exports(mod)
11
+ directives = mod.__export_directives
12
+ exported_symbols = directives.inject [] do |exported, directive|
13
+ symbols = export_directive mod, directive
14
+ exported + symbols
15
+ end
16
+ set_exported_symbols mod, exported_symbols
17
+ end
18
+
19
+ def export_directive(mod, directive)
20
+ send directive[:method], mod, *directive[:args]
21
+ rescue NameError => e
22
+ Modulation.raise_error e, directive[:export_caller]
23
+ end
24
+
25
+ def export(mod, *symbols)
26
+ case symbols.first
27
+ when Hash
28
+ symbols = export_hash(mod, symbols.first)
29
+ when Array
30
+ symbols = symbols.first
31
+ end
32
+
33
+ validate_exported_symbols(mod, symbols)
34
+ symbols
35
+ end
36
+
37
+ def export_from_receiver(mod, name)
38
+ if name =~ Modulation::RE_CONST
39
+ ExportFromReceiver.from_const(mod, name)
40
+ else
41
+ raise 'export_from_receiver expects a const reference'
42
+ end
43
+ end
44
+
45
+ def validate_exported_symbols(mod, symbols)
46
+ defined_methods = mod.singleton_class.instance_methods(true)
47
+ defined_constants = mod.singleton_class.constants(false)
48
+
49
+ symbols.each do |sym|
50
+ if sym =~ Modulation::RE_CONST
51
+ validate_exported_symbol(sym, defined_constants, :const)
52
+ else
53
+ validate_exported_symbol(sym, defined_methods, :method)
54
+ end
55
+ end
56
+ end
57
+
58
+ def validate_exported_symbol(sym, list, kind)
59
+ return if list.include? sym
60
+
61
+ raise_exported_symbol_not_found_error(sym, kind)
62
+ end
63
+
64
+ # @return [Array] array of exported symbols
65
+ def export_hash(mod, hash)
66
+ singleton = mod.singleton_class
67
+ hash.each { |k, v| export_hash_entry(singleton, k, v) }
68
+ hash.keys
69
+ end
70
+
71
+ def export_hash_entry(singleton, key, value)
72
+ symbol_value = value.is_a?(Symbol)
73
+ const_value = value =~ Modulation::RE_CONST
74
+ if value && const_value && singleton.const_defined?(value)
75
+ value = singleton.const_get(value)
76
+ end
77
+
78
+ generate_exported_hash_entry(singleton, key, value, symbol_value)
79
+ end
80
+
81
+ def generate_exported_hash_entry(singleton, key, value, symbol_value)
82
+ const_key = key =~ Modulation::RE_CONST
83
+ if const_key
84
+ singleton.const_set(key, value)
85
+ elsif symbol_value && singleton.method_defined?(value)
86
+ singleton.alias_method(key, value)
87
+ else
88
+ value_proc = value.is_a?(Proc) ? value : proc { value }
89
+ singleton.define_method(key, &value_proc)
90
+ end
91
+ end
92
+
93
+ # Marks all non-exported methods as private, exposes exported constants
8
94
  # @param mod [Module] module with exported symbols
9
95
  # @param symbols [Array] array of exported symbols
10
96
  # @return [void]
@@ -12,7 +98,7 @@ module Modulation
12
98
  mod.__module_info[:exported_symbols] = symbols
13
99
  singleton = mod.singleton_class
14
100
 
15
- privatize_non_exported_methods(mod, singleton, symbols)
101
+ privatize_non_exported_methods(singleton, symbols)
16
102
  expose_exported_constants(mod, singleton, symbols)
17
103
  end
18
104
 
@@ -20,13 +106,7 @@ module Modulation
20
106
  # @param singleton [Class] sinleton for module
21
107
  # @param symbols [Array] array of exported symbols
22
108
  # @return [void]
23
- def privatize_non_exported_methods(mod, singleton, symbols)
24
- defined_methods = singleton.instance_methods(true)
25
- difference = symbols.select { |s| s =~ /^[a-z]/ } - defined_methods
26
- unless difference.empty?
27
- raise_exported_symbol_not_found_error(difference.first, mod, :method)
28
- end
29
-
109
+ def privatize_non_exported_methods(singleton, symbols)
30
110
  singleton.instance_methods(false).each do |sym|
31
111
  next if symbols.include?(sym)
32
112
 
@@ -41,10 +121,6 @@ module Modulation
41
121
  # @return [void]
42
122
  def expose_exported_constants(mod, singleton, symbols)
43
123
  defined_constants = singleton.constants(false)
44
- difference = symbols.select { |s| s =~ /^[A-Z]/ } - defined_constants
45
- unless difference.empty?
46
- raise_exported_symbol_not_found_error(difference.first, mod, :const)
47
- end
48
124
  process_module_constants(mod, singleton, symbols, defined_constants)
49
125
  end
50
126
 
@@ -61,12 +137,11 @@ module Modulation
61
137
 
62
138
  NOT_FOUND_MSG = '%s %s not found in module'
63
139
 
64
- def raise_exported_symbol_not_found_error(sym, mod, kind)
140
+ def raise_exported_symbol_not_found_error(sym, kind)
65
141
  msg = format(
66
142
  NOT_FOUND_MSG, kind == :method ? 'Method' : 'Constant', sym
67
143
  )
68
- error = NameError.new(msg)
69
- Modulation.raise_error(error, mod.__export_backtrace)
144
+ raise NameError, msg
70
145
  end
71
146
  end
72
147
  end
@@ -7,56 +7,47 @@ module Modulation
7
7
  attr_accessor :__module_info
8
8
  attr_reader :__export_default_info
9
9
 
10
- # Adds given symbols to the exported_symbols array
11
- # @param symbols [Array] array of symbols
12
- # @return [void]
13
- def export(*symbols)
14
- case symbols.first
15
- when Hash
16
- symbols = __convert_export_hash(symbols.first)
17
- when Array
18
- symbols = symbols.first
19
- end
20
-
21
- __exported_symbols.concat(symbols)
22
- __export_backtrace = caller
10
+ def __before_reload
11
+ @__module_info[:exported_symbols] = []
12
+ @__export_directives = nil
13
+ __reset_dependencies
23
14
  end
24
15
 
25
- def __convert_export_hash(hash)
26
- @__exported_hash = hash
27
- hash.keys
16
+ def __export_directives
17
+ @__export_directives || []
28
18
  end
29
19
 
30
- RE_CONST = /^[A-Z]/.freeze
31
-
32
- def __post_load
33
- return unless @__exported_hash
34
-
35
- singleton = singleton_class
36
- @__exported_hash.map do |k, v|
37
- __convert_export_hash_entry(singleton, k, v)
38
- k
39
- end
20
+ def __exported_symbols
21
+ __module_info[:exported_symbols]
40
22
  end
41
23
 
42
- def __convert_export_hash_entry(singleton, key, value)
43
- symbol = value.is_a?(Symbol)
44
- if symbol && value =~ RE_CONST && singleton.const_defined?(value)
45
- value = singleton.const_get(value)
24
+ # Adds given symbols to the exported_symbols array
25
+ # @param symbols [Array] array of symbols
26
+ # @return [void]
27
+ def export(*symbols)
28
+ if @__export_default_info
29
+ raise 'Cannot mix calls to export and export_default in same module'
46
30
  end
47
31
 
48
- __add_exported_hash_entry(singleton, key, value, symbol)
32
+ @__export_directives ||= []
33
+ @__export_directives << {
34
+ method: :export,
35
+ args: symbols,
36
+ export_caller: caller
37
+ }
49
38
  end
50
39
 
51
- def __add_exported_hash_entry(singleton, key, value, symbol)
52
- if key =~ RE_CONST
53
- singleton.const_set(key, value)
54
- elsif symbol && singleton.method_defined?(value)
55
- singleton.alias_method(key, value)
56
- else
57
- value_proc = value.is_a?(Proc) ? value : proc { value }
58
- singleton.define_method(key, &value_proc)
40
+ def export_from_receiver(name)
41
+ if @__export_default_info
42
+ raise 'Cannot mix calls to export_from_receiver and export_default in same module'
59
43
  end
44
+
45
+ @__export_directives ||= []
46
+ @__export_directives << {
47
+ method: :export_from_receiver,
48
+ args: name,
49
+ export_caller: caller
50
+ }
60
51
  end
61
52
 
62
53
  # Sets a module's value, so when imported it will represent the given value,
@@ -64,7 +55,10 @@ module Modulation
64
55
  # @param value [Symbol, any] symbol or value
65
56
  # @return [void]
66
57
  def export_default(value)
67
- self.__export_backtrace = caller
58
+ unless __export_directives.empty?
59
+ raise 'Cannot mix calls to export and export_default in the same module'
60
+ end
61
+
68
62
  @__export_default_info = { value: value, caller: caller }
69
63
  end
70
64
 
@@ -85,41 +79,6 @@ module Modulation
85
79
  Modulation.reload(self)
86
80
  end
87
81
 
88
- # Defers exporting of symbols for a namespace (nested module), to be
89
- # performed after the entire module has been loaded
90
- # @param namespace [Module] namespace module
91
- # @param symbols [Array] array of symbols
92
- # @return [void]
93
- def __defer_namespace_export(namespace, symbols)
94
- @__namespace_exports ||= Hash.new { |h, k| h[k] = [] }
95
- @__namespace_exports[namespace].concat(symbols)
96
- end
97
-
98
- # Performs exporting of symbols for all namespaces defined in the module,
99
- # marking unexported methods and constants as private
100
- # @return [void]
101
- def __perform_deferred_namespace_exports
102
- return unless @__namespace_exports
103
-
104
- @__namespace_exports.each do |m, symbols|
105
- Builder.set_exported_symbols(m, symbols)
106
- end
107
- end
108
-
109
- # Returns exported_symbols array
110
- # @return [Array] array of exported symbols
111
- def __exported_symbols
112
- @__exported_symbols ||= []
113
- end
114
-
115
- def __export_backtrace
116
- @__export_backtrace
117
- end
118
-
119
- def __export_backtrace=(backtrace)
120
- @__export_backtrace = backtrace
121
- end
122
-
123
82
  # Allow modules to use attr_accessor/reader/writer and include methods by
124
83
  # forwarding calls to singleton_class
125
84
  %i[attr_accessor attr_reader attr_writer include].each do |sym|
@@ -168,12 +127,14 @@ module Modulation
168
127
  end
169
128
 
170
129
  def __reset_dependencies
171
- __dependencies.each do |mod|
130
+ return unless @__dependencies
131
+
132
+ @__dependencies.each do |mod|
172
133
  next unless mod.respond_to?(:__dependent_modules)
173
134
 
174
135
  mod.__dependent_modules.delete(self)
175
136
  end
176
- __dependencies.clear
137
+ @__dependencies.clear
177
138
  end
178
139
  end
179
140
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- VERSION = '0.31'
4
+ VERSION = '0.32'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulation
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.31'
4
+ version: '0.32'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-28 00:00:00.000000000 Z
11
+ date: 2019-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -57,6 +57,8 @@ files:
57
57
  - lib/modulation/builder.rb
58
58
  - lib/modulation/core.rb
59
59
  - lib/modulation/default_export.rb
60
+ - lib/modulation/export_default.rb
61
+ - lib/modulation/export_from_receiver.rb
60
62
  - lib/modulation/exports.rb
61
63
  - lib/modulation/ext.rb
62
64
  - lib/modulation/gem.rb