neutron-ruby-electron 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7b94b9dba9d64ca5279b5b46edaaa489678eb8fd
4
+ data.tar.gz: 15f706d8e6bd8744117795b0af8bacf34fadc4c2
5
+ SHA512:
6
+ metadata.gz: 4710ac1b334f0bc28a47bf4c62a266b34016bf1a21b90e127e0a5542e1dbee4dcee2c3d14f3b48337faefc854f06ed07766fdb9c334a20fd8d094fb12ed52dc8
7
+ data.tar.gz: ffc603482d038a546562ad1983353b899303074947277778aec85969e8534b5517d64863d97767807eb53f0df2bdd80fcb5166984e3b38e68fa4b7e10d24abab
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /local_test/
10
+ /tmp/
11
+
12
+ /ciccio/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in neutron.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 pioz
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Neutron
2
+
3
+ Neutron is a mini framework to build desktop app with [Electron](https://electron.atom.io/) and [Ruby](https://www.ruby-lang.org/).
4
+ Neutron follow the pattern `MVC` where the `V` is handled by Electron and (if you want) [ReactJS](https://facebook.github.io/react/).
5
+ The `MC` can be handled with pure Ruby code.
6
+
7
+
8
+ ## Installation
9
+
10
+ Install it yourself as:
11
+
12
+ $ gem install neutron-ruby-electron
13
+
14
+
15
+ ## Usage
16
+
17
+ First of all generate a new Neutron project:
18
+
19
+ $ neutron project_name
20
+
21
+ It will generate a new folder with the follow tree
22
+ ```
23
+ .
24
+ ├── LICENSE.txt
25
+ ├── README.md
26
+ └── src
27
+ ├── Gemfile
28
+ ├── Gemfile.lock
29
+ ├── assets
30
+ │   ├── index.html
31
+ │   ├── javascripts
32
+ │   │   ├── components
33
+ │   │   │   └── neutron_entry_point_component.js
34
+ │   │   └── neutron.js
35
+ │   └── stylesheets
36
+ ├── backend.rb
37
+ ├── main.rb
38
+ ├── main_window.js
39
+ ├── node_modules/
40
+ └── package.json
41
+ ```
42
+ You can run the just generated app with the command:
43
+
44
+ $ cd project_name/src
45
+ $ ruby main.rb
46
+
47
+ - The `main.rb` is the main of your app.
48
+ - The `main_window.js` is the Electron entry point file where the [Electron app](https://electron.atom.io/docs/api/app/) and the [main browser window](https://electron.atom.io/docs/api/browser-window/) are created.
49
+ - The `backend.rb` is a Ruby class where you can add your instance methods that can be called from the view, from your React components of your Javascript code. This class extends `Neutron::Controller` and looks like this:
50
+ ```
51
+ class MyController < Neutron::Controller
52
+ def ping
53
+ return 'pong'
54
+ end
55
+
56
+ def add(a, b)
57
+ return(a + b)
58
+ end
59
+ end
60
+ ```
61
+ - The `neutron_entry_point_component.js` is the main component that render all your application.
62
+
63
+
64
+ ### Communication between Electron and Ruby
65
+
66
+ Inside a React component you can import the Neutron module with
67
+
68
+ import neutron from 'neutron'
69
+
70
+ and use neutron to call a controller method like this:
71
+
72
+ neutron.send('method_name', [params], options).then((result) => {...}).catch((error) => {...})
73
+
74
+ You can call a Ruby Controller method with `neutron.send` method that return a JS promise with the return value of the method or an error.
75
+
76
+ For example you can have a component like this:
77
+ ```
78
+ import React from 'react'
79
+ import neutron from 'neutron'
80
+
81
+ export default class Sum extends React.Component {
82
+
83
+ constructor(props) {
84
+ super(props)
85
+ this.state = {}
86
+ }
87
+
88
+ sum() {
89
+ const a = parseInt(this.a.value)
90
+ const b = parseInt(this.b.value)
91
+ neutron.send('add', [a, b]).then((result) => {
92
+ this.setState({result: result})
93
+ }).catch((error) => {
94
+ console.log(error)
95
+ })
96
+ }
97
+
98
+ render() {
99
+ return(
100
+ <div>
101
+ <p>
102
+ <input ref={(input) => this.a = input}/>
103
+ +
104
+ <input ref={(input) => this.b = input}/>
105
+ = {this.state.result}
106
+ </p>
107
+ <p>
108
+ <button onClick={this.sum.bind(this)}>Calculate</button>
109
+ </p>
110
+ </div>
111
+ )
112
+ }
113
+ }
114
+ ```
115
+ In this componenet the sum is calculated by the method `add` in the Ruby controller.
116
+
117
+
118
+ ## Contributing
119
+
120
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pioz/neutron.
121
+
122
+
123
+ ## License
124
+
125
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "neutron"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/neutron ADDED
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'neutron/version'
5
+ require 'neutron/generator'
6
+ require 'optparse'
7
+
8
+ def parse_args(args)
9
+ options = {react: true, jquery: false, bootstrap: true, development: false, debug: false}
10
+ opt_parser = OptionParser.new do |opts|
11
+ opts.banner = "Usage: neutron [options] PROJECT_NAME"
12
+ opts.separator ''
13
+ opts.separator 'Options:'
14
+ opts.on('-r', '--[no-]react', 'Create a project with ReactJS support') do |opt|
15
+ options[:react] = opt
16
+ end
17
+ opts.on('-j', '--[no-]jquery', 'Create a project with jQuery support') do |opt|
18
+ options[:jquery] = opt
19
+ end
20
+ opts.on('-b', '--[no-]bootstrap', 'Create a project with Twitter Bootstrap support') do |opt|
21
+ options[:bootstrap] = opt
22
+ end
23
+ opts.on_tail('--dev-mode', 'Development mode') do |opt|
24
+ options[:development] = opt
25
+ end
26
+ opts.on_tail('-d', '--[no-]debug', 'Debug mode') do |opt|
27
+ options[:debug] = opt
28
+ end
29
+ opts.on_tail('-v', '--version', 'Print Neutron version') do
30
+ puts Neutron::VERSION
31
+ exit
32
+ end
33
+ opts.on_tail('-h', '--help', 'Help') do
34
+ puts opts
35
+ exit
36
+ end
37
+ end
38
+ opt_parser.parse!(args)
39
+ if args.size != 1
40
+ puts opt_parser.help
41
+ exit
42
+ end
43
+ options[:path] = args.first
44
+ options[:name] = File.basename(options[:path])
45
+ return options
46
+ end
47
+
48
+ Neutron::Generator.new(parse_args(ARGV)).generate!
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ require 'sys/proctable'
2
+
3
+ module Neutron
4
+
5
+ class App
6
+
7
+ def initialize(controller:)
8
+ raise 'Controller must be inherit from Neutron::Controller' unless controller.is_a?(::Neutron::Controller)
9
+ @controller = controller
10
+ end
11
+
12
+ def run
13
+ path = File.expand_path('..', caller[0].split(':').first)
14
+ begin
15
+ Thread.new { @controller.run }
16
+ `cd '#{path}' && npm run boot`
17
+ ensure
18
+ process_path = File.join(path, 'node_modules/electron-prebuilt-compile/lib/es6-init.js')
19
+ p = Sys::ProcTable.ps.select { |p| p.cmdline.include?(process_path) }.first
20
+ Process.kill('HUP', p.pid) if p
21
+ @controller.stop
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,145 @@
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ module Neutron
5
+
6
+ Thread.abort_on_exception = true
7
+
8
+ class Controller < UNIXServer
9
+
10
+ def initialize(path = '/tmp/neutron.sock')
11
+ File.delete(path) if File.exists?(path)
12
+
13
+ super(path)
14
+ @path = path
15
+ @run = true
16
+
17
+ trap('INT') do
18
+ STDOUT.puts 'Closing socket...'
19
+ self.stop
20
+ end
21
+ end
22
+
23
+ def stop
24
+ @run = false
25
+ self.close
26
+ File.delete(@path) if File.exists?(@path)
27
+ end
28
+
29
+ def run
30
+ while @run do
31
+ begin
32
+ Thread.fork(self.accept) do |sock| # begin
33
+ Thread.current[:threads] = {}
34
+ while (data = sock.gets)
35
+ break if data.empty?
36
+ data.chop!
37
+ # STDOUT.puts data
38
+ Thread.fork(sock, data, Thread.current[:threads]) do |sock, data, threads|
39
+ Thread.current[:sock] = sock
40
+ Thread.current[:threads] = threads
41
+ response = dispatch(data)
42
+ if response
43
+ send_string(sock, response.to_json + "\n")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ rescue Errno::EBADF
49
+ # socket has been closed and server is waiting on socket.accept
50
+ end
51
+ end
52
+ end
53
+
54
+ def notify(event, result = nil)
55
+ if result.is_a?(Hash) && result[:error].is_a?(Hash)
56
+ result[:error][:code] ||= SERVER_ERROR
57
+ result[:error][:message] ||= 'Server error'
58
+ response = {jsonrpc: '2.0', error: result[:error], event: event, id: -1}
59
+ else
60
+ response = {jsonrpc: '2.0', result: result, event: event, id: -1}
61
+ end
62
+ sock = Thread.current[:sock]
63
+ if sock
64
+ send_string(sock, response.to_json + "\n")
65
+ end
66
+ end
67
+
68
+ PARSE_ERROR = -32700
69
+ INVALID_REQUEST = -32600
70
+ METHOD_NOT_FOUND = -32601
71
+ INTERNAL_ERROR = -32603
72
+ SERVER_ERROR = -32000
73
+
74
+ protected
75
+
76
+ def run_thread(&block)
77
+ #c = caller[0][/`.*'/][1..-2].to_sym
78
+ callers = caller.map{|c| c[/`.*'/][1..-2].to_sym}
79
+ c = callers.select{|c| Thread.current[:threads].keys.include?(c)}.first
80
+ sock = Thread.current[:sock]
81
+ Thread.current[:threads][c] << Thread.fork do
82
+ Thread.current[:sock] = sock
83
+ block.call
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def send_string(sock, s)
90
+ while(s.length > 0)
91
+ sent = sock.send(s, 0)
92
+ s = s[sent..-1]
93
+ end
94
+ sock.flush
95
+ end
96
+
97
+ def dispatch(data)
98
+ begin
99
+ jsonrpc = JSON.parse(data)
100
+ rescue JSON::ParserError
101
+ return {jsonrpc: '2.0', error: {code: PARSE_ERROR, message: 'Parse error'}, id: nil}
102
+ end
103
+ if jsonrpc['jsonrpc'] != '2.0' || !jsonrpc['id'].is_a?(Integer) || jsonrpc['id'] < 0
104
+ return {jsonrpc: '2.0', error: {code: INVALID_REQUEST, message: 'Invalid request'}, id: jsonrpc['id']}
105
+ end
106
+ if !self.respond_to?(jsonrpc['method'])
107
+ return {jsonrpc: '2.0', error: {code: METHOD_NOT_FOUND, message: 'Method not found'}, id: jsonrpc['id']}
108
+ end
109
+
110
+ # begin
111
+ # result = run_method(jsonrpc['method'], jsonrpc['params'], once: jsonrpc['once'])
112
+ # rescue Exception => e
113
+ # return {jsonrpc: '2.0', error: {code: INTERNAL_ERROR, message: e.message}, id: jsonrpc['id']}
114
+ # end
115
+ result = run_method(jsonrpc['method'], jsonrpc['params'], once: jsonrpc['once'])
116
+
117
+ if result.is_a?(Hash) && result[:error].is_a?(Hash)
118
+ result[:error][:code] ||= SERVER_ERROR
119
+ result[:error][:message] ||= 'Server error'
120
+ return {jsonrpc: '2.0', error: result[:error], event: jsonrpc['method'], id: jsonrpc['id']}
121
+ else
122
+ r = result.is_a?(Hash) && !result[:result].nil? ? result[:result] : result
123
+ # Reply with a non error result
124
+ return {jsonrpc: '2.0', result: r, event: jsonrpc['method'], id: jsonrpc['id']}
125
+ end
126
+ end
127
+
128
+ def run_method(method, params, once: true)
129
+ if once
130
+ Thread.current[:threads][method.to_sym] ||= []
131
+ Thread.current[:threads][method.to_sym].each do |t|
132
+ t.kill if t.alive?
133
+ Thread.current[:threads][method.to_sym].delete(t)
134
+ end
135
+ Thread.current[:threads][method.to_sym] << Thread.current
136
+ end
137
+ if params
138
+ return self.__send__(method, *params)
139
+ else
140
+ return self.__send__(method)
141
+ end
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,77 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'etc'
4
+
5
+
6
+ module Neutron
7
+
8
+ TEMPLATE_PATH = File.expand_path('../template', __FILE__)
9
+ NEUTRON_NPM_PACKAGE_PATH = File.expand_path('../neutron_npm_package', __FILE__)
10
+
11
+ class Generator < Thor::Group
12
+ include Thor::Actions
13
+
14
+ def initialize(options)
15
+ super()
16
+ @options = options
17
+ end
18
+
19
+ def self.source_root
20
+ File.dirname(__FILE__)
21
+ end
22
+
23
+ def generate!
24
+ self.copy_template
25
+ self.generate_package_json
26
+ end
27
+
28
+ def copy_template
29
+ directory(TEMPLATE_PATH, options[:path], exclude_pattern: /components/)
30
+ if options[:react]
31
+ directory(File.join(TEMPLATE_PATH, 'src/assets/javascripts/components'), File.join(options[:path], 'src/assets/javascripts/components'))
32
+ end
33
+ inside(File.join(options[:path], 'src')) do
34
+ system 'bundle install'
35
+ end
36
+ end
37
+
38
+ def generate_package_json
39
+ say_status :generate, File.join(options[:path], 'src', 'package.json')
40
+ inside(File.join(options[:path], 'src')) do
41
+ # Init NPM
42
+ `npm init -y`
43
+ # Install local neutron npm package
44
+ system "npm install --save-dev #{NEUTRON_NPM_PACKAGE_PATH}"
45
+ # Install NPM packages
46
+ %w(electron electron-prebuilt-compile).each do |package|
47
+ system "npm install --save-dev #{package}"
48
+ end
49
+ if options[:jquery] || options[:bootstrap]
50
+ system "npm install --save jquery"
51
+ end
52
+ if options[:bootstrap]
53
+ system "npm install --save bootstrap"
54
+ end
55
+ if options[:react]
56
+ system "npm install --save react"
57
+ system "npm install --save react-dom"
58
+ end
59
+ # Edit package.json
60
+ username = Etc.getlogin.downcase
61
+ json = JSON.parse(File.read('package.json'))
62
+ json['name'] = options[:name]
63
+ json['description'] = 'Another Neutron app'
64
+ json['repository'] = "https://github.com/#{username}/#{options[:name]}"
65
+ json['author'] = username
66
+ json['license'] = 'MIT'
67
+ json['main'] = 'main_window.js'
68
+ json['scripts']['boot'] = './node_modules/.bin/electron .'
69
+ json['scripts']['buildjs'] = 'NODE_ENV=production ./node_modules/.bin/electron-compile .'
70
+ #json['babel'] = {'presets' => ['es2015']}
71
+ File.open('package.json', 'w') {|f| f << json.to_json}
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,128 @@
1
+ // import Events from 'events'
2
+ // import net from 'net'
3
+ const Events = require('events')
4
+ const net = require('net')
5
+
6
+
7
+ let global_jsonrpc_id = 1
8
+
9
+
10
+ class Neutron extends Events.EventEmitter {
11
+
12
+ constructor(path = '/tmp/neutron.sock') {
13
+ super()
14
+ this.setMaxListeners(0)
15
+ this.promises = {}
16
+ this.path = path
17
+ this.socket = new net.Socket()
18
+ this.msg = ''
19
+
20
+ this.socket.on('data', (data) => {
21
+ const s = data.toString()
22
+ this.msg += s
23
+ if (s[s.length-1] === "\n") {
24
+ const msg = this.msg.slice(0, -1)
25
+ this.msg = ''
26
+ const messages = msg.split("\n")
27
+ for (let i = 0; i < messages.length; i++) {
28
+ try {
29
+ const jsonrpc = JSON.parse(messages[i])
30
+ if (typeof(jsonrpc['id']) !== 'number') {
31
+ this.emit('err', {message: 'Missing id on jsonrpc reply'})
32
+ this.emit('all')
33
+ } else {
34
+ if (jsonrpc['error']) {
35
+ const error = jsonrpc['error']
36
+ error.toString = () => {
37
+ this.message
38
+ }
39
+ if (jsonrpc['event'])
40
+ this.emit(`err-${jsonrpc['event']}`, error)
41
+ this.emit(`err-${jsonrpc['id']}`, error)
42
+ this.emit('all')
43
+ } else {
44
+ if (jsonrpc['event'])
45
+ this.emit(jsonrpc['event'], jsonrpc['result'])
46
+ this.emit(`json-${jsonrpc['id']}`, jsonrpc['result'])
47
+ this.emit('all')
48
+ }
49
+ }
50
+ } catch(error) {
51
+ this.emit('err', {message: 'Error to parse the response'})
52
+ this.emit('all')
53
+ }
54
+ }
55
+ }
56
+ })
57
+
58
+ this.socket.on('connect', () => {
59
+ this.emit('connect')
60
+ this.emit('all')
61
+ })
62
+
63
+ this.socket.on('end', () => {
64
+ this.emit('end')
65
+ this.emit('all')
66
+ this.reconnect(2000)
67
+ })
68
+
69
+ this.socket.on('error', (error) => {
70
+ this.emit('err-connection', error)
71
+ this.emit('err', error)
72
+ this.emit('all')
73
+ this.reconnect(2000)
74
+ })
75
+ }
76
+
77
+ connect() {
78
+ // remote.process.on('uncaughtException', (error) => {
79
+ // this.emit('connection-error', error)
80
+ // this.emit('all')
81
+ // this.emit('all-errors')
82
+ // this.reconnect(2000)
83
+ // })
84
+ this.socket.connect({path: this.path})
85
+ }
86
+
87
+ reconnect(timeout = 0) {
88
+ setTimeout(() => {
89
+ console.log('Try to reconnect to the socket ' + this.path + '...')
90
+ this.socket.end()
91
+ this.socket.connect({path: this.path})
92
+ }, timeout)
93
+ }
94
+
95
+ close() {
96
+ this.socket.end()
97
+ }
98
+
99
+ real_send(method, params, {once = true} = {}) {
100
+ const id = global_jsonrpc_id++
101
+ this.socket.write(JSON.stringify({
102
+ jsonrpc: '2.0',
103
+ method: method,
104
+ params: params,
105
+ once: once,
106
+ id: id
107
+ }) + "\n")
108
+ return id
109
+ }
110
+
111
+ send(method, params, {once = true} = {}) {
112
+ const id = this.real_send(method, params, {once: once})
113
+ return new Promise((resolve, reject) => {
114
+ this.on(`json-${id}`, resolve)
115
+ this.on(`err-${id}`, reject)
116
+ this.on('err', reject)
117
+ })
118
+ }
119
+
120
+ }
121
+
122
+ const neutron = new Neutron()
123
+ neutron.on('connect', () => {
124
+ console.log('Electron connected to Ruby!')
125
+ })
126
+ neutron.connect()
127
+ // export default neutron
128
+ module.exports = neutron
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "neutron",
3
+ "version": "0.1.0",
4
+ "description": "Mini framework to build desktop app with Electron and Ruby",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/pioz/neutron.git"
12
+ },
13
+ "keywords": [
14
+ "ruby",
15
+ "electron"
16
+ ],
17
+ "author": "pioz",
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": "https://github.com/pioz/neutron/issues"
21
+ },
22
+ "homepage": "https://github.com/pioz/neutron#readme"
23
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) <%= Time.now.year %> <%= Etc.getlogin.downcase %>
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ # <%= options[:name] %>
2
+
3
+ Project README here...
@@ -0,0 +1,94 @@
1
+ require 'bundler'
2
+
3
+ PACKAGE_NAME = '<%= options[:name] %>'
4
+ VERSION = '0.1.0'
5
+ RUBY_V = '2.2'
6
+ TRAVELING_RUBY_VERSION = "20150715-#{RUBY_V}.2"
7
+
8
+ desc 'Package your app'
9
+ task package: ['package:linux:x86', 'package:linux:x86_64', 'package:osx']
10
+
11
+ namespace :package do
12
+ namespace :linux do
13
+ desc 'Package your app for Linux x86'
14
+ task :x86 => "packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-linux-x86.tar.gz" do
15
+ create_package('linux-x86')
16
+ end
17
+
18
+ desc 'Package your app for Linux x86_64'
19
+ task :x86_64 => "packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-linux-x86_64.tar.gz" do
20
+ create_package('linux-x86_64')
21
+ end
22
+ end
23
+
24
+ desc 'Package your app for OS X'
25
+ task :osx do
26
+ create_package('osx')
27
+ end
28
+
29
+ end
30
+
31
+ def create_package(target)
32
+ if RUBY_VERSION !~ /^#{RUBY_V}\./
33
+ abort "You can only use Ruby #{RUBY_V}, because that's what Traveling Ruby uses."
34
+ end
35
+ package_dir = "#{PACKAGE_NAME}-#{VERSION}-#{target}"
36
+ sh "rm -rf #{package_dir}"
37
+ sh "rm -rf tmp/"
38
+ sh "mkdir tmp"
39
+ sh "mkdir -p #{package_dir}/lib/ruby"
40
+ sh "rsync -a src #{package_dir}/lib"
41
+ download_runtime('osx')
42
+ sh "tar -xzf tmp/traveling-ruby-#{TRAVELING_RUBY_VERSION}-#{target}.tar.gz -C #{package_dir}/lib/ruby"
43
+ create_wrapper("#{package_dir}/#{PACKAGE_NAME}")
44
+ sh "chmod 755 #{package_dir}/#{PACKAGE_NAME}"
45
+ bundle_install
46
+ sh "cp -pR tmp/vendor #{package_dir}/lib/"
47
+ sh "cp src/Gemfile src/Gemfile.lock #{package_dir}/lib/vendor/"
48
+ sh "mkdir #{package_dir}/lib/vendor/.bundle"
49
+ sh "cp tmp/bundler-config #{package_dir}/lib/vendor/.bundle/config"
50
+ if !ENV['DIR_ONLY']
51
+ sh "tar -czf #{package_dir}.tar.gz #{package_dir}"
52
+ sh "rm -rf #{package_dir}"
53
+ end
54
+ sh "rm -rf tmp/"
55
+ end
56
+
57
+ def download_runtime(target)
58
+ sh "cd tmp && curl -L -O --fail https://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-#{TRAVELING_RUBY_VERSION}-#{target}.tar.gz"
59
+ end
60
+
61
+ def bundle_install
62
+ sh "cp src/Gemfile src/Gemfile.lock tmp/"
63
+ Bundler.with_clean_env do
64
+ sh "cd tmp && env BUNDLE_IGNORE_CONFIG=1 bundle install --path vendor --without development"
65
+ <% if options[:development] -%>
66
+ sh "cd tmp && env BUNDLE_IGNORE_CONFIG=1 bundle package --all"
67
+ sh "mv tmp/vendor/cache/neutron tmp/vendor/ruby/#{RUBY_V}.0/gems"
68
+ <% end -%>
69
+ end
70
+ sh "rm -rf tmp/vendor/cache"
71
+ sh "rm -rf tmp/vendor/ruby/#{RUBY_V}.0/cache"
72
+ bundle_config = <<-CONFIG
73
+ BUNDLE_PATH: .
74
+ BUNDLE_WITHOUT: development
75
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
76
+ CONFIG
77
+ File.open('tmp/bundler-config', 'w') {|f| f << bundle_config.gsub(/^\s{4}/, '') }
78
+ end
79
+
80
+ def create_wrapper(out)
81
+ wrapper = <<-WRAPPER
82
+ #!/bin/bash
83
+ set -e
84
+
85
+ SELFDIR="`dirname \"$0\"`"
86
+ SELFDIR="`cd \"$SELFDIR\" && pwd`"
87
+
88
+ export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile"
89
+ unset BUNDLE_IGNORE_CONFIG
90
+
91
+ exec "NODE_ENV=production $SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/src/main.rb"
92
+ WRAPPER
93
+ File.open(out, 'w') {|f| f << wrapper.gsub(/^\s{4}/, '') }
94
+ end
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ <% if options[:development] -%>
4
+ gem 'neutron', path: '<%= File.expand_path('..', __FILE__) %>'
5
+ <% else -%>
6
+ gem 'neutron'
7
+ <% end -%>
@@ -0,0 +1,27 @@
1
+ <html>
2
+ <head>
3
+ <script src='./javascripts/neutron.js'></script>
4
+ <link href='./stylesheets/neutron.less' type='text/less' rel='stylesheet'>
5
+ </head>
6
+ <body>
7
+ <% if options[:react] -%>
8
+ <div id='react-entry-point'></div>
9
+ <script>
10
+ ReactDOM.render(React.createElement(NeutronEntryPointComponent, null), document.getElementById('react-entry-point'))
11
+ </script>
12
+ <% else -%>
13
+ <div>
14
+ <h2>Welcome to NEUTRON</h2>
15
+ <p>
16
+ <button id='b'>Try Neutron</button>
17
+ <script>
18
+ document.getElementById('b').addEventListener('click', () => {
19
+ for(let i = 0; i < 10; i++)
20
+ neutron.send('add', [0, i], {once: false}).then(result => console.log(result)).catch(e => console.log(e))
21
+ })
22
+ </script>
23
+ </p>
24
+ </div>
25
+ <% end -%>
26
+ </body>
27
+ </html>
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+ import neutron from 'neutron'
3
+
4
+ export default class NeutronEntryPointComponent extends React.Component {
5
+
6
+ button_click() {
7
+ for(let i = 0; i < 10; i++)
8
+ neutron.send('add', [0, i], {once: false}).then(result => console.log(result)).catch(e => console.log(e))
9
+ }
10
+
11
+ render() {
12
+ return(
13
+ <div>
14
+ <h2>Welcome to NEUTRON</h2>
15
+ <p>
16
+ <button className='btn btn-default' onClick={this.button_click}>Try Neutron</button>
17
+ </p>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import neutron from 'neutron'
3
+
4
+ export default class Sum extends React.Component {
5
+
6
+ constructor(props) {
7
+ super(props)
8
+ this.state = {}
9
+ }
10
+
11
+ sum() {
12
+ const a = parseInt(this.a.value)
13
+ const b = parseInt(this.b.value)
14
+ neutron.send('add', [a, b]).then((result) => {
15
+ this.setState({result: result})
16
+ }).catch((error) => {
17
+ console.log(error)
18
+ })
19
+ }
20
+
21
+ render() {
22
+ return(
23
+ <div>
24
+ <p>
25
+ <input ref={(input) => this.a = input}/>
26
+ +
27
+ <input ref={(input) => this.b = input}/>
28
+ = {this.state.result}
29
+ </p>
30
+ <p>
31
+ <button onClick={this.sum.bind(this)}>Calculate</button>
32
+ </p>
33
+ </div>
34
+ )
35
+ }
36
+ }
@@ -0,0 +1,23 @@
1
+ <% if options[:jquery] || options[:bootstrap] -%>
2
+ import jQuery from 'jquery'
3
+ <% end -%>
4
+ <% if options[:react] -%>
5
+ import ReactDOM from 'react-dom'
6
+ import React from 'react'
7
+ import NeutronEntryPointComponent from './javascripts/components/neutron_entry_point_component'
8
+ <% else -%>
9
+ import neutron from 'neutron'
10
+ <% end -%>
11
+ <% if options[:jquery] || options[:bootstrap] -%>
12
+ window.jQuery = window.$ = jQuery
13
+ <% end -%>
14
+ <% if options[:react] -%>
15
+ window.ReactDOM = ReactDOM
16
+ window.React = React
17
+ window.NeutronEntryPointComponent = NeutronEntryPointComponent
18
+ <% else -%>
19
+ window.neutron = neutron
20
+ <% end -%>
21
+ <% if options[:bootstrap] -%>
22
+ require('bootstrap')
23
+ <% end -%>
@@ -0,0 +1,4 @@
1
+ <% if options[:bootstrap] -%>
2
+ @import '../../node_modules/bootstrap/less/bootstrap.less';
3
+ @icon-font-path: '../../node_modules/bootstrap/fonts/';
4
+ <% end -%>
@@ -0,0 +1,9 @@
1
+ require 'neutron/controller'
2
+
3
+ class Backend < Neutron::Controller
4
+
5
+ def add(a, b)
6
+ return a + b
7
+ end
8
+
9
+ end
@@ -0,0 +1,11 @@
1
+ <% if options[:development] -%>
2
+ require 'bundler'
3
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', __FILE__)
4
+ Bundler.require
5
+
6
+ <% end -%>
7
+ require 'neutron/app'
8
+ require File.expand_path('../backend.rb', __FILE__)
9
+
10
+ app = Neutron::App.new(controller: Backend.new)
11
+ app.run
@@ -0,0 +1,40 @@
1
+ import { app, BrowserWindow } from 'electron'
2
+ import { enableLiveReload } from 'electron-compile'
3
+ import path from 'path'
4
+ import url from 'url'
5
+
6
+ if (process.env.NODE_ENV !== 'production')
7
+ enableLiveReload()
8
+
9
+ let win = null
10
+
11
+ function createWindow() {
12
+ win = new BrowserWindow({title: 'Another Neutron app', width: 800, height: 600})
13
+ win.loadURL(url.format({
14
+ pathname: path.join(__dirname, 'assets/index.html'),
15
+ protocol: 'file:',
16
+ slashes: true
17
+ }))
18
+ if (process.env.NODE_ENV !== 'production')
19
+ win.webContents.openDevTools()
20
+ win.on('close', (event) => {
21
+ if (app.quitting)
22
+ win = null
23
+ else {
24
+ event.preventDefault()
25
+ win.hide()
26
+ }
27
+ })
28
+ }
29
+
30
+ app.on('ready', createWindow)
31
+
32
+ app.on('window-all-closed', () => {
33
+ if (process.platform !== 'darwin') {
34
+ app.quit()
35
+ }
36
+ })
37
+
38
+ app.on('activate', () => { win.show() })
39
+
40
+ app.on('before-quit', () => app.quitting = true)
@@ -0,0 +1,3 @@
1
+ module Neutron
2
+ VERSION = "0.1.0"
3
+ end
data/neutron.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'neutron/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "neutron-ruby-electron"
8
+ spec.version = Neutron::VERSION
9
+ spec.authors = ["pioz"]
10
+ spec.email = ["epilotto@gmx.com"]
11
+
12
+ spec.summary = %q{Mini framework to build desktop app with Electron and Ruby}
13
+ spec.description = %q{Mini framework to build desktop app with Electron and Ruby}
14
+ spec.homepage = "https://github.com/pioz/neutron"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org/"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "bin"
30
+ spec.executables = ["neutron"]
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.14"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+
36
+ spec.add_runtime_dependency "thor", "~> 0.19"
37
+ spec.add_runtime_dependency "sys-proctable", "~> 1.1"
38
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: neutron-ruby-electron
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - pioz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-07 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.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
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: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.19'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.19'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sys-proctable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ description: Mini framework to build desktop app with Electron and Ruby
70
+ email:
71
+ - epilotto@gmx.com
72
+ executables:
73
+ - neutron
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/neutron
84
+ - bin/setup
85
+ - lib/neutron/app.rb
86
+ - lib/neutron/controller.rb
87
+ - lib/neutron/generator.rb
88
+ - lib/neutron/neutron_npm_package/index.js
89
+ - lib/neutron/neutron_npm_package/package.json
90
+ - lib/neutron/template/LICENSE.txt.tt
91
+ - lib/neutron/template/README.md.tt
92
+ - lib/neutron/template/Rakefile.tt
93
+ - lib/neutron/template/src/Gemfile.tt
94
+ - lib/neutron/template/src/assets/index.html.tt
95
+ - lib/neutron/template/src/assets/javascripts/components/neutron_entry_point_component.js.tt
96
+ - lib/neutron/template/src/assets/javascripts/components/sum.js
97
+ - lib/neutron/template/src/assets/javascripts/neutron.js.tt
98
+ - lib/neutron/template/src/assets/stylesheets/neutron.less.tt
99
+ - lib/neutron/template/src/backend.rb.tt
100
+ - lib/neutron/template/src/main.rb.tt
101
+ - lib/neutron/template/src/main_window.js.tt
102
+ - lib/neutron/version.rb
103
+ - neutron.gemspec
104
+ homepage: https://github.com/pioz/neutron
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ allowed_push_host: https://rubygems.org/
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.6.12
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Mini framework to build desktop app with Electron and Ruby
129
+ test_files: []