remote_ruby 0.3.0 → 1.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -2
  3. data/.rubocop.yml +37 -6
  4. data/CHANGELOG.md +64 -0
  5. data/LICENSE.txt +1 -1
  6. data/README.md +348 -81
  7. data/lib/remote_ruby/adapter_builder.rb +75 -0
  8. data/lib/remote_ruby/cache_adapter.rb +41 -0
  9. data/lib/remote_ruby/{connection_adapter/caching_adapter.rb → caching_adapter.rb} +23 -13
  10. data/lib/remote_ruby/code_templates/compiler/main.rb.erb +17 -29
  11. data/lib/remote_ruby/compat_io_reader.rb +36 -0
  12. data/lib/remote_ruby/compat_io_writer.rb +38 -0
  13. data/lib/remote_ruby/compiler.rb +8 -8
  14. data/lib/remote_ruby/connection_adapter.rb +16 -16
  15. data/lib/remote_ruby/execution_context.rb +56 -74
  16. data/lib/remote_ruby/extensions.rb +14 -0
  17. data/lib/remote_ruby/parser_factory.rb +29 -0
  18. data/lib/remote_ruby/plugin.rb +25 -0
  19. data/lib/remote_ruby/{flavour/rails_flavour.rb → rails_plugin.rb} +7 -5
  20. data/lib/remote_ruby/remote_context.rb +52 -0
  21. data/lib/remote_ruby/remote_error.rb +55 -0
  22. data/lib/remote_ruby/source_extractor.rb +2 -12
  23. data/lib/remote_ruby/ssh_adapter.rb +128 -0
  24. data/lib/remote_ruby/stream_prefixer.rb +25 -0
  25. data/lib/remote_ruby/tee_writer.rb +16 -0
  26. data/lib/remote_ruby/text_mode_adapter.rb +63 -0
  27. data/lib/remote_ruby/text_mode_builder.rb +44 -0
  28. data/lib/remote_ruby/tmp_file_adapter.rb +62 -0
  29. data/lib/remote_ruby/version.rb +1 -1
  30. data/lib/remote_ruby.rb +57 -15
  31. metadata +72 -28
  32. data/.github/workflows/main.yml +0 -26
  33. data/.gitignore +0 -17
  34. data/Gemfile +0 -23
  35. data/lib/remote_ruby/connection_adapter/cache_adapter.rb +0 -41
  36. data/lib/remote_ruby/connection_adapter/eval_adapter.rb +0 -96
  37. data/lib/remote_ruby/connection_adapter/local_stdin_adapter.rb +0 -29
  38. data/lib/remote_ruby/connection_adapter/ssh_stdin_adapter.rb +0 -34
  39. data/lib/remote_ruby/connection_adapter/stdin_process_adapter.rb +0 -35
  40. data/lib/remote_ruby/flavour.rb +0 -27
  41. data/lib/remote_ruby/runner.rb +0 -55
  42. data/lib/remote_ruby/stream_cacher.rb +0 -36
  43. data/lib/remote_ruby/unmarshaler.rb +0 -59
  44. data/remote_ruby.gemspec +0 -36
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # Builds connection adapter based on the provided parameters.
5
+ # Can wrap the adapter in caching and text mode adapters.
6
+ class AdapterBuilder
7
+ attr_reader :adapter_params, :use_cache, :save_cache, :text_mode
8
+
9
+ def initialize(adapter_klass: nil, use_cache: false, save_cache: false, **params)
10
+ @adapter_klass = adapter_klass
11
+ @use_cache = use_cache
12
+ @save_cache = save_cache
13
+ @adapter_params = params
14
+
15
+ RemoteRuby.ensure_cache_dir if save_cache
16
+ end
17
+
18
+ def adapter_klass
19
+ return @adapter_klass if @adapter_klass
20
+
21
+ if adapter_params[:host]
22
+ ::RemoteRuby::SSHAdapter
23
+ else
24
+ ::RemoteRuby::TmpFileAdapter
25
+ end
26
+ end
27
+
28
+ def build(code_hash)
29
+ res = adapter_klass.new(**adapter_params)
30
+
31
+ cache_mode = use_cache && cache_exists?(code_hash)
32
+
33
+ if cache_mode
34
+ cache_adapter(code_hash, res.connection_name)
35
+ elsif save_cache
36
+ caching_adapter(res, code_hash)
37
+ else
38
+ res
39
+ end
40
+ end
41
+
42
+ def cache_adapter(code_hash, connection_name)
43
+ ::RemoteRuby::CacheAdapter.new(
44
+ cache_path: cache_path(code_hash),
45
+ connection_name: connection_name
46
+ )
47
+ end
48
+
49
+ def caching_adapter(adapter, code_hash)
50
+ ::RemoteRuby::CachingAdapter.new(
51
+ adapter: adapter,
52
+ cache_path: cache_path(code_hash)
53
+ )
54
+ end
55
+
56
+ def context_hash(code_hash)
57
+ Digest::MD5.hexdigest(
58
+ self.class.name +
59
+ adapter_klass.name.to_s +
60
+ adapter_params.to_s +
61
+ code_hash
62
+ )
63
+ end
64
+
65
+ def cache_path(code_hash)
66
+ hsh = context_hash(code_hash)
67
+ File.join(RemoteRuby.cache_dir, hsh)
68
+ end
69
+
70
+ def cache_exists?(code_hash)
71
+ hsh = cache_path(code_hash)
72
+ File.exist?("#{hsh}.stdout") || File.exist?("#{hsh}.stderr") || File.exist?("#{hsh}.result")
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # An adapter which takes stdout, stderr and result streams
5
+ # from files and ignores all stdin. Only used to read from cache.
6
+ class CacheAdapter < ConnectionAdapter
7
+ attr_reader :connection_name
8
+
9
+ def initialize(cache_path:, connection_name:)
10
+ super
11
+ @cache_path = cache_path
12
+ @connection_name = connection_name
13
+ end
14
+
15
+ def open(_code, _stdin, stdout, stderr)
16
+ IO.copy_stream(stdout_file_path, stdout)
17
+ IO.copy_stream(stderr_file_path, stderr)
18
+
19
+ File.binread(result_file_path) if result_file_path
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :cache_path
25
+
26
+ def stdout_file_path
27
+ fp = "#{cache_path}.stdout"
28
+ File.exist?(fp) ? fp : File::NULL
29
+ end
30
+
31
+ def stderr_file_path
32
+ fp = "#{cache_path}.stderr"
33
+ File.exist?(fp) ? fp : File::NULL
34
+ end
35
+
36
+ def result_file_path
37
+ fp = "#{cache_path}.result"
38
+ File.exist?(fp) ? fp : File::NULL
39
+ end
40
+ end
41
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'remote_ruby/stream_cacher'
3
+ require 'remote_ruby/tee_writer'
4
4
 
