modulation 0.31 → 1.0.1
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 +32 -0
- data/README.md +203 -28
- data/bin/mdl +2 -37
- data/lib/modulation.rb +1 -1
- data/lib/modulation/builder.rb +8 -10
- data/lib/modulation/core.rb +38 -14
- data/lib/modulation/{default_export.rb → export_default.rb} +4 -5
- data/lib/modulation/export_from_receiver.rb +44 -0
- data/lib/modulation/exports.rb +112 -19
- data/lib/modulation/module_mixin.rb +42 -79
- data/lib/modulation/modules/bootstrap.rb +48 -0
- data/lib/modulation/modules/cli.rb +79 -0
- data/lib/modulation/modules/creator.rb +37 -0
- data/lib/modulation/modules/packer.rb +78 -0
- data/lib/modulation/paths.rb +22 -15
- data/lib/modulation/version.rb +1 -1
- metadata +37 -5
- data/lib/modulation/packer.rb +0 -105
@@ -0,0 +1,48 @@
|
|
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
|
+
location = info[:location]
|
31
|
+
info[:source] = read_file(location) if location
|
32
|
+
info
|
33
|
+
end
|
34
|
+
|
35
|
+
def find(path)
|
36
|
+
@dictionary[path]
|
37
|
+
end
|
38
|
+
|
39
|
+
def read_dictionary(offset)
|
40
|
+
@data.seek(@data_offset + offset)
|
41
|
+
eval Zlib::Inflate.inflate(@data.read)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_file(path)
|
45
|
+
(offset, length) = @dictionary[path]
|
46
|
+
@data.seek(@data_offset + offset)
|
47
|
+
Zlib::Inflate.inflate(@data.read(length))
|
48
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
export_default :CLI
|
4
|
+
|
5
|
+
# Command line interface functionality
|
6
|
+
class CLI
|
7
|
+
def initialize(argv)
|
8
|
+
@argv = argv
|
9
|
+
|
10
|
+
process
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
cmd = ARGV.shift
|
15
|
+
if respond_to? cmd.to_sym
|
16
|
+
send cmd
|
17
|
+
else
|
18
|
+
ARGV.unshift cmd
|
19
|
+
run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@argv.each { |arg| run_file arg }
|
25
|
+
rescue StandardError => e
|
26
|
+
cleanup_backtrace(e)
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_file(arg)
|
31
|
+
fn, method = filename_and_method_from_arg(arg)
|
32
|
+
mod = import(File.expand_path(fn))
|
33
|
+
mod.send(method) if method
|
34
|
+
end
|
35
|
+
|
36
|
+
FILENAME_AND_METHOD_RE = /^([^\:]+)\:(.+)$/.freeze
|
37
|
+
|
38
|
+
def filename_and_method_from_arg(arg)
|
39
|
+
if arg =~ FILENAME_AND_METHOD_RE
|
40
|
+
match = Regexp.last_match
|
41
|
+
[match[1], match[2].to_sym]
|
42
|
+
else
|
43
|
+
[arg, :main]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
BACKTRACE_RE = /^(#{Modulation::DIR})|(bin\/mdl)/.freeze
|
48
|
+
|
49
|
+
def cleanup_backtrace(error)
|
50
|
+
backtrace = error.backtrace.reject { |l| l =~ BACKTRACE_RE }
|
51
|
+
error.set_backtrace(backtrace)
|
52
|
+
end
|
53
|
+
|
54
|
+
def collect_deps(path, array)
|
55
|
+
if File.directory?(path)
|
56
|
+
Dir["#{path}/**/*.rb"].each { |fn| collect_deps(fn, array) }
|
57
|
+
else
|
58
|
+
array << File.expand_path(path)
|
59
|
+
mod = import(File.expand_path(path))
|
60
|
+
if mod.respond_to?(:__traverse_dependencies)
|
61
|
+
mod.__traverse_dependencies { |m| array << m.__module_info[:location] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def deps
|
67
|
+
paths = []
|
68
|
+
@argv.each { |arg| collect_deps(arg, paths) }
|
69
|
+
puts(*paths)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pack
|
73
|
+
STDOUT << import('@modulation/packer').pack(@argv, hide_filenames: true)
|
74
|
+
end
|
75
|
+
|
76
|
+
def version
|
77
|
+
puts "Modulation version #{Modulation::VERSION}"
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,37 @@
|
|
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 { |k, v| process_hash_entry(k, v, m, s) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_hash_entry(key, value, mod, singleton)
|
24
|
+
if key =~ Modulation::RE_CONST
|
25
|
+
mod.const_set(key, value)
|
26
|
+
elsif key =~ RE_ATTR
|
27
|
+
mod.instance_variable_set(key, value)
|
28
|
+
elsif value.respond_to?(:to_proc)
|
29
|
+
singleton.send(:define_method, key) { |*args| instance_exec(*args, &value) }
|
30
|
+
else
|
31
|
+
singleton.send(:define_method, key) { value }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def from_string(str)
|
36
|
+
Modulation::Builder.make(source: str)
|
37
|
+
end
|
@@ -0,0 +1,78 @@
|
|
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
|
+
dictionary = { entry_point: entry_point_filename }
|
48
|
+
data = (+'').encode('ASCII-8BIT')
|
49
|
+
last_offset = 0
|
50
|
+
files.each_with_object(dictionary) do |(path, content), dict|
|
51
|
+
last_offset = add_packed_file(path, content, data, dict, last_offset)
|
52
|
+
end
|
53
|
+
data << Zlib::Deflate.deflate(dictionary.inspect)
|
54
|
+
|
55
|
+
{ dict_offset: last_offset, data: data }
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_packed_file(path, content, data, dict, last_offset)
|
59
|
+
zipped = Zlib::Deflate.deflate(content)
|
60
|
+
size = zipped.bytesize
|
61
|
+
|
62
|
+
data << zipped
|
63
|
+
dict[path] = [last_offset, size]
|
64
|
+
last_offset + size
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_bootstrap(package_info, _entry_point)
|
68
|
+
format(
|
69
|
+
bootstrap_template,
|
70
|
+
modulation_version: Modulation::VERSION,
|
71
|
+
dict_offset: package_info[:dict_offset],
|
72
|
+
data: package_info[:data]
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def bootstrap_template
|
77
|
+
BOOTSTRAP_CODE.encode('ASCII-8BIT').gsub(/^\s+/, '').chomp
|
78
|
+
end
|
data/lib/modulation/paths.rb
CHANGED
@@ -5,7 +5,8 @@ module Modulation
|
|
5
5
|
module Paths
|
6
6
|
class << self
|
7
7
|
def process(path, caller_location)
|
8
|
-
|
8
|
+
path = expand_tag(path)
|
9
|
+
absolute_path(path, caller_location) ||
|
9
10
|
lookup_gem_path(path)
|
10
11
|
end
|
11
12
|
|
@@ -13,27 +14,32 @@ module Modulation
|
|
13
14
|
CALLER_FILE_REGEXP = /^([^\:]+)\:?/.freeze
|
14
15
|
TAGGED_REGEXP = /^@([^\/]+)(\/.+)?$/.freeze
|
15
16
|
|
16
|
-
def
|
17
|
+
def tags
|
18
|
+
@tags ||= {
|
19
|
+
'modulation' => modules_path
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def modules_path
|
24
|
+
File.join(Modulation::DIR, 'modulation/modules')
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_tags(new_tags, caller_location)
|
17
28
|
caller_file = caller_location[CALLER_FILE_REGEXP, 1]
|
18
29
|
caller_dir = caller_file ? File.dirname(caller_file) : nil
|
19
30
|
|
20
|
-
|
21
|
-
|
22
|
-
@tags[k.to_s] = caller_dir ? File.expand_path(path, caller_dir) : path
|
31
|
+
new_tags.each do |k, path|
|
32
|
+
tags[k.to_s] = caller_dir ? File.expand_path(path, caller_dir) : path
|
23
33
|
end
|
24
34
|
end
|
25
35
|
|
26
|
-
|
27
|
-
return nil unless @tags
|
36
|
+
RE_TAG = /^@([^\/]+)/.freeze
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
path = path ? File.join(base_path, path) : base_path
|
36
|
-
check_path(path)
|
38
|
+
def expand_tag(path)
|
39
|
+
path.sub RE_TAG do
|
40
|
+
tag = Regexp.last_match[1]
|
41
|
+
tags[tag] || (raise "Invalid tag #{tag}")
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
# Resolves the absolute path to the provided reference. If the file is not
|
@@ -54,6 +60,7 @@ module Modulation
|
|
54
60
|
# @param caller_location [String] caller location
|
55
61
|
# @return [String] absolute directory path
|
56
62
|
def absolute_dir_path(path, caller_location)
|
63
|
+
path = expand_tag(path)
|
57
64
|
caller_file = caller_location[CALLER_FILE_REGEXP, 1]
|
58
65
|
return nil unless caller_file
|
59
66
|
|
data/lib/modulation/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modulation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: minitest
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,12 +84,16 @@ files:
|
|
56
84
|
- lib/modulation.rb
|
57
85
|
- lib/modulation/builder.rb
|
58
86
|
- lib/modulation/core.rb
|
59
|
-
- lib/modulation/
|
87
|
+
- lib/modulation/export_default.rb
|
88
|
+
- lib/modulation/export_from_receiver.rb
|
60
89
|
- lib/modulation/exports.rb
|
61
90
|
- lib/modulation/ext.rb
|
62
91
|
- lib/modulation/gem.rb
|
63
92
|
- lib/modulation/module_mixin.rb
|
64
|
-
- lib/modulation/
|
93
|
+
- lib/modulation/modules/bootstrap.rb
|
94
|
+
- lib/modulation/modules/cli.rb
|
95
|
+
- lib/modulation/modules/creator.rb
|
96
|
+
- lib/modulation/modules/packer.rb
|
65
97
|
- lib/modulation/paths.rb
|
66
98
|
- lib/modulation/version.rb
|
67
99
|
homepage: http://github.com/digital-fabric/modulation
|
@@ -88,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
120
|
- !ruby/object:Gem::Version
|
89
121
|
version: '0'
|
90
122
|
requirements: []
|
91
|
-
rubygems_version: 3.0.
|
123
|
+
rubygems_version: 3.0.8
|
92
124
|
signing_key:
|
93
125
|
specification_version: 4
|
94
126
|
summary: 'Modulation: explicit dependency management for Ruby'
|
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
|