modulation 0.8 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|