5
5
  module RemoteRuby
6
6
  # An adapter decorator which extends the adapter passed in to its
@@ -12,17 +12,21 @@ module RemoteRuby
12
12
  @adapter = adapter
13
13
  end
14
14
 
15
- def connection_name
16
- adapter.connection_name
17
- end
15
+ def open(code, stdin, stdout, stderr)
16
+ res = nil
17
+ with_cache do |stdout_cache, stderr_cache, result_cache|
18
+ tee_out = ::RemoteRuby::TeeWriter.new(stdout, stdout_cache)
19
+ tee_err = ::RemoteRuby::TeeWriter.new(stderr, stderr_cache)
18
20
 
19
- def open(code)
20
- with_cache do |stdout_cache, stderr_cache|
21
- adapter.open(code) do |stdout, stderr|
22
- yield ::RemoteRuby::StreamCacher.new(stdout, stdout_cache),
23
- ::RemoteRuby::StreamCacher.new(stderr, stderr_cache)
24
- end
21
+ res = adapter.open(code, stdin, tee_out, tee_err)
22
+ result_cache.write(res)
25
23
  end
24
+
25
+ res
26
+ end
27
+
28
+ def connection_name
29
+ adapter.connection_name
26
30
  end
27
31
 
28
32
  private
@@ -30,13 +34,15 @@ module RemoteRuby
30
34
  attr_reader :cache_path, :adapter
31
35
 
32
36
  def with_cache
33
- stderr_cache = File.open(stderr_file_path, 'w')
34
- stdout_cache = File.open(stdout_file_path, 'w')
37
+ stderr_cache = File.open(stderr_file_path, 'wb')
38
+ stdout_cache = File.open(stdout_file_path, 'wb')
39
+ result_cache = File.open(result_file_path, 'wb')
35
40
 
36
- yield stdout_cache, stderr_cache
41
+ yield stdout_cache, stderr_cache, result_cache
37
42
  ensure
38
43
  stdout_cache.close
