jnv-iruby 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/Rakefile +1 -0
- data/Ruby Notebook.ipynb +117 -0
- data/bin/iruby_kernel +92 -0
- data/bin/iruby_profile +37 -0
- data/iruby.gemspec +31 -0
- data/lib/iruby.rb +4 -0
- data/lib/iruby/display_hook.rb +33 -0
- data/lib/iruby/kernel.rb +220 -0
- data/lib/iruby/kernel_completer.rb +30 -0
- data/lib/iruby/message.rb +44 -0
- data/lib/iruby/out_stream.rb +95 -0
- data/lib/iruby/output/html.rb +168 -0
- data/lib/iruby/profile.rb +71 -0
- data/lib/iruby/session.rb +329 -0
- data/lib/iruby/version.rb +3 -0
- data/static/base/images/ipynblogo.png +0 -0
- data/static/base/images/ipynblogo.svg +268 -0
- data/static/custom/custom.css +1 -0
- data/static/custom/custom.js +8 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7e6455a899a0f5681f903057acdb3d9bbb6eaa26
|
4
|
+
data.tar.gz: dc4375cd4ca2bddc059dcc414f92dcc7895c2f54
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67542fdf88d6c60c9d450f17ffcc4ed9ffa5a101b148117139cc85def8f8e5a0d570dacdd99b80d96a47892a0ce672381559f5e4709a772e7c7466fa4f3a926d
|
7
|
+
data.tar.gz: 7eee6d884ef082dc39340e6d4cf4581ab1ddcdbad323ad5b580f05fcb2f8145e2558e4c96358a98aa0c3ee2b0d3876992479f808d40318bb1cdfeac956f6ec33
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Damián Silvani
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# IRuby
|
2
|
+
|
3
|
+
This is a Ruby kernel for IPython.
|
4
|
+
|
5
|
+
It adds a special `iruby_profile` command for staging some customization
|
6
|
+
that enables the Ruby kernel by default, and sets syntax-highlighting in the notebook
|
7
|
+
to Ruby mode.
|
8
|
+
|
9
|
+
### Usage
|
10
|
+
|
11
|
+
Clone this repository and run `bin/iruby_profile` to create the profile, then
|
12
|
+
use IPython as usual:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
git clone git://github.com/minrk/iruby
|
16
|
+
cd iruby
|
17
|
+
# build and install IRuby
|
18
|
+
gem build iruby.gemspec
|
19
|
+
$ gem install iruby-*.gem
|
20
|
+
# Create an IPython profile with default config
|
21
|
+
$ iruby_profile --create
|
22
|
+
$ ipython notebook --profile=ruby
|
23
|
+
```
|
24
|
+
|
25
|
+
|
26
|
+
## Background
|
27
|
+
|
28
|
+
### Building an in-browser REPL for Ruby (IRuby)
|
29
|
+
|
30
|
+
Hey, I'm Josh Adams. I'm a partner and CTO at isotope|eleven. We alo host
|
31
|
+
Birmingham, AL's Open Source Software meetup - BOSS.
|
32
|
+
|
33
|
+
At one of these sessions in early 2012, Tom Brander did a presentation and used
|
34
|
+
IPython in his browser to manage it (there was much code and it was executed
|
35
|
+
live). This was the first time I'd seen IPython in the browser where it
|
36
|
+
actually worked like it was supposed to, and I was extremely impressed.
|
37
|
+
|
38
|
+
If you've not seen IPython, it looks like this <* Insert Screenshot Here *> in
|
39
|
+
its web-browser mode. It also manages a lot of console-basd REPLs.
|
40
|
+
|
41
|
+
Anyway, it has notebooks that you can save out to execute later, and you can
|
42
|
+
pass them around as little code snippets for other people to check out. It's
|
43
|
+
very impressive.
|
44
|
+
|
45
|
+
But I'm primarily a Rubyist, and I'm happy that way :) I couldn't sit by while
|
46
|
+
Python had this awesome tool that we lacked. I looked around for a bit, and
|
47
|
+
there was nothing like IPython in our ecosystem. There were, however, quite a
|
48
|
+
few people asking about it. So I figured I'd do something about it.
|
49
|
+
|
50
|
+
#### The Architecture
|
51
|
+
|
52
|
+
So the IPython guys did a great job explaining their core architecture, both in
|
53
|
+
words and in pared-down code, in a blog post they wrote concerning it. In
|
54
|
+
general, it works like this <* Diagram *>
|
55
|
+
|
56
|
+
There's a kernel that runs in the background and gets connected to by a
|
57
|
+
frontend. They communicate using zeromq, and they send json formatted messages
|
58
|
+
back and forth. These messages are in a very well defined structure. Anyway,
|
59
|
+
this way the frontend of the repl is disconnected from the environment that's
|
60
|
+
running it.
|
61
|
+
|
62
|
+
So the code repository they linked to in their blog post included the kernel and
|
63
|
+
the frontend as small-ish python files - around 300 and 200 lines respectively.
|
64
|
+
We had a hack weekend at isotope|eleven where myself and Robby Clements got
|
65
|
+
together and (when we weren't playing Counterstrike1.6) did the closest thing to
|
66
|
+
a straight port that we could swing. Within about 2 hours of work, we had a
|
67
|
+
working proof of concept that was primarily a 1 to 1 port.
|
68
|
+
|
69
|
+
The next move was to build the web frontend. This just consists of a websocket
|
70
|
+
server and a fairly basic frontend webpage.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Ruby Notebook.ipynb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
{
|
2
|
+
"metadata": {
|
3
|
+
"name": "Ruby Notebook"
|
4
|
+
},
|
5
|
+
"nbformat": 3,
|
6
|
+
"nbformat_minor": 0,
|
7
|
+
"worksheets": [
|
8
|
+
{
|
9
|
+
"cells": [
|
10
|
+
{
|
11
|
+
"cell_type": "markdown",
|
12
|
+
"metadata": {},
|
13
|
+
"source": [
|
14
|
+
"# This is an IPython notebook, backed by a ruby kernel.\n",
|
15
|
+
"\n",
|
16
|
+
"I wrote a kernel in Ruby that adheres to the [IPython](http://ipython.org/) messaging protocol. Then I modified IPython, with the help of minrk from #ipython, to instantiate my Ruby Kernel instead of its own Python kernel.\n",
|
17
|
+
"\n",
|
18
|
+
"The IPython KernelManager initializes the RubyKernel with popen, and from that point communication occurs over ZeroMQ, exactly as in IPython's kernel.\n",
|
19
|
+
"\n",
|
20
|
+
"Once that was done, it's easy to execute ruby against that kernel right in the browser."
|
21
|
+
]
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"cell_type": "code",
|
25
|
+
"collapsed": false,
|
26
|
+
"input": [
|
27
|
+
"class Neat\n",
|
28
|
+
" def eh?\n",
|
29
|
+
" \"hell yes it is.\"\n",
|
30
|
+
" end\n",
|
31
|
+
"end\n",
|
32
|
+
"\n",
|
33
|
+
"Neat.new.eh?"
|
34
|
+
],
|
35
|
+
"language": "ruby",
|
36
|
+
"metadata": {},
|
37
|
+
"outputs": []
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"cell_type": "markdown",
|
41
|
+
"metadata": {},
|
42
|
+
"source": [
|
43
|
+
"## What does this give you?\n",
|
44
|
+
"\n",
|
45
|
+
"This gives us a very fancy web notebook interface for Ruby. It's a very good tool for programming presentations. Tom Brander's presentation to the BOSS meetup group made me realize exactly how far IPython had come since I last saw it, and I knew I wanted to have it for ruby.\n",
|
46
|
+
"\n",
|
47
|
+
"It's basically an in-browser REPL loop, with some extra goodies."
|
48
|
+
]
|
49
|
+
},
|
50
|
+
{
|
51
|
+
"cell_type": "code",
|
52
|
+
"collapsed": false,
|
53
|
+
"input": [
|
54
|
+
"class ThatsPrettyCool\n",
|
55
|
+
" def how_does_it_work?\n",
|
56
|
+
" puts \"I'm glad you asked!\"\n",
|
57
|
+
" :see_below\n",
|
58
|
+
" end\n",
|
59
|
+
"end\n",
|
60
|
+
"\n",
|
61
|
+
"ThatsPrettyCool.new.how_does_it_work?"
|
62
|
+
],
|
63
|
+
"language": "ruby",
|
64
|
+
"metadata": {},
|
65
|
+
"outputs": []
|
66
|
+
},
|
67
|
+
{
|
68
|
+
"cell_type": "markdown",
|
69
|
+
"metadata": {},
|
70
|
+
"source": [
|
71
|
+
"### Here's how it works\n",
|
72
|
+
"\n",
|
73
|
+
"Each notebook has its own kernel. When you open a notebook in the web interface, a kernel is started in the background by the IPython webserver. A websocket is then used to connect the frontend directly to the kernel, more or less, and they pass messages back and forth.\n",
|
74
|
+
"\n",
|
75
|
+
"All of the messages are in a JSON format. The actual information passed over the wire between the web server and the kernel is a little different, but it basically just includes some session/auth information, and then the components of the message serialized, too be unserialized on the Kernel into the full json message again.\n",
|
76
|
+
"\n",
|
77
|
+
"### What does a message look like?\n",
|
78
|
+
"\n",
|
79
|
+
"A given JSON message looks like this:\n",
|
80
|
+
"\n",
|
81
|
+
" {\"header\"=>\n",
|
82
|
+
" {\"username\"=>\"username\",\n",
|
83
|
+
" \"msg_id\"=>\"38C1E3299BB14F7C99D95504CEAE4856\",\n",
|
84
|
+
" \"msg_type\"=>\"execute_request\",\n",
|
85
|
+
" \"session\"=>\"BAC5EA31BC6F4C1D888DD5F8C46B6F9D\"},\n",
|
86
|
+
" \"msg_id\"=>\"msg_id\",\n",
|
87
|
+
" \"msg_type\"=>\"msg_type\",\n",
|
88
|
+
" \"parent_header\"=>{},\n",
|
89
|
+
" \"content\"=>\n",
|
90
|
+
" {\"user_variables\"=>[],\n",
|
91
|
+
" \"allow_stdin\"=>false,\n",
|
92
|
+
" \"code\"=>\n",
|
93
|
+
" \"class ThatsPrettyCool\\n def how_does_it_work?\\n puts \\\"I'm glad you asked!\\\"\\n end\\nend\\n\\nThatsPrettyCool.new.how_does_it_work?\",\n",
|
94
|
+
" \"silent\"=>false,\n",
|
95
|
+
" \"user_expressions\"=>{}},\n",
|
96
|
+
" \"buffers\"=>[]}"
|
97
|
+
]
|
98
|
+
},
|
99
|
+
{
|
100
|
+
"cell_type": "markdown",
|
101
|
+
"metadata": {},
|
102
|
+
"source": [
|
103
|
+
"### What remains to be done for this to be 'complete'?\n",
|
104
|
+
"\n",
|
105
|
+
"- Right now, I'm not passing back the 'return value of the last expression' like you'd expect a proper REPL to do. I'm not exactly sure how to get the analog of python's sys.displayhook working, but I welcome anyone to teach me how it's done :) I can probably figure it out from the pry source.\n",
|
106
|
+
"- backtraces are a little bit wonky right now and don't give you the proper backtrace into the executed code, but rather into the Kernel's source where the eval loop runs...not very helpful.\n",
|
107
|
+
"- There aren't any tests.\n",
|
108
|
+
"- It would be nice to support the fancy IPython graph and html-table gooies, I'll look into that.\n",
|
109
|
+
"- IPython supports tab-completion in the browser REPL, but my kernel doesn't know how to respond to those messages.\n",
|
110
|
+
"- There are some other message types I've not yet implemented, but I don't really see the problem at present :)"
|
111
|
+
]
|
112
|
+
}
|
113
|
+
],
|
114
|
+
"metadata": {}
|
115
|
+
}
|
116
|
+
]
|
117
|
+
}
|
data/bin/iruby_kernel
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
USAGE = <<-EOS
|
4
|
+
This script is not intended to be used manually.
|
5
|
+
|
6
|
+
To start an IPython Notebook server with IRuby, first create a profile with
|
7
|
+
`iruby_profile`, then use `ipython notebook` to start the server:
|
8
|
+
|
9
|
+
$ iruby_profile --create
|
10
|
+
$ ipython notebook --profile=ruby
|
11
|
+
|
12
|
+
Read more at http://ipython.org/ipython-doc/dev/interactive/notebook.html
|
13
|
+
|
14
|
+
EOS
|
15
|
+
|
16
|
+
def start_kernel!(config_path, boot_file=nil)
|
17
|
+
#ENV["BUNDLE_GEM"] = "/home/munshkr/overol-parsers/Gemfile"
|
18
|
+
#ENV["APP_ROOT"] = "/home/munshkr/overol-parsers"
|
19
|
+
|
20
|
+
require boot_file if boot_file
|
21
|
+
require 'iruby/kernel'
|
22
|
+
|
23
|
+
configfile = File.read(config_path)
|
24
|
+
config = JSON.parse(configfile)
|
25
|
+
|
26
|
+
c = ZMQ::Context.new
|
27
|
+
|
28
|
+
shell_port = config['shell_port']
|
29
|
+
pub_port = config['iopub_port']
|
30
|
+
hb_port = config['hb_port']
|
31
|
+
|
32
|
+
ip = '127.0.0.1'
|
33
|
+
connection = ('tcp://%s' % ip) + ':%i'
|
34
|
+
shell_conn = connection % shell_port
|
35
|
+
pub_conn = connection % pub_port
|
36
|
+
hb_conn = connection % hb_port
|
37
|
+
|
38
|
+
$stdout.puts "Starting the kernel..."
|
39
|
+
$stdout.puts "On:",shell_conn, pub_conn, hb_conn
|
40
|
+
|
41
|
+
session = IRuby::Session.new('kernel')
|
42
|
+
|
43
|
+
reply_socket = c.socket(ZMQ::XREP)
|
44
|
+
reply_socket.bind(shell_conn)
|
45
|
+
|
46
|
+
pub_socket = c.socket(ZMQ::PUB)
|
47
|
+
pub_socket.bind(pub_conn)
|
48
|
+
|
49
|
+
hb_thread = Thread.new do
|
50
|
+
hb_socket = c.socket(ZMQ::REP)
|
51
|
+
hb_socket.bind(hb_conn)
|
52
|
+
ZMQ::Device.new(ZMQ::FORWARDER, hb_socket, hb_socket)
|
53
|
+
end
|
54
|
+
|
55
|
+
stdout = IRuby::OutStream.new(session, pub_socket, 'stdout')
|
56
|
+
#stderr = OutStream.new(session, pub_socket, 'stderr')
|
57
|
+
old_stdout = STDOUT
|
58
|
+
$stdout = stdout
|
59
|
+
#$stderr = stderr
|
60
|
+
|
61
|
+
|
62
|
+
kernel = IRuby::Kernel.new(session, reply_socket, pub_socket)
|
63
|
+
display_hook = IRuby::DisplayHook.new(kernel, session, pub_socket)
|
64
|
+
$displayhook = display_hook
|
65
|
+
|
66
|
+
# For debugging convenience, put sleep and a string in the namespace, so we
|
67
|
+
# have them every time we start.
|
68
|
+
#kernel.user_ns['sleep'] = sleep
|
69
|
+
#kernel.user_ns['s'] = 'Test string'
|
70
|
+
|
71
|
+
old_stdout.puts "Use Ctrl-\\ (NOT Ctrl-C!) to terminate."
|
72
|
+
kernel.start()
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
if ARGV.size == 0 || ARGV.size > 2
|
77
|
+
puts USAGE
|
78
|
+
exit(1)
|
79
|
+
end
|
80
|
+
config_path, boot_file = ARGV
|
81
|
+
|
82
|
+
if !File.exists?(config_path)
|
83
|
+
puts "Config file '#{config_path}' does not exist"
|
84
|
+
exit(2)
|
85
|
+
end
|
86
|
+
|
87
|
+
if boot_file && !File.exists?(boot_file)
|
88
|
+
puts "File '#{boot_file}' does not exist"
|
89
|
+
exit(3)
|
90
|
+
end
|
91
|
+
|
92
|
+
start_kernel!(config_path, boot_file)
|
data/bin/iruby_profile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'trollop'
|
3
|
+
|
4
|
+
opts = Trollop::options do
|
5
|
+
banner <<-EOS
|
6
|
+
`iruby_profile` creates an IPython profile already customized for using the
|
7
|
+
IRuby kernel. You can specify a custom name with --name, the real name will
|
8
|
+
have 'iruby_' prepended.
|
9
|
+
|
10
|
+
Examples:
|
11
|
+
$ iruby_profile --create
|
12
|
+
# creates profile at $(ipython locate profile ruby)
|
13
|
+
|
14
|
+
$ iruby_profile --create --name='special'
|
15
|
+
# creates profile at $(ipython locate profile special)
|
16
|
+
|
17
|
+
To use the profile, start IPython as usual:
|
18
|
+
$ ipython notebook --profile='ruby'
|
19
|
+
|
20
|
+
Options:
|
21
|
+
EOS
|
22
|
+
|
23
|
+
opt :create, 'Create a profile', default: false
|
24
|
+
opt :name, 'Profile name', type: :string, default: 'ruby'
|
25
|
+
end
|
26
|
+
|
27
|
+
if not opts[:create_given]
|
28
|
+
puts "Try --help first."
|
29
|
+
exit(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
if opts[:create]
|
33
|
+
require 'iruby/profile'
|
34
|
+
|
35
|
+
p = IRuby::Profile.new(opts[:name])
|
36
|
+
p.create!
|
37
|
+
end
|
data/iruby.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jnv-iruby"
|
8
|
+
spec.version = IRuby::VERSION
|
9
|
+
spec.authors = ["Damián Silvani", "Min RK", "martin sarsale", "Josh Adams"]
|
10
|
+
spec.email = ["benjaminrk@gmail.com"]
|
11
|
+
spec.description = %q{Ruby Kernel for IPython}
|
12
|
+
spec.summary = %q{A Ruby kernel for IPython frontends (notebook console, etc.)}
|
13
|
+
spec.homepage = "https://github.com/minrk/iruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_runtime_dependency "bond"
|
25
|
+
spec.add_runtime_dependency "ffi-rzmq"
|
26
|
+
spec.add_runtime_dependency "json"
|
27
|
+
spec.add_runtime_dependency "term-ansicolor"
|
28
|
+
spec.add_runtime_dependency "trollop"
|
29
|
+
spec.add_runtime_dependency "uuid"
|
30
|
+
spec.add_runtime_dependency "gruff"
|
31
|
+
end
|
data/lib/iruby.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'iruby/message'
|
2
|
+
|
3
|
+
module IRuby
|
4
|
+
class DisplayHook
|
5
|
+
def initialize kernel, session, pub_socket
|
6
|
+
@kernel = kernel
|
7
|
+
@session = session
|
8
|
+
@pub_socket = pub_socket
|
9
|
+
@parent_header = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def display(obj)
|
13
|
+
if obj.nil?
|
14
|
+
return
|
15
|
+
end
|
16
|
+
# STDERR.puts @kernel.user_ns
|
17
|
+
# @user_ns._ = obj
|
18
|
+
# STDERR.puts "displayhook call:"
|
19
|
+
# STDERR.puts @parent_header.inspect
|
20
|
+
#@pub_socket.send(msg.to_json)
|
21
|
+
data = {}
|
22
|
+
output = obj
|
23
|
+
data['text/plain'] = output
|
24
|
+
data[obj.mime] = output
|
25
|
+
content = {data: data, metadata: {}, execution_count: @kernel.execution_count}
|
26
|
+
@session.send(@pub_socket, 'pyout', content, @parent_header)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_parent parent
|
30
|
+
@parent_header = Message.extract_header(parent)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|