modulation 0.32 → 0.33

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 598679c69845dd516cf7f19ff4ae9f60e254bf488abc1460cd08b121714b4cba
4
- data.tar.gz: 6b962c6f2cea4a300918eea8ff0d142999e01a32d64e7e8d814d5d15cbfb0984
3
+ metadata.gz: 66ef0e0890aa7cba8f462f22cfc1ebbe81ab4cdc48b3ad9733958dc002370d85
4
+ data.tar.gz: aeda0965bda28cd93eb2b318f8cacc24ea342e49084230cf0cdb522d05f1182d
5
5
  SHA512:
6
- metadata.gz: 1d8618b49710007df4312cfaefdaacc6291a7ca926945dae9378a6cb77efb0cd54947db9fe1cd84cfc8e39e9295aa22ed12db34be953f6df8eea0a072b32e596
7
- data.tar.gz: ed6a9627b2e6dd89e0c3751b59362bf5b78dffb334934028aa2a8895922b64b80d79101a35f9e0ad7882fd6515d71bae87f3d1560c2f8a5d8cbe9ee856e460a2
6
+ metadata.gz: 12ea537e1662feb88a7ae35cf46a6b23e8970f4255088b20c32962792d0cc4bff4cbd611a1eadc431feb81cb768191917dc6a7eb19e2d9bc75c50ecb50e8caf4
7
+ data.tar.gz: 1a6b08a109333d8a4ef4513e1607ceb84f177d4287f7114699f9d1d48d7c29959d37b16205dc36eaf627a8418cd886bb6eed6a71782bad4cd8fe7adbf1e50826
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ 0.33 2019-10-02
2
+ ---------------
3
+
4
+ * Add backward compatibility with Ruby 2.4.x
5
+ * Add support for creating modules programmatically
6
+ * Fix use of tags in import_map, auto_import_map, include_from, extend_from
7
+
1
8
  0.32 2019-09-03
2
9
  ---------------
3
10
 
data/README.md CHANGED
@@ -38,6 +38,7 @@ a functional style, minimizing boilerplate code.
38
38
  - [Mocking of dependencies](#mocking-dependencies) for testing purposes.
39
39
  - Can be used to [write gems](#writing-gems-using-modulation).
40
40
  - [Dependency introspection](#dependency-introspection).
41
+ - Support for [creating modules programmatically](#programmatic-module-creation).
41
42
  - Easier [unit-testing](#unit-testing-modules) of private methods and
42
43
  constants.
43
44
  - Pack entire applications [into a single
@@ -361,6 +362,51 @@ end
361
362
  what_is = ::THE_MEANING_OF_LIFE
362
363
  ```
363
364
 
365
+ ### Programmatic module creation
366
+
367
+ In addition to loading modules from files, modules can be created dynamically at
368
+ runtime using `Modulation.create`. You can create modules by supplying a hash
369
+ prototype, a string or a block:
370
+
371
+ ```ruby
372
+ # Using a hash prototype
373
+ m = Modulation.create(
374
+ add: -> x, y { x + y },
375
+ mul: -> x, y { x * y }
376
+ )
377
+ m.add(2, 3)
378
+ m.mul(2, 3)
379
+
380
+ # Using a string
381
+ m = Modulation.create <<~RUBY
382
+ export :foo
383
+
384
+ def foo
385
+ :bar
386
+ end
387
+ RUBY
388
+
389
+ m.foo
390
+
391
+ # Using a block
392
+ m = Modulation.create do { |mod|
393
+ export :foo
394
+
395
+ def foo
396
+ :bar
397
+ end
398
+
399
+ class mod::BAZ
400
+ ...
401
+ end
402
+ }
403
+
404
+ m.foo
405
+ ```
406
+
407
+ The creation of a objects using a hash prototype is also available as a separate
408
+ gem called [eg](https://github.com/digital-fabric/eg/).
409
+
364
410
  ### Unit testing modules
365
411
 
366
412
  Methods and constants that are not exported can be tested using the `#__expose!`
@@ -55,7 +55,7 @@ module Modulation
55
55
  # @return [void]
56
56
  def load_module_code(mod, info)
57
57
  path = info[:location]
58
- mod.instance_eval(info[:source] || IO.read(path), path)
58
+ mod.instance_eval(info[:source] || IO.read(path), path || '(source)')
59
59
  end
60
60
 
61
61
  def finalize_module_exports(info, mod)
@@ -125,7 +125,7 @@ module Modulation
125
125
  def add_module_constants(mod, target, *symbols)
126
126
  exported = mod.__module_info[:exported_symbols]
127
127
  unless symbols.empty?
128
- symbols.select! { |s| s =~ /^[A-Z]/ }
128
+ symbols.select! { |s| s =~ Modulation::RE_CONST }
129
129
  exported = filter_exported_symbols(exported, symbols)
130
130
  end
131
131
  mod.singleton_class.constants(false).each do |sym|
@@ -150,7 +150,7 @@ module Modulation
150
150
  # file and a caller location
151
151
  # @return [void]
152
152
  def define_auto_import_const_missing_method(receiver, auto_import_hash)
153
- receiver.singleton_class.define_method(:const_missing) do |sym|
153
+ receiver.singleton_class.send(:define_method, :const_missing) do |sym|
154
154
  (path, caller_location) = auto_import_hash[sym]
155
155
  path ? const_set(sym, import(path, caller_location)) : super(sym)
156
156
  end
@@ -4,6 +4,7 @@
4
4
  module Modulation
5
5
  require_relative './paths'
6
6
  require_relative './builder'
7
+ require_relative './creator'
7
8
  require_relative './module_mixin'
8
9
 
9
10
  RE_CONST = /^[A-Z]/.freeze
@@ -153,6 +154,19 @@ module Modulation
153
154
  def add_tags(tags)
154
155
  Paths.add_tags(tags, caller(CALLER_RANGE).first)
155
156
  end
157
+
158
+ def create(arg = nil, &block)
159
+ return Creator.from_block(block) if block
160
+
161
+ case arg
162
+ when Hash
163
+ Creator.from_hash(arg)
164
+ when String
165
+ Creator.from_string(arg)
166
+ else
167
+ raise 'Invalid argument'
168
+ end
169
+ end
156
170
  end
157
171
  end
158
172
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulation
4
+ # Implements programmtically created modules
5
+ module Creator
6
+ RE_CONST = /^[A-Z]/.freeze
7
+ RE_ATTR = /^@(.+)$/.freeze
8
+
9
+ class << self
10
+ # Creates a module from a prototype hash
11
+ # @param hash [Hash] prototype hash
12
+ # @return [Module] created object
13
+ def from_hash(hash)
14
+ Module.new.tap do |m|
15
+ s = m.singleton_class
16
+ hash.each do |k, v|
17
+ if k =~ RE_CONST
18
+ m.const_set(k, v)
19
+ elsif k =~ RE_ATTR
20
+ m.instance_variable_set(k, v)
21
+ elsif v.respond_to?(:to_proc)
22
+ s.send(:define_method, k) { |*args| instance_exec(*args, &v) }
23
+ else
24
+ s.send(:define_method, k) { v }
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def from_string(str)
31
+ m = Builder.make(source: str)
32
+ end
33
+
34
+ def from_block(block)
35
+ Module.new.tap { |m| m.instance_eval(&block) }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -14,8 +14,7 @@ module Modulation
14
14
  def transform_export_default_value(value, mod)
15
15
  return value unless value.is_a?(Symbol)
16
16
 
17
- case value
18
- when /^[A-Z]/
17
+ if value =~ Modulation::RE_CONST
19
18
  get_module_constant(mod, value)
20
19
  else
21
20
  get_module_method(mod, value)
@@ -15,19 +15,21 @@ module Modulation
15
15
  # @return [Array] list of receiver methods
16
16
  def create_forwarding_methods(mod, receiver)
17
17
  receiver_methods(receiver).each do |m|
18
- mod.singleton_class.define_method(m) do |*args, &block|
18
+ mod.singleton_class.send(:define_method, m) do |*args, &block|
19
19
  receiver.send(m, *args, &block)
20
20
  end
21
21
  end
22
22
  end
23
23
 
24
+ RE_RESERVED_METHOD = /^__/.freeze
25
+
24
26
  def receiver_methods(receiver)
25
27
  ignored_klass = case receiver
26
28
  when Class, Module then receiver.class
27
29
  else Object
28
30
  end
29
-
30
- methods = receiver.methods.select { |m| m !~ /^__/ }
31
+
32
+ methods = receiver.methods.reject { |m| m =~ RE_RESERVED_METHOD }
31
33
  methods - ignored_klass.instance_methods
32
34
  end
33
35
 
@@ -35,11 +35,11 @@ module Modulation
35
35
  end
36
36
 
37
37
  def export_from_receiver(mod, name)
38
- if name =~ Modulation::RE_CONST
39
- ExportFromReceiver.from_const(mod, name)
40
- else
38
+ if name !~ Modulation::RE_CONST
41
39
  raise 'export_from_receiver expects a const reference'
42
40
  end
41
+
42
+ ExportFromReceiver.from_const(mod, name)
43
43
  end
44
44
 
45
45
  def validate_exported_symbols(mod, symbols)
@@ -86,7 +86,7 @@ module Modulation
86
86
  singleton.alias_method(key, value)
87
87
  else
88
88
  value_proc = value.is_a?(Proc) ? value : proc { value }
89
- singleton.define_method(key, &value_proc)
89
+ singleton.send(:define_method, key, &value_proc)
90
90
  end
91
91
  end
92
92
 
@@ -37,10 +37,12 @@ module Modulation
37
37
  }
38
38
  end
39
39
 
40
+ EXPORT_DEFAULT_ERROR_MSG = <<~MSG
41
+ Cannot mix calls to export_from_receiver and export_default in same module
42
+ MSG
43
+
40
44
  def export_from_receiver(name)
41
- if @__export_default_info
42
- raise 'Cannot mix calls to export_from_receiver and export_default in same module'
43
- end
45
+ raise EXPORT_DEFAULT_ERROR_MSG if @__export_default_info
44
46
 
45
47
  @__export_directives ||= []
46
48
  @__export_directives << {
@@ -5,7 +5,8 @@ module Modulation
5
5
  module Paths
6
6
  class << self
7
7
  def process(path, caller_location)
8
- tagged_path(path) || absolute_path(path, caller_location) ||
8
+ path = expand_tag(path)
9
+ absolute_path(path, caller_location) ||
9
10
  lookup_gem_path(path)
10
11
  end
11
12
 
@@ -23,17 +24,13 @@ module Modulation
23
24
  end
24
25
  end
25
26
 
26
- def tagged_path(path)
27
- return nil unless @tags
27
+ RE_TAG = /^@([^\/]+)/.freeze
28
28
 
29
- _, tag, path = path.match(TAGGED_REGEXP).to_a
30
- return nil unless tag
31
-
32
- base_path = @tags[tag]
33
- return nil unless base_path
34
-
35
- path = path ? File.join(base_path, path) : base_path
36
- check_path(path)
29
+ def expand_tag(path)
30
+ path.sub RE_TAG do
31
+ tag = Regexp.last_match[1]
32
+ (@tags && @tags[tag]) || (raise "Invalid tag #{tag}")
33
+ end
37
34
  end
38
35
 
39
36
  # Resolves the absolute path to the provided reference. If the file is not
@@ -54,6 +51,7 @@ module Modulation
54
51
  # @param caller_location [String] caller location
55
52
  # @return [String] absolute directory path
56
53
  def absolute_dir_path(path, caller_location)
54
+ path = expand_tag(path)
57
55
  caller_file = caller_location[CALLER_FILE_REGEXP, 1]
58
56
  return nil unless caller_file
59
57
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- VERSION = '0.32'
4
+ VERSION = '0.33'
5
5
  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.32'
4
+ version: '0.33'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-03 00:00:00.000000000 Z
11
+ date: 2019-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -56,7 +56,7 @@ files:
56
56
  - lib/modulation.rb
57
57
  - lib/modulation/builder.rb
58
58
  - lib/modulation/core.rb
59
- - lib/modulation/default_export.rb
59
+ - lib/modulation/creator.rb
60
60
  - lib/modulation/export_default.rb
61
61
  - lib/modulation/export_from_receiver.rb
62
62
  - lib/modulation/exports.rb
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Modulation
4
- # default export functionality
5
- module ExportDefault
6
- class << self
7
- # Returns exported value for a default export
8
- # If the given value is a symbol, returns the value of the corresponding
9
- # constant. If the symbol refers to a method, returns a proc enveloping
10
- # the method. Raises if symbol refers to non-existent constant or method.
11
- # @param value [any] export_default value
12
- # @param mod [Module] module
13
- # @return [any] exported value
14
- def transform_export_default_value(value, mod)
15
- return value unless value.is_a?(Symbol)
16
-
17
- case value
18
- when /^[A-Z]/
19
- get_module_constant(mod, value)
20
- else
21
- get_module_method(mod, value)
22
- end
23
- end
24
-
25
- def get_module_constant(mod, value)
26
- unless mod.singleton_class.constants(true).include?(value)
27
- Exports.raise_exported_symbol_not_found_error(value, :const)
28
- end
29
-
30
- mod.singleton_class.const_get(value)
31
- end
32
-
33
- def get_module_method(mod, value)
34
- unless mod.singleton_class.instance_methods(true).include?(value)
35
- Exports.raise_exported_symbol_not_found_error(value, :method)
36
- end
37
-
38
- proc { |*args, &block| mod.send(value, *args, &block) }
39
- end
40
-
41
- # Error message to be displayed when trying to set a singleton value as
42
- # default export
43
- DEFAULT_VALUE_ERROR_MSG =
44
- 'Default export cannot be boolean, numeric, or symbol'
45
-
46
- # Sets the default value for a module using export_default
47
- # @param value [any] default value
48
- # @param info [Hash] module info
49
- # @param mod [Module] module
50
- # @return [any] default value
51
- def set_module_default_value(value, info, mod, caller)
52
- value = transform_export_default_value(value, mod)
53
- case value
54
- when nil, true, false, Numeric, Symbol
55
- raise(TypeError, DEFAULT_VALUE_ERROR_MSG, caller)
56
- end
57
- set_reload_info(value, mod.__module_info)
58
- Modulation.loaded_modules[info[:location]] = value
59
- end
60
-
61
- # Adds methods for module_info and reloading to a value exported as
62
- # default
63
- # @param value [any] export_default value
64
- # @param info [Hash] module info
65
- # @return [void]
66
- def set_reload_info(value, info)
67
- value.define_singleton_method(:__module_info) { info }
68
- value.define_singleton_method(:__reload!) do
69
- Modulation::Builder.make(info)
70
- end
71
- end
72
- end
73
- end
74
- end