schmooze 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b6a0a8f35d4f1469388e762084d088396808bddb
4
+ data.tar.gz: a580bee0dcadce15ad6b0b0cd3da7b0c7cbca61a
5
+ SHA512:
6
+ metadata.gz: 72e0014d48a46d389e088c9ce5675d353febdf1b0cbdb514e496890428ffa067d8c3f1d905f4a3d3892acebc6c9bddd2bf14d8db0ba40148896139e540ddfff6
7
+ data.tar.gz: aa9d86a8bed3b12741f68096b01d0865224227bd2c82ed1394abf45f920d9d2d70f05a056d3420dc88659be76f3fc685e58f77a2df318d9e1edfb7bf1b92a693
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ node_modules
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in schmooze.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Shopify Inc.
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.
@@ -0,0 +1,106 @@
1
+ # Schmooze
2
+
3
+ Schmooze lets Ruby and Node.js work together intimately. It has a DSL that allows you to define what methods you need, and it executes code by spawning a Node.js process and sending messages back and forth.
4
+
5
+ ## Requirements
6
+
7
+ Schmooze requires that you have [nodejs](https://nodejs.org/en/) installed and in the `$PATH`.
8
+
9
+ ## Gem Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'schmooze'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ ## Usage
22
+
23
+ To use Schmooze, you first need to create a sublcass of `Schmooze::Base`. Your subclass needs to list all of the package dependencies, and methods that you want to have available. For example, here is a Schmooze class that interfaces with [Babel](https://babeljs.io/):
24
+
25
+ ```ruby
26
+ require 'schmooze'
27
+
28
+ class BabelSchmoozer < Schmooze::Base
29
+ dependencies babel: 'babel-core'
30
+
31
+ method :transform, 'babel.transform'
32
+ method :version, 'function() { return [process.version, babel.version]; }'
33
+ end
34
+ ```
35
+
36
+ Note that the `babel-core` package is available under the name `babel`, because that's how we requested it.
37
+
38
+ To define a method, you simply give it a name and pass in a JavaScript string that should resolve to a function. Let's put this class to use!
39
+
40
+ First we need to make sure we install any needed packages.
41
+
42
+ `$ npm install babel-core babel-preset-es2015`
43
+
44
+ All we need to do next is to instantiate the class with a path to where the node modules are installed, and then we can call the methods! (Note that we need to pass in `ast: false` because of a [caveat](#caveats)).
45
+
46
+ ```ruby
47
+ $ pry
48
+ Ruby 2.2.2
49
+ pry> load './babel_schmoozer.rb'
50
+ pry> babel = BabelSchmoozer.new(__dir__)
51
+ pry> babel.version
52
+ => ["v5.5.0", "6.5.2"]
53
+ pry> puts babel.transform('a = () => 1', ast: false, presets: ['es2015'])['code']
54
+ "use strict";
55
+
56
+ a = function a() {
57
+ return 1;
58
+ };
59
+ ```
60
+
61
+ This could easily be turned into a Sprockets plugin.
62
+
63
+ ## Error handling
64
+
65
+ Errors happen, and Schmooze tries to make them as painless as possible to handle. If there is a dependency missing, Schmooze will throw a helpful Error when you try to initialize the class. Here is an example from the tests:
66
+
67
+ ```ruby
68
+ class ErrorSchmoozer < Schmooze::Base
69
+ dependencies nonexistant: 'this-package-is-not-here'
70
+ end
71
+ ErrorSchmoozer.new(__dir__)
72
+ ```
73
+
74
+ This will raise
75
+
76
+ ```
77
+ Schmooze::DependencyError: Cannot find module 'this-package-is-not-here'.
78
+ You need to add it to '/Users/bouke/code/schmooze/test/fixtures/uninstalled_package/package.json' and run 'npm install'
79
+ ```
80
+
81
+ Any JavaScript errors that happen get converted to Ruby errors under the `Schmooze::Javascript` namespace. For example (once again, from the tests):
82
+
83
+ ```ruby
84
+ class CoffeeSchmoozer < Schmooze::Base
85
+ dependencies coffee: 'coffee-script'
86
+ method :compile, 'coffee.compile'
87
+ end
88
+
89
+ CoffeeSchmoozer.new(dir).compile('<=> 1')
90
+ ```
91
+
92
+ This will raise
93
+
94
+ ```
95
+ Schmooze::JavaScript::SyntaxError: [stdin]:1:1: error: unexpected <=
96
+ <=> 1
97
+ ^^
98
+ ```
99
+
100
+ ## Caveats
101
+
102
+ * Because we serialize the return values from JavaScript to JSON, you can't return circular data structures (like the Babel AST).
103
+
104
+ ## Contributing
105
+
106
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/schmooze.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,13 @@
1
+ machine:
2
+ node:
3
+ version: v5.7.0
4
+ ruby:
5
+ version: 2.2.2
6
+
7
+ dependencies:
8
+ override:
9
+ - script/setup
10
+
11
+ test:
12
+ override:
13
+ - script/test
@@ -0,0 +1,7 @@
1
+ module Schmooze
2
+ end
3
+
4
+ require 'schmooze/version'
5
+ require 'schmooze/errors'
6
+ require 'schmooze/processor_generator'
7
+ require 'schmooze/base'
@@ -0,0 +1,118 @@
1
+ require 'json'
2
+ require 'open3'
3
+
4
+ require 'schmooze/processor_generator'
5
+
6
+ module Schmooze
7
+ class Base
8
+ class << self
9
+ protected
10
+ def dependencies(deps)
11
+ @imports ||= []
12
+ deps.each do |identifier, package|
13
+ @imports << {
14
+ identifier: identifier,
15
+ package: package
16
+ }
17
+ end
18
+ end
19
+
20
+ def method(name, code)
21
+ @methods ||= []
22
+ @methods << {
23
+ name: name,
24
+ code: code
25
+ }
26
+
27
+ define_method(name) do |*args|
28
+ call_js_method(name, args)
29
+ end
30
+ end
31
+
32
+ def finalize(stdin, stdout, stderr, process_thread)
33
+ proc do
34
+ stdin.close
35
+ stdout.close
36
+ stderr.close
37
+ Process.kill(0, process_thread.pid)
38
+ process_thread.value
39
+ end
40
+ end
41
+ end
42
+
43
+ def initialize(root, env={})
44
+ @env = env
45
+ @root = root
46
+ @code = ProcessorGenerator.generate(self.class.instance_variable_get(:@imports) || [], self.class.instance_variable_get(:@methods) || [])
47
+
48
+ spawn_process
49
+ end
50
+
51
+ def pid
52
+ @process_thread.pid
53
+ end
54
+
55
+ private
56
+ def spawn_process
57
+ @stdin, @stdout, @stderr, @process_thread = Open3.popen3(
58
+ @env,
59
+ 'node',
60
+ '-e',
61
+ @code,
62
+ chdir: @root
63
+ )
64
+ ensure_packages_are_initiated
65
+ ObjectSpace.define_finalizer(self, self.class.send(:finalize, @stdin, @stdout, @stderr, @process_thread))
66
+ end
67
+
68
+ def ensure_packages_are_initiated
69
+ input = @stdout.gets
70
+ result = JSON.parse(input)
71
+ unless result[0] == 'ok'
72
+ @stdin.close
73
+ @stdout.close
74
+ @stderr.close
75
+ @process_thread.join
76
+
77
+ error_message = result[1]
78
+ if /\AError: Cannot find module '(.*)'\z/ =~ error_message
79
+ package_name = $1
80
+ package_json_path = File.join(@root, 'package.json')
81
+ begin
82
+ package = JSON.parse(File.read(package_json_path))
83
+ %w(dependencies devDependencies).each do |key|
84
+ if package.has_key?(key) && package[key].has_key?(package_name)
85
+ raise Schmooze::DependencyError, "Cannot find module '#{package_name}'. The module was found in '#{package_json_path}' however, please run 'npm install' from '#{@root}'"
86
+ end
87
+ end
88
+ rescue Errno::ENOENT
89
+ end
90
+ raise Schmooze::DependencyError, "Cannot find module '#{package_name}'. You need to add it to '#{package_json_path}' and run 'npm install'"
91
+ else
92
+ raise Schmooze::Error, error_message
93
+ end
94
+ end
95
+ end
96
+
97
+ def call_js_method(method, args)
98
+ @stdin.puts JSON.dump([method, args])
99
+ input = @stdout.gets
100
+ raise Errno::EPIPE, "Can't read from stdout" if input.nil?
101
+
102
+ status, message, error_class = JSON.parse(input)
103
+
104
+ if status == 'ok'
105
+ message
106
+ else
107
+ if error_class.nil?
108
+ raise Schmooze::JavaScript::UnknownError, message
109
+ else
110
+ raise Schmooze::JavaScript.const_get(error_class, false), message
111
+ end
112
+ end
113
+ rescue Errno::EPIPE
114
+ # TODO(bouk): restart or something? If this happens the process is completely broken
115
+ raise ::StandardError, "Schmooze process failed:\n#{@stderr.read}"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,11 @@
1
+ module Schmooze
2
+ Error = Class.new(StandardError)
3
+ DependencyError = Class.new(Error)
4
+ module JavaScript
5
+ Error = Class.new(::Schmooze::Error)
6
+ UnknownError = Class.new(Error)
7
+ def self.const_missing(name)
8
+ const_set(name, Class.new(Error))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+
3
+ module Schmooze
4
+ class ProcessorGenerator
5
+ class << self
6
+ def generate(imports, methods)
7
+ %{try {
8
+ #{imports.map {|import| generate_import(import)}.join}} catch (e) {
9
+ process.stdout.write(JSON.stringify(['err', e.toString()]));
10
+ process.stdout.write("\\n");
11
+ process.exit(1);
12
+ }
13
+ process.stdout.write("[\\"ok\\"]\\n");
14
+ var __methods__ = {};
15
+ #{methods.map{ |method| generate_method(method[:name], method[:code]) }.join}
16
+ function __handle_error__(error) {
17
+ if (error instanceof Error) {
18
+ process.stdout.write(JSON.stringify(['err', error.toString().replace(new RegExp('^' + error.name + ': '), ''), error.name]));
19
+ } else {
20
+ process.stdout.write(JSON.stringify(['err', error.toString()]));
21
+ }
22
+ process.stdout.write("\\n");
23
+ }
24
+ require('readline').createInterface({
25
+ input: process.stdin,
26
+ terminal: false,
27
+ }).on('line', function(line) {
28
+ var input = JSON.parse(line);
29
+ try {
30
+ Promise.resolve(__methods__[input[0]].apply(null, input[1])
31
+ ).then(function (result) {
32
+ process.stdout.write(JSON.stringify(['ok', result]));
33
+ process.stdout.write("\\n");
34
+ }).catch(__handle_error__);
35
+ } catch(error) {
36
+ __handle_error__(error);
37
+ }
38
+ });
39
+ }
40
+ end
41
+
42
+ def generate_method(name, code)
43
+ "__methods__[#{name.to_json}] = (#{code});\n"
44
+ end
45
+
46
+ def generate_import(import)
47
+ package, mid, path = import[:package].partition('.')
48
+ " var #{import[:identifier]} = require(#{package.to_json})#{mid}#{path};\n"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Schmooze
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'schmooze/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'schmooze'
8
+ spec.version = Schmooze::VERSION
9
+ spec.authors = ['Bouke van der Bijl']
10
+ spec.email = ['bouke@shopify.com']
11
+
12
+ spec.license = "MIT"
13
+ spec.summary = %q{Schmooze lets Ruby and Node.js work together intimately.}
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.description = File.read(File.join(__dir__, 'README.md'))
16
+ spec.homepage = 'https://github.com/Shopify/schmooze'
17
+
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.11'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'minitest', '~> 5.0'
23
+ end
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -e
3
+ gem install bundler
4
+ bundle install
5
+ pushd test/fixtures/coffee
6
+ npm install
7
+ popd
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ set -e
3
+ bundle exec rake test
metadata ADDED
@@ -0,0 +1,206 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schmooze
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Bouke van der Bijl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-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: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: |
56
+ # Schmooze
57
+
58
+ Schmooze lets Ruby and Node.js work together intimately. It has a DSL that allows you to define what methods you need, and it executes code by spawning a Node.js process and sending messages back and forth.
59
+
60
+ ## Requirements
61
+
62
+ Schmooze requires that you have [nodejs](https://nodejs.org/en/) installed and in the `$PATH`.
63
+
64
+ ## Gem Installation
65
+
66
+ Add this line to your application's Gemfile:
67
+
68
+ ```ruby
69
+ gem 'schmooze'
70
+ ```
71
+
72
+ And then execute:
73
+
74
+ $ bundle
75
+
76
+ ## Usage
77
+
78
+ To use Schmooze, you first need to create a sublcass of `Schmooze::Base`. Your subclass needs to list all of the package dependencies, and methods that you want to have available. For example, here is a Schmooze class that interfaces with [Babel](https://babeljs.io/):
79
+
80
+ ```ruby
81
+ require 'schmooze'
82
+
83
+ class BabelSchmoozer < Schmooze::Base
84
+ dependencies babel: 'babel-core'
85
+
86
+ method :transform, 'babel.transform'
87
+ method :version, 'function() { return [process.version, babel.version]; }'
88
+ end
89
+ ```
90
+
91
+ Note that the `babel-core` package is available under the name `babel`, because that's how we requested it.
92
+
93
+ To define a method, you simply give it a name and pass in a JavaScript string that should resolve to a function. Let's put this class to use!
94
+
95
+ First we need to make sure we install any needed packages.
96
+
97
+ `$ npm install babel-core babel-preset-es2015`
98
+
99
+ All we need to do next is to instantiate the class with a path to where the node modules are installed, and then we can call the methods! (Note that we need to pass in `ast: false` because of a [caveat](#caveats)).
100
+
101
+ ```ruby
102
+ $ pry
103
+ Ruby 2.2.2
104
+ pry> load './babel_schmoozer.rb'
105
+ pry> babel = BabelSchmoozer.new(__dir__)
106
+ pry> babel.version
107
+ => ["v5.5.0", "6.5.2"]
108
+ pry> puts babel.transform('a = () => 1', ast: false, presets: ['es2015'])['code']
109
+ "use strict";
110
+
111
+ a = function a() {
112
+ return 1;
113
+ };
114
+ ```
115
+
116
+ This could easily be turned into a Sprockets plugin.
117
+
118
+ ## Error handling
119
+
120
+ Errors happen, and Schmooze tries to make them as painless as possible to handle. If there is a dependency missing, Schmooze will throw a helpful Error when you try to initialize the class. Here is an example from the tests:
121
+
122
+ ```ruby
123
+ class ErrorSchmoozer < Schmooze::Base
124
+ dependencies nonexistant: 'this-package-is-not-here'
125
+ end
126
+ ErrorSchmoozer.new(__dir__)
127
+ ```
128
+
129
+ This will raise
130
+
131
+ ```
132
+ Schmooze::DependencyError: Cannot find module 'this-package-is-not-here'.
133
+ You need to add it to '/Users/bouke/code/schmooze/test/fixtures/uninstalled_package/package.json' and run 'npm install'
134
+ ```
135
+
136
+ Any JavaScript errors that happen get converted to Ruby errors under the `Schmooze::Javascript` namespace. For example (once again, from the tests):
137
+
138
+ ```ruby
139
+ class CoffeeSchmoozer < Schmooze::Base
140
+ dependencies coffee: 'coffee-script'
141
+ method :compile, 'coffee.compile'
142
+ end
143
+
144
+ CoffeeSchmoozer.new(dir).compile('<=> 1')
145
+ ```
146
+
147
+ This will raise
148
+
149
+ ```
150
+ Schmooze::JavaScript::SyntaxError: [stdin]:1:1: error: unexpected <=
151
+ <=> 1
152
+ ^^
153
+ ```
154
+
155
+ ## Caveats
156
+
157
+ * Because we serialize the return values from JavaScript to JSON, you can't return circular data structures (like the Babel AST).
158
+
159
+ ## Contributing
160
+
161
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/schmooze.
162
+ email:
163
+ - bouke@shopify.com
164
+ executables: []
165
+ extensions: []
166
+ extra_rdoc_files: []
167
+ files:
168
+ - ".gitignore"
169
+ - Gemfile
170
+ - LICENSE
171
+ - README.md
172
+ - Rakefile
173
+ - circle.yml
174
+ - lib/schmooze.rb
175
+ - lib/schmooze/base.rb
176
+ - lib/schmooze/errors.rb
177
+ - lib/schmooze/processor_generator.rb
178
+ - lib/schmooze/version.rb
179
+ - schmooze.gemspec
180
+ - script/setup
181
+ - script/test
182
+ homepage: https://github.com/Shopify/schmooze
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.4.5
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: Schmooze lets Ruby and Node.js work together intimately.
206
+ test_files: []