modulation 0.10 → 0.11
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 -11
- data/lib/modulation/builder.rb +141 -0
- data/lib/modulation/core.rb +89 -0
- data/lib/modulation/ext.rb +50 -0
- data/lib/modulation/module_mixin.rb +77 -0
- data/lib/modulation/paths.rb +53 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c196472c3122b723a0d76849c604cb6b8ce13cc2613cb418bf8ecbf9c794bbe8
|
4
|
+
data.tar.gz: cd027cc00a894ed92c43db09570ee301b82efb002d410d6daf9a584aca8c8357
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c4d82cb9ae8ea84364dffbdd56603bf7db864d4254a587545436c983e157d315068cde11e51fbfec84cb8db08382769506e8f5273f70a64f4440d56150b18f4
|
7
|
+
data.tar.gz: bf00f6e03d79ce2a2c77322aee2f433780989030cceffc031421dcec419c1f9e7bf4a9c8cf178864942e12ceb713760e4b5634a430b5b21e5840e342608d151a
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Modulation - better dependency management for Ruby
|
2
2
|
|
3
3
|
[INSTALL](#installing-modulation) |
|
4
|
-
[GUIDE](#
|
4
|
+
[GUIDE](#organizing-your-code-with-modulation) |
|
5
5
|
[EXAMPLES](https://github.com/ciconia/modulation/tree/master/examples) |
|
6
6
|
[DOCS](https://www.rubydoc.info/gems/modulation/)
|
7
7
|
|
@@ -18,12 +18,18 @@ code in a functional style, with a minimum of boilerplate code.
|
|
18
18
|
|
19
19
|
## Features
|
20
20
|
|
21
|
-
-
|
22
|
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
|
26
|
-
-
|
21
|
+
- Provides complete isolation of each module: constant declarations in one file
|
22
|
+
don't leak into another.
|
23
|
+
- Supports circular dependencies.
|
24
|
+
- Enforces explicit exporting and importing of methods, classes, modules and
|
25
|
+
constants.
|
26
|
+
- Allows [default exports](#default-exports) for modules exporting a single
|
27
|
+
class or value.
|
28
|
+
- Can [reload](#reloading-modules) modules at runtime without breaking your
|
29
|
+
code in wierd ways.
|
30
|
+
- Supports [nested namespaces](#using-nested-namespaces) with explicit exports.
|
31
|
+
- Allows [mocking of dependencies](#mocking-dependencies) for testing purposes.
|
32
|
+
- Can be used to [write gems](#writing-gems-using-modulation).
|
27
33
|
|
28
34
|
## Rationale
|
29
35
|
|
@@ -80,14 +86,16 @@ $ gem install modulation
|
|
80
86
|
|
81
87
|
## Organizing your code with Modulation
|
82
88
|
|
83
|
-
Modulation
|
89
|
+
Modulation builds on the idea of a Ruby module as a
|
90
|
+
["collection of methods and constants"](https://ruby-doc.org/core-2.5.1/Module.html).
|
84
91
|
Using modulation, any Ruby source file can be a module. Modules usually export
|
85
92
|
method and constant declarations (usually an API for a specific, well-defined
|
86
93
|
functionality) to be shared with other modules. Modules can also import
|
87
94
|
declarations from other modules.
|
88
95
|
|
89
|
-
Each module is
|
90
|
-
|
96
|
+
Each module is evaluated in the context of a newly-created `Module` instance,
|
97
|
+
with some additional methods that make it possible to identify the module's
|
98
|
+
source location and reload it.
|
91
99
|
|
92
100
|
### Exporting declarations
|
93
101
|
|
@@ -189,11 +197,12 @@ export_default(
|
|
189
197
|
|
190
198
|
*app.rb*
|
191
199
|
```ruby
|
200
|
+
require 'modulation'
|
192
201
|
config = import('./config')
|
193
202
|
db.connect(config[:host], config[:port])
|
194
203
|
```
|
195
204
|
|
196
|
-
###
|
205
|
+
### Using nested namespaces
|
197
206
|
|
198
207
|
Code inside modules can be further organised by separating it into nested
|
199
208
|
namespaces. The `export` method can be used to turn a normal nested module
|
@@ -243,6 +252,7 @@ end
|
|
243
252
|
Sequences.fib(5)
|
244
253
|
|
245
254
|
# extend integers
|
255
|
+
require 'modulation'
|
246
256
|
class Integer
|
247
257
|
include_from('./seq.rb')
|
248
258
|
|
@@ -293,11 +303,43 @@ end
|
|
293
303
|
what = ::MEANING_OF_LIFE
|
294
304
|
```
|
295
305
|
|
306
|
+
### Mocking dependencies
|
307
|
+
|
308
|
+
Modules loaded by Modulation can be easily mocked when running tests or specs,
|
309
|
+
using `Modulation.mock`:
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
require 'minitest/autorun'
|
313
|
+
require 'modulation'
|
314
|
+
|
315
|
+
module MockStorage
|
316
|
+
extend self
|
317
|
+
|
318
|
+
def get_user(user_id)
|
319
|
+
{
|
320
|
+
user_id: user_id,
|
321
|
+
name: 'John Doe',
|
322
|
+
email: 'johndoe@gmail.com'
|
323
|
+
}
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class UserControllerTest < Minitest::Test
|
328
|
+
def test_user_storage
|
329
|
+
Modulation.mock('../lib/storage', MockStorage) do
|
330
|
+
controller = UserController.
|
331
|
+
assert_equal
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
```
|
336
|
+
|
296
337
|
### Reloading modules
|
297
338
|
|
298
339
|
Modules can be easily reloaded in order to implement hot code reloading:
|
299
340
|
|
300
341
|
```ruby
|
342
|
+
require 'modulation'
|
301
343
|
SQL = import('./sql')
|
302
344
|
...
|
303
345
|
SQL.__reload!
|
@@ -321,6 +363,7 @@ exported value with a `#__reload!` method. The value will need to be
|
|
321
363
|
reassigned:
|
322
364
|
|
323
365
|
```ruby
|
366
|
+
require 'modulation'
|
324
367
|
settings = import('settings')
|
325
368
|
...
|
326
369
|
settings = settings.__reload!
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Modulation
|
4
|
+
# Implements creation of module instances
|
5
|
+
module Builder
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Loads a module from file or block, wrapping it in a module facade
|
9
|
+
# @param info [Hash] module info
|
10
|
+
# @param block [Proc] module block
|
11
|
+
# @return [Class] module facade
|
12
|
+
def make(info, &block)
|
13
|
+
default = nil
|
14
|
+
mod = create(info) { |default_info| default = default_info }
|
15
|
+
Modulation.loaded_modules[info[:location]] = mod
|
16
|
+
load_module_code(mod, info, &block)
|
17
|
+
if default
|
18
|
+
set_module_default_value(default[:value], info, mod, default[:caller])
|
19
|
+
else
|
20
|
+
set_exported_symbols(mod, mod.__exported_symbols, true)
|
21
|
+
mod
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initializes a new module ready to evaluate a file module
|
26
|
+
# @note The given block is used to pass the value given to `export_default`
|
27
|
+
# @param info [Hash] module info
|
28
|
+
# @return [Module] new module
|
29
|
+
def create(info, &export_default_block)
|
30
|
+
Module.new.tap do |mod|
|
31
|
+
mod.extend(mod)
|
32
|
+
mod.extend(ModuleMixin)
|
33
|
+
mod.__module_info = info
|
34
|
+
mod.__export_default_block = export_default_block
|
35
|
+
mod.const_set(:MODULE, mod)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Loads a source file or a block into the given module
|
40
|
+
# @param mod [Module] module
|
41
|
+
# @param info [Hash] module info
|
42
|
+
# @return [void]
|
43
|
+
def load_module_code(mod, info, &block)
|
44
|
+
old_top_level_module = Modulation.top_level_module
|
45
|
+
Modulation.top_level_module = mod
|
46
|
+
if block
|
47
|
+
mod.module_eval(&block)
|
48
|
+
else
|
49
|
+
path = info[:location]
|
50
|
+
mod.module_eval(IO.read(path), path)
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
Modulation.top_level_module = old_top_level_module
|
54
|
+
end
|
55
|
+
|
56
|
+
# Marks all non-exported methods as private
|
57
|
+
# @param mod [Module] module with exported symbols
|
58
|
+
# @param symbols [Array] array of exported symbols
|
59
|
+
# @param perform_deferred_exports [Boolean] perform deferred export flag
|
60
|
+
# @return [void]
|
61
|
+
def set_exported_symbols(mod, symbols, perform_deferred_exports = false)
|
62
|
+
mod.__perform_deferred_namespace_exports if perform_deferred_exports
|
63
|
+
|
64
|
+
mod.instance_methods.each do |sym|
|
65
|
+
next if symbols.include?(sym)
|
66
|
+
mod.send(:private, sym)
|
67
|
+
end
|
68
|
+
mod.constants.each do |sym|
|
69
|
+
next if sym == :MODULE || symbols.include?(sym)
|
70
|
+
mod.send(:private_constant, sym)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns exported value for a default export
|
75
|
+
# If the given value is a symbol, returns the value of the corresponding
|
76
|
+
# constant.
|
77
|
+
# @param value [any] export_default value
|
78
|
+
# @param mod [Module] module
|
79
|
+
# @return [any] exported value
|
80
|
+
def transform_export_default_value(value, mod)
|
81
|
+
value.is_a?(Symbol) ? mod.const_get(value) : value
|
82
|
+
rescue NameError
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Loads code for a module being reloaded, turning warnings off in order to
|
87
|
+
# not generate warnings upon re-assignment of constants
|
88
|
+
def reload_module_code(mod)
|
89
|
+
orig_verbose = $VERBOSE
|
90
|
+
$VERBOSE = nil
|
91
|
+
load_module_code(mod, mod.__module_info)
|
92
|
+
ensure
|
93
|
+
$VERBOSE = orig_verbose
|
94
|
+
end
|
95
|
+
|
96
|
+
# Removes methods and constants from module
|
97
|
+
# @param mod [Module] module
|
98
|
+
# @return [void]
|
99
|
+
def cleanup_module(mod)
|
100
|
+
mod.constants(false).each { |c| mod.send(:remove_const, c) }
|
101
|
+
mod.methods(false).each { |sym| mod.send(:undef_method, sym) }
|
102
|
+
|
103
|
+
private_methods = mod.private_methods(false) -
|
104
|
+
Module.private_instance_methods(false)
|
105
|
+
private_methods.each { |sym| mod.send(:undef_method, sym) }
|
106
|
+
|
107
|
+
mod.__exported_symbols.clear
|
108
|
+
end
|
109
|
+
|
110
|
+
# Error message to be displayed when trying to set a singleton value as
|
111
|
+
# default export
|
112
|
+
DEFAULT_VALUE_ERROR_MSG =
|
113
|
+
'Default export cannot be boolean, numeric, or symbol'
|
114
|
+
|
115
|
+
# Sets the default value for a module using export_default
|
116
|
+
# @param value [any] default value
|
117
|
+
# @param info [Hash] module info
|
118
|
+
# @param mod [Module] module
|
119
|
+
# @return [any] default value
|
120
|
+
def set_module_default_value(value, info, mod, caller)
|
121
|
+
value = transform_export_default_value(value, mod)
|
122
|
+
case value
|
123
|
+
when nil, true, false, Numeric, Symbol
|
124
|
+
raise(TypeError, DEFAULT_VALUE_ERROR_MSG, caller)
|
125
|
+
end
|
126
|
+
set_reload_info(value, mod.__module_info)
|
127
|
+
Modulation.loaded_modules[info[:location]] = value
|
128
|
+
end
|
129
|
+
|
130
|
+
# Adds methods for module_info and reloading to a value exported as default
|
131
|
+
# @param value [any] export_default value
|
132
|
+
# @param info [Hash] module info
|
133
|
+
# @return [void]
|
134
|
+
def set_reload_info(value, info)
|
135
|
+
value.define_singleton_method(:__module_info) { info }
|
136
|
+
value.define_singleton_method(:__reload!) do
|
137
|
+
Modulation::Builder.make(info)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Implements main Modulation functionality
|
4
|
+
module Modulation
|
5
|
+
require_relative './paths'
|
6
|
+
require_relative './builder'
|
7
|
+
require_relative './module_mixin'
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# @return [Hash] hash of loaded modules, mapping absolute paths to modules
|
12
|
+
attr_reader :loaded_modules
|
13
|
+
|
14
|
+
# @return [Module] currently loaded top-level module
|
15
|
+
attr_accessor :top_level_module
|
16
|
+
|
17
|
+
# Resets the loaded modules hash
|
18
|
+
def reset!
|
19
|
+
@loaded_modules = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Show full backtrace for errors occuring while loading a module. Normally
|
23
|
+
# Modulation will remove stack frames occurring inside the modulation.rb code
|
24
|
+
# in order to make backtraces more readable when debugging.
|
25
|
+
def full_backtrace!
|
26
|
+
@full_backtrace = true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Imports a module from a file
|
30
|
+
# If the module is already loaded, returns the loaded module.
|
31
|
+
# @param path [String] unqualified file name
|
32
|
+
# @param caller_location [String] caller location
|
33
|
+
# @return [Module] loaded module object
|
34
|
+
def import(path, caller_location = caller(1..1).first)
|
35
|
+
path = Paths.absolute_path(path, caller_location)
|
36
|
+
@loaded_modules[path] || create_module_from_file(path)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a new module from a source file
|
40
|
+
# @param path [String] source file name
|
41
|
+
# @return [Module] module
|
42
|
+
def create_module_from_file(path)
|
43
|
+
Builder.make(location: path)
|
44
|
+
rescue StandardError => e
|
45
|
+
@full_backtrace ? raise : raise_with_clean_backtrace(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
# (Re-)raises an error, filtering its backtrace to remove stack frames
|
49
|
+
# occuring in Modulation code
|
50
|
+
# @param error [Error] raised error
|
51
|
+
# @return [void]
|
52
|
+
def raise_with_clean_backtrace(error)
|
53
|
+
backtrace = error.backtrace.reject { |l| l.include?(__FILE__) }
|
54
|
+
raise(error, error.message, backtrace)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reloads the given module from its source file
|
58
|
+
# @param mod [Module, String] module to reload
|
59
|
+
# @return [Module] module
|
60
|
+
def reload(mod)
|
61
|
+
if mod.is_a?(String)
|
62
|
+
path = mod
|
63
|
+
mod = @loaded_modules[File.expand_path(mod)]
|
64
|
+
raise "No module loaded from #{path}" unless mod
|
65
|
+
end
|
66
|
+
|
67
|
+
Builder.cleanup_module(mod)
|
68
|
+
Builder.reload_module_code(mod)
|
69
|
+
|
70
|
+
mod.tap { Builder.set_exported_symbols(mod, mod.__exported_symbols, true) }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps the given path to the given mock module, restoring the previously
|
74
|
+
# loaded module (if any) after calling the given block
|
75
|
+
# @param path [String] module path
|
76
|
+
# @param mod [Module] module
|
77
|
+
# @param caller_location [String] caller location
|
78
|
+
# @return [void]
|
79
|
+
def mock(path, mod, caller_location = caller(1..1).first)
|
80
|
+
path = Paths.absolute_path(path, caller_location)
|
81
|
+
old_module = @loaded_modules[path]
|
82
|
+
@loaded_modules[path] = mod
|
83
|
+
yield if block_given?
|
84
|
+
ensure
|
85
|
+
@loaded_modules[path] = old_module if block_given?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Modulation.reset!
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Kernel extensions
|
4
|
+
module Kernel
|
5
|
+
# Returns an encapsulated imported module.
|
6
|
+
# @param path [String] module file name
|
7
|
+
# @param caller_location [String] caller location
|
8
|
+
# @return [Class] module facade
|
9
|
+
def import(path, caller_location = caller(1..1).first)
|
10
|
+
Modulation.import(path, caller_location)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Module extensions
|
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
|
+
|
30
|
+
# Extends the receiver with exported methods from the given file name
|
31
|
+
# @param path [String] module filename
|
32
|
+
# @return [void]
|
33
|
+
def extend_from(path)
|
34
|
+
mod = import(path, caller(1..1).first)
|
35
|
+
mod.instance_methods(false).each do |sym|
|
36
|
+
self.class.send(:define_method, sym, mod.method(sym).to_proc)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Includes exported methods from the given file name in the receiver
|
41
|
+
# The module's methods will be available as instance methods
|
42
|
+
# @param path [String] module filename
|
43
|
+
# @return [void]
|
44
|
+
def include_from(path)
|
45
|
+
mod = import(path, caller(1..1).first)
|
46
|
+
mod.instance_methods(false).each do |sym|
|
47
|
+
send(:define_method, sym, mod.method(sym).to_proc)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Modulation
|
4
|
+
# Extension methods for loaded modules
|
5
|
+
module ModuleMixin
|
6
|
+
# read and write module information
|
7
|
+
attr_accessor :__module_info
|
8
|
+
|
9
|
+
# Adds given symbols to the exported_symbols array
|
10
|
+
# @param symbols [Array] array of symbols
|
11
|
+
# @return [void]
|
12
|
+
def export(*symbols)
|
13
|
+
symbols = symbols.first if symbols.first.is_a?(Array)
|
14
|
+
__exported_symbols.concat(symbols)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets a module's value, so when imported it will represent the given value,
|
18
|
+
# instead of a module facade
|
19
|
+
# @param value [Symbol, any] symbol or value
|
20
|
+
# @return [void]
|
21
|
+
def export_default(value)
|
22
|
+
@__export_default_block&.call(value: value, caller: caller)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a text representation of the module for inspection
|
26
|
+
# @return [String] module string representation
|
27
|
+
def inspect
|
28
|
+
module_name = name || 'Module'
|
29
|
+
if __module_info[:location]
|
30
|
+
"#{module_name}:#{__module_info[:location]}"
|
31
|
+
else
|
32
|
+
module_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets export_default block, used for setting the returned module object to
|
37
|
+
# a class or constant
|
38
|
+
# @param block [Proc] default export block
|
39
|
+
# @return [void]
|
40
|
+
def __export_default_block=(block)
|
41
|
+
@__export_default_block = block
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reload module
|
45
|
+
# @return [Module] module
|
46
|
+
def __reload!
|
47
|
+
Modulation.reload(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Defers exporting of symbols for a namespace (nested module), to be
|
51
|
+
# performed after the entire module has been loaded
|
52
|
+
# @param namespace [Module] namespace module
|
53
|
+
# @param symbols [Array] array of symbols
|
54
|
+
# @return [void]
|
55
|
+
def __defer_namespace_export(namespace, symbols)
|
56
|
+
@__namespace_exports ||= Hash.new { |h, k| h[k] = [] }
|
57
|
+
@__namespace_exports[namespace].concat(symbols)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Performs exporting of symbols for all namespaces defined in the module,
|
61
|
+
# marking unexported methods and constants as private
|
62
|
+
# @return [void]
|
63
|
+
def __perform_deferred_namespace_exports
|
64
|
+
return unless @__namespace_exports
|
65
|
+
|
66
|
+
@__namespace_exports.each do |m, symbols|
|
67
|
+
Builder.set_exported_symbols(m, symbols)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns exported_symbols array
|
72
|
+
# @return [Array] array of exported symbols
|
73
|
+
def __exported_symbols
|
74
|
+
@__exported_symbols ||= []
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Modulation
|
4
|
+
# Implements methods for expanding relative or incomplete module file names
|
5
|
+
module Paths
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Regexp for extracting filename from caller reference
|
9
|
+
CALLER_FILE_REGEXP = /^([^\:]+)\:/
|
10
|
+
|
11
|
+
# Resolves the absolute path to the provided reference. If the file is not
|
12
|
+
# found, will try to resolve to a gem
|
13
|
+
# @param path [String] unqualified file name
|
14
|
+
# @param caller_location [String] caller location
|
15
|
+
# @return [String] absolute file name
|
16
|
+
def absolute_path(path, caller_location = caller(1..1).first)
|
17
|
+
orig_path = path
|
18
|
+
caller_file = caller_location[CALLER_FILE_REGEXP, 1]
|
19
|
+
raise 'Could not expand path' unless caller_file
|
20
|
+
|
21
|
+
path = File.expand_path(path, File.dirname(caller_file))
|
22
|
+
check_path(path) || lookup_gem(orig_path) ||
|
23
|
+
(raise "Module not found: #{path}")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks that the given path references an existing file, adding the .rb
|
27
|
+
# extension if needed
|
28
|
+
# @param path [String] absolute file path (with/without .rb extension)
|
29
|
+
# @return [String, nil] path of file or nil if not found
|
30
|
+
def check_path(path)
|
31
|
+
if File.file?("#{path}.rb")
|
32
|
+
path + '.rb'
|
33
|
+
elsif File.file?(path)
|
34
|
+
path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Resolves the provided file name into a gem. If no gem is found, returns
|
39
|
+
# nil
|
40
|
+
# @param name [String] gem name
|
41
|
+
# @return [String] absolute path to gem main source file
|
42
|
+
def lookup_gem(name)
|
43
|
+
spec = Gem::Specification.find_by_name(name)
|
44
|
+
unless spec.dependencies.map(&:name).include?('modulation')
|
45
|
+
raise NameError, 'Cannot import gem not based on modulation'
|
46
|
+
end
|
47
|
+
path = File.join(spec.full_require_paths, "#{name}.rb")
|
48
|
+
File.file?(path) ? path : nil
|
49
|
+
rescue Gem::MissingSpecError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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.
|
4
|
+
version: '0.11'
|
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-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: "Modulation provides an better way to organize Ruby code. Modulation
|
14
14
|
lets you \nexplicitly import and export declarations in order to better control
|
@@ -22,7 +22,12 @@ extra_rdoc_files:
|
|
22
22
|
files:
|
23
23
|
- README.md
|
24
24
|
- lib/modulation.rb
|
25
|
+
- lib/modulation/builder.rb
|
26
|
+
- lib/modulation/core.rb
|
27
|
+
- lib/modulation/ext.rb
|
25
28
|
- lib/modulation/gem.rb
|
29
|
+
- lib/modulation/module_mixin.rb
|
30
|
+
- lib/modulation/paths.rb
|
26
31
|
homepage: http://github.com/ciconia/modulation
|
27
32
|
licenses:
|
28
33
|
- MIT
|