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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +69 -20
- data/lib/modulation/builder.rb +5 -7
- data/lib/modulation/core.rb +24 -14
- data/lib/modulation/default_export.rb +3 -3
- data/lib/modulation/export_default.rb +74 -0
- data/lib/modulation/export_from_receiver.rb +42 -0
- data/lib/modulation/exports.rb +92 -17
- data/lib/modulation/module_mixin.rb +38 -77
- data/lib/modulation/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 598679c69845dd516cf7f19ff4ae9f60e254bf488abc1460cd08b121714b4cba
|
4
|
+
data.tar.gz: 6b962c6f2cea4a300918eea8ff0d142999e01a32d64e7e8d814d5d15cbfb0984
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d8618b49710007df4312cfaefdaacc6291a7ca926945dae9378a6cb77efb0cd54947db9fe1cd84cfc8e39e9295aa22ed12db34be953f6df8eea0a072b32e596
|
7
|
+
data.tar.gz: ed6a9627b2e6dd89e0c3751b59362bf5b78dffb334934028aa2a8895922b64b80d79101a35f9e0ad7882fd6515d71bae87f3d1560c2f8a5d8cbe9ee856e460a2
|
data/CHANGELOG.md
CHANGED
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
|
-
-
|
31
|
-
|
32
|
-
-
|
33
|
-
|
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
|
-
-
|
38
|
-
|
39
|
-
-
|
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
|
-
-
|
44
|
-
-
|
40
|
+
- [Dependency introspection](#dependency-introspection).
|
41
|
+
- Easier [unit-testing](#unit-testing-modules) of private methods and
|
45
42
|
constants.
|
46
|
-
-
|
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
|
85
|
-
|
86
|
-
|
87
|
-
|
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.
|
data/lib/modulation/builder.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative('exports')
|
4
|
-
require_relative('
|
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
|
-
|
63
|
+
ExportDefault.set_module_default_value(
|
65
64
|
default[:value], info, mod, default[:caller]
|
66
65
|
)
|
67
66
|
else
|
68
|
-
Exports.
|
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.
|
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.
|
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
|
data/lib/modulation/core.rb
CHANGED
@@ -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}
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
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,
|
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,
|
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
|
data/lib/modulation/exports.rb
CHANGED
@@ -1,10 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './export_from_receiver'
|
4
|
+
|
3
5
|
module Modulation
|
4
|
-
#
|
6
|
+
# Functionality related to symbol export
|
5
7
|
module Exports
|
6
8
|
class << self
|
7
|
-
#
|
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(
|
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(
|
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,
|
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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
26
|
-
@
|
27
|
-
hash.keys
|
16
|
+
def __export_directives
|
17
|
+
@__export_directives || []
|
28
18
|
end
|
29
19
|
|
30
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
32
|
+
@__export_directives ||= []
|
33
|
+
@__export_directives << {
|
34
|
+
method: :export,
|
35
|
+
args: symbols,
|
36
|
+
export_caller: caller
|
37
|
+
}
|
49
38
|
end
|
50
39
|
|
51
|
-
def
|
52
|
-
if
|
53
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/modulation/version.rb
CHANGED
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.
|
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-
|
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
|