iruby 0.0.1

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: 39e9bb6cc42415e3ab312341fd04724112d2952a
4
+ data.tar.gz: 7ffcf1e9550a71da16c554dada617e81e4fa7a41
5
+ SHA512:
6
+ metadata.gz: 699ae86c55615f002265cb48b9b53463c9ce228aff076a296b2f338a74423799b3781cdada02ad848b13ad307945da3d46be78e8cbc1274280836d4acbc1c08a
7
+ data.tar.gz: 34aa0376810e9e1a1f8f0f6d9dc68c10bd2d714ee690966dff6cd224ca6b3ab5068aee3009324aba7b566a2a7c8114223c2fafe9ba4e36658b3056f6634ee9db
@@ -0,0 +1,19 @@
1
+ .ipynb_checkpoints/
2
+ *.gem
3
+ *.rbc
4
+ *.log
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE 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.
@@ -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.
@@ -0,0 +1,13 @@
1
+ require 'rake/testtask'
2
+
3
+ begin
4
+ require 'bundler/gem_tasks'
5
+ rescue Exception
6
+ end
7
+
8
+ Rake::TestTask.new('test') do |t|
9
+ t.libs << 'lib'
10
+ t.libs << 'test'
11
+ t.test_files = FileList['test/**/*_test.rb']
12
+ t.verbose = true
13
+ end
@@ -0,0 +1,180 @@
1
+ {
2
+ "metadata": {
3
+ "name": ""
4
+ },
5
+ "nbformat": 3,
6
+ "nbformat_minor": 0,
7
+ "worksheets": [
8
+ {
9
+ "cells": [
10
+ {
11
+ "cell_type": "code",
12
+ "collapsed": false,
13
+ "input": [
14
+ "puts \"hi\"\n",
15
+ "raise \"zofe\""
16
+ ],
17
+ "language": "python",
18
+ "metadata": {},
19
+ "outputs": [
20
+ {
21
+ "output_type": "stream",
22
+ "stream": "stdout",
23
+ "text": [
24
+ "hi\n"
25
+ ]
26
+ },
27
+ {
28
+ "ename": "RuntimeError",
29
+ "evalue": "zofe",
30
+ "output_type": "pyerr",
31
+ "traceback": [
32
+ "\u001b[31mRuntimeError\u001b[0m: zofe",
33
+ "\u001b[37m<main>:1:in `<main>'\u001b[0m",
34
+ "\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `eval'\u001b[0m",
35
+ "\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `execute_request'\u001b[0m",
36
+ "\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:176:in `start'\u001b[0m",
37
+ "\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:60:in `run_kernel'\u001b[0m",
38
+ "\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:15:in `run'\u001b[0m",
39
+ "\u001b[37m/home/minad/code/git/iruby/bin/iruby:6:in `<main>'\u001b[0m"
40
+ ]
41
+ }
42
+ ],
43
+ "prompt_number": 3
44
+ },
45
+ {
46
+ "cell_type": "markdown",
47
+ "metadata": {},
48
+ "source": [
49
+ "# This is an IPython notebook, backed by a ruby kernel.\n",
50
+ "\n",
51
+ "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",
52
+ "\n",
53
+ "The IPython KernelManager initializes the RubyKernel with popen, and from that point communication occurs over ZeroMQ, exactly as in IPython's kernel.\n",
54
+ "\n",
55
+ "Once that was done, it's easy to execute ruby against that kernel right in the browser."
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "collapsed": false,
61
+ "input": [
62
+ "class Neat\n",
63
+ " def eh?\n",
64
+ " \"hell yes it is.\"\n",
65
+ " end\n",
66
+ "end\n",
67
+ "\n",
68
+ "Neat.new.eh?\n",
69
+ "\n",
70
+ "puts \"hi\""
71
+ ],
72
+ "language": "python",
73
+ "metadata": {},
74
+ "outputs": [
75
+ {
76
+ "output_type": "stream",
77
+ "stream": "stdout",
78
+ "text": [
79
+ "hi\n"
80
+ ]
81
+ }
82
+ ],
83
+ "prompt_number": 2
84
+ },
85
+ {
86
+ "cell_type": "markdown",
87
+ "metadata": {},
88
+ "source": [
89
+ "## What does this give you?\n",
90
+ "\n",
91
+ "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",
92
+ "\n",
93
+ "It's basically an in-browser REPL loop, with some extra goodies."
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "collapsed": false,
99
+ "input": [
100
+ "class ThatsPrettyCool\n",
101
+ " def how_does_it_work?\n",
102
+ " puts \"I'm glad you asked!\"\n",
103
+ " :see_below\n",
104
+ " end\n",
105
+ "end\n",
106
+ "\n",
107
+ "ThatsPrettyCool.new.how_does_it_work?"
108
+ ],
109
+ "language": "python",
110
+ "metadata": {},
111
+ "outputs": [
112
+ {
113
+ "output_type": "stream",
114
+ "stream": "stdout",
115
+ "text": [
116
+ "I'm glad you asked!\n"
117
+ ]
118
+ },
119
+ {
120
+ "metadata": {},
121
+ "output_type": "pyout",
122
+ "prompt_number": 4,
123
+ "text": [
124
+ "see_below"
125
+ ]
126
+ }
127
+ ],
128
+ "prompt_number": 4
129
+ },
130
+ {
131
+ "cell_type": "markdown",
132
+ "metadata": {},
133
+ "source": [
134
+ "### Here's how it works\n",
135
+ "\n",
136
+ "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",
137
+ "\n",
138
+ "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",
139
+ "\n",
140
+ "### What does a message look like?\n",
141
+ "\n",
142
+ "A given JSON message looks like this:\n",
143
+ "\n",
144
+ " {\"header\"=>\n",
145
+ " {\"username\"=>\"username\",\n",
146
+ " \"msg_id\"=>\"38C1E3299BB14F7C99D95504CEAE4856\",\n",
147
+ " \"msg_type\"=>\"execute_request\",\n",
148
+ " \"session\"=>\"BAC5EA31BC6F4C1D888DD5F8C46B6F9D\"},\n",
149
+ " \"msg_id\"=>\"msg_id\",\n",
150
+ " \"msg_type\"=>\"msg_type\",\n",
151
+ " \"parent_header\"=>{},\n",
152
+ " \"content\"=>\n",
153
+ " {\"user_variables\"=>[],\n",
154
+ " \"allow_stdin\"=>false,\n",
155
+ " \"code\"=>\n",
156
+ " \"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",
157
+ " \"silent\"=>false,\n",
158
+ " \"user_expressions\"=>{}},\n",
159
+ " \"buffers\"=>[]}"
160
+ ]
161
+ },
162
+ {
163
+ "cell_type": "markdown",
164
+ "metadata": {},
165
+ "source": [
166
+ "### What remains to be done for this to be 'complete'?\n",
167
+ "\n",
168
+ "- 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",
169
+ "- 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",
170
+ "- There aren't any tests.\n",
171
+ "- It would be nice to support the fancy IPython graph and html-table gooies, I'll look into that.\n",
172
+ "- IPython supports tab-completion in the browser REPL, but my kernel doesn't know how to respond to those messages.\n",
173
+ "- There are some other message types I've not yet implemented, but I don't really see the problem at present :)"
174
+ ]
175
+ }
176
+ ],
177
+ "metadata": {}
178
+ }
179
+ ]
180
+ }
@@ -0,0 +1,217 @@
1
+ module IRuby
2
+ module Output
3
+ module HTML
4
+ def self.table(data)
5
+ #
6
+ # data = {a: 1, b:2}
7
+
8
+ if data.respond_to?(:keys)
9
+ d = data
10
+ else
11
+ d = data
12
+ end
13
+
14
+ r = "<table>"
15
+ if d.respond_to?(:keys) # hash
16
+ columns = [0,1]
17
+ elsif d.first.respond_to?(:keys) # array of hashes
18
+ columns = d.first.keys
19
+ r << "<tr>#{columns.map{|c| "<th>#{c.to_s}</th>"}.join}</tr>"
20
+ else # array
21
+ columns = (0 .. d.first.length)
22
+ end
23
+ d.each{|row|
24
+ r << "<tr>"
25
+ columns.each{|column|
26
+ r << "<td>#{row[column].to_s}</td>"
27
+ }
28
+ r << "</tr>"
29
+ }
30
+ r << "</table>"
31
+ r
32
+ end
33
+
34
+ def self.image(image)
35
+ data = image.respond_to?(:to_blob) ? image.to_blob : image
36
+ "<img src='data:image/png;base64,#{Base64.encode64(data)}'>"
37
+ end
38
+
39
+ def self.chart_pie(o)
40
+ data=o.delete(:data)
41
+ title=o.delete(:title)
42
+ size=o.delete(:size) || 300
43
+ g = Gruff::Pie.new(size)
44
+ g.title = title if title
45
+ data.each do |data|
46
+ label = data[0].strip
47
+ label = "?" if label == ''
48
+ g.data(label, data[1])
49
+ end
50
+ image(g.to_blob)
51
+ end
52
+
53
+ def self.chart_bar(o)
54
+ data=o.delete(:data)
55
+ title=o.delete(:title)
56
+ size=o.delete(:size) || 300
57
+
58
+ klass = o.delete(:stacked) ? Gruff::StackedBar : Gruff::Bar
59
+ g = klass.new(size)
60
+
61
+ if labels=o.delete(:labels)
62
+ if ! labels.respond_to?(:keys)
63
+ labels = Hash[labels.map.with_index{|v,k| [k,v]}]
64
+ end
65
+ g.labels = labels
66
+ end
67
+
68
+ g.title = title if title
69
+ data.each do |data|
70
+ g.data(data[0], data[1])
71
+ end
72
+ image(g.to_blob)
73
+
74
+ end
75
+
76
+ module Gmaps
77
+ def self.points2latlng(points)
78
+ "[" + points.reject{|p| not p.lat or not p.lon}.map{|p|
79
+ icon_url = nil
80
+ icon_url = p.icon_url if p.respond_to?(:icon_url)
81
+ icon_url = "http://www.google.com/intl/en_us/mapfiles/ms/micons/#{p.icon}-dot.png"if p.respond_to?(:icon)
82
+ "{" + [
83
+ "location: new google.maps.LatLng(#{p.lat.to_f}, #{p.lon.to_f})",
84
+ p.respond_to?(:weight) && p.weight && "weight: #{p.weight.to_i} ",
85
+ p.respond_to?(:label) && "label: #{p.label.to_s.to_json}",
86
+ p.respond_to?(:z_index) && "z_index: #{p.z_index.to_json}",
87
+ icon_url && "icon_url: #{icon_url.to_json}",
88
+ ].reject{|x| ! x}
89
+ .join(",") + "}"
90
+ }.join(',') + "]"
91
+ end
92
+ def self.base_map(o)
93
+ zoom = o.delete(:zoom)
94
+ center = o.delete(:center)
95
+ map_type = o.delete(:map_type)
96
+ width = o.delete(:width) || "500px"
97
+ height = o.delete(:height) || "500px"
98
+ r = <<E
99
+ <div id='map-canvas' style='width: #{width}; height: #{height};'></div>
100
+ <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=visualization&callback=initialize"></script>
101
+
102
+ <script>
103
+ function initialize() {
104
+ var latlngbounds = new google.maps.LatLngBounds();
105
+ var zoom = #{zoom.to_json};
106
+ var center = #{center.to_json};
107
+ var map_type = #{map_type.to_json} || google.maps.MapTypeId.SATELLITE;
108
+
109
+ var mapOptions = {
110
+ mapTypeId: map_type
111
+ };
112
+
113
+ if (zoom){
114
+ mapOptions.zoom = zoom
115
+ }
116
+ if (center){
117
+ mapOptions.center = new google.maps.LatLng(center.lat, center.lon)
118
+ }
119
+
120
+ map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
121
+
122
+ #{yield}
123
+ }
124
+ </script>
125
+ E
126
+ r
127
+
128
+ end
129
+ def self.heatmap(o)
130
+ data = o.delete(:points)
131
+ points = points2latlng(data)
132
+ radius = o.delete(:radius)
133
+ raise "Missing :points parameter" if not data
134
+ base_map(o){<<E
135
+ var points = #{points};
136
+ if (! zoom){
137
+ for (var i = 0; i < points.length; i++) {
138
+ latlngbounds.extend(points[i].location);
139
+ }
140
+ map.fitBounds(latlngbounds);
141
+ }
142
+
143
+
144
+ var pointArray = new google.maps.MVCArray(points);
145
+
146
+ heatmap = new google.maps.visualization.HeatmapLayer({
147
+ radius: #{radius.to_json} || 10,
148
+ data: pointArray
149
+ });
150
+
151
+ heatmap.setMap(map);
152
+ E
153
+ }
154
+
155
+ end
156
+ def self.markers(o)
157
+ data = o.delete(:points)
158
+ points = points2latlng(data)
159
+ radius = o.delete(:radius)
160
+ raise "Missing :points parameter" if not data
161
+ base_map(o){<<E
162
+ var points = #{points};
163
+ if (! zoom){
164
+ for (var i = 0; i < points.length; i++) {
165
+ latlngbounds.extend(points[i].location);
166
+ }
167
+ map.fitBounds(latlngbounds);
168
+ }
169
+
170
+ for (var i=0; i<points.length; i++){
171
+ var marker = new google.maps.Marker({
172
+ position: points[i].location,
173
+ map: map,
174
+ icon: points[i].icon_url,
175
+ zIndex: points[i].z_index,
176
+ title: points[i].label
177
+ });
178
+ }
179
+
180
+ E
181
+ }
182
+
183
+ end
184
+ end
185
+ #stolen from https://github.com/Bantik/heatmap/blob/master/lib/heatmap.rb
186
+ module WordCloud
187
+
188
+ def self.wordcloud(histogram={})
189
+ html = %{<div class="wordcloud">}
190
+ histogram.keys.sort{|a,b| histogram[a] <=> histogram[b]}.reverse.each do |k|
191
+ next if histogram[k] < 1
192
+ _max = histogram_max(histogram) * 2
193
+ _size = element_size(histogram, k)
194
+ _heat = element_heat(histogram[k], _max)
195
+ html << %{
196
+ <span class="wordcloud_element" style="color: ##{_heat}#{_heat}#{_heat}; font-size: #{_size}px;">#{k}</span>
197
+ }
198
+ end
199
+ html << %{<br style="clear: both;" /></div>}
200
+ end
201
+
202
+ def self.histogram_max(histogram)
203
+ histogram.map{|k,v| histogram[k]}.max
204
+ end
205
+
206
+ def self.element_size(histogram, key)
207
+ (((histogram[key] / histogram.map{|k,v| histogram[k]}.reduce(&:+).to_f) * 100) + 5).to_i
208
+ end
209
+
210
+ def self.element_heat(val, max)
211
+ sprintf("%02x" % (200 - ((200.0 / max) * val)))
212
+ end
213
+
214
+ end
215
+ end
216
+ end
217
+ end