modulation 0.9.1 → 0.10
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/lib/modulation.rb +2 -356
- data/lib/modulation/gem.rb +10 -4
- 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: 59463e46fe92ef35c671e713faccfef2cc66434cc50d82c0098e9b585f9716e6
|
4
|
+
data.tar.gz: 35f0cdf9e58f5d4482008d768ce9b70ab04209bc311d132bdca5aadcb7afabc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f5e19c1025c591fe4db182222092341018eed467b0fa8cf1ecdf79d51da554ab934b105505c62c0e57c18a69cfa566a92df1d34db6f7bf76eac4e647e4d5e95
|
7
|
+
data.tar.gz: d3bd42db5a9d6bf57121b433e78a1abe3121f77f25dea0c517226ff95e3c14e1b83b667877bf150df172966676578ea956bb05577c02b3b6597003f8ff42c328
|
data/lib/modulation.rb
CHANGED
@@ -1,358 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'fileutils'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
# Returns an encapsulated imported module.
|
7
|
-
# @param fn [String] module file name
|
8
|
-
# @param caller_location [String] caller location
|
9
|
-
# @return [Class] module facade
|
10
|
-
def import(fn, caller_location = caller.first)
|
11
|
-
Modulation.import_module(fn, caller_location)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Module extensions
|
16
|
-
class Module
|
17
|
-
# Exports symbols from a namespace module declared inside an importable
|
18
|
-
# module. Exporting the actual symbols is deferred until the entire code
|
19
|
-
# has been loaded
|
20
|
-
# @param symbols [Array] array of symbols
|
21
|
-
# @return [void]
|
22
|
-
def export(*symbols)
|
23
|
-
unless Modulation.__top_level_module__
|
24
|
-
raise NameError, "Can't export symbols outside of an imported module"
|
25
|
-
end
|
26
|
-
|
27
|
-
extend self
|
28
|
-
Modulation.__top_level_module__.__defer_namespace_export(self, symbols)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Extends the receiver with exported methods from the given file name
|
32
|
-
# @param fn [String] module filename
|
33
|
-
# @return [void]
|
34
|
-
def extend_from(fn)
|
35
|
-
mod = import(fn, caller.first)
|
36
|
-
mod.instance_methods(false).each do |sym|
|
37
|
-
self.class.send(:define_method, sym, mod.method(sym).to_proc)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Includes exported methods from the given file name in the receiver
|
42
|
-
# The module's methods will be available as instance methods
|
43
|
-
# @param fn [String] module filename
|
44
|
-
# @return [void]
|
45
|
-
def include_from(fn)
|
46
|
-
mod = import(fn, caller.first)
|
47
|
-
mod.instance_methods(false).each do |sym|
|
48
|
-
send(:define_method, sym, mod.method(sym).to_proc)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
class Modulation
|
54
|
-
# Hash mapping fully-qualified paths to loaded modules
|
55
|
-
@@loaded_modules = {}
|
56
|
-
|
57
|
-
# Reference to currently loaded top-level module, used for correctly
|
58
|
-
# exporting symbols from namespaces
|
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)
|
64
|
-
@@full_backtrace = false
|
65
|
-
|
66
|
-
public
|
67
|
-
|
68
|
-
# Show full backtrace for errors occuring while loading a module. Normally
|
69
|
-
# Modulation will remove stack frames occurring inside the modulation.rb code
|
70
|
-
# in order to make backtraces more readable when debugging.
|
71
|
-
def self.full_backtrace!
|
72
|
-
@@full_backtrace = true
|
73
|
-
end
|
74
|
-
|
75
|
-
# Imports a module from a file
|
76
|
-
# If the module is already loaded, returns the loaded module.
|
77
|
-
# @param fn [String] unqualified file name
|
78
|
-
# @param caller_location [String] caller location
|
79
|
-
# @return [Module] loaded module object
|
80
|
-
def self.import_module(fn, caller_location = caller.first)
|
81
|
-
fn = module_absolute_path(fn, caller_location)
|
82
|
-
@@loaded_modules[fn] || create_module_from_file(fn)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Returns the currently loaded top level module
|
86
|
-
# @return [Module] currently loaded module
|
87
|
-
def self.__top_level_module__
|
88
|
-
@@top_level_module
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
# Resolves the absolute path to the provided reference. If the file is not
|
94
|
-
# found, will try to resolve to a gem
|
95
|
-
# @param fn [String] unqualified file name
|
96
|
-
# @param caller_location [String] caller location
|
97
|
-
# @return [String] absolute file name
|
98
|
-
def self.module_absolute_path(fn, caller_location = caller.first)
|
99
|
-
orig_fn = fn
|
100
|
-
caller_file = (caller_location =~ /^([^\:]+)\:/) ?
|
101
|
-
$1 : (raise "Could not expand path")
|
102
|
-
fn = File.expand_path(fn, File.dirname(caller_file))
|
103
|
-
if File.file?("#{fn}.rb")
|
104
|
-
fn + '.rb'
|
105
|
-
else
|
106
|
-
if File.file?(fn)
|
107
|
-
return fn
|
108
|
-
else
|
109
|
-
lookup_gem(orig_fn) || (raise "Module not found: #{fn}")
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Resolves the provided file name into a gem. If no gem is found, returns nil
|
115
|
-
# @param name [String] gem name
|
116
|
-
# @return [String] absolute path to gem main source file
|
117
|
-
def self.lookup_gem(name)
|
118
|
-
spec = Gem::Specification.find_by_name(name)
|
119
|
-
unless(spec.dependencies.map(&:name)).include?('modulation')
|
120
|
-
raise NameError, "Cannot import gem not based on modulation"
|
121
|
-
end
|
122
|
-
fn = File.join(spec.full_require_paths, "#{name}.rb")
|
123
|
-
File.file?(fn) ? fn : nil
|
124
|
-
rescue Gem::MissingSpecError
|
125
|
-
nil
|
126
|
-
end
|
127
|
-
|
128
|
-
# Creates a new module from a source file
|
129
|
-
# @param fn [String] source file name
|
130
|
-
# @return [Module] module
|
131
|
-
def self.create_module_from_file(fn)
|
132
|
-
make_module(location: fn)
|
133
|
-
rescue => e
|
134
|
-
@@full_backtrace ? raise : raise_with_clean_backtrace(e)
|
135
|
-
end
|
136
|
-
|
137
|
-
# (Re-)raises an error, filtering its backtrace to remove stack frames
|
138
|
-
# occuring in Modulation code
|
139
|
-
def self.raise_with_clean_backtrace(e)
|
140
|
-
backtrace = e.backtrace.reject {|l| l.include?(__FILE__)}
|
141
|
-
raise(e, e.message, backtrace)
|
142
|
-
end
|
143
|
-
|
144
|
-
# Loads a module from file or block, wrapping it in a module facade
|
145
|
-
# @param info [Hash] module info
|
146
|
-
# @param block [Proc] module block
|
147
|
-
# @return [Class] module facade
|
148
|
-
def self.make_module(info, &block)
|
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
|
155
|
-
@@loaded_modules[info[:location]] = m
|
156
|
-
m.__module_info = info
|
157
|
-
load_module_code(m, info, &block)
|
158
|
-
if default_value != :__no_default_value__
|
159
|
-
set_module_default_value(default_value, info, m, default_value_caller)
|
160
|
-
else
|
161
|
-
m.__perform_deferred_namespace_exports
|
162
|
-
set_exported_symbols(m, m.__exported_symbols)
|
163
|
-
m
|
164
|
-
end
|
165
|
-
end
|
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
|
-
|
194
|
-
# Returns exported value for a default export
|
195
|
-
# If the given value is a symbol, returns the value of the corresponding
|
196
|
-
# constant.
|
197
|
-
# @param value [any] export_default value
|
198
|
-
# @param mod [Module] module
|
199
|
-
# @return [any] exported value
|
200
|
-
def self.transform_export_default_value(value, mod)
|
201
|
-
if value.is_a?(Symbol) && (mod.const_defined?(value) rescue nil)
|
202
|
-
mod.const_get(value)
|
203
|
-
else
|
204
|
-
value
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
# Initializes a new module ready to evaluate a file module
|
209
|
-
# @note The given block is used to pass the value given to `export_default`
|
210
|
-
# @return [Module] new module
|
211
|
-
def self.initialize_module(&export_default_block)
|
212
|
-
Module.new.tap do |m|
|
213
|
-
m.extend(m)
|
214
|
-
m.extend(ModuleMethods)
|
215
|
-
m.__export_default_block = export_default_block
|
216
|
-
m.const_set(:MODULE, m)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# Loads a source file or a block into the given module
|
221
|
-
# @param m [Module] module
|
222
|
-
# @param fn [String] source file path
|
223
|
-
# @return [void]
|
224
|
-
def self.load_module_code(m, info, &block)
|
225
|
-
old_top_level_module = @@top_level_module
|
226
|
-
@@top_level_module = m
|
227
|
-
if block
|
228
|
-
m.module_eval(&block)
|
229
|
-
else
|
230
|
-
fn = info[:location]
|
231
|
-
m.module_eval(IO.read(fn), fn)
|
232
|
-
end
|
233
|
-
ensure
|
234
|
-
@@top_level_module = old_top_level_module
|
235
|
-
end
|
236
|
-
|
237
|
-
# Sets exported_symbols ivar and marks all non-exported methods as private
|
238
|
-
# @param m [Module] module with exported symbols
|
239
|
-
# @param symbols [Array] array of exported symbols
|
240
|
-
# @return [void]
|
241
|
-
def self.set_exported_symbols(m, symbols)
|
242
|
-
# m.__exported_symbols = symbols
|
243
|
-
m.instance_methods.each do |sym|
|
244
|
-
next if symbols.include?(sym)
|
245
|
-
m.send(:private, sym)
|
246
|
-
end
|
247
|
-
m.constants.each do |sym|
|
248
|
-
next if sym == :MODULE || symbols.include?(sym)
|
249
|
-
m.send(:private_constant, sym)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
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
|
286
|
-
module ModuleMethods
|
287
|
-
# read and write module information
|
288
|
-
attr_accessor :__module_info
|
289
|
-
|
290
|
-
# Adds given symbols to the exported_symbols array
|
291
|
-
# @param symbols [Array] array of symbols
|
292
|
-
# @return [void]
|
293
|
-
def export(*symbols)
|
294
|
-
symbols = symbols.first if Array === symbols.first
|
295
|
-
__exported_symbols.concat(symbols)
|
296
|
-
end
|
297
|
-
|
298
|
-
# Sets a module's value, so when imported it will represent the given value,
|
299
|
-
# instead of a module facade
|
300
|
-
# @param v [Symbol, any] symbol or value
|
301
|
-
# @return [void]
|
302
|
-
def export_default(v)
|
303
|
-
@__export_default_block.call(v, caller) if @__export_default_block
|
304
|
-
end
|
305
|
-
|
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
|
315
|
-
end
|
316
|
-
|
317
|
-
# Sets export_default block, used for setting the returned module object to
|
318
|
-
# a class or constant
|
319
|
-
# @param block [Proc] default export block
|
320
|
-
# @return [void]
|
321
|
-
def __export_default_block=(block)
|
322
|
-
@__export_default_block = block
|
323
|
-
end
|
324
|
-
|
325
|
-
# Reload module
|
326
|
-
# @return [Module] module
|
327
|
-
def __reload!
|
328
|
-
Modulation.reload(self)
|
329
|
-
end
|
330
|
-
|
331
|
-
# Defers exporting of symbols for a namespace (nested module), to be
|
332
|
-
# performed after the entire module has been loaded
|
333
|
-
# @param namespace [Module] namespace module
|
334
|
-
# @param symbols [Array] array of symbols
|
335
|
-
# @return [void]
|
336
|
-
def __defer_namespace_export(namespace, symbols)
|
337
|
-
@__namespace_exports ||= Hash.new {|h, k| h[k] = []}
|
338
|
-
@__namespace_exports[namespace].concat(symbols)
|
339
|
-
end
|
340
|
-
|
341
|
-
# Performs exporting of symbols for all namespaces defined in the module,
|
342
|
-
# marking unexported methods and constants as private
|
343
|
-
# @return [void]
|
344
|
-
def __perform_deferred_namespace_exports
|
345
|
-
return unless @__namespace_exports
|
346
|
-
|
347
|
-
@__namespace_exports.each do |m, symbols|
|
348
|
-
Modulation.set_exported_symbols(m, symbols)
|
349
|
-
end
|
350
|
-
end
|
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
|
3
|
+
require_relative 'modulation/ext'
|
4
|
+
require_relative 'modulation/core'
|
data/lib/modulation/gem.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative('../modulation')
|
2
4
|
|
3
5
|
# Kernel extensions - mock up the Modulation API with nop methods, so
|
4
6
|
# requiring a gem would work. Sample usage:
|
5
|
-
#
|
7
|
+
#
|
6
8
|
# require 'modulation/gem'
|
7
9
|
# export_default :MyGem
|
8
|
-
#
|
10
|
+
#
|
9
11
|
# module MyGem
|
10
12
|
# MyClass = import('my_class')
|
11
13
|
# MyOtherClass = import('my_other_class')
|
12
14
|
# end
|
13
15
|
module Kernel
|
16
|
+
# Stub for export method, does nothing in the context of a required gem
|
14
17
|
def export(*args); end
|
15
|
-
|
16
|
-
|
18
|
+
|
19
|
+
# Stub for export_default method, does nothing in the context of a required
|
20
|
+
# gem
|
21
|
+
def export_default(value); end
|
22
|
+
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.10'
|
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-19 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
|