modulation 0.33 → 0.34

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: 66ef0e0890aa7cba8f462f22cfc1ebbe81ab4cdc48b3ad9733958dc002370d85
4
- data.tar.gz: aeda0965bda28cd93eb2b318f8cacc24ea342e49084230cf0cdb522d05f1182d
3
+ metadata.gz: 1b35f333f45c8017d3fd44700fc5cd3d16dcdd575be012b3f86f2af17c2fcaef
4
+ data.tar.gz: de1b7cf9b6cd1d39080fd2ef971d99b23fa2b91f18a90db2bbcacf9debfdd7c7
5
5
  SHA512:
6
- metadata.gz: 12ea537e1662feb88a7ae35cf46a6b23e8970f4255088b20c32962792d0cc4bff4cbd611a1eadc431feb81cb768191917dc6a7eb19e2d9bc75c50ecb50e8caf4
7
- data.tar.gz: 1a6b08a109333d8a4ef4513e1607ceb84f177d4287f7114699f9d1d48d7c29959d37b16205dc36eaf627a8418cd886bb6eed6a71782bad4cd8fe7adbf1e50826
6
+ metadata.gz: 914789190308c5b9133d4bc3f2a5b6209fe9123ba1d984e7e77b1062f6392b9cb09a4db32b44b631db631080e655bf7dfb32b1955693eb39d0573a85487dba4d
7
+ data.tar.gz: 2cba7fed4ae3c0a36abb3f8a2f1505074191ba52cfa7510022060730accf39a932e00b6e3323e7b0643ac8e6932f6046be4e10fa7bff435004a6ceb75492bd68
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ 0.34 2019-10-14
2
+ ---------------
3
+
4
+ * Obfuscate bootstrapping code in packages
5
+ * Move packer, bootstrap, CLI, creator code into stock modules
6
+ * Improve README
7
+
1
8
  0.33 2019-10-02
2
9
  ---------------
3
10
 
data/README.md CHANGED
@@ -28,21 +28,21 @@ a functional style, minimizing boilerplate code.
28
28
 
29
29
  ## Features
30
30
 
31
- - Complete isolation of each module.
32
- - Explicit exporting and importing of methods and constants.
33
- - Support for circular dependencies.
34
- - Support for [default exports](#default-exports) for modules exporting a single
35
- class or value.
36
- - [Lazy Loading](#lazy-loading) improves start up time and memory consumption.
37
- - [Hot module reloading](#reloading-modules)
38
- - [Mocking of dependencies](#mocking-dependencies) for testing purposes.
39
- - Can be used to [write gems](#writing-gems-using-modulation).
40
- - [Dependency introspection](#dependency-introspection).
41
- - Support for [creating modules programmatically](#programmatic-module-creation).
42
- - Easier [unit-testing](#unit-testing-modules) of private methods and
43
- constants.
44
- - Pack entire applications [into a single
45
- file](#packing-applications-with-modulation).
31
+ - **[Complete isolation of each module](#organizing-your-code-with-modulation)**
32
+ prevents literring of the global namespace.
33
+ - **Explicit [exporting](#exporting-declarations) and
34
+ [importing](#importing-declarations) of methods and constants** lets you
35
+ control the public interface for each module, as well as keep track of all
36
+ dependencies in your code.
37
+ - **[Lazy Loading](#lazy-loading)** improves start up time and memory
38
+ consumption.
39
+ - **[Hot module reloading](#reloading-modules)** streamlines your development
40
+ process.
41
+ - **[Dependency mocking](#mocking-dependencies)** facilitates testing.
42
+ - **[Dependency introspection](#dependency-introspection)** lets you introspect
43
+ your dependencies at runtime.
44
+ - **[Application packing](#packing-applications-with-modulation)** lets you
45
+ bundle your code in a single, optionally obfuscated file (WIP).
46
46
 
47
47
  ## Rationale
48
48
 
@@ -96,7 +96,7 @@ gem 'modulation'
96
96
  ## Organizing your code with Modulation
97
97
 
98
98
  Modulation builds on the idea of a Ruby `Module` as a
99
- ["collection of methods and constants"](https://ruby-doc.org/core-2.5.1/Module.html).
99
+ ["collection of methods and constants"](https://ruby-doc.org/core-2.6.5/Module.html).
100
100
  Using modulation, each Ruby source file becomes a module. Modules usually
101
101
  export method and constant declarations (usually an API for a specific,
102
102
  well-defined functionality) to be shared with other modules. Modules can also
@@ -110,6 +110,8 @@ operations such as [hot reloading](#reloading-modules).
110
110
  Modulation provides an alternative APIs for loading modules. Instead of using
111
111
  `require` and `require_relative`, you use `import`, `import_map` and other APIs.
112
112
 
113
+ ## Basic Usage
114
+
113
115
  ### Exporting declarations
114
116
 
115
117
  Any class, module or constant be exported using `#export`:
@@ -211,6 +213,16 @@ User = import('./models')::User
211
213
  user = User.new(...)
212
214
  ```
213
215
 
216
+ ### A word about paths
217
+
218
+ Paths given to `import` are always considered relative to the importing file,
219
+ unless they are absolute (e.g. `/home/dave/repo/my_app`), specify a
220
+ [tag](#using-tags-to-designate-common-subdirectories) or reference a
221
+ [gem](#importing-gems-using-modulation). This is true for all Modulation APIs
222
+ that accept path arguments.
223
+
224
+ ## Advanced Usage
225
+
214
226
  ### Using tags to designate common subdirectories
215
227
 
216
228
  Normally, module paths are always relative to the file calling the `#import`
@@ -224,12 +236,25 @@ sources. A tagged source is simply a path associated with a label. For example,
224
236
  an application may tag `lib/models` simply as `@models`. Once tags are defined,
225
237
  they can be used when importing files, e.g. `import('@models/post')`.
226
238
 
239
+ To define tags, use `Modulation.add_tags`:
240
+
241
+ ```ruby
242
+ Modulation.add_tags(
243
+ models: '../lib/models',
244
+ views: '../lib/views'
245
+ )
246
+
247
+ ...
248
+
249
+ User = import '@models/user'
250
+ ```
251
+
227
252
  ### Importing all source files in a directory
228
253
 
229
254
  To load all source files in a directory you can use `#import_all`:
230
255
 
231
256
  ```ruby
232
- import_all('./ext') # will load ./ext/kernel.rb, ./ext/socket.rb etc
257
+ import_all('./ext')
233
258
  ```
234
259
 
235
260
  Groups of modules providing a uniform interface can also be loaded using
@@ -323,6 +348,12 @@ config = import('./config')
323
348
  db.connect(config[:host], config[:port])
324
349
  ```
325
350
 
351
+ ### Circular dependencies
352
+
353
+ Circular dependencies, while not the best practice for organizing a code base,
354
+ are sometimes useful. Modulation supports circular dependencies, with the
355
+ exception of modules with default exports.
356
+
326
357
  ### Accessing a module's root namespace from nested modules within itself
327
358
 
328
359
  The special constant `MODULE` allows you to access the containing module from
@@ -577,7 +608,7 @@ overwrite any value retained in the instance variable. To assign initial values,
577
608
  use the `||=` operator as in the example above. See also the
578
609
  [reload example](examples/reload).
579
610
 
580
- ## Dependency introspection
611
+ ### Dependency introspection
581
612
 
582
613
  Modulation allows runtime introspection of dependencies between modules. You can
583
614
  interrogate a module's dependencies (i.e. the modules it imports) by calling
data/bin/mdl CHANGED
@@ -4,40 +4,5 @@
4
4
  require 'bundler/setup'
5
5
  require 'modulation'
6
6
 
7
- def self.run
8
- ARGV.each do |arg|
9
- fn, method = (arg =~ /^([^\:]+)\:(.+)$/) ? [$1, $2.to_sym] : [arg, :main]
10
- mod = import(File.expand_path(fn))
11
- mod.send(method) if method
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
17
- end
18
-
19
- def collect_deps(fn, paths)
20
- if File.directory?(fn)
21
- Dir["#{fn}/**/*.rb"].each { |fn| collect_deps(fn, paths) }
22
- else
23
- paths << File.expand_path(fn)
24
- mod = import(File.expand_path(fn))
25
- if mod.respond_to?(:__traverse_dependencies)
26
- mod.__traverse_dependencies { |m| paths << m.__module_info[:location] }
27
- end
28
- end
29
- end
30
-
31
- def self.deps
32
- paths = []
33
- ARGV.each { |arg| collect_deps(arg, paths) }
34
- puts *paths
35
- end
36
-
37
- def self.pack
38
- require 'modulation/packer'
39
- STDOUT << Modulation::Packer.pack(ARGV, hide_filenames: true)
40
- end
41
-
42
- cmd = ARGV.shift
43
- respond_to?(cmd.to_sym) ? send(cmd) : (ARGV.unshift(cmd); run)
7
+ CLI = import '@modulation/cli'
8
+ CLI.new ARGV
data/lib/modulation.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- DIR = File.dirname(__FILE__)
4
+ DIR = __dir__
5
5
  end
6
6
 
7
7
  require_relative 'modulation/ext'
@@ -4,7 +4,6 @@
4
4
  module Modulation
5
5
  require_relative './paths'
6
6
  require_relative './builder'
7
- require_relative './creator'
8
7
  require_relative './module_mixin'
9
8
 
10
9
  RE_CONST = /^[A-Z]/.freeze
@@ -156,13 +155,14 @@ module Modulation
156
155
  end
157
156
 
158
157
  def create(arg = nil, &block)
159
- return Creator.from_block(block) if block
158
+ creator = import '@modulation/creator'
159
+ return creator.from_block(block) if block
160
160
 
161
161
  case arg
162
162
  when Hash
163
- Creator.from_hash(arg)
163
+ creator.from_hash(arg)
164
164
  when String
165
- Creator.from_string(arg)
165
+ creator.from_string(arg)
166
166
  else
167
167
  raise 'Invalid argument'
168
168
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :run, :transform_module_info
4
+
5
+ require 'zlib'
6
+
7
+ def run(data, dict_offset)
8
+ setup(data, dict_offset)
9
+ import(@dictionary[:entry_point]).send(:main)
10
+ end
11
+
12
+ def setup(data, dict_offset)
13
+ patch_builder
14
+ @data = data
15
+ @data_offset = data.pos
16
+ @dictionary = read_dictionary(dict_offset)
17
+ end
18
+
19
+ def patch_builder
20
+ class << Modulation::Builder
21
+ alias_method :orig_make, :make
22
+ def make(info)
23
+ info = MODULE.transform_module_info(info)
24
+ orig_make(info)
25
+ end
26
+ end
27
+ end
28
+
29
+ def transform_module_info(info)
30
+ if find(info[:location])
31
+ info[:source] = read_file(info[:location])
32
+ end
33
+ info
34
+ end
35
+
36
+ def find(path)
37
+ @dictionary[path]
38
+ end
39
+
40
+ def read_dictionary(offset)
41
+ @data.seek(@data_offset + offset)
42
+ eval Zlib::Inflate.inflate(@data.read)
43
+ end
44
+
45
+ def read_file(path)
46
+ (offset, length) = @dictionary[path]
47
+ @data.seek(@data_offset + offset)
48
+ Zlib::Inflate.inflate(@data.read(length))
49
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ export_default :CLI
4
+
5
+ class CLI
6
+ def initialize(argv)
7
+ @argv = argv
8
+
9
+ process
10
+ end
11
+
12
+ def process
13
+ cmd = ARGV.shift
14
+ if respond_to? cmd.to_sym
15
+ send cmd
16
+ else
17
+ ARGV.unshift cmd
18
+ run
19
+ end
20
+ end
21
+
22
+ def run
23
+ @argv.each { |arg| run_file arg }
24
+ rescue StandardError => e
25
+ cleanup_backtrace(e)
26
+ raise e
27
+ end
28
+
29
+ def run_file(arg)
30
+ fn, method = filename_and_method_from_arg(arg)
31
+ mod = import(File.expand_path(fn))
32
+ mod.send(method) if method
33
+ end
34
+
35
+ FILENAME_AND_METHOD_RE = /^([^\:]+)\:(.+)$/.freeze
36
+
37
+ def filename_and_method_from_arg(arg)
38
+ if arg =~ FILENAME_AND_METHOD_RE
39
+ match = Regexp.last_match
40
+ [match[1], match[2].to_sym]
41
+ else
42
+ [arg, :main]
43
+ end
44
+ end
45
+
46
+ BACKTRACE_RE = /^(#{Modulation::DIR})|(bin\/mdl)/.freeze
47
+
48
+ def cleanup_backtrace(error)
49
+ backtrace = error.backtrace.reject { |l| l =~ BACKTRACE_RE }
50
+ error.set_backtrace(backtrace)
51
+ end
52
+
53
+ def collect_deps(path, array)
54
+ if File.directory?(path)
55
+ Dir["#{path}/**/*.rb"].each { |fn| collect_deps(fn, array) }
56
+ else
57
+ array << File.expand_path(path)
58
+ mod = import(File.expand_path(path))
59
+ if mod.respond_to?(:__traverse_dependencies)
60
+ mod.__traverse_dependencies { |m| array << m.__module_info[:location] }
61
+ end
62
+ end
63
+ end
64
+
65
+ def deps
66
+ paths = []
67
+ @argv.each { |arg| collect_deps(arg, paths) }
68
+ puts(*paths)
69
+ end
70
+
71
+ def pack
72
+ STDOUT << import('@modulation/packer').pack(@argv, hide_filenames: true)
73
+ end
74
+
75
+ def version
76
+ puts "Modulation version #{Modulation::VERSION}"
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :from_block,
4
+ :from_hash,
5
+ :from_string
6
+
7
+ RE_ATTR = /^@(.+)$/.freeze
8
+
9
+ def from_block(block)
10
+ Module.new.tap { |m| m.instance_eval(&block) }
11
+ end
12
+
13
+ # Creates a module from a prototype hash
14
+ # @param hash [Hash] prototype hash
15
+ # @return [Module] created object
16
+ def from_hash(hash)
17
+ Module.new.tap do |m|
18
+ s = m.singleton_class
19
+ hash.each do |k, v|
20
+ if k =~ Modulation::RE_CONST
21
+ m.const_set(k, v)
22
+ elsif k =~ RE_ATTR
23
+ m.instance_variable_set(k, v)
24
+ elsif v.respond_to?(:to_proc)
25
+ s.send(:define_method, k) { |*args| instance_exec(*args, &v) }
26
+ else
27
+ s.send(:define_method, k) { v }
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def from_string(str)
34
+ m = Modulation::Builder.make(source: str)
35
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :pack
4
+
5
+ require 'modulation/version'
6
+ require 'zlib'
7
+
8
+ BOOTSTRAP_CODE = <<~SRC
9
+ # encoding: ASCII-8BIT
10
+ require 'bundler/inline'
11
+
12
+ gemfile do
13
+ source 'https://rubygems.org'
14
+ gem 'modulation', '~> %<modulation_version>s'
15
+ end
16
+
17
+ import('@modulation/bootstrap').run(DATA, %<dict_offset>d)
18
+ __END__
19
+ %<data>s
20
+ SRC
21
+
22
+ def pack(paths, _options = {})
23
+ paths = [paths] unless paths.is_a?(Array)
24
+ entry_point_filename = File.expand_path(paths.first)
25
+
26
+ deps = collect_dependencies(paths)
27
+ package_info = generate_packed_data(deps, entry_point_filename)
28
+ generate_bootstrap(package_info, entry_point_filename)
29
+ end
30
+
31
+ def collect_dependencies(paths)
32
+ paths.each_with_object([]) do |fn, deps|
33
+ mod = import(File.expand_path(fn))
34
+ deps << File.expand_path(fn)
35
+ mod.__traverse_dependencies { |m| deps << m.__module_info[:location] }
36
+ end
37
+ end
38
+
39
+ def generate_packed_data(deps, entry_point_filename)
40
+ files = deps.each_with_object({}) do |path, dict|
41
+ dict[path] = IO.read(path)
42
+ end
43
+ pack_files(files, entry_point_filename)
44
+ end
45
+
46
+ def pack_files(files, entry_point_filename)
47
+ data = (+'').encode('ASCII-8BIT')
48
+ last_offset = 0
49
+ dictionary = {
50
+ entry_point: entry_point_filename
51
+ }
52
+ files.each_with_object(dictionary) do |(path, content), dict|
53
+ zipped = Zlib::Deflate.deflate(content)
54
+ size = zipped.bytesize
55
+
56
+ data << zipped
57
+ dict[path] = [last_offset, size]
58
+ last_offset += size
59
+ end
60
+ packed_dictionary = Zlib::Deflate.deflate(dictionary.inspect)
61
+ data << packed_dictionary
62
+
63
+ {
64
+ dict_offset: last_offset,
65
+ data: data
66
+ }
67
+ end
68
+
69
+ def generate_bootstrap(package_info, entry_point)
70
+ format(
71
+ bootstrap_template,
72
+ modulation_version: Modulation::VERSION,
73
+ dict_offset: package_info[:dict_offset],
74
+ data: package_info[:data]
75
+ )
76
+ end
77
+
78
+ def bootstrap_template
79
+ BOOTSTRAP_CODE.encode('ASCII-8BIT').gsub(/^\s+/, '').chomp
80
+ end
@@ -14,13 +14,18 @@ module Modulation
14
14
  CALLER_FILE_REGEXP = /^([^\:]+)\:?/.freeze
15
15
  TAGGED_REGEXP = /^@([^\/]+)(\/.+)?$/.freeze
16
16
 
17
- def add_tags(tags, caller_location)
17
+ def tags
18
+ @tags ||= {
19
+ 'modulation' => File.join(Modulation::DIR, 'modulation/modules')
20
+ }
21
+ end
22
+
23
+ def add_tags(new_tags, caller_location)
18
24
  caller_file = caller_location[CALLER_FILE_REGEXP, 1]
19
25
  caller_dir = caller_file ? File.dirname(caller_file) : nil
20
26
 
21
- @tags ||= {}
22
- tags.each do |k, path|
23
- @tags[k.to_s] = caller_dir ? File.expand_path(path, caller_dir) : path
27
+ new_tags.each do |k, path|
28
+ tags[k.to_s] = caller_dir ? File.expand_path(path, caller_dir) : path
24
29
  end
25
30
  end
26
31
 
@@ -29,7 +34,7 @@ module Modulation
29
34
  def expand_tag(path)
30
35
  path.sub RE_TAG do
31
36
  tag = Regexp.last_match[1]
32
- (@tags && @tags[tag]) || (raise "Invalid tag #{tag}")
37
+ tags[tag] || (raise "Invalid tag #{tag}")
33
38
  end
34
39
  end
35
40
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- VERSION = '0.33'
4
+ VERSION = '0.34'
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.33'
4
+ version: '0.34'
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-10-02 00:00:00.000000000 Z
11
+ date: 2019-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -56,14 +56,16 @@ files:
56
56
  - lib/modulation.rb
57
57
  - lib/modulation/builder.rb
58
58
  - lib/modulation/core.rb
59
- - lib/modulation/creator.rb
60
59
  - lib/modulation/export_default.rb
61
60
  - lib/modulation/export_from_receiver.rb
62
61
  - lib/modulation/exports.rb
63
62
  - lib/modulation/ext.rb
64
63
  - lib/modulation/gem.rb
65
64
  - lib/modulation/module_mixin.rb
66
- - lib/modulation/packer.rb
65
+ - lib/modulation/modules/bootstrap.rb
66
+ - lib/modulation/modules/cli.rb
67
+ - lib/modulation/modules/creator.rb
68
+ - lib/modulation/modules/packer.rb
67
69
  - lib/modulation/paths.rb
68
70
  - lib/modulation/version.rb
69
71
  homepage: http://github.com/digital-fabric/modulation
@@ -90,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
92
  - !ruby/object:Gem::Version
91
93
  version: '0'
92
94
  requirements: []
93
- rubygems_version: 3.0.3
95
+ rubygems_version: 3.0.6
94
96
  signing_key:
95
97
  specification_version: 4
96
98
  summary: 'Modulation: explicit dependency management for Ruby'
@@ -1,39 +0,0 @@
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
@@ -1,105 +0,0 @@
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
11
- # encoding: ASCII-8BIT
12
- require 'bundler/inline'
13
-
14
- gemfile do
15
- source 'https://rubygems.org'
16
- gem 'modulation', '~> %<modulation_version>s', require: 'modulation/packer'
17
- end
18
-
19
- Modulation::Bootstrap.setup(DATA, %<dictionary>s)
20
- import(%<entry_point>s).send(:main)
21
- __END__
22
- %<data>s
23
- SRC
24
-
25
- def self.pack(paths, _options = {})
26
- paths = [paths] unless paths.is_a?(Array)
27
- deps = collect_dependencies(paths)
28
- entry_point_filename = File.expand_path(paths.first)
29
- dictionary, data = generate_packed_data(deps)
30
- generate_bootstrap(dictionary, data, entry_point_filename)
31
- end
32
-
33
- def self.collect_dependencies(paths)
34
- paths.each_with_object([]) do |fn, deps|
35
- mod = import(File.expand_path(fn))
36
- deps << File.expand_path(fn)
37
- mod.__traverse_dependencies { |m| deps << m.__module_info[:location] }
38
- end
39
- end
40
-
41
- def self.generate_packed_data(deps)
42
- files = deps.each_with_object({}) do |path, dict|
43
- dict[path] = IO.read(path)
44
- end
45
- pack_files(files)
46
- end
47
-
48
- def self.pack_files(files)
49
- data = (+'').encode('ASCII-8BIT')
50
- last_offset = 0
51
- dictionary = files.each_with_object({}) do |(path, content), dict|
52
- zipped = Zlib::Deflate.deflate(content)
53
- size = zipped.bytesize
54
-
55
- data << zipped
56
- dict[path] = [last_offset, size]
57
- last_offset += size
58
- end
59
- [dictionary, data]
60
- end
61
-
62
- def self.generate_bootstrap(dictionary, data, entry_point)
63
- format(bootstrap_template, modulation_version: Modulation::VERSION,
64
- dictionary: dictionary.inspect,
65
- entry_point: entry_point.inspect,
66
- data: data)
67
- end
68
-
69
- def self.bootstrap_template
70
- BOOTSTRAP_CODE.encode('ASCII-8BIT').gsub(/^\s+/, '').chomp
71
- end
72
- end
73
-
74
- # Packed app bootstrapping code
75
- module Bootstrap
76
- def self.setup(data, dictionary)
77
- patch_builder
78
- @data = data
79
- @data_offset = data.pos
80
- @dictionary = dictionary
81
- end
82
-
83
- def self.patch_builder
84
- class << Modulation::Builder
85
- alias_method :orig_make, :make
86
- def make(info)
87
- if Modulation::Bootstrap.find(info[:location])
88
- info[:source] = Modulation::Bootstrap.read(info[:location])
89
- end
90
- orig_make(info)
91
- end
92
- end
93
- end
94
-
95
- def self.find(path)
96
- @dictionary[path]
97
- end
98
-
99
- def self.read(path)
100
- (offset, length) = @dictionary[path]
101
- @data.seek(@data_offset + offset)
102
- Zlib::Inflate.inflate(@data.read(length))
103
- end
104
- end
105
- end