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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/modulation.rb +2 -356
  3. data/lib/modulation/gem.rb +10 -4
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43e1c7012351eb39206b38cdddb767f3c54c5ceea061fb386840bc05cb28352a
4
- data.tar.gz: 3981002e94faed9fc855469677b693c77003e914d55653f2f15ee61776175bc3
3
+ metadata.gz: 59463e46fe92ef35c671e713faccfef2cc66434cc50d82c0098e9b585f9716e6
4
+ data.tar.gz: 35f0cdf9e58f5d4482008d768ce9b70ab04209bc311d132bdca5aadcb7afabc1
5
5
  SHA512:
6
- metadata.gz: d5139f0f6a1eaf734c0a9fbfb5941832a129cb3cdde305e5f893c27ebcb630d2ff77740ce587809d913ce56181179dd4159f59165a19c58b23b8c5c109c0efab
7
- data.tar.gz: 2441098da64cbdd408b2c5e8c6083de4e90d702ae68a3433ca3dd54f4cebaec8d859d30e5c58a7d9f04a6c0dc44d76ee7644c74b51692139729837b1ad9251ba
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
- # Kernel extensions
5
- module Kernel
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'
@@ -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
- def export_default(v); end
16
- end
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.9.1
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-16 00:00:00.000000000 Z
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