nodo 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +128 -0
- data/lib/nodo/client.rb +38 -0
- data/lib/nodo/constant.rb +13 -0
- data/lib/nodo/core.rb +209 -0
- data/lib/nodo/dependency.rb +13 -0
- data/lib/nodo/errors.rb +49 -0
- data/lib/nodo/function.rb +13 -0
- data/lib/nodo/railtie.rb +8 -0
- data/lib/nodo/script.rb +13 -0
- data/lib/nodo/version.rb +3 -0
- data/lib/nodo.rb +24 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f997b2c506f2cec9bfd235c68cb0db93fcb103fa7f9b40f03740ca27b59ffb4
|
4
|
+
data.tar.gz: 8a1ba6ae691ee2a992b2af5a127c43c70178cc403134db04a08f9597dd8588b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77c7a96627569da1b4093eb0fbfb6ab94c0c7dae4e425b019d52e06183f098daac57b88f68924fbe117afc1a0cf8be3aec143fd8e6935c839ebd573dde5afd70
|
7
|
+
data.tar.gz: 8057351d0968d8bc357af3926b805f82ce6e05949ee54efb9df40293ba4faf0ce8d337550318de0114c7f9dc2df05958ea481dc35aa2981a0d95501c9cb18c64
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Matthias Grosser
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/nodo.svg)](http://badge.fury.io/rb/nodo)
|
2
|
+
|
3
|
+
# Nōdo – call Node.js from Ruby
|
4
|
+
|
5
|
+
`Nodo` provides a Ruby environment to interact with JavaScript running inside a Node process.
|
6
|
+
|
7
|
+
ノード means "node" in Japanese.
|
8
|
+
|
9
|
+
## Why Nodo?
|
10
|
+
|
11
|
+
Nodo will dispatch all JS function calls to a single long-running Node process.
|
12
|
+
|
13
|
+
JavaScript code is run in a namespaced environment, where you can access your initialized
|
14
|
+
JS objects during sequential function calls without having to re-initialize them.
|
15
|
+
|
16
|
+
IPC is done via unix sockets, greatly improving performance over classic process/eval solutions.
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
In your Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'nodo'
|
24
|
+
```
|
25
|
+
|
26
|
+
### Node.js
|
27
|
+
|
28
|
+
Nodo requires a working installation of Node.js.
|
29
|
+
|
30
|
+
If the executable is located in your `PATH`, no configuration is required. Otherwise, the path to to binary can be set using:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Nodo.binary = '/usr/local/bin/node'
|
34
|
+
```
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
In Nodo, you define JS functions as you would define Ruby methods:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class Foo < Nodo::Core
|
42
|
+
|
43
|
+
function :say_hi, <<~JS
|
44
|
+
(name) => {
|
45
|
+
return `Hello ${name}!`;
|
46
|
+
}
|
47
|
+
JS
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
foo = Foo.new
|
52
|
+
foo.say_hi('Nodo')
|
53
|
+
=> "Hello Nodo!"
|
54
|
+
```
|
55
|
+
|
56
|
+
### Using npm modules
|
57
|
+
|
58
|
+
Install your modules to `node_modules`:
|
59
|
+
|
60
|
+
```shell
|
61
|
+
$ yarn add uuid
|
62
|
+
```
|
63
|
+
|
64
|
+
Then `require` your dependencies:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class Bar < Nodo::Core
|
68
|
+
require :uuid
|
69
|
+
|
70
|
+
function :v4, <<~JS
|
71
|
+
() => {
|
72
|
+
return uuid.v4();
|
73
|
+
}
|
74
|
+
JS
|
75
|
+
end
|
76
|
+
|
77
|
+
bar = Bar.new
|
78
|
+
bar.v4 => "b305f5c4-db9a-4504-b0c3-4e097a5ec8b9"
|
79
|
+
```
|
80
|
+
|
81
|
+
### Aliasing requires
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class FooBar < Nodo::Core
|
85
|
+
require commonjs: '@rollup/plugin-commonjs'
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
### Setting NODE_PATH
|
90
|
+
|
91
|
+
By default, `./node_modules` is used as the `NODE_PATH`.
|
92
|
+
|
93
|
+
To set a custom path:
|
94
|
+
```ruby
|
95
|
+
Nodo.modules_root = 'path/to/node_modules'
|
96
|
+
```
|
97
|
+
|
98
|
+
For Rails applications, it will be set to `vendor/node_modules`.
|
99
|
+
To use the Rails 6 default of putting `node_modules` to `RAILS_ROOT`:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# config/initializers/nodo.rb
|
103
|
+
Nodo.modules_root = Rails.root.join('node_modules')
|
104
|
+
```
|
105
|
+
|
106
|
+
### Defining JS constants
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class BarFoo < Nodo::Core
|
110
|
+
const :HELLO, "World"
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### Execute some custom JS during initialization
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class BarFoo < Nodo::Core
|
118
|
+
|
119
|
+
script <<~JS
|
120
|
+
// some custom JS
|
121
|
+
// to be executed during initialization
|
122
|
+
JS
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
### Inheritance
|
127
|
+
|
128
|
+
Subclasses will inherit functions, constants, dependencies and scripts from their superclasses, while only functions can be overwritten.
|
data/lib/nodo/client.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Nodo
|
4
|
+
class Client < Net::HTTP
|
5
|
+
UNIX_REGEXP = /\Aunix:\/\//i
|
6
|
+
|
7
|
+
def initialize(address, port = nil)
|
8
|
+
super(address, port)
|
9
|
+
case address
|
10
|
+
when UNIX_REGEXP
|
11
|
+
@socket_type = 'unix'
|
12
|
+
@socket_path = address.sub(UNIX_REGEXP, '')
|
13
|
+
# Host header is required for HTTP/1.1
|
14
|
+
@address = 'localhost'
|
15
|
+
@port = 80
|
16
|
+
else
|
17
|
+
@socket_type = 'inet'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect
|
22
|
+
if @socket_type == 'unix'
|
23
|
+
connect_unix
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect_unix
|
30
|
+
s = Timeout.timeout(@open_timeout) { UNIXSocket.open(@socket_path) }
|
31
|
+
@socket = Net::BufferedIO.new(s)
|
32
|
+
@socket.read_timeout = @read_timeout
|
33
|
+
@socket.continue_timeout = @continue_timeout
|
34
|
+
@socket.debug_output = @debug_output
|
35
|
+
on_connect
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/nodo/core.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
module Nodo
|
2
|
+
class Core
|
3
|
+
SOCKET_NAME = 'nodo.sock'
|
4
|
+
DEFINE_METHOD = '__nodo_define_class__'
|
5
|
+
TIMEOUT = 5
|
6
|
+
ARRAY_CLASS_ATTRIBUTES = %i[dependencies constants scripts].freeze
|
7
|
+
HASH_CLASS_ATTRIBUTES = %i[functions].freeze
|
8
|
+
CLASS_ATTRIBUTES = (ARRAY_CLASS_ATTRIBUTES + HASH_CLASS_ATTRIBUTES).freeze
|
9
|
+
|
10
|
+
@@node_pid = nil
|
11
|
+
@@tmpdir = nil
|
12
|
+
@@mutex = Mutex.new
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
attr_accessor :class_defined
|
17
|
+
|
18
|
+
def inherited(subclass)
|
19
|
+
CLASS_ATTRIBUTES.each do |attr|
|
20
|
+
subclass.send "#{attr}=", send(attr).dup
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def instance
|
25
|
+
@instance ||= new
|
26
|
+
end
|
27
|
+
|
28
|
+
def class_defined?
|
29
|
+
!!class_defined
|
30
|
+
end
|
31
|
+
|
32
|
+
def clsid
|
33
|
+
name || "Class:0x#{object_id.to_s(0x10)}"
|
34
|
+
end
|
35
|
+
|
36
|
+
CLASS_ATTRIBUTES.each do |attr|
|
37
|
+
define_method "#{attr}=" do |value|
|
38
|
+
instance_variable_set :"@#{attr}", value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ARRAY_CLASS_ATTRIBUTES.each do |attr|
|
43
|
+
define_method "#{attr}" do
|
44
|
+
instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", [])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
HASH_CLASS_ATTRIBUTES.each do |attr|
|
49
|
+
define_method "#{attr}" do
|
50
|
+
instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", {})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def require(*mods)
|
55
|
+
deps = mods.last.is_a?(Hash) ? mods.pop : {}
|
56
|
+
mods = mods.map { |m| [m, m] }.to_h
|
57
|
+
self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def function(name, code)
|
61
|
+
self.functions = functions.merge(name => Function.new(name, code, caller.first))
|
62
|
+
define_method(name) { |*args| call_js_method(name, args) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def const(name, value)
|
66
|
+
self.constants = constants + [Constant.new(name, value)]
|
67
|
+
end
|
68
|
+
|
69
|
+
def script(code)
|
70
|
+
self.scripts = scripts + [Script.new(code)]
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate_core_code
|
74
|
+
<<~JS
|
75
|
+
global.nodo = require(#{nodo_js});
|
76
|
+
|
77
|
+
const socket = process.argv[1];
|
78
|
+
if (!socket) {
|
79
|
+
process.stderr.write('Socket path is required\\n');
|
80
|
+
process.exit(1);
|
81
|
+
}
|
82
|
+
|
83
|
+
const shutdown = () => {
|
84
|
+
nodo.core.close(() => { process.exit(0) });
|
85
|
+
};
|
86
|
+
|
87
|
+
process.on('SIGINT', shutdown);
|
88
|
+
process.on('SIGTERM', shutdown);
|
89
|
+
|
90
|
+
nodo.core.run(socket);
|
91
|
+
JS
|
92
|
+
end
|
93
|
+
|
94
|
+
def generate_class_code
|
95
|
+
<<~JS
|
96
|
+
(() => {
|
97
|
+
const __nodo_log = nodo.log;
|
98
|
+
const __nodo_klass__ = {};
|
99
|
+
#{dependencies.map(&:to_js).join}
|
100
|
+
#{constants.map(&:to_js).join}
|
101
|
+
#{functions.values.map(&:to_js).join}
|
102
|
+
#{scripts.map(&:to_js).join}
|
103
|
+
return __nodo_klass__;
|
104
|
+
})()
|
105
|
+
JS
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
def finalize(pid, tmpdir)
|
111
|
+
proc do
|
112
|
+
Process.kill(:SIGTERM, pid)
|
113
|
+
Process.wait(pid)
|
114
|
+
FileUtils.remove_entry(tmpdir) if File.directory?(tmpdir)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def nodo_js
|
121
|
+
Pathname.new(__FILE__).dirname.join('nodo.js').to_s.to_json
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize
|
126
|
+
@@mutex.synchronize do
|
127
|
+
ensure_process_is_spawned
|
128
|
+
wait_for_socket
|
129
|
+
ensure_class_is_defined
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def node_pid
|
134
|
+
@@node_pid
|
135
|
+
end
|
136
|
+
|
137
|
+
def tmpdir
|
138
|
+
@@tmpdir
|
139
|
+
end
|
140
|
+
|
141
|
+
def socket_path
|
142
|
+
tmpdir && tmpdir.join(SOCKET_NAME)
|
143
|
+
end
|
144
|
+
|
145
|
+
def clsid
|
146
|
+
self.class.clsid
|
147
|
+
end
|
148
|
+
|
149
|
+
def ensure_process_is_spawned
|
150
|
+
return if node_pid
|
151
|
+
spawn_process
|
152
|
+
end
|
153
|
+
|
154
|
+
def ensure_class_is_defined
|
155
|
+
return if self.class.class_defined?
|
156
|
+
call_js_method(DEFINE_METHOD, self.class.generate_class_code)
|
157
|
+
self.class.class_defined = true
|
158
|
+
# rescue => e
|
159
|
+
# raise Error, e.message
|
160
|
+
end
|
161
|
+
|
162
|
+
def spawn_process
|
163
|
+
@@tmpdir = Pathname.new(Dir.mktmpdir('nodo'))
|
164
|
+
env = Nodo.env.merge('NODE_PATH' => Nodo.modules_root.to_s)
|
165
|
+
@@node_pid = Process.spawn(env, Nodo.binary, '-e', self.class.generate_core_code, '--', socket_path.to_s)
|
166
|
+
ObjectSpace.define_finalizer(self, self.class.send(:finalize, node_pid, tmpdir))
|
167
|
+
end
|
168
|
+
|
169
|
+
def wait_for_socket
|
170
|
+
start = Time.now
|
171
|
+
until socket_path.exist?
|
172
|
+
raise TimeoutError, "socket #{socket_path} not found" if Time.now - start > TIMEOUT
|
173
|
+
sleep(0.2)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def call_js_method(method, args)
|
178
|
+
raise CallError, 'Node process not ready' unless node_pid
|
179
|
+
raise CallError, "Class #{clsid} not defined" unless self.class.class_defined? || method == DEFINE_METHOD
|
180
|
+
function = self.class.functions[method]
|
181
|
+
raise NameError, "undefined function `#{method}' for #{self.class}" unless function || method == DEFINE_METHOD
|
182
|
+
request = Net::HTTP::Post.new("/#{clsid}/#{method}", 'Content-Type': 'application/json')
|
183
|
+
request.body = JSON.dump(args)
|
184
|
+
client = Client.new("unix://#{socket_path}")
|
185
|
+
response = client.request(request)
|
186
|
+
if response.is_a?(Net::HTTPOK)
|
187
|
+
parse_response(response)
|
188
|
+
else
|
189
|
+
handle_error(response, function)
|
190
|
+
end
|
191
|
+
rescue Errno::EPIPE, IOError
|
192
|
+
# TODO: restart or something? If this happens the process is completely broken
|
193
|
+
raise Error, 'Node process failed'
|
194
|
+
end
|
195
|
+
|
196
|
+
def handle_error(response, function)
|
197
|
+
if response.body
|
198
|
+
result = parse_response(response)
|
199
|
+
raise JavaScriptError.new(result['error'], function) if result.is_a?(Hash) && result.key?('error')
|
200
|
+
end
|
201
|
+
raise CallError, "Node returned #{response.code}"
|
202
|
+
end
|
203
|
+
|
204
|
+
def parse_response(response)
|
205
|
+
JSON.parse(response.body.force_encoding('UTF-8'))
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
data/lib/nodo/errors.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Nodo
|
2
|
+
class Error < StandardError; end
|
3
|
+
class TimeoutError < Error; end
|
4
|
+
class CallError < Error; end
|
5
|
+
|
6
|
+
class JavaScriptError < Error
|
7
|
+
attr_reader :attributes
|
8
|
+
|
9
|
+
def initialize(attributes = {}, function = nil)
|
10
|
+
@attributes = attributes || {}
|
11
|
+
if backtrace = generate_backtrace(attributes['stack'])
|
12
|
+
backtrace.unshift function.source_location if function && function.source_location
|
13
|
+
set_backtrace backtrace
|
14
|
+
end
|
15
|
+
@message = generate_message
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@message
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# "filename:lineNo: in `method''' or “filename:lineNo.''
|
25
|
+
|
26
|
+
def generate_backtrace(stack)
|
27
|
+
backtrace = []
|
28
|
+
if stack and lines = stack.split("\n")
|
29
|
+
lines.shift
|
30
|
+
lines.each do |line|
|
31
|
+
if match = line.match(/\A *at (?<call>.+) \((?<src>.*):(?<line>\d+):(?<column>\d+)\)/)
|
32
|
+
backtrace << "#{match[:src]}:#{match[:line]}:in `#{match[:call]}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
backtrace unless backtrace.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_message
|
40
|
+
message = "#{attributes['message'] || 'Unknown error'}"
|
41
|
+
if loc = attributes['loc']
|
42
|
+
message << loc.inject(' in') { |s, (key, value)| s << " #{key}: #{value}" }
|
43
|
+
end
|
44
|
+
message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class DependencyError < JavaScriptError; end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Nodo
|
2
|
+
class Function
|
3
|
+
attr_reader :name, :code, :source_location
|
4
|
+
|
5
|
+
def initialize(name, code, source_location)
|
6
|
+
@name, @code, @source_location = name, code, source_location
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_js
|
10
|
+
"const #{name} = __nodo_klass__.#{name} = (#{code});\n"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/nodo/railtie.rb
ADDED
data/lib/nodo/script.rb
ADDED
data/lib/nodo/version.rb
ADDED
data/lib/nodo.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'json'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
module Nodo
|
7
|
+
class << self
|
8
|
+
attr_accessor :modules_root, :env, :binary
|
9
|
+
end
|
10
|
+
self.modules_root = './node_modules'
|
11
|
+
self.env = {}
|
12
|
+
self.binary = 'node'
|
13
|
+
end
|
14
|
+
|
15
|
+
require_relative 'nodo/version'
|
16
|
+
require_relative 'nodo/errors'
|
17
|
+
require_relative 'nodo/dependency'
|
18
|
+
require_relative 'nodo/function'
|
19
|
+
require_relative 'nodo/script'
|
20
|
+
require_relative 'nodo/constant'
|
21
|
+
require_relative 'nodo/client'
|
22
|
+
require_relative 'nodo/core'
|
23
|
+
|
24
|
+
require_relative 'nodo/railtie' if defined?(Rails)
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nodo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthias Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-02 00:00:00.000000000 Z
|
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'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Fast Ruby bridge to run JavaScript inside a Node process
|
70
|
+
email:
|
71
|
+
- mtgrosser@gmx.net
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- LICENSE
|
77
|
+
- README.md
|
78
|
+
- lib/nodo.rb
|
79
|
+
- lib/nodo/client.rb
|
80
|
+
- lib/nodo/constant.rb
|
81
|
+
- lib/nodo/core.rb
|
82
|
+
- lib/nodo/dependency.rb
|
83
|
+
- lib/nodo/errors.rb
|
84
|
+
- lib/nodo/function.rb
|
85
|
+
- lib/nodo/railtie.rb
|
86
|
+
- lib/nodo/script.rb
|
87
|
+
- lib/nodo/version.rb
|
88
|
+
homepage: https://github.com/mtgrosser/nodo
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 2.3.0
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.0.3
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Call Node.js from Ruby
|
111
|
+
test_files: []
|