modulation 0.27 → 0.28

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: 04f2ab2cc42f117c9c8fb1b306b2239fbe9bca4a96f890e693bb92fb6c900318
4
- data.tar.gz: 9ead222ae4fc275174b3e5d62a3b3b30e094761e5801de7b3a9fe21a346174be
3
+ metadata.gz: 630df5ec1eef61abafa86781e8f73c892dcf01c738bc02b695e767586208c728
4
+ data.tar.gz: 4f98dbd921afc4c3e0f33d82d19083e56cfacffee7192bf0ad621e638b5cf6a0
5
5
  SHA512:
6
- metadata.gz: 2bd5727f5ca807fcc6238b0679234ba5df79102eaa6153a5acb8db8eb66054a1d9bdb2f8250292cad4784101c1c58b343766b19a3a07be013dfb67ce13b7c175
7
- data.tar.gz: 63a1bc298932c0b1d4bbf804099df2940755b23358f4e4c72023c61054d03102c97f4138324570821685eaeb82bd0101146c9a4d469f245d8af093b906a623bd
6
+ metadata.gz: 726ac2711660e422fac0caeeb41828d4fafea79c3ef6da7abb24ea0160990248b2c1830b4c69f278b2f798a0038bd9c2b361dc8152bd33f726bda4f67eb71fca
7
+ data.tar.gz: 4ccfd538a57c5a8183409e42e04d9a5df14f22068d2900b269841690fbec878473c1b52ed84e8e2ec1b87736a4f915463d829670731f80e732fe1dee8068cc26
data/CHANGELOG.md CHANGED
@@ -1,7 +1,13 @@
1
+ 0.28 2019-08-23
2
+ ---------------
3
+
4
+ * Implement tagged sources
5
+ * Improve application packer
6
+
1
7
  0.27 2019-08-21
2
8
  ---------------
3
9
 
4
- * Add initial packing functionality.
10
+ * Add initial packing functionality
5
11
 
6
12
  0.26 2019-08-20
7
13
  ---------------
data/README.md CHANGED
@@ -25,6 +25,28 @@ a functional style, minimizing boilerplate code.
25
25
  > Modulation, it is not intended as a comprehensive solution for using
26
26
  > third-party libraries.
27
27
 
