modulation 0.26 → 0.27

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: 98cf3c047ad9ba16b0658581dfd6031fd5cdcfb8ce74f50aaaba490f13945216
4
- data.tar.gz: '09777077cec02eb4f48d4edf9307d55226d992a096226ebeab689ae0a9e34e47'
3
+ metadata.gz: 04f2ab2cc42f117c9c8fb1b306b2239fbe9bca4a96f890e693bb92fb6c900318
4
+ data.tar.gz: 9ead222ae4fc275174b3e5d62a3b3b30e094761e5801de7b3a9fe21a346174be
5
5
  SHA512:
6
- metadata.gz: 6912cf32746a94e17909a3ec05f7b7cd891c6c70e93a6ee389fae72165d058845a85dbbc482c205baa053bab16cd71b3514ed92f40a1021dd7a6918945eeb3c9
7
- data.tar.gz: 02a489e032fb5451977eae9f58a8dadfba87dc073e451d712765721abfd0f20c8a69755894a7b1d72e8c5a346e216bc62979b4761f09703e4d93fb4128341b13
6
+ metadata.gz: 2bd5727f5ca807fcc6238b0679234ba5df79102eaa6153a5acb8db8eb66054a1d9bdb2f8250292cad4784101c1c58b343766b19a3a07be013dfb67ce13b7c175
7
+ data.tar.gz: 63a1bc298932c0b1d4bbf804099df2940755b23358f4e4c72023c61054d03102c97f4138324570821685eaeb82bd0101146c9a4d469f245d8af093b906a623bd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ 0.27 2019-08-21
2
+ ---------------
3
+
4
+ * Add initial packing functionality.
5
+
1
6
  0.26 2019-08-20
2
7
  ---------------
3
8
 
@@ -24,7 +29,7 @@
24
29
  0.22 2019-05-15
25
30
  ---------------
26
31
 
27
- * Export_default of a method now exportsa proc calling that method
32
+ * Export_default of a method now exports a proc calling that method
28
33
  * Raise error on export of undefined symbols
29
34
 
30
35
  0.21 2019-02-19
data/README.md CHANGED
@@ -83,7 +83,8 @@ easy to understand.
83
83
  - Module dependencies can be [introspected](#dependency-introspection).
84
84
  - Facilitates [unit-testing](#unit-testing-modules) of private methods and
85
85
  constants.
86
- - Can load all source files in directory at once.
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).
87
88
 
88
89
  ## Installing Modulation
89
90
 
@@ -517,6 +518,42 @@ m1.__depedencies #=> [<Module m2>]
517
518
  m1.__dependencies.first.__dependent_modules #=> [<Module m1>]
518
519
  ```
519
520
 
521
+ ## Running Modulation-based applications
522
+
523
+ Modulation provides a binary script for running Modulation-based applications.
524
+ `mdl` is a wrapper around Ruby that loads your application's main file as a
525
+ module, and then runs your application's entry point method. Let's look at a
526
+ sample application:
527
+
528
+ *app.rb*
529
+ ```ruby
530
+ def greet(name)
531
+ puts "Hello, #{name}!"
532
+ end
533
+
534
+ def main
535
+ print "Enter your name: "
536
+ name = gets
537
+ greet(name)
538
+ end
539
+ ```
540
+
541
+ To run this application, execute `mdl app.rb`, or `mdl run app.rb`. `mdl` will
542
+ automatically require the `modulation` gem and call the application's entry
543
+ point, `#main`.
544
+
545
+ ## Packing applications with Modulation
546
+
547
+ > *Note*: application packing is at the present time an experimental feature.
548
+ > There might be security concerns for packaging your app, such as leaking
549
+ > filenames from the developer's machine.
550
+
551
+ Modulation can also be used to package your entire application into a single
552
+ portable file that can be copied to another machine and run as is. To package
553
+ your app, use `mdl pack`. This command will perform a dynamic analysis of all
554
+ the app's dependencies and will put them together into a single Ruby file.
555
+ For more information have a look at the [app](examples/app) example.
556
+
520
557
  ## Writing gems using Modulation
521
558
 
522
559
  Modulation can be used to write gems, providing fine-grained control over your
@@ -553,6 +590,48 @@ MyFeature = import 'my_gem/my_feature'
553
590
  > use Modulation to export symbols, Modulation will refuse to import any gem
554
591
  > that does not depend on Modulation.
555
592
 
593
+ ## Writing modules that patch external classes or modules
594
+
595
+ It is generally recommended you refrain from causing side effects or patching
596
+ external code in your modules. When you do have to patch external classes or
597
+ modules (i.e. core, stdlib, or some third-party code) in your module, it's
598
+ useful to remember that any module may be eventually reloaded by the application
599
+ code. This means that any patching done during the loading of your module must
600
+ be [idempotent](https://en.wikipedia.org/wiki/Idempotence), i.e. have the same
601
+ effect when performed multiple times. Take for example the following module
602
+ code:
603
+
604
+ ```ruby
605
+ module ::Kernel
606
+ # aliasing #sleep more than once will break your code
607
+ alias_method :orig_sleep, :sleep
608
+
609
+ def sleep(duration)
610
+ STDERR.puts "Going to sleep..."
611
+ orig_sleep(duration)
612
+ STDERR.puts "Woke up!"
613
+ end
614
+ end
615
+ ```
616
+
617
+ Running the above code more than once would cause an infinite loop when calling
618
+ `Kernel#sleep`. In order to prevent this situation, modulation provides the
619
+ `Module#alias_method_once` method, which prevents aliasing the original method
620
+ more than once:
621
+
622
+ ```ruby
623
+ module ::Kernel
624
+ # alias_method_once is idempotent
625
+ alias_method_once :orig_sleep, :sleep
626
+
627
+ def sleep(duration)
628
+ STDERR.puts "Going to sleep..."
629
+ orig_sleep(duration)
630
+ STDERR.puts "Woke up!"
631
+ end
632
+ end
633
+ ```
634
+
556
635
  ## Coding style recommendations
557
636
 
558
637
  * Import modules into constants, not variables:
data/bin/mdl ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'modulation'
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
+ end
14
+
15
+ def collect_deps(fn, paths)
16
+ if File.directory?(fn)
17
+ Dir["#{fn}/**/*.rb"].each { |fn| collect_deps(fn, paths) }
18
+ else
19
+ paths << File.expand_path(fn)
20
+ mod = import(File.expand_path(fn))
21
+ if mod.respond_to?(:__traverse_dependencies)
22
+ mod.__traverse_dependencies { |m| paths << m.__module_info[:location] }
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.deps
28
+ paths = []
29
+ ARGV.each { |arg| collect_deps(arg, paths) }
30
+ puts *paths
31
+ end
32
+
33
+ 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)
44
+ end
45
+
46
+ cmd = ARGV.shift
47
+ respond_to?(cmd.to_sym) ? send(cmd) : (ARGV.unshift(cmd); run)
@@ -55,7 +55,7 @@ module Modulation
55
55
  # @return [void]
56
56
  def load_module_code(mod, info)
57
57
  path = info[:location]
58
- mod.instance_eval(IO.read(path), path)
58
+ mod.instance_eval(info[:source] || IO.read(path), path)
59
59
  mod.__post_load
60
60
  end
61
61
 
@@ -0,0 +1,89 @@
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
@@ -4,6 +4,12 @@ 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]
11
+ end
12
+
7
13
  # Regexp for extracting filename from caller reference
8
14
  CALLER_FILE_REGEXP = /^([^\:]+)\:/.freeze
9
15
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulation
4
- VERSION = '0.26'
4
+ VERSION = '0.27'
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.26'
4
+ version: '0.27'
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-20 00:00:00.000000000 Z
11
+ date: 2019-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -44,7 +44,7 @@ description: " Modulation provides an alternative way of organizing your Ruby c
44
44
  global namespace with a myriad modules, or complex multi-level nested\nmodule hierarchies.\n"
45
45
  email: ciconia@gmail.com
46
46
  executables:
47
- - rbm
47
+ - mdl
48
48
  extensions: []
49
49
  extra_rdoc_files:
50
50
  - README.md
@@ -52,7 +52,7 @@ extra_rdoc_files:
52
52
  files:
53
53
  - CHANGELOG.md
54
54
  - README.md
55
- - bin/rbm
55
+ - bin/mdl
56
56
  - lib/modulation.rb
57
57
  - lib/modulation/builder.rb
58
58
  - lib/modulation/core.rb
@@ -61,6 +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
65
  - lib/modulation/paths.rb
65
66
  - lib/modulation/version.rb
66
67
  homepage: http://github.com/digital-fabric/modulation
data/bin/rbm DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'modulation'
5
-
6
- ARGV.each { |fn| import(File.expand_path(fn)) }