39
44
  stderr_cache.close
45
+ result_cache.close
40
46
  end
41
47
 
42
48
  def stdout_file_path
@@ -46,5 +52,9 @@ module RemoteRuby
46
52
  def stderr_file_path
47
53
  "#{cache_path}.stderr"
48
54
  end
55
+
56
+ def result_file_path
57
+ "#{cache_path}.result"
58
+ end
49
59
  end
50
60
  end
@@ -1,44 +1,32 @@
1
1
  # Set up Bundler if Gemfile is present
2
2
  require 'bundler/setup' if File.exist?('Gemfile')
3
3
 
4
- require 'base64'
4
+ <%= inline_file('remote_context.rb') %>
5
5
 
6
- __marshalled_locals_names__ = []
6
+ __context__ = RemoteRuby::RemoteContext.new(__FILE__)
7
7
 
8
8
  # Unmarshalling local variables
9
9
  <% client_locals_base64.each do |name, base64_val| %>
10
- <%= name %> = begin
11
- val = Marshal.load(Base64.strict_decode64('<%= base64_val %>'))
12
- __marshalled_locals_names__ << :<%= name %>
13
- val
14
- rescue ArgumentError
15
- warn("Warning: could not resolve type for '<%=name %>' variable")
16
- nil
17
- end
10
+ <%= name %> = __context__.unmarshal(:<%= name %>, '<%= base64_val %>')
18
11
  <% end %>
19
12
 
20
- __return_val__ = begin
13
+ $stdout.sync = true
14
+ $stderr.sync = true
21
15
 
22
- <% if code_headers.any? %>
23
- # Start of flavour-added code
24
- <%= code_headers.join %>
25
- # End of flavour-added code
26
- <% end %>
16
+ __context__.execute do
17
+ <% if plugins.any? %>
18
+ # Start of plugin-added code
19
+ <%plugins.each do |pl| %>
20
+ # <%= pl.class.name %>
21
+ <%= pl.code_header.gsub(/^/,' ') %>
22
+ # End of <%= pl.class.name %>
23
+ <%end%>
24
+ # End of plugin-added code
27
25
 
26
+ <% end %>
28
27
  # Start of client code
29
- <%= ruby_code %>
28
+ <%= ruby_code.gsub(/^/, ' ') %>
30
29
  # End of client code
31
30
  end
32
31
 
33
- __marshalled_locals_names__ << :__return_val__
34
-
35
- # Marshalling local variables and result
36
-
37
- $stdout.puts "%%%MARSHAL"
38
-
39
- __marshalled_locals_names__.each do |lv|
40
- data = Marshal.dump(eval(lv.to_s))
41
- data_length = data.size
42
- $stdout.puts "#{lv}:#{data_length}"
43
- $stdout.write(data)
44
- end
32
+ File.binwrite(__FILE__, __context__.dump)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # Wraps any object that responds to #readpartial with a readable IO object
5
+ class CompatIOReader
6
+ attr_reader :readable
7
+
8
+ def initialize(io)
9
+ if io.is_a?(IO)
10
+ @readable = io
11
+ return
12
+ end
13
+
14
+ raise 'Object must respond to #readpartial' unless io.respond_to?(:readpartial)
15
+
16
+ @readable, @writeable = IO.pipe
17
+ @thread = start(io)
18
+ end
19
+
20
+ def join
21
+ return unless @writeable
22
+
23
+ @writeable.close
24
+ @thread.join
25
+ @readable.close
26
+ end
27
+
28
+ private
29
+
30
+ def start(io)
31
+ Thread.new do
32
+ IO.copy_stream(io, @writeable)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # Wraps any object that responds to #write with a writeable IO object
5
+ class CompatIOWriter
6
+ attr_reader :writeable
7
+
8
+ def initialize(io)
9
+ if io.is_a?(IO)
10
+ @writeable = io
11
+ return
12
+ end
13
+
14
+ raise 'Object must respond to #write' unless io.respond_to?(:write)
15
+
16
+ @readable, @writeable = IO.pipe
17
+ @thread = start(io)
18
+ end
19
+
20
+ def join
21
+ return unless @readable
22
+
23
+ @readable.close
24
+ @thread.join
25
+ @writeable.close
26
+ end
27
+
28
+ private
29
+
30
+ def start(io)
31
+ Thread.new do
32
+ IO.copy_stream(@readable, io)
33
+ rescue IOError
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -8,10 +8,10 @@ module RemoteRuby
8
8
  # Receives client Ruby code, locals and their values and creates Ruby code
