modulation 0.33 → 0.34

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