modulation 0.27 → 0.28

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 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