modulation 0.8 → 0.9
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 +54 -16
- data/lib/modulation.rb +112 -35
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3899712e3a895dcc3c3aa712bdf03cf7f01243ec73194b2b3548594ef34d8ce6
|
4
|
+
data.tar.gz: a79b4b422014895a5bbdf86dd3e1d239941e273c476e757688dd0a93d5ca62ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 571677f6138bd087cfeac6469b3e00a692aa6699a77485b87786f6a2dd41faf1d8a9db22970e1ce47d9c93615897ba54bac161887d69ada26529c7ad7bf3b7ed
|
7
|
+
data.tar.gz: 34c76b1028909118f75872a03affadf35285e3bd40bcd8b3505803a61135a85eefe65948913ca16b4c06a945e6fc88d6077ad29340d60c569fa7739fa7b5fdab
|
data/README.md
CHANGED
@@ -1,17 +1,24 @@
|
|
1
|
-
# Modulation -
|
1
|
+
# Modulation - better dependency management for Ruby
|
2
2
|
|
3
|
-
Modulation provides an
|
4
|
-
littering the global namespace with classes and modules, Mrodulation lets you
|
3
|
+
Modulation provides an better way to organize Ruby code. Modulation lets you
|
5
4
|
explicitly import and export declarations in order to better control
|
6
|
-
dependencies in your codebase.
|
5
|
+
dependencies in your codebase. Modulation helps you refrain from littering
|
6
|
+
the global namespace with a myriad modules, or declaring complex nested
|
7
|
+
class hierarchies.
|
7
8
|
|
8
|
-
|
9
|
-
control over which parts of a module's code
|
10
|
-
|
11
|
-
minimum of boilerplate code.
|
9
|
+
Using Modulation, you will always be able to tell know where a piece of code
|
10
|
+
comes from, and you'll have full control over which parts of a module's code
|
11
|
+
you wish to expose to the outside world. Modulation also helps you write Ruby
|
12
|
+
code in a functional style, with a minimum of boilerplate code.
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
## Features
|
15
|
+
|
16
|
+
- Complete isolation of each module for better control of dependencies.
|
17
|
+
- Explicit exporting of methods, classes, modules and other constants.
|
18
|
+
- Default exports for modules exporting a single class or value.
|
19
|
+
- Nested namespaces with explicit exports.
|
20
|
+
- Modules can be reloaded at runtime without breaking dependencies.
|
21
|
+
- Can be used to write gems.
|
15
22
|
|
16
23
|
## Rationale
|
17
24
|
|
@@ -156,7 +163,7 @@ User.new(...)
|
|
156
163
|
|
157
164
|
The default exported value can also be defined directly thus:
|
158
165
|
|
159
|
-
*config.rb*
|
166
|
+
*config.rb*
|
160
167
|
```ruby
|
161
168
|
export_default(
|
162
169
|
host: 'localhost',
|
@@ -232,8 +239,6 @@ end
|
|
232
239
|
5.seq(:fib)
|
233
240
|
```
|
234
241
|
|
235
|
-
### Organizing
|
236
|
-
|
237
242
|
### Accessing a module from nested namespaces within itself
|
238
243
|
|
239
244
|
The special constant `MODULE` allows you to access the containing module from
|
@@ -273,6 +278,39 @@ end
|
|
273
278
|
what = ::MEANING_OF_LIFE
|
274
279
|
```
|
275
280
|
|
281
|
+
### Reloading modules
|
282
|
+
|
283
|
+
Modules can be easily reloaded in order to implement hot code reloading:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
SQL = import('./sql')
|
287
|
+
...
|
288
|
+
SQL.__reload!
|
289
|
+
```
|
290
|
+
|
291
|
+
Another way to reload modules is using `Modulation.reload`, which accepts a
|
292
|
+
module or a filename:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
require 'filewatcher'
|
296
|
+
|
297
|
+
FileWatcher.new(['lib']).watch do |fn, event|
|
298
|
+
if(event == :changed)
|
299
|
+
Modulation.reload(fn)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
Reloading of default exports is also possible. Modulation will extend the
|
305
|
+
exported value with a `#__reload!` method. The value will need to be
|
306
|
+
reassigned:
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
settings = import('settings')
|
310
|
+
...
|
311
|
+
settings = settings.__reload!
|
312
|
+
```
|
313
|
+
|
276
314
|
## Writing gems using Modulation
|
277
315
|
|
278
316
|
Modulation can be used to write gems, providing fine-grained control over your
|
@@ -299,7 +337,7 @@ Gems written using modulation can also be loaded using `import`. If modulation
|
|
299
337
|
does not find the module specified by the given relative path, it will attempt
|
300
338
|
to load a gem by the same name.
|
301
339
|
|
302
|
-
> **Note**: using `import` to load a gem is very much *
|
340
|
+
> **Note**: using `import` to load a gem is very much *experimental*, and might
|
303
341
|
> introduce problems not encountered when loading with `require` such as
|
304
342
|
> shadowing of global namespaces, or any other bizarre and unexpected
|
305
343
|
> behaviors. Actually, there's not much point in using it to load a gem which
|
@@ -328,13 +366,13 @@ to load a gem by the same name.
|
|
328
366
|
Foo = import('./foo')
|
329
367
|
Bar = import('./bar')
|
330
368
|
Baz = import('./baz')
|
331
|
-
|
332
369
|
...
|
333
370
|
```
|
334
371
|
|
335
372
|
## Known limitations and problems
|
336
373
|
|
337
374
|
- Modulation is (probably) not production-ready.
|
375
|
+
- Modulation is not thread-safe.
|
338
376
|
- Modulation probably doesn't play well with `Marshal`.
|
339
377
|
- Modulation probably doesn't play well with code-analysis tools.
|
340
|
-
- Modulation doesn't play well with rdoc/yard.
|
378
|
+
- Modulation probably doesn't play well with rdoc/yard.
|
data/lib/modulation.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
-
# Kernel extensions
|
4
|
+
# Kernel extensions
|
5
5
|
module Kernel
|
6
6
|
# Returns an encapsulated imported module.
|
7
7
|
# @param fn [String] module file name
|
@@ -12,6 +12,7 @@ module Kernel
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
# Module extensions
|
15
16
|
class Module
|
16
17
|
# Exports symbols from a namespace module declared inside an importable
|
17
18
|
# module. Exporting the actual symbols is deferred until the entire code
|
@@ -19,12 +20,12 @@ class Module
|
|
19
20
|
# @param symbols [Array] array of symbols
|
20
21
|
# @return [void]
|
21
22
|
def export(*symbols)
|
22
|
-
unless Modulation.
|
23
|
+
unless Modulation.__top_level_module__
|
23
24
|
raise NameError, "Can't export symbols outside of an imported module"
|
24
25
|
end
|
25
26
|
|
26
27
|
extend self
|
27
|
-
Modulation.
|
28
|
+
Modulation.__top_level_module__.__defer_namespace_export(self, symbols)
|
28
29
|
end
|
29
30
|
|
30
31
|
# Extends the receiver with exported methods from the given file name
|
@@ -50,10 +51,20 @@ class Module
|
|
50
51
|
end
|
51
52
|
|
52
53
|
class Modulation
|
54
|
+
# Hash mapping fully-qualified paths to loaded modules
|
53
55
|
@@loaded_modules = {}
|
56
|
+
|
57
|
+
# Reference to currently loaded top-level module, used for correctly
|
58
|
+
# exporting symbols from namespaces
|
54
59
|
@@top_level_module = nil
|
60
|
+
|
61
|
+
# Flag denoting whether to provide full backtrace on errors during
|
62
|
+
# loading of a module (normally Modulation removes stack frames
|
63
|
+
# occuring in Modulation code)
|
55
64
|
@@full_backtrace = false
|
56
65
|
|
66
|
+
public
|
67
|
+
|
57
68
|
# Show full backtrace for errors occuring while loading a module. Normally
|
58
69
|
# Modulation will remove stack frames occurring inside the modulation.rb code
|
59
70
|
# in order to make backtraces more readable when debugging.
|
@@ -73,10 +84,12 @@ class Modulation
|
|
73
84
|
|
74
85
|
# Returns the currently loaded top level module
|
75
86
|
# @return [Module] currently loaded module
|
76
|
-
def self.
|
87
|
+
def self.__top_level_module__
|
77
88
|
@@top_level_module
|
78
89
|
end
|
79
90
|
|
91
|
+
private
|
92
|
+
|
80
93
|
# Resolves the absolute path to the provided reference. If the file is not
|
81
94
|
# found, will try to resolve to a gem
|
82
95
|
# @param fn [String] unqualified file name
|
@@ -133,21 +146,51 @@ class Modulation
|
|
133
146
|
# @param block [Proc] module block
|
134
147
|
# @return [Class] module facade
|
135
148
|
def self.make_module(info, &block)
|
136
|
-
|
137
|
-
|
149
|
+
default_value = :__no_default_value__
|
150
|
+
default_value_caller = nil
|
151
|
+
m = initialize_module do |v, caller|
|
152
|
+
default_value = v
|
153
|
+
default_value_caller = caller
|
154
|
+
end
|
138
155
|
@@loaded_modules[info[:location]] = m
|
139
156
|
m.__module_info = info
|
140
157
|
load_module_code(m, info, &block)
|
141
|
-
|
142
|
-
|
143
|
-
if export_default
|
144
|
-
@@loaded_modules[info[:location]] = transform_export_default_value(export_default, m)
|
145
|
-
|
158
|
+
if default_value != :__no_default_value__
|
159
|
+
set_module_default_value(default_value, info, m, default_value_caller)
|
146
160
|
else
|
147
|
-
m.
|
161
|
+
m.__perform_deferred_namespace_exports
|
162
|
+
set_exported_symbols(m, m.__exported_symbols)
|
163
|
+
m
|
148
164
|
end
|
149
165
|
end
|
150
166
|
|
167
|
+
DEFAULT_VALUE_ERROR_MSG = "Default export cannot be boolean, numeric, or symbol"
|
168
|
+
private_constant(:DEFAULT_VALUE_ERROR_MSG)
|
169
|
+
|
170
|
+
# Sets the default value for a module using export_default
|
171
|
+
# @param value [any] default value
|
172
|
+
# @param info [Hash] module info
|
173
|
+
# @param m [Module] module
|
174
|
+
# @return [any] default value
|
175
|
+
def self.set_module_default_value(value, info, m, caller)
|
176
|
+
value = transform_export_default_value(value, m)
|
177
|
+
case value
|
178
|
+
when nil, true, false, Numeric, Symbol
|
179
|
+
raise(TypeError, DEFAULT_VALUE_ERROR_MSG, caller)
|
180
|
+
end
|
181
|
+
set_reload_info(value, m.__module_info)
|
182
|
+
@@loaded_modules[info[:location]] = value
|
183
|
+
end
|
184
|
+
|
185
|
+
# Adds methods for module_info and reloading to a value exported as default
|
186
|
+
# @param value [any] export_default value
|
187
|
+
# @param info [Hash] module info
|
188
|
+
# @return [void]
|
189
|
+
def self.set_reload_info(value, info)
|
190
|
+
value.define_singleton_method(:__module_info) {info}
|
191
|
+
value.define_singleton_method(:__reload!) {Modulation.make_module(info)}
|
192
|
+
end
|
193
|
+
|
151
194
|
# Returns exported value for a default export
|
152
195
|
# If the given value is a symbol, returns the value of the corresponding
|
153
196
|
# constant.
|
@@ -155,7 +198,7 @@ class Modulation
|
|
155
198
|
# @param mod [Module] module
|
156
199
|
# @return [any] exported value
|
157
200
|
def self.transform_export_default_value(value, mod)
|
158
|
-
if value.is_a?(Symbol) && mod.const_defined?(value)
|
201
|
+
if value.is_a?(Symbol) && (mod.const_defined?(value) rescue nil)
|
159
202
|
mod.const_get(value)
|
160
203
|
else
|
161
204
|
value
|
@@ -207,22 +250,43 @@ class Modulation
|
|
207
250
|
end
|
208
251
|
end
|
209
252
|
|
210
|
-
#
|
253
|
+
# Reloads the given module from its source file
|
254
|
+
# @param m [Module, String] module to reload
|
255
|
+
# @return [Module] module
|
256
|
+
def self.reload(m)
|
257
|
+
if m.is_a?(String)
|
258
|
+
fn, m = m, @@loaded_modules[File.expand_path(m)]
|
259
|
+
raise "No module loaded from #{fn}" unless m
|
260
|
+
end
|
261
|
+
|
262
|
+
cleanup_module(m)
|
263
|
+
|
264
|
+
orig_verbose, $VERBOSE = $VERBOSE, nil
|
265
|
+
load_module_code(m, m.__module_info)
|
266
|
+
$VERBOSE = orig_verbose
|
267
|
+
|
268
|
+
m.__perform_deferred_namespace_exports
|
269
|
+
m.tap {set_exported_symbols(m, m.__exported_symbols)}
|
270
|
+
end
|
271
|
+
|
272
|
+
# Removes methods and constants from module
|
273
|
+
# @param m [Module] module
|
274
|
+
# @return [void]
|
275
|
+
def self.cleanup_module(m)
|
276
|
+
m.constants(false).each {|c| m.send(:remove_const, c)}
|
277
|
+
m.methods(false).each {|sym| m.send(:undef_method, sym)}
|
278
|
+
|
279
|
+
private_methods = m.private_methods(false) - Module.private_instance_methods(false)
|
280
|
+
private_methods.each {|sym| m.send(:undef_method, sym)}
|
281
|
+
|
282
|
+
m.__exported_symbols.clear
|
283
|
+
end
|
284
|
+
|
285
|
+
# Extension methods for loaded modules
|
211
286
|
module ModuleMethods
|
212
287
|
# read and write module information
|
213
288
|
attr_accessor :__module_info
|
214
289
|
|
215
|
-
# Returns a text representation of the module for inspection
|
216
|
-
# @return [String] module string representation
|
217
|
-
def inspect
|
218
|
-
module_name = name || 'Module'
|
219
|
-
if __module_info[:location]
|
220
|
-
"#{module_name}:#{__module_info[:location]}"
|
221
|
-
else
|
222
|
-
"#{module_name}"
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
290
|
# Adds given symbols to the exported_symbols array
|
227
291
|
# @param symbols [Array] array of symbols
|
228
292
|
# @return [void]
|
@@ -236,16 +300,18 @@ class Modulation
|
|
236
300
|
# @param v [Symbol, any] symbol or value
|
237
301
|
# @return [void]
|
238
302
|
def export_default(v)
|
239
|
-
@__export_default_block.call(v) if @__export_default_block
|
303
|
+
@__export_default_block.call(v, caller) if @__export_default_block
|
240
304
|
end
|
241
305
|
|
242
|
-
#
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
306
|
+
# Returns a text representation of the module for inspection
|
307
|
+
# @return [String] module string representation
|
308
|
+
def inspect
|
309
|
+
module_name = name || 'Module'
|
310
|
+
if __module_info[:location]
|
311
|
+
"#{module_name}:#{__module_info[:location]}"
|
312
|
+
else
|
313
|
+
"#{module_name}"
|
314
|
+
end
|
249
315
|
end
|
250
316
|
|
251
317
|
# Sets export_default block, used for setting the returned module object to
|
@@ -256,6 +322,12 @@ class Modulation
|
|
256
322
|
@__export_default_block = block
|
257
323
|
end
|
258
324
|
|
325
|
+
# Reload module
|
326
|
+
# @return [Module] module
|
327
|
+
def __reload!
|
328
|
+
Modulation.reload(self)
|
329
|
+
end
|
330
|
+
|
259
331
|
# Defers exporting of symbols for a namespace (nested module), to be
|
260
332
|
# performed after the entire module has been loaded
|
261
333
|
# @param namespace [Module] namespace module
|
@@ -276,6 +348,11 @@ class Modulation
|
|
276
348
|
Modulation.set_exported_symbols(m, symbols)
|
277
349
|
end
|
278
350
|
end
|
279
|
-
end
|
280
|
-
end
|
281
351
|
|
352
|
+
# Returns exported_symbols array
|
353
|
+
# @return [Array] array of exported symbols
|
354
|
+
def __exported_symbols
|
355
|
+
@exported_symbols ||= []
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
metadata
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modulation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.9'
|
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-08-
|
11
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: "Modulation provides an
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
description: "Modulation provides an better way to organize Ruby code. Modulation
|
14
|
+
lets you \nexplicitly import and export declarations in order to better control
|
15
|
+
\ndependencies in your codebase. Modulation helps you refrain from littering\nthe
|
16
|
+
global namespace with a myriad modules, or declaring complex nested\nclass hierarchies.\n\nUsing
|
17
|
+
Modulation, you will always be able to tell know where a piece of code \ncomes from,
|
18
|
+
and you'll have full control over which parts of a module's code \nyou wish to expose
|
19
|
+
to the outside world. Modulation also helps you write Ruby \ncode in a functional
|
20
|
+
style, with a minimum of boilerplate code.\n"
|
20
21
|
email: ciconia@gmail.com
|
21
22
|
executables: []
|
22
23
|
extensions: []
|
@@ -54,5 +55,5 @@ rubyforge_project:
|
|
54
55
|
rubygems_version: 2.7.3
|
55
56
|
signing_key:
|
56
57
|
specification_version: 4
|
57
|
-
summary: 'Modulation:
|
58
|
+
summary: 'Modulation: better dependency management for Ruby'
|
58
59
|
test_files: []
|