9
9
  # to be executed on the remote host.
10
10
  class Compiler
11
- def initialize(ruby_code, client_locals: {}, flavours: [])
11
+ def initialize(ruby_code, client_locals: {}, plugins: [])
12
12
  @ruby_code = ruby_code
13
13
  @client_locals = client_locals
14
- @flavours = flavours
14
+ @plugins = plugins
15
15
  end
16
16
 
17
17
  def code_hash
@@ -23,10 +23,14 @@ module RemoteRuby
23
23
 
24
24
  template_file =
25
25
  ::RemoteRuby.lib_path('remote_ruby/code_templates/compiler/main.rb.erb')
26
- template = ERB.new(File.read(template_file))
26
+ template = ERB.new(File.read(template_file), trim_mode: '<>')
27
27
  @compiled_code = template.result(binding)
28
28
  end
29
29
 
30
+ def inline_file(fname)
31
+ File.read(::RemoteRuby.lib_path('remote_ruby', fname))
32
+ end
33
+
30
34
  def client_locals_base64
31
35
  return @client_locals_base64 if @client_locals_base64
32
36
 
@@ -44,7 +48,7 @@ module RemoteRuby
44
48
 
45
49
  private
46
50
 
47
- attr_reader :ruby_code, :client_locals, :flavours
51
+ attr_reader :ruby_code, :client_locals, :plugins
48
52
 
49
53
  def process_local(name, data)
50
54
  bin_data = Marshal.dump(data)
@@ -52,9 +56,5 @@ module RemoteRuby
52
56
  rescue TypeError => e
53
57
  warn "Cannot send variable '#{name}': #{e.message}"
54
58
  end
55
-
56
- def code_headers
57
- flavours.map(&:code_header)
58
- end
59
59
  end
60
60
  end
@@ -1,30 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RemoteRuby
4
- # Base class for other connection adapters.
4
+ # Base class for connection adapters.
5
5
  class ConnectionAdapter
6
6
  # Initializers of adapters should receive only keyword arguments.
7
7
  # May be overriden in a child class.
8
8
  def initialize(**args); end
9
9
 
10
- # This will be displayed as a prefix when adapter writes something to
11
- # emulated standard output or standard error. May be overriden in a child
12
- # class.
13
- def connection_name
14
- self.class.name
10
+ # Override in child class.
11
+ # Accepts compiled Ruby code as string,
12
+ # readable IO for stdin, writable IO for stdout and stderr.
13
+ # Should return a binary result stream.
14
+ def open(_code, _stdin, _stdout, _stderr)
15
+ # :nocov:
16
+ raise NotImplementedError
17
+ # :nocov:
15
18
  end
16
19
 
17
- # Override in child class. Receives Ruby code as string and yields
18
- # two readable streams: for emulated standard output and standard error.
19
- def open(_code)
20
- raise NotImplementedError
20
+ def connection_name
21
+ "#{self.class.name} "
21
22
  end
22
23
  end
23
24
  end
24
25
 
25
- require 'remote_ruby/connection_adapter/eval_adapter'
26
- require 'remote_ruby/connection_adapter/stdin_process_adapter'
27
- require 'remote_ruby/connection_adapter/ssh_stdin_adapter'
28
- require 'remote_ruby/connection_adapter/local_stdin_adapter'
29
- require 'remote_ruby/connection_adapter/cache_adapter'
30
- require 'remote_ruby/connection_adapter/caching_adapter'
26
+ require 'remote_ruby/cache_adapter'
27
+ require 'remote_ruby/caching_adapter'
28
+ require 'remote_ruby/ssh_adapter'
29
+ require 'remote_ruby/tmp_file_adapter'
30
+ require 'remote_ruby/text_mode_adapter'
@@ -7,53 +7,65 @@ require 'remote_ruby/compiler'
7
7
  require 'remote_ruby/connection_adapter'
8
8
  require 'remote_ruby/locals_extractor'
9
9
  require 'remote_ruby/source_extractor'
10
- require 'remote_ruby/flavour'
11
- require 'remote_ruby/runner'
10
+ require 'remote_ruby/plugin'
11
+ require 'remote_ruby/remote_context'
12
+ require 'remote_ruby/remote_error'
13
+ require 'remote_ruby/adapter_builder'
14
+ require 'remote_ruby/text_mode_builder'
12
15
 
