modulation 0.7 → 0.8
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/README.md +40 -1
- data/lib/modulation.rb +106 -63
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddfb3af45389c8ef1d34c3ac29b7db56ec60ff48e7c2678c36e2b913db159e9f
|
4
|
+
data.tar.gz: 696b0e52f95113340fde1a51430f67892e7047362180755185bde46222d3077b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b49084b372e1d754457f7e3063c12945ac537c0ac3afa17befc67a1478ec3d58810cb2c10b7dad9c3f91ff835f9eb4e06c5384bf2df5ab5d52961c152f195cfb
|
7
|
+
data.tar.gz: d257762508c35b71dd4497c9950044f2621d598c12a2e419fb3a7a8c434690bc1b99f967dee029186fc6aaadcba811ec3f58941caf923bf18d8302f050dd3424
|
data/README.md
CHANGED
@@ -131,7 +131,7 @@ user = User.new(...)
|
|
131
131
|
```
|
132
132
|
|
133
133
|
> **Note about paths**: module paths are always relative to the file
|
134
|
-
> calling the `import` method
|
134
|
+
> calling the `import` method, just like `require_relative`.
|
135
135
|
|
136
136
|
### Default exports
|
137
137
|
|
@@ -171,6 +171,43 @@ config = import('./config')
|
|
171
171
|
db.connect(config[:host], config[:port])
|
172
172
|
```
|
173
173
|
|
174
|
+
### Further organising module functionality into nested namespaces
|
175
|
+
|
176
|
+
Code inside modules can be further organised by separating it into nested
|
177
|
+
namespaces. The `export` method can be used to turn a normal nested module
|
178
|
+
into a self-contained singleton-like object and prevent access to internal
|
179
|
+
implementation details:
|
180
|
+
|
181
|
+
*net.rb*
|
182
|
+
```ruby
|
183
|
+
export :Async, :TCPServer
|
184
|
+
|
185
|
+
module Async
|
186
|
+
export :await
|
187
|
+
|
188
|
+
def await
|
189
|
+
Fiber.new do
|
190
|
+
yield Fiber.current
|
191
|
+
Fiber.yield
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class TCPServer
|
197
|
+
...
|
198
|
+
def read
|
199
|
+
Async.await do |fiber|
|
200
|
+
on(:read) {|data| fiber.resume data}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
> Note: when `export` is called inside a `module` declaration, Modulation calls
|
207
|
+
> `extend self` implicitly, just like it does for the top-level loaded module.
|
208
|
+
> That way there's no need to declare methods using the `def self.xxx` syntax,
|
209
|
+
> and the module can still be used to extend arbitrary classes or objects.
|
210
|
+
|
174
211
|
### Importing methods into classes and modules
|
175
212
|
|
176
213
|
Modulation provides the `extend_from` and `include_from` methods to include
|
@@ -195,6 +232,8 @@ end
|
|
195
232
|
5.seq(:fib)
|
196
233
|
```
|
197
234
|
|
235
|
+
### Organizing
|
236
|
+
|
198
237
|
### Accessing a module from nested namespaces within itself
|
199
238
|
|
200
239
|
The special constant `MODULE` allows you to access the containing module from
|
data/lib/modulation.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require 'fileutils'
|
2
1
|
# frozen_string_literal: true
|
2
|
+
require 'fileutils'
|
3
3
|
|
4
4
|
# Kernel extensions - modul's API
|
5
5
|
module Kernel
|
@@ -12,21 +12,28 @@ module Kernel
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Object extensions
|
16
|
-
class Object
|
17
|
-
# Returns the objects metaclass (shamelessly stolen from the metaid gem).
|
18
|
-
# @return [Class] object's metaclass
|
19
|
-
def metaclass; class << self; self; end; end
|
20
|
-
end
|
21
|
-
|
22
15
|
class Module
|
16
|
+
# Exports symbols from a namespace module declared inside an importable
|
17
|
+
# module. Exporting the actual symbols is deferred until the entire code
|
18
|
+
# has been loaded
|
19
|
+
# @param symbols [Array] array of symbols
|
20
|
+
# @return [void]
|
21
|
+
def export(*symbols)
|
22
|
+
unless Modulation.top_level_module
|
23
|
+
raise NameError, "Can't export symbols outside of an imported module"
|
24
|
+
end
|
25
|
+
|
26
|
+
extend self
|
27
|
+
Modulation.top_level_module.__defer_namespace_export(self, symbols)
|
28
|
+
end
|
29
|
+
|
23
30
|
# Extends the receiver with exported methods from the given file name
|
24
31
|
# @param fn [String] module filename
|
25
32
|
# @return [void]
|
26
33
|
def extend_from(fn)
|
27
34
|
mod = import(fn, caller.first)
|
28
|
-
mod.
|
29
|
-
|
35
|
+
mod.instance_methods(false).each do |sym|
|
36
|
+
self.class.send(:define_method, sym, mod.method(sym).to_proc)
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
@@ -36,7 +43,7 @@ class Module
|
|
36
43
|
# @return [void]
|
37
44
|
def include_from(fn)
|
38
45
|
mod = import(fn, caller.first)
|
39
|
-
mod.
|
46
|
+
mod.instance_methods(false).each do |sym|
|
40
47
|
send(:define_method, sym, mod.method(sym).to_proc)
|
41
48
|
end
|
42
49
|
end
|
@@ -44,23 +51,38 @@ end
|
|
44
51
|
|
45
52
|
class Modulation
|
46
53
|
@@loaded_modules = {}
|
54
|
+
@@top_level_module = nil
|
47
55
|
@@full_backtrace = false
|
48
56
|
|
57
|
+
# Show full backtrace for errors occuring while loading a module. Normally
|
58
|
+
# Modulation will remove stack frames occurring inside the modulation.rb code
|
59
|
+
# in order to make backtraces more readable when debugging.
|
49
60
|
def self.full_backtrace!
|
50
61
|
@@full_backtrace = true
|
51
62
|
end
|
52
63
|
|
53
64
|
# Imports a module from a file
|
54
65
|
# If the module is already loaded, returns the loaded module.
|
55
|
-
# @param fn [String]
|
56
|
-
# @param caller_location [String]
|
66
|
+
# @param fn [String] unqualified file name
|
67
|
+
# @param caller_location [String] caller location
|
57
68
|
# @return [Module] loaded module object
|
58
69
|
def self.import_module(fn, caller_location = caller.first)
|
59
70
|
fn = module_absolute_path(fn, caller_location)
|
60
|
-
@@loaded_modules[fn]
|
71
|
+
@@loaded_modules[fn] || create_module_from_file(fn)
|
61
72
|
end
|
62
73
|
|
63
|
-
|
74
|
+
# Returns the currently loaded top level module
|
75
|
+
# @return [Module] currently loaded module
|
76
|
+
def self.top_level_module
|
77
|
+
@@top_level_module
|
78
|
+
end
|
79
|
+
|
80
|
+
# Resolves the absolute path to the provided reference. If the file is not
|
81
|
+
# found, will try to resolve to a gem
|
82
|
+
# @param fn [String] unqualified file name
|
83
|
+
# @param caller_location [String] caller location
|
84
|
+
# @return [String] absolute file name
|
85
|
+
def self.module_absolute_path(fn, caller_location = caller.first)
|
64
86
|
orig_fn = fn
|
65
87
|
caller_file = (caller_location =~ /^([^\:]+)\:/) ?
|
66
88
|
$1 : (raise "Could not expand path")
|
@@ -76,6 +98,9 @@ class Modulation
|
|
76
98
|
end
|
77
99
|
end
|
78
100
|
|
101
|
+
# Resolves the provided file name into a gem. If no gem is found, returns nil
|
102
|
+
# @param name [String] gem name
|
103
|
+
# @return [String] absolute path to gem main source file
|
79
104
|
def self.lookup_gem(name)
|
80
105
|
spec = Gem::Specification.find_by_name(name)
|
81
106
|
unless(spec.dependencies.map(&:name)).include?('modulation')
|
@@ -93,13 +118,14 @@ class Modulation
|
|
93
118
|
def self.create_module_from_file(fn)
|
94
119
|
make_module(location: fn)
|
95
120
|
rescue => e
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
121
|
+
@@full_backtrace ? raise : raise_with_clean_backtrace(e)
|
122
|
+
end
|
123
|
+
|
124
|
+
# (Re-)raises an error, filtering its backtrace to remove stack frames
|
125
|
+
# occuring in Modulation code
|
126
|
+
def self.raise_with_clean_backtrace(e)
|
127
|
+
backtrace = e.backtrace.reject {|l| l.include?(__FILE__)}
|
128
|
+
raise(e, e.message, backtrace)
|
103
129
|
end
|
104
130
|
|
105
131
|
# Loads a module from file or block, wrapping it in a module facade
|
@@ -109,13 +135,16 @@ class Modulation
|
|
109
135
|
def self.make_module(info, &block)
|
110
136
|
export_default = nil
|
111
137
|
m = initialize_module {|v| export_default = v}
|
138
|
+
@@loaded_modules[info[:location]] = m
|
112
139
|
m.__module_info = info
|
113
140
|
load_module_code(m, info, &block)
|
141
|
+
m.__perform_deferred_namespace_exports
|
114
142
|
|
115
143
|
if export_default
|
116
|
-
transform_export_default_value(export_default, m)
|
144
|
+
@@loaded_modules[info[:location]] = transform_export_default_value(export_default, m)
|
145
|
+
|
117
146
|
else
|
118
|
-
m.tap {
|
147
|
+
m.tap {set_exported_symbols(m, m.__exported_symbols)}
|
119
148
|
end
|
120
149
|
end
|
121
150
|
|
@@ -126,8 +155,8 @@ class Modulation
|
|
126
155
|
# @param mod [Module] module
|
127
156
|
# @return [any] exported value
|
128
157
|
def self.transform_export_default_value(value, mod)
|
129
|
-
if value.is_a?(Symbol) && mod.
|
130
|
-
mod.
|
158
|
+
if value.is_a?(Symbol) && mod.const_defined?(value)
|
159
|
+
mod.const_get(value)
|
131
160
|
else
|
132
161
|
value
|
133
162
|
end
|
@@ -138,10 +167,10 @@ class Modulation
|
|
138
167
|
# @return [Module] new module
|
139
168
|
def self.initialize_module(&export_default_block)
|
140
169
|
Module.new.tap do |m|
|
170
|
+
m.extend(m)
|
141
171
|
m.extend(ModuleMethods)
|
142
|
-
m.metaclass.include(ModuleMetaclassMethods)
|
143
172
|
m.__export_default_block = export_default_block
|
144
|
-
m.
|
173
|
+
m.const_set(:MODULE, m)
|
145
174
|
end
|
146
175
|
end
|
147
176
|
|
@@ -149,44 +178,40 @@ class Modulation
|
|
149
178
|
# @param m [Module] module
|
150
179
|
# @param fn [String] source file path
|
151
180
|
# @return [void]
|
152
|
-
def self.load_module_code(m, info)
|
153
|
-
|
154
|
-
m
|
181
|
+
def self.load_module_code(m, info, &block)
|
182
|
+
old_top_level_module = @@top_level_module
|
183
|
+
@@top_level_module = m
|
184
|
+
if block
|
185
|
+
m.module_eval(&block)
|
186
|
+
else
|
187
|
+
fn = info[:location]
|
188
|
+
m.module_eval(IO.read(fn), fn)
|
189
|
+
end
|
190
|
+
ensure
|
191
|
+
@@top_level_module = old_top_level_module
|
155
192
|
end
|
156
193
|
|
157
|
-
#
|
158
|
-
module
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
if metaclass.const_defined?(name)
|
167
|
-
unless !@__exported_symbols || @__exported_symbols.include?(name)
|
168
|
-
raise NameError, "private constant `#{name}' accessed in #{inspect}", caller
|
169
|
-
end
|
170
|
-
metaclass.const_get(name).tap {|value| const_set(name, value)}
|
171
|
-
else
|
172
|
-
raise NameError, "uninitialized constant #{inspect}::#{name}", caller
|
173
|
-
end
|
194
|
+
# Sets exported_symbols ivar and marks all non-exported methods as private
|
195
|
+
# @param m [Module] module with exported symbols
|
196
|
+
# @param symbols [Array] array of exported symbols
|
197
|
+
# @return [void]
|
198
|
+
def self.set_exported_symbols(m, symbols)
|
199
|
+
# m.__exported_symbols = symbols
|
200
|
+
m.instance_methods.each do |sym|
|
201
|
+
next if symbols.include?(sym)
|
202
|
+
m.send(:private, sym)
|
174
203
|
end
|
204
|
+
m.constants.each do |sym|
|
205
|
+
next if sym == :MODULE || symbols.include?(sym)
|
206
|
+
m.send(:private_constant, sym)
|
207
|
+
end
|
208
|
+
end
|
175
209
|
|
210
|
+
# Module façade methods
|
211
|
+
module ModuleMethods
|
176
212
|
# read and write module information
|
177
213
|
attr_accessor :__module_info
|
178
214
|
|
179
|
-
# Sets exported_symbols ivar and marks all non-exported methods as private
|
180
|
-
# @param m [Module] imported module
|
181
|
-
# @param symbols [Array] array of exported symbols
|
182
|
-
# @return [void]
|
183
|
-
def __set_exported_symbols(m, symbols)
|
184
|
-
@__exported_symbols = symbols
|
185
|
-
metaclass.instance_methods(false).each do |m|
|
186
|
-
metaclass.send(:private, m) unless symbols.include?(m)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
215
|
# Returns a text representation of the module for inspection
|
191
216
|
# @return [String] module string representation
|
192
217
|
def inspect
|
@@ -197,10 +222,7 @@ class Modulation
|
|
197
222
|
"#{module_name}"
|
198
223
|
end
|
199
224
|
end
|
200
|
-
end
|
201
225
|
|
202
|
-
# Module façade metaclass methods
|
203
|
-
module ModuleMetaclassMethods
|
204
226
|
# Adds given symbols to the exported_symbols array
|
205
227
|
# @param symbols [Array] array of symbols
|
206
228
|
# @return [void]
|
@@ -217,7 +239,7 @@ class Modulation
|
|
217
239
|
@__export_default_block.call(v) if @__export_default_block
|
218
240
|
end
|
219
241
|
|
220
|
-
# read and write module info
|
242
|
+
# read and write module info===
|
221
243
|
attr_accessor :__module_info
|
222
244
|
|
223
245
|
# Returns exported_symbols array
|
@@ -233,6 +255,27 @@ class Modulation
|
|
233
255
|
def __export_default_block=(block)
|
234
256
|
@__export_default_block = block
|
235
257
|
end
|
258
|
+
|
259
|
+
# Defers exporting of symbols for a namespace (nested module), to be
|
260
|
+
# performed after the entire module has been loaded
|
261
|
+
# @param namespace [Module] namespace module
|
262
|
+
# @param symbols [Array] array of symbols
|
263
|
+
# @return [void]
|
264
|
+
def __defer_namespace_export(namespace, symbols)
|
265
|
+
@__namespace_exports ||= Hash.new {|h, k| h[k] = []}
|
266
|
+
@__namespace_exports[namespace].concat(symbols)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Performs exporting of symbols for all namespaces defined in the module,
|
270
|
+
# marking unexported methods and constants as private
|
271
|
+
# @return [void]
|
272
|
+
def __perform_deferred_namespace_exports
|
273
|
+
return unless @__namespace_exports
|
274
|
+
|
275
|
+
@__namespace_exports.each do |m, symbols|
|
276
|
+
Modulation.set_exported_symbols(m, symbols)
|
277
|
+
end
|
278
|
+
end
|
236
279
|
end
|
237
280
|
end
|
238
281
|
|
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.8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: "Modulation provides an alternative way to organize Ruby code. Instead
|
14
14
|
of \nlittering the global namespace with classes and modules, Modulation lets\nyou
|