web-repl 0.1

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.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014 Ari Russo
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,66 @@
1
+ # web-repl
2
+
3
+ This is a Javascript [REPL](http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) that runs in Ruby. Evaluation is done by a web browser instance.
4
+
5
+ One use of this is to control the Chrome Developer Console remotely.
6
+
7
+ #### Background
8
+
9
+ I was working on a toy program recently that needed the browser to be in full screen mode, which made using the regular Chrome console very difficult to use. I came up with this program as an alternative.
10
+
11
+ There are similar tools that run in nodejs for example but since my program uses a Ruby backend anyway, this is convenient for me.
12
+
13
+ It communicates over websocket.
14
+
15
+ There is basically no attention to security here, so please use at your own discretion.
16
+
17
+ #### Usage
18
+
19
+ ###### Browser
20
+
21
+ To enable the browser side of this, include something like this in the head of your webpage:
22
+
23
+ ```html
24
+ <script src="js/replConnection.js"></script>
25
+ <script type="text/javascript">
26
+ window.onload = function() {
27
+ var repl = new ReplConnection("localhost", 9007, { debug: true, reconnect: true });
28
+ repl.start();
29
+ }
30
+ </script>
31
+ ```
32
+
33
+ The javascript assets for this project are located in the [/js directory](https://github.com/arirusso/web-repl/tree/master/js).
34
+
35
+ There is also a full example of a webpage (with [rack](http://rack.github.io/) configuration) in the [/examples/page directory](https://github.com/arirusso/web-repl/tree/master/examples/page)
36
+
37
+ ###### REPL
38
+
39
+ The REPL can be used either in a Ruby program/console or there is a "binary" Ruby script.
40
+
41
+ In Ruby the usage looks like this:
42
+
43
+ ```ruby
44
+ require "web-repl"
45
+
46
+ WebRepl.start(:host => "localhost", :port => 9007)
47
+ ```
48
+
49
+ You can see an explanation of background usage here.
50
+
51
+ To use this as a script, run this from the command line. (The script should install with the gem)
52
+
53
+ web-repl localhost:9007
54
+
55
+ #### Installation
56
+
57
+ gem install web-repl
58
+
59
+ or with Bundler
60
+
61
+ gem "web-repl"
62
+
63
+ #### License
64
+
65
+ Licensed under Apache 2.0, See the file LICENSE
66
+ Copyright (c) 2014 [Ari Russo](http://arirusso.com)
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require "optparse"
5
+ require "web-repl"
6
+
7
+ ARGV[0]
8
+
9
+ raise OptionParser::MissingArgument if ARGV[0].nil?
10
+
11
+ host, port = *ARGV[0].scan(/(\w+):(\d{4,})/)[0]
12
+
13
+ raise OptionParser::MissingArgument if host.nil? || port.nil?
14
+
15
+ WebRepl.start(:host => host, :port => port)
16
+ exit 0
@@ -0,0 +1 @@
1
+ function ReplConnection(e,t,n){if(n===null){n={}}this.debug=!!n.debug;this.reconnect=!!n.reconnect;this.retryTime=n.retryTime||1e3;this.onReceive=n.onReceive;this.host=e;this.port=t;this.active=false;this.socket;this.supported="WebSocket"in window;if(this.supported){console.log("REPL: socket ok")}else{console.log("REPL: socket not supported")}}ReplConnection.prototype.eval=function(statement){response={};try{response.value=eval(statement)}catch(err){response.error=err.message}return response};ReplConnection.prototype.initSocket=function(e){if(this.socket!==undefined&&this.socket!==null){this.socket.close()}var t="ws://"+this.host+":"+this.port+"/echo";this.socket=new WebSocket(t);e()};ReplConnection.prototype.handleSocketOpen=function(){this.active=true;console.log("REPL: socket ready")};ReplConnection.prototype.tryConnection=function(){console.log("REPL: waiting for connection");if(this.reconnect){var e=this;window.setTimeout(function(){e.start()},this.retryTime)}};ReplConnection.prototype.handleSocketClose=function(e){if(!this.active){this.tryConnection()}else{console.log("REPL: socket closed");this.active=false;if(this.reconnect){this.tryConnection()}}};ReplConnection.prototype.handleMessageReceived=function(e){if(this.debug){console.log("REPL: message received");console.log(e)}var t=JSON.parse(e.data);var n=t.timestamp;t.timestamp=new Date(n);if(this.onReceive!==undefined){this.onReceive(t)}var r=this.eval(t.statement);r.timestamp=(new Date).getTime();var i=JSON.stringify(r);if(this.debug){console.log("REPL: replying ");console.log(r)}this.socket.send(i)};ReplConnection.prototype.initEventHandling=function(){var e=this;this.socket.onopen=function(){e.handleSocketOpen()};this.socket.onclose=function(t){e.handleSocketClose(t)};this.socket.onmessage=function(t){e.handleMessageReceived(t)}};ReplConnection.prototype.start=function(e){if(this.supported){var t=this;this.initSocket(function(){t.initEventHandling();if(e!==undefined){e(t)}})}}
@@ -0,0 +1,117 @@
1
+ // A connection to the REPL server using Websocket
2
+ function ReplConnection(host, port, options) {
3
+ if (options === null) {
4
+ options = {};
5
+ }
6
+ this.debug = !!options.debug;
7
+ this.reconnect = !!options.reconnect;
8
+ this.retryTime = options.retryTime || 1000;
9
+ this.onReceive = options.onReceive;
10
+ this.host = host;
11
+ this.port = port;
12
+ this.active = false;
13
+ this.socket;
14
+ this.supported = ("WebSocket" in window);
15
+ if (this.supported) {
16
+ console.log("REPL: socket ok");
17
+ } else {
18
+ console.log("REPL: socket not supported");
19
+ }
20
+ }
21
+
22
+ // This is the "eval" for the REPL
23
+ ReplConnection.prototype.eval = function(statement) {
24
+ response = {}
25
+ try {
26
+ response.value = eval(statement);
27
+ } catch(err) {
28
+ response.error = err.message;
29
+ }
30
+ return response;
31
+ }
32
+
33
+ // Initialize the Websocket connection
34
+ ReplConnection.prototype.initSocket = function(successCallback) {
35
+ if (this.socket !== undefined && this.socket !== null) {
36
+ this.socket.close();
37
+ }
38
+ var address = "ws://" + this.host + ":" + this.port + "/echo";
39
+ this.socket = new WebSocket(address);
40
+ successCallback();
41
+ }
42
+
43
+ // To be run when the Websocket registers as being open
44
+ ReplConnection.prototype.handleSocketOpen = function() {
45
+ this.active = true;
46
+ console.log("REPL: socket ready");
47
+ }
48
+
49
+ // Try to create a Websocket connection
50
+ ReplConnection.prototype.tryConnection = function() {
51
+ console.log("REPL: waiting for connection");
52
+ if (this.reconnect) {
53
+ var connection = this;
54
+ window.setTimeout(function() {
55
+ connection.start();
56
+ }, this.retryTime);
57
+ }
58
+ }
59
+
60
+ // To be run when the Websocket registers as being closed. This includes when it's waiting for a connection.
61
+ ReplConnection.prototype.handleSocketClose = function(event) {
62
+ if (!this.active) {
63
+ this.tryConnection();
64
+ } else {
65
+ console.log("REPL: socket closed");
66
+ this.active = false;
67
+ if (this.reconnect) {
68
+ this.tryConnection();
69
+ }
70
+ }
71
+ }
72
+
73
+ // To be run when the Websocket registers an event over the connection.
74
+ ReplConnection.prototype.handleMessageReceived = function(event) {
75
+ if (this.debug) {
76
+ console.log("REPL: message received");
77
+ console.log(event);
78
+ }
79
+ var message = JSON.parse(event.data);
80
+ // turn the timestamp from the rec'd message into a real date
81
+ var timestamp = message.timestamp;
82
+ message.timestamp = new Date(timestamp);
83
+ //
84
+ if (this.onReceive !== undefined) {
85
+ this.onReceive(message); // fire the custom callback
86
+ }
87
+ // prepare the response
88
+ var response = this.eval(message.statement); // evaluate the statement
89
+ response.timestamp = new Date().getTime(); // timestamp for the returned message
90
+ var json = JSON.stringify(response);
91
+ if (this.debug) {
92
+ console.log("REPL: replying ");
93
+ console.log(response);
94
+ }
95
+ this.socket.send(json);
96
+ }
97
+
98
+ // Initialize the Websocket event handling actions
99
+ ReplConnection.prototype.initEventHandling = function() {
100
+ var connection = this;
101
+ this.socket.onopen = function() { connection.handleSocketOpen() };
102
+ this.socket.onclose = function(event) { connection.handleSocketClose(event); };
103
+ this.socket.onmessage = function(event) { connection.handleMessageReceived(event); };
104
+ }
105
+
106
+ // Initialize the Websocket and start waiting for a REPL connection
107
+ ReplConnection.prototype.start = function(successCallback) {
108
+ if (this.supported) {
109
+ var connection = this;
110
+ this.initSocket(function() {
111
+ connection.initEventHandling();
112
+ if (successCallback !== undefined) {
113
+ successCallback(connection);
114
+ }
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,22 @@
1
+ # libs
2
+ require "colorize"
3
+ require "em-websocket"
4
+ require "json"
5
+ require "readline"
6
+ require "socket"
7
+
8
+ # classes
9
+ require "web-repl/messager"
10
+ require "web-repl/patch"
11
+ require "web-repl/repl"
12
+
13
+ module WebRepl
14
+
15
+ VERSION = "0.1"
16
+
17
+ # Shortcut to REPL.start
18
+ def self.start(*a)
19
+ REPL.start(*a)
20
+ end
21
+
22
+ end
@@ -0,0 +1,48 @@
1
+ module WebRepl
2
+
3
+ # Handles sending and receiving messages to/from the socket
4
+ class Messager
5
+
6
+ # @param [EventMachine::WebSocket] socket
7
+ # @param [Hash] options
8
+ # @option options [Boolean] :debug
9
+ def initialize(socket, options = {})
10
+ @socket = socket
11
+ @debug = options[:debug]
12
+ end
13
+
14
+ # Handle an inputted message
15
+ # @param [String] raw_message A raw inputted JSON message
16
+ # @return [Hash]
17
+ def in(raw_message, &block)
18
+ hash = JSON.parse(raw_message, :symbolize_names => true)
19
+ hash[:timestamp] = Time.at(hash[:timestamp].to_i / 1000) if !hash[:timestamp].nil?
20
+ yield(hash) if block_given?
21
+ hash
22
+ end
23
+
24
+ # Generate a new timestamp in js format
25
+ # @return [Fixnum]
26
+ def new_timestamp
27
+ Time.now.to_i * 1000 # javascript time int format
28
+ end
29
+
30
+ # Send a message over the socket
31
+ # @param [Hash] message A message to send
32
+ # @return [String, nil] If a message was sent, its JSON string; otherwise nil
33
+ def out(message)
34
+ if !@socket.nil?
35
+ message[:timestamp] ||= new_timestamp
36
+ json = message.to_json
37
+ @debug.puts "Sending message: #{json}" if @debug
38
+ @socket.send(json)
39
+ json
40
+ else
41
+ @debug.puts "Warning: No connection" if @debug
42
+ nil
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,39 @@
1
+ # Patch EventMachine::WebSocket so that we can initialize EM on demand. This is useful when
2
+ # having multiple EMs working. It won't error out by calling EM.run repeatedly.
3
+ #
4
+ module EventMachine
5
+ module WebSocket
6
+ def self.start(options = {}, &block)
7
+ EM.epoll
8
+ if EM.reactor_running?
9
+ handle_start(options, &block)
10
+ else
11
+ EM.run { handle_start(options, &block) }
12
+ end
13
+ end
14
+
15
+ def self.run(options = {}, &block)
16
+ if EM.reactor_running?
17
+ handle_run(options, &block)
18
+ else
19
+ EM.run { handle_run(options, &block) }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def self.handle_run(options = {}, &block)
26
+ host, port = options.values_at(:host, :port)
27
+ EM.start_server(host, port, Connection, options) do |c|
28
+ yield c
29
+ end
30
+ end
31
+
32
+ def self.handle_start(options = {}, &block)
33
+ trap("TERM") { stop }
34
+ trap("INT") { stop }
35
+ run(options, &block)
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,128 @@
1
+ module WebRepl
2
+
3
+ # The main REPL object
4
+ class REPL
5
+
6
+ attr_reader :thread
7
+
8
+ # Start a repl connection
9
+ # @param [Hash] config A hash of config options to be passed to EM::WebSocket.run directly
10
+ # @param [Hash] options
11
+ # @option options [IO, nil] :debug A debug logger or nil if debug is not needed (default: nil)
12
+ # @option options [Boolean] :background Do not wait for input, just run in the bg
13
+ # @return [WebRepl::REPL]
14
+ def self.start(config, options = {})
15
+ new(config, options).tap { |repl| repl.start(options) }
16
+ end
17
+
18
+ # @param [Hash] config A hash of config options to be passed to EM::WebSocket.run directly
19
+ # @param [Hash] options
20
+ # @option options [IO, nil] :debug A debug logger or nil if debug is not needed (default: nil)
21
+ def initialize(config, options = {})
22
+ @config = config
23
+ @socket = nil
24
+ @messager = nil
25
+ @buffer = []
26
+ @debug = options[:debug]
27
+ end
28
+
29
+ # Send a statement to the browser for evaluation
30
+ # @param [Fixnum, String] statement A Javascript statement to be evaluated
31
+ # @return [String, nil] The data that was sent to the browser, or nil if sending could not be completed.
32
+ def evaluate(statement)
33
+ @messager.out({ :statement => statement }) unless @messager.nil?
34
+ end
35
+
36
+ # Prompt the Ruby user for input and send that input to the browser for evaluation (blocking)
37
+ # @return [String, nil] The data that was sent to the browser, or nil if sending could not be completed
38
+ def gets
39
+ line = Readline.readline('> ', true)
40
+ return nil if line.nil?
41
+ if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
42
+ Readline::HISTORY.pop
43
+ end
44
+ statement = line.strip
45
+ case statement
46
+ when "exit" then exit
47
+ else
48
+ evaluate(statement)
49
+ end
50
+ end
51
+
52
+ # Wait for a response from the browser
53
+ def wait_for_response
54
+ loop until !@buffer.empty?
55
+ end
56
+
57
+ # Start the Websocket connection (blocking)
58
+ # @param [Hash] options
59
+ # @option options [Boolean] :background Do not wait for input, just run in the bg
60
+ def start(options = {}, &block)
61
+ @thread = Thread.new do
62
+ EM::WebSocket.run(@config) do |ws|
63
+ if @socket.nil?
64
+ @socket = ws
65
+ @messager = Messager.new(@socket)
66
+ configure_event_handling(:background => options[:background], &block)
67
+ end
68
+ end
69
+ end
70
+ acknowledge_handshake do
71
+ yield if block_given?
72
+ gets unless !!options[:background]
73
+ end
74
+ @thread.join unless !!options[:background]
75
+ end
76
+
77
+ # Execute a block when a connection is made
78
+ # @return [TrueClass]
79
+ def acknowledge_handshake(&block)
80
+ Thread.new do
81
+ loop until !@handshake.nil?
82
+ yield
83
+ end
84
+ end
85
+
86
+ # Close the REPL
87
+ def close
88
+ @socket.close unless @socket.nil?
89
+ @thread.kill unless @thread.nil?
90
+ end
91
+
92
+ private
93
+
94
+ def handle_open(handshake, options = {})
95
+ puts "web-repl: Connection open"
96
+ @handshake = handshake
97
+ end
98
+
99
+ def handle_close
100
+ puts "web-repl: Connection closed"
101
+ @handshake = nil
102
+ end
103
+
104
+ def handle_message_received(raw_message, options = {})
105
+ @messager.in(raw_message) do |message|
106
+ @buffer.clear
107
+ @buffer << message
108
+ keys = { :error => :red, :value => :white }
109
+ text = nil
110
+ keys.each do |k,v|
111
+ text ||= message[k].to_s.send(v) unless message[k].nil?
112
+ end
113
+ puts(text)
114
+ end
115
+ gets unless !!options[:background]
116
+ end
117
+
118
+ # Configure the Websocket event handling
119
+ # @param [Hash] options
120
+ # @option options [Boolean] :background Do not wait for input, just run in the bg
121
+ def configure_event_handling(options = {})
122
+ @socket.onopen { |handshake| handle_open(handshake) }
123
+ @socket.onclose { handle_close }
124
+ @socket.onmessage { |raw_message| handle_message_received(raw_message) }
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,8 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + '/../lib'
3
+
4
+ require 'test/unit'
5
+ require "mocha/test_unit"
6
+ require "shoulda-context"
7
+
8
+ require "web-repl"
@@ -0,0 +1,88 @@
1
+ require "helper"
2
+
3
+ class WebRepl::MessagerTest < Test::Unit::TestCase
4
+
5
+ include WebRepl
6
+
7
+ context "Messager" do
8
+
9
+ setup do
10
+ @socket = Object.new
11
+ @messager = Messager.new(@socket)
12
+ end
13
+
14
+ context "#in" do
15
+
16
+ setup do
17
+ @message = { :value => "blah", :timestamp => 1396406728702 }.to_json
18
+ @result = @messager.in(@message)
19
+ end
20
+
21
+ should "convert from String to Hash" do
22
+ assert_not_nil @result
23
+ assert_equal Hash, @result.class
24
+ assert_equal "blah", @result[:value]
25
+ end
26
+
27
+ should "convert timestamp from js time to ruby" do
28
+ timestamp = @result[:timestamp]
29
+ assert_not_nil timestamp
30
+ assert_equal Time, timestamp.class
31
+ assert_equal 2014, timestamp.year
32
+ assert_equal 4, timestamp.month
33
+ assert_equal 22, timestamp.hour
34
+ end
35
+
36
+ end
37
+
38
+ context "#new_timestamp" do
39
+
40
+ should "be js int time format" do
41
+ result = @messager.new_timestamp
42
+ assert_not_nil result
43
+ assert_equal Fixnum, result.class
44
+ assert result.to_s.size > Time.new.to_i.to_s.size
45
+ assert_equal (result / 1000).to_s.size, Time.new.to_i.to_s.size
46
+ end
47
+
48
+ end
49
+
50
+ context "#out" do
51
+
52
+ setup do
53
+ @message = { :statement => "something" }
54
+ end
55
+
56
+ should "not overwrite timestamp" do
57
+ @socket.expects(:send).once
58
+ ts = Time.now.to_i / 1000
59
+ @message[:timestamp] = ts
60
+ @messager.out(@message)
61
+ assert_equal ts, @message[:timestamp]
62
+ end
63
+
64
+ should "generate new timestamp" do
65
+ @socket.expects(:send).once
66
+ @messager.out(@message)
67
+ assert_not_nil @message[:timestamp]
68
+ assert_equal Fixnum, @message[:timestamp].class
69
+ end
70
+
71
+ should "return nil if fails" do
72
+ messager = Messager.new(nil)
73
+ result = messager.out(@message)
74
+ assert_nil result
75
+ end
76
+
77
+ should "return json string if success" do
78
+ @socket.expects(:send).once
79
+ result = @messager.out(@message)
80
+ assert_not_nil result
81
+ assert_equal String, result.class
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,14 @@
1
+ require "helper"
2
+
3
+ class WebRepl::ReplTest < Test::Unit::TestCase
4
+
5
+ include WebRepl
6
+
7
+ context "Repl" do
8
+
9
+ # TODO
10
+ #
11
+ end
12
+
13
+ end
14
+
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web-repl
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ari Russo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-websocket
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mocha
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: shoulda-context
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Javascript/Web REPL in Ruby
95
+ email:
96
+ - ari.russo@gmail.com
97
+ executables:
98
+ - web-repl
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - bin/web-repl
103
+ - js/replConnection-0.1.min.js
104
+ - js/replConnection.js
105
+ - lib/web-repl/messager.rb
106
+ - lib/web-repl/patch.rb
107
+ - lib/web-repl/repl.rb
108
+ - lib/web-repl.rb
109
+ - test/helper.rb
110
+ - test/messager_test.rb
111
+ - test/repl_test.rb
112
+ - LICENSE
113
+ - README.md
114
+ homepage: http://github.com/arirusso/web-repl
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: 1.3.6
132
+ requirements: []
133
+ rubyforge_project: web-repl
134
+ rubygems_version: 1.8.25
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Javascript/Web REPL in Ruby
138
+ test_files:
139
+ - test/messager_test.rb
140
+ - test/repl_test.rb