28
+ ## Features
29
+
30
+ - Provides complete isolation of each module: constant definitions in one file
31
+ do not leak into another.
32
+ - Enforces explicit exporting and importing of methods, classes, modules and
33
+ constants.
34
+ - Supports circular dependencies.
35
+ - Supports [default exports](#default-exports) for modules exporting a single
36
+ class or value.
37
+ - Modules can be [lazy loaded](#lazy-loading) to improve start up time and
38
+ memory consumption.
39
+ - Modules can be [reloaded](#reloading-modules) at runtime without breaking your
40
+ code in wierd ways.
41
+ - Allows [mocking of dependencies](#mocking-dependencies) for testing purposes.
42
+ - Can be used to [write gems](#writing-gems-using-modulation).
43
+ - Module dependencies can be [introspected](#dependency-introspection).
44
+ - Facilitates [unit-testing](#unit-testing-modules) of private methods and
45
+ constants.
46
+ - Can load all source files in directory [at once](#importing-all-source-files-in-a-directory).
47
+ - Packs entire applications [into a single
48
+ file](#packing-applications-with-modulation).
49
+
28
50
  ## Rationale
29
51
 
30
52
  You're probably asking yourself "what the ****?" , but when your Ruby app grows
@@ -65,33 +87,12 @@ are hidden unless explicitly exported, and the global namespace remains
65
87
  clutter-free. All dependencies between source files are explicit, visible, and
66
88
  easy to understand.
67
89
 
68
- ## Features
69
-
70
- - Provides complete isolation of each module: constant definitions in one file
71
- do not leak into another.
72
- - Enforces explicit exporting and importing of methods, classes, modules and
73
- constants.
74
- - Supports circular dependencies.
75
- - Supports [default exports](#default-exports) for modules exporting a single
76
- class or value.
77
- - Modules can be [lazy loaded](#lazy-loading) to improve start up time and
78
- memory consumption.
79
- - Modules can be [reloaded](#reloading-modules) at runtime without breaking your
80
- code in wierd ways.
81
- - Allows [mocking of dependencies](#mocking-dependencies) for testing purposes.
82
- - Can be used to [write gems](#writing-gems-using-modulation).
83
- - Module dependencies can be [introspected](#dependency-introspection).
84
- - Facilitates [unit-testing](#unit-testing-modules) of private methods and
85
- constants.
86
- - Can load all source files in directory [at once](#importing-all-source-files-in-a-directory).
87
- - Pack entire applications [into a single file](#packing-applications-with-modulation).
88
-
89
90
  ## Installing Modulation
90
91
 
91
- You can install the Modulation as a gem, or add it in your `Gemfile`:
92
+ You can install the Modulation using `gem install`, or add it to your `Gemfile`:
92
93
 
93
- ```bash
94
- $ gem install modulation
94
+ ```ruby
95
+ gem 'modulation'
95
96
  ```
96
97
 
97
98
  ## Organizing your code with Modulation
@@ -198,8 +199,18 @@ User = import('./models')::User
198
199
  user = User.new(...)
199
200
  ```
200
201
 
201
- > **Note about paths**: module paths are always relative to the file
202
- > calling the `#import` method, just like `#require_relative`.
202
+ ### Using tags to designate common subdirectories
203
+
204
+ Normally, module paths are always relative to the file calling the `#import`
205
+ method, just like `#require_relative`. This can become a problem once you start
206
+ moving your source files around. In addition, in applications where your source
207
+ files are arranged in multiple directories, it can quickly become tedious to do
208
+ stuff like `Post = import('../models/post')`.
209
+
210
+ Modulation provides an alternative to relative paths in the form of tagged
211
+ sources. A tagged source is simply a path associated with a label. For example,
212
+ an application may tag `lib/models` simply as `@models`. Once tags are defined,
213
+ they can be used when importing files, e.g. `import('@models/post')`.
203
214
 
204
215
  ### Importing all source files in a directory
205
216
 
@@ -484,6 +495,31 @@ settings = import('settings')
484
495
  settings = settings.__reload!
485
496
  ```
486
497
 
498
+ ### Retaining state between reloads
499
+
500
+ Before a module is reloaded, all of its methods and constants are removed. In
501
+ some cases, a module might need to retain state across reloads. You can do this
502
+ by simply using instance variables:
503
+
504
+ ```ruby
505
+ export :value, :inc
506
+
507
+ @counter ||= 0
508
+
509
+ def value
510
+ @counter
511
+ end
512
+
513
+ def incr
514
+ @counter += 1
515
+ end
516
+ ```
517
+
518
+ Care must be taken not to reassign values outside of methods, as this will
519
+ overwrite any value retained in the instance variable. To assign initial values,
520
+ use the `||=` operator as in the example above. See also the
521
+ [reload example](examples/reload).
522
+
487
523
  ## Dependency introspection
488
524
 
489
525
  Modulation allows runtime introspection of dependencies between modules. You can
data/bin/mdl CHANGED
@@ -10,6 +10,10 @@ def self.run
10
10
  mod = import(File.expand_path(fn))
11
11
  mod.send(method) if method
12
12
  end
13
+ rescue StandardError => e
14
+ backtrace = e.backtrace.reject { |l| l =~ /^(#{Modulation::DIR})|(bin\/mdl)/ }
15
+ e.set_backtrace(backtrace)
16
+ raise e
13
17
  end
14
18
 
15
19
  def collect_deps(fn, paths)
@@ -31,16 +35,8 @@ def self.deps
31
35
  end
32
36
 
33
37
  def self.pack
34
- paths = []
35
- ARGV.each do |arg|
36
- fn, method = (arg =~ /^([^\:]+)\:(.+)$/) ? [$1, $2.to_sym] : [arg, :main]
37
- mod = import(File.expand_path(fn))
38
- paths << File.expand_path(fn)
39
- mod.__traverse_dependencies { |m| paths << m.__module_info[:location] }
40
- end
41
-
42
- require 'modulation/packing'
43
- STDOUT << Modulation::Packing.pack(paths, hide_filenames: true)
38
+ require 'modulation/packer'
39
+ STDOUT << Modulation::Packer.pack(ARGV, hide_filenames: true)
44
40
  end
45
41
 
46
42
  cmd = ARGV.shift
@@ -100,6 +100,63 @@ module Modulation
100
100
  mod.__exported_symbols.clear
101
101
  mod.__reset_dependencies
102
102
  end
103
+
104
+ # Adds all or part of a module's methods to a target object
105
+ # If no symbols are given, all methods are added
106
+ # @param mod [Module] imported module
107
+ # @param target [Object] object to add methods to
108
+ # @param symbols [Array<Symbol>] list of methods to add
109
+ # @return [void]
110
+ def add_module_methods(mod, target, *symbols)
111
+ methods = mod.singleton_class.instance_methods(false)
112
+ unless symbols.empty?
113
+ symbols.select! { |s| s =~ /^[a-z]/ }
114
+ methods = filter_exported_symbols(methods, symbols)
115
+ end
116
+ methods.each do |sym|
117
+ target.send(:define_method, sym, &mod.method(sym))
118
+ end
119
+ end
120
+
121
+ # Adds all or part of a module's constants to a target object
122
+ # If no symbols are given, all constants are added
123
+ # @param mod [Module] imported module
124
+ # @param target [Object] object to add constants to
125
+ # @param symbols [Array<Symbol>] list of constants to add
126
+ # @return [void]
127
+ def add_module_constants(mod, target, *symbols)
128
+ exported = mod.__module_info[:exported_symbols]
129
+ unless symbols.empty?
130
+ symbols.select! { |s| s =~ /^[A-Z]/ }
131
+ exported = filter_exported_symbols(exported, symbols)
132
+ end
133
+ mod.singleton_class.constants(false).each do |sym|
134
+ next unless exported.include?(sym)
135
+
136
+ target.const_set(sym, mod.singleton_class.const_get(sym))
137
+ end
138
+ end
139
+
140
+ def filter_exported_symbols(exported, requested)
141
+ not_exported = requested - exported
142
+ unless not_exported.empty?
143
+ raise NameError, "symbol #{not_exported.first.inspect} not exported"
144
+ end
145
+
146
+ exported & requested
147
+ end
148
+
149
+ # Defines a const_missing method used for auto-importing on a given object
150
+ # @param receiver [Object] object to receive the const_missing method call
151
+ # @param auto_import_hash [Hash] a hash mapping constant names to a source
152
+ # file and a caller location
153
+ # @return [void]
154
+ def define_auto_import_const_missing_method(receiver, auto_import_hash)
155
+ receiver.singleton_class.define_method(:const_missing) do |sym|
156
+ (path, caller_location) = auto_import_hash[sym]
157
+ path ? const_set(sym, import(path, caller_location)) : super(sym)
158
+ end
159
+ end
103
160
  end
104
161
  end
105
162
  end
@@ -32,8 +32,7 @@ module Modulation
32
32
  # @param caller_location [String] caller location
33
33
  # @return [Module] loaded module object
34
34
  def import(path, caller_location = caller(1..1).first)
35
- abs_path = Paths.absolute_path(path, caller_location) ||
36
- Paths.lookup_gem_path(path)
35
+ abs_path = Paths.process(path, caller_location)
37
36
 
38
37
  case abs_path
39
38
  when String
@@ -71,63 +70,6 @@ module Modulation
71
70
  end
72
71
  end
73
72
 
74
- # Adds all or part of a module's methods to a target object
75
- # If no symbols are given, all methods are added
76
- # @param mod [Module] imported module
77
- # @param target [Object] object to add methods to
78
- # @param symbols [Array<Symbol>] list of methods to add
79
- # @return [void]
80
- def add_module_methods(mod, target, *symbols)
81
- methods = mod.singleton_class.instance_methods(false)
82
- unless symbols.empty?
83
- symbols.select! { |s| s =~ /^[a-z]/ }
84
- methods = filter_exported_symbols(methods, symbols)
85
- end
86
- methods.each do |sym|
87
- target.send(:define_method, sym, &mod.method(sym))
88
- end
89
- end
90
-
91
- # Adds all or part of a module's constants to a target object
92
- # If no symbols are given, all constants are added
93
- # @param mod [Module] imported module
94
- # @param target [Object] object to add constants to
95
- # @param symbols [Array<Symbol>] list of constants to add
96
- # @return [void]
97
- def add_module_constants(mod, target, *symbols)
98
- exported = mod.__module_info[:exported_symbols]
99
- unless symbols.empty?
100
- symbols.select! { |s| s =~ /^[A-Z]/ }
101
- exported = filter_exported_symbols(exported, symbols)
102
- end
103
- mod.singleton_class.constants(false).each do |sym|
104
- next unless exported.include?(sym)
105
-
106
- target.const_set(sym, mod.singleton_class.const_get(sym))
107
- end
108
- end
109
-
110
- def filter_exported_symbols(exported, requested)
111
- not_exported = requested - exported
112
- unless not_exported.empty?
113
- raise NameError, "symbol #{not_exported.first.inspect} not exported"
114
- end
115
-
116
- exported & requested
117
- end
118
-
119
- # Defines a const_missing method used for auto-importing on a given object
120
- # @param receiver [Object] object to receive the const_missing method call
121
- # @param auto_import_hash [Hash] a hash mapping constant names to a source
122
- # file and a caller location
123
- # @return [void]
124
- def define_auto_import_const_missing_method(receiver, auto_import_hash)
125
- receiver.singleton_class.define_method(:const_missing) do |sym|
126
- (path, caller_location) = auto_import_hash[sym]
127
- path ? const_set(sym, import(path, caller_location)) : super(sym)
128
- end
129
- end
130
-
131
73
  # Creates a new module from a source file
132
74
  # @param path [String] source file name
133
75
  # @return [Module] module
@@ -180,6 +122,10 @@ module Modulation
180
122
  ensure
181
123
  @loaded_modules[path] = old_module if block_given?
182
124
  end
125
+
126
+ def add_tags(tags)
127
+ Paths.add_tags(tags, caller(1..1).first)
128
+ end
183
129
  end
184
130
  end
185
131
 
@@ -45,7 +45,7 @@ class Module
45
45
 
46
46
  def setup_auto_import_registry
47
47
  @__auto_import_registry = {}
48
- Modulation.define_auto_import_const_missing_method(
48
+ Modulation::Builder.define_auto_import_const_missing_method(
49
49
  self,
50
50
  @__auto_import_registry
51
51
  )
@@ -56,8 +56,8 @@ class Module
56
56
  # @return [void]
57
57
  def extend_from(path)
58
58
  mod = import(path, caller(1..1).first)
59
- Modulation.add_module_methods(mod, self.class)
60
- Modulation.add_module_constants(mod, self)
59
+ Modulation::Builder.add_module_methods(mod, self.class)
60
+ Modulation::Builder.add_module_constants(mod, self)
61
61
  end
62
62
 
63
63
  # Includes exported methods from the given file name in the receiver
@@ -67,8 +67,8 @@ class Module
67
67
  # @return [void]
68
68
  def include_from(path, *symbols)
69
69
  mod = import(path, caller(1..1).first)
70
- Modulation.add_module_methods(mod, self, *symbols)
71
- Modulation.add_module_constants(mod, self, *symbols)
70
+ Modulation::Builder.add_module_methods(mod, self, *symbols)
71
+ Modulation::Builder.add_module_constants(mod, self, *symbols)
72
72
  end
73
73
 
74
74
  # Aliases the given method only if the alias does not exist, implementing in
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../modulation'
4
+ require_relative '../modulation/version'
5
+ require 'zlib'
6
+
7
+ module Modulation
8
+ # Implements packing functionality
9
+ module Packer
10
+ BOOTSTRAP_CODE = <<~SRC.encode('ASCII-8BIT').chomp
11
+ # encoding: ASCII-8BIT
12
+ require 'bundler/inline'
13
+
14
+ gemfile do
15
+ source 'https://rubygems.org'
16
+ gem 'modulation', '~> %<modulation_version>s'
17
+ end
18
+
19
+ require 'modulation/packer'
20
+ Modulation::Bootstrap.setup(DATA, %<dictionary>s)
21
+ import(%<entry_point>s).send(:main)
22
+ __END__
23
+ %<data>s
24
+ SRC
25
+
26
+ def self.pack(paths, _options = {})
27
+ paths = [paths] unless paths.is_a?(Array)
28
+ deps = collect_dependencies(paths)
29
+ entry_point_filename = File.expand_path(paths.first)
30
+ dictionary, data = generate_packed_data(deps)
31
+ generate_bootstrap(dictionary, data, entry_point_filename)
32
+ end
33
+
34
+ def self.collect_dependencies(paths)
35
+ paths.each_with_object([]) do |fn, deps|
36
+ mod = import(File.expand_path(fn))
37
+ deps << File.expand_path(fn)
38
+ mod.__traverse_dependencies { |m| deps << m.__module_info[:location] }
39
+ end
40
+ end
41
+
42
+ def self.generate_packed_data(deps)
43
+ files = deps.each_with_object({}) do |path, dict|
44
+ dict[path] = IO.read(path)
45
+ end
46
+ # files[INLINE_GEMFILE_PATH] = generate_gemfile
47
+ pack_files(files)
48
+ end
49
+
50
+ def self.pack_files(files)
51
+ data = (+'').encode('ASCII-8BIT')
52
+ last_offset = 0
53
+ dictionary = files.each_with_object({}) do |(path, content), dict|
54
+ zipped = Zlib::Deflate.deflate(content)
55
+ size = zipped.bytesize
56
+
57
+ data << zipped
58
+ dict[path] = [last_offset, size]
59
+ last_offset += size
60
+ end
61
+ [dictionary, data]
62
+ end
63
+
64
+ # def self.generate_gemfile
65
+ # format(INLINE_GEMFILE_CODE)
66
+ # end
67
+
68
+ def self.generate_bootstrap(dictionary, data, entry_point)
69
+ format(BOOTSTRAP_CODE, modulation_version: Modulation::VERSION,
70
+ dictionary: dictionary.inspect,
71
+ entry_point: entry_point.inspect,
72
+ data: data)
73
+ end
74
+ end
75
+
76
+ # Packed app bootstrapping code
77
+ module Bootstrap
78
+ def self.setup(data, dictionary)
79
+ patch_builder
80
+ @data = data
81
+ @data_offset = data.pos
82
+ @dictionary = dictionary
83
+ end
84
+
85
+ def self.patch_builder
86
+ class << Modulation::Builder
87
+ alias_method :orig_make, :make
88
+ def make(info)
89
+ if Modulation::Bootstrap.find(info[:location])
90
+ info[:source] = Modulation::Bootstrap.read(info[:location])
91
+ end
92
+ orig_make(info)
93
+ end
94
+ end
95
+ end
96
+
97
+ def self.find(path)
98
+ @dictionary[path]
99
+ end
100
+
101
+ def self.read(path)
102
+ (offset, length) = @dictionary[path]
103
+ @data.seek(@data_offset + offset)
104
+ Zlib::Inflate.inflate(@data.read(length))
105
+ end
106
+ end
107
+ end
@@ -4,14 +4,37 @@ module Modulation
4
4
  # Implements methods for expanding relative or incomplete module file names
5
5
  module Paths
6
6
  class << self
7
- TAGGED_REGEXP = /^@(.+)$/.freeze
8
-
9
- def tagged_path(path)
10
- path[TAGGED_REGEXP, 1]
7
+ def process(path, caller_location)
8
+ tagged_path(path) || absolute_path(path, caller_location) ||
9
+ lookup_gem_path(path)
11
10
  end
12
11
 
13
12
  # Regexp for extracting filename from caller reference
14
- CALLER_FILE_REGEXP = /^([^\:]+)\:/.freeze
13
+ CALLER_FILE_REGEXP = /^([^\:]+)\:?/.freeze
14
+ TAGGED_REGEXP = /^@([^\/]+)(\/.+)?$/.freeze
15
+
16
+ def add_tags(tags, caller_location)
17
+ caller_file = caller_location[CALLER_FILE_REGEXP, 1]
18
+ caller_dir = caller_file ? File.dirname(caller_file) : nil
19
+
20
+ @tags ||= {}
21
+ tags.each do |k, path|
22
+ @tags[k.to_s] = caller_dir ? File.expand_path(path, caller_dir) : path
23
+ end
24
+ end
25
+
26
+ def tagged_path(path)
27
+ return nil unless @tags
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)
37
+ end
15
38
 
16
39
  # Resolves the absolute path to the provided reference. If the file is not
17
40
  # found, will try to resolve to a gem
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- VERSION = '0.27'
4
+ VERSION = '0.28'
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.27'
4
+ version: '0.28'
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-08-21 00:00:00.000000000 Z
11
+ date: 2019-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -61,7 +61,7 @@ files:
61
61
  - lib/modulation/ext.rb
62
62
  - lib/modulation/gem.rb
63
63
  - lib/modulation/module_mixin.rb
64
- - lib/modulation/packing.rb
64
+ - lib/modulation/packer.rb
65
65
  - lib/modulation/paths.rb
66
66
  - lib/modulation/version.rb
67
67
  homepage: http://github.com/digital-fabric/modulation
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../modulation'
4
- require 'zlib'
5
- require 'digest/md5'
6
-
7
- module Modulation
8
- # Implements packing functionality
9
- module Packing
10
- BOOTSTRAP = <<~SRC.encode('ASCII-8BIT')
11
- # encoding: ASCII-8BIT
12
- require 'modulation'
13
- require 'zlib'
14
-
15
- b = Modulation::Builder
16
- z = Zlib::Inflate
17
- b::C = {%<module_dictionary>s}
18
-
19
- class << b
20
- alias_method :orig_make, :make
21
-
22
- def make(info)
23
- (pr = Modulation::Builder::C.delete(info[:location])) ? pr.call : orig_make(info)
24
- end
25
- end
26
-
27
- import(%<entry_point>s).send(:main)
28
- SRC
29
-
30
- MAKE_CODE = <<~SRC
31
- proc { b.orig_make(location: %<location>s, source: %<source>s) }
32
- SRC
33
-
34
- UNZIP_CODE = 'z.inflate(%<data>s)'
35
-
36
- # MAKE_CODE = <<~SRC
37
- # Modulation::Builder.orig_make(location: %s, source: %s)
38
- # SRC
39
-
40
- # UNCAN_CODE = <<~SRC
41
- # proc { RubyVM::InstructionSequence.load_from_binary(
42
- # Zlib::Inflate.inflate(%s)).eval
43
- # }
44
- # SRC
45
-
46
- def self.pack(paths, _options = {})
47
- paths = [paths] unless paths.is_a?(Array)
48
-
49
- entry_point_filename = nil
50
- dictionary = paths.each_with_object({}) do |path, dict|
51
- warn "Processing #{path}"
52
- source = IO.read(path)
53
- entry_point_filename ||= path
54
- dict[path] = pack_module(path, source)
55
- end
56
-
57
- generate_bootstrap(dictionary, entry_point_filename)
58
- end
59
-
60
- def self.pack_module(path, source)
61
- # code = (MAKE_CODE % [fn.inspect, module_source.inspect])
62
- # seq = RubyVM::InstructionSequence.compile(code, options)
63
- # canned = Zlib::Deflate.deflate(seq.to_binary)
64
- # dict[fn] = UNCAN_CODE % canned.inspect
65
-
66
- zipped = Zlib::Deflate.deflate(source)
67
- code = format(UNZIP_CODE, data: zipped.inspect)
68
- format(MAKE_CODE, location: path.inspect, source: code)
69
- end
70
-
71
- def self.generate_bootstrap(module_dictionary, entry_point)
72
- format(
73
- BOOTSTRAP,
74
- module_dictionary: format_module_dictionary(module_dictionary),
75
- entry_point: entry_point.inspect
76
- ).chomp.gsub(/^\s+/, '').gsub(/\n+/, "\n")
77
- end
78
-
79
- def self.format_module_dictionary(module_dictionary)
80
- module_dictionary.map do |fn, code|
81
- format(
82
- '%<filename>s => %<code>s',
83
- filename: fn.inspect,
84
- code: code
85
- ).chomp
86
- end.join(',')
87
- end
88
- end
89
- end