modulation 0.26 → 0.27

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