iruby 0.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.
@@ -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