13
16
  module RemoteRuby
14
17
  # This class is responsible for executing blocks on the remote host with the
15
18
  # specified adapters. This is the entrypoint to RemoteRuby logic.
16
19
  class ExecutionContext
17
20
  def initialize(**params)
18
- add_flavours(params)
19
- @use_cache = params.delete(:use_cache) || false
20
- @save_cache = params.delete(:save_cache) || false
21
- @cache_dir = params.delete(:cache_dir) || File.join(Dir.pwd, 'cache')
22
- @out_stream = params.delete(:out_stream) || $stdout
23
- @err_stream = params.delete(:err_stream) || $stderr
24
- @adapter_klass = params.delete(:adapter) || ::RemoteRuby::SSHStdinAdapter
25
- @params = params
26
-
27
- FileUtils.mkdir_p(@cache_dir)
21
+ add_plugins(params)
22
+ configure_streams(params)
23
+ @dump_code = params.delete(:dump_code) || false
24
+ @text_mode_builder = RemoteRuby::TextModeBuilder.new(
25
+ out_tty: out_stream.tty?,
26
+ err_tty: err_stream.tty?,
27
+ params: params
28
+ )
29
+ @adapter_builder = RemoteRuby::AdapterBuilder.new(**params)
28
30
  end
29
31
 
30
32
  def execute(locals = nil, &block)
31
33
  source = code_source(block)
32
34
  locals ||= extract_locals(block)
33
35
 
34
- result = execute_code(source, **locals)
36
+ compiler = build_compiler(source, locals)
37
+ context = execute_code(compiler)
38
+
39
+ assign_locals(locals.keys, context.locals, block)
35
40
 
36
- assign_locals(locals.keys, result[:locals], block)
41
+ if context.error?
42
+ raise RemoteRuby::RemoteError.new(
43
+ compiler.compiled_code,
44
+ context,
45
+ code_path(compiler.code_hash)
46
+ )
47
+ end
37
48
 
38
- result[:result]
49
+ context.result
39
50
  end
40
51
 
41
52
  private
42
53
 
43
- attr_reader :params, :adapter_klass, :use_cache, :save_cache, :cache_dir,
44
- :out_stream, :err_stream, :flavours
54
+ attr_reader :dump_code, :in_stream, :out_stream, :err_stream, :plugins, :adapter_builder, :text_mode_builder
45
55
 
46
- def assign_locals(local_names, values, block)
47
- local_names.each do |local|
48
- next unless values.key?(local)
56
+ def add_plugins(params)
57
+ @plugins = ::RemoteRuby::Plugin.build_plugins(params)
58
+ end
49
59
 
50
- block.binding.local_variable_set(local, values[local])
51
- end
60
+ def configure_streams(params)
61
+ @in_stream = params.delete(:in_stream) || $stdin
62
+ @out_stream = params.delete(:out_stream) || $stdout
63
+ @err_stream = params.delete(:err_stream) || $stderr
52
64
  end
53
65
 
54
66
  def extract_locals(block)
55
67
  extractor =
56
- ::RemoteRuby::LocalsExtractor.new(block, ignore_types: self.class)
68
+ ::RemoteRuby::LocalsExtractor.new(block, ignore_types: RemoteRuby.ignored_types)
57
69
  extractor.locals
58
70
  end
59
71
 
@@ -62,74 +74,44 @@ module RemoteRuby
62
74
  source_extractor.extract(&block)
63
75
  end
64
76
 
65
- def context_hash(code_hash)
66
- Digest::MD5.hexdigest(
67
- self.class.name +
68
- adapter_klass.name.to_s +
69
- params.to_s +
70
- code_hash
71
- )
72
- end
73
-
74
- def cache_path(code_hash)
75
- hsh = context_hash(code_hash)
76
- File.join(cache_dir, hsh)
77
- end
78
-
79
- def cache_exists?(code_hash)
80
- hsh = cache_path(code_hash)
81
- File.exist?("#{hsh}.stdout") || File.exist?("#{hsh}.stderr")
82
- end
83
-
84
- def compiler(ruby_code, client_locals)
77
+ def build_compiler(ruby_code, client_locals)
85
78
  RemoteRuby::Compiler.new(
86
79
  ruby_code,
87
80
  client_locals: client_locals,
88
- flavours: flavours
81
+ plugins: plugins
89
82
  )
90
83
  end
91
84
 
92
- def execute_code(ruby_code, client_locals = {})
93
- compiler = compiler(ruby_code, client_locals)
85
+ def execute_code(compiler)
86
+ write_code(compiler.code_hash, compiler.compiled_code)
94
87
 
95
- runner = ::RemoteRuby::Runner.new(
96
- code: compiler.compiled_code,
97
- adapter: adapter(compiler.code_hash),
98
- out_stream: out_stream,
99
- err_stream: err_stream
100
- )
88
+ adapter = adapter_builder.build(compiler.code_hash)
89
+ adapter = text_mode_builder.build(adapter)
101
90
 
102
- runner.run
91
+ # rubocop:disable Security/MarshalLoad
92
+ Marshal.load(adapter.open(compiler.compiled_code, in_stream, out_stream, err_stream))
93
+ # rubocop:enable Security/MarshalLoad
103
94
  end
104
95
 
105
- def adapter(code_hash)
106
- actual_adapter = adapter_klass.new(**params)
96
+ def assign_locals(local_names, values, block)
97
+ local_names.each do |local|
98
+ next unless values.key?(local)
107
99
 
108
- if use_cache && cache_exists?(code_hash)
109
- cache_adapter(actual_adapter, code_hash)
110
- elsif save_cache
111
- caching_adapter(actual_adapter, code_hash)
112
- else
113
- actual_adapter
100
+ block.binding.local_variable_set(local, values[local])
114
101
  end
115
102
  end
116
103
 
117
- def cache_adapter(adapter, code_hash)
118
- ::RemoteRuby::CacheAdapter.new(
119
- connection_name: adapter.connection_name,
120
- cache_path: cache_path(code_hash)
121
- )
122
- end
104
+ def code_path(code_hash)
105
+ return nil unless dump_code
123
106
 
124
- def caching_adapter(adapter, code_hash)
125
- ::RemoteRuby::CachingAdapter.new(
126
- adapter: adapter,
127
- cache_path: cache_path(code_hash)
128
- )
107
+ File.join(RemoteRuby.code_dir, "#{code_hash}.rb")
129
108
  end
130
109
 
131
- def add_flavours(params)
132
- @flavours = ::RemoteRuby::Flavour.build_flavours(params)
110
+ def write_code(code_hash, code)
111
+ return unless dump_code
112
+
113
+ RemoteRuby.ensure_code_dir
114
+ File.write(code_path(code_hash), code)
133
115
  end
134
116
  end
135
117
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'remote_ruby/execution_context'
4
+
5
+ module RemoteRuby
6
+ # Module to include in the global scope to provide the `remotely` method
7
+ module Extensions
8
+ def remotely(args = {}, &block)
9
+ locals = args.delete(:locals)
10
+ execution_context = ::RemoteRuby::ExecutionContext.new(**args)
11
+ execution_context.execute(locals, &block)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # Serves to dynamically require the parser gem and configure it.
5
+ # This is done in a separate module to have a possibility
6
+ # to suppress parser gem warnings about Ruby compatibility.
7
+ module ParserFactory
8
+ def self.require_parser
9
+ begin
10
+ prev = $VERBOSE
11
+ $VERBOSE = nil if RemoteRuby.suppress_parser_warnings
12
+ require 'parser/current'
13
+ require 'unparser'
14
+ ensure
15
+ $VERBOSE = prev
16
+ end
17
+
18
+ # Opt-in to most recent AST format
19
+ Parser::Builders::Default.emit_lambda = true
20
+ Parser::Builders::Default.emit_procarg0 = true
21
+ Parser::Builders::Default.emit_encoding = true
22
+ Parser::Builders::Default.emit_index = true
23
+ Parser::Builders::Default.emit_arg_inside_procarg0 = true
24
+ Parser::Builders::Default.emit_forward_arg = true
25
+ Parser::Builders::Default.emit_kwargs = true
26
+ Parser::Builders::Default.emit_match_pattern = true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemoteRuby
4
+ # Base class for Plugins to execution context to insert additonal
5
+ # code to the generated remote code.
6
+ class Plugin
7
+ class << self
8
+ def build_plugins(args = {})
9
+ res = []
10
+
11
+ RemoteRuby.plugins.each do |name, klass|
12
+ options = args.delete(name)
13
+
14
+ res << klass.new(**options) if options
15
+ end
16
+
17
+ res
18
+ end
19
+ end
20
+
21
+ def initialize(**args); end
22
+
23
+ def code_header; end
24
+ end
25
+ end