modulation 0.9.1 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
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