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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +49 -18
- data/bin/mdl +2 -37
- data/lib/modulation.rb +1 -1
- data/lib/modulation/core.rb +4 -4
- data/lib/modulation/modules/bootstrap.rb +49 -0
- data/lib/modulation/modules/cli.rb +78 -0
- data/lib/modulation/modules/creator.rb +35 -0
- data/lib/modulation/modules/packer.rb +80 -0
- data/lib/modulation/paths.rb +10 -5
- data/lib/modulation/version.rb +1 -1
- metadata +7 -5
- data/lib/modulation/creator.rb +0 -39
- data/lib/modulation/packer.rb +0 -105
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b35f333f45c8017d3fd44700fc5cd3d16dcdd575be012b3f86f2af17c2fcaef
|
4
|
+
data.tar.gz: de1b7cf9b6cd1d39080fd2ef971d99b23fa2b91f18a90db2bbcacf9debfdd7c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 914789190308c5b9133d4bc3f2a5b6209fe9123ba1d984e7e77b1062f6392b9cb09a4db32b44b631db631080e655bf7dfb32b1955693eb39d0573a85487dba4d
|
7
|
+
data.tar.gz: 2cba7fed4ae3c0a36abb3f8a2f1505074191ba52cfa7510022060730accf39a932e00b6e3323e7b0643ac8e6932f6046be4e10fa7bff435004a6ceb75492bd68
|
data/CHANGELOG.md
CHANGED
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
|
-
|
33
|
-
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
- [
|
38
|
-
|
39
|
-
-
|
40
|
-
|
41
|
-
-
|
42
|
-
-
|
43
|
-
|
44
|
-
-
|
45
|
-
file
|
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
|
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')
|
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
|
-
|
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
|
-
|
8
|
-
|
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
data/lib/modulation/core.rb
CHANGED
@@ -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
|
-
|
158
|
+
creator = import '@modulation/creator'
|
159
|
+
return creator.from_block(block) if block
|
160
160
|
|
161
161
|
case arg
|
162
162
|
when Hash
|
163
|
-
|
163
|
+
creator.from_hash(arg)
|
164
164
|
when String
|
165
|
-
|
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
|
data/lib/modulation/paths.rb
CHANGED
@@ -14,13 +14,18 @@ module Modulation
|
|
14
14
|
CALLER_FILE_REGEXP = /^([^\:]+)\:?/.freeze
|
15
15
|
TAGGED_REGEXP = /^@([^\/]+)(\/.+)?$/.freeze
|
16
16
|
|
17
|
-
def
|
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
|
-
|
22
|
-
|
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
|
-
|
37
|
+
tags[tag] || (raise "Invalid tag #{tag}")
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
data/lib/modulation/version.rb
CHANGED
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.
|
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-
|
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/
|
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.
|
95
|
+
rubygems_version: 3.0.6
|
94
96
|
signing_key:
|
95
97
|
specification_version: 4
|
96
98
|
summary: 'Modulation: explicit dependency management for Ruby'
|
data/lib/modulation/creator.rb
DELETED
@@ -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
|
data/lib/modulation/packer.rb
DELETED
@@ -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
|