jnv-iruby 0.0.2

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,220 @@
1
+ require 'ffi-rzmq'
2
+ require 'json'
3
+ require 'ostruct'
4
+ require 'term/ansicolor'
5
+
6
+ require 'iruby/kernel_completer'
7
+ require 'iruby/session'
8
+ require 'iruby/out_stream'
9
+ require 'iruby/display_hook'
10
+ require 'iruby/output/html'
11
+
12
+
13
+ class String
14
+ include Term::ANSIColor
15
+ end
16
+
17
+ module IRuby
18
+
19
+ class ResponseWithMime
20
+ def initialize(response, mime)
21
+ @response = response
22
+ @mime = mime
23
+ end
24
+ attr_reader :mime
25
+ def inspect
26
+ @response.inspect
27
+ end
28
+ def to_s
29
+ @response.to_s
30
+ end
31
+ end
32
+
33
+ class BindingWithMime < OpenStruct
34
+ def mime_type=(v)
35
+ @mime_type = v
36
+ end
37
+ def mime_type
38
+ @mime_type || 'text/plain'
39
+ end
40
+ end
41
+
42
+
43
+ class Kernel
44
+ attr_accessor :user_ns
45
+
46
+ def self.html(s)
47
+ @output_mime = "text/html"
48
+ s
49
+ end
50
+
51
+ def execution_count
52
+ @execution_count
53
+ end
54
+
55
+ def output_mime
56
+ @output_mime
57
+ end
58
+
59
+ def initialize session, reply_socket, pub_socket
60
+ @session = session
61
+ @reply_socket = reply_socket
62
+ @pub_socket = pub_socket
63
+ @user_ns = BindingWithMime.new.send(:binding)
64
+ @history = []
65
+ @execution_count = 0
66
+ #@compiler = CommandCompiler.new()
67
+ @completer = KernelCompleter.new(@user_ns)
68
+
69
+ # Build dict of handlers for message types
70
+ @handlers = {}
71
+ ['execute_request', 'complete_request', 'kernel_info_request'].each do |msg_type|
72
+ @handlers[msg_type] = msg_type
73
+ end
74
+ end
75
+
76
+ def abort_queue
77
+ while true
78
+ #begin
79
+ ident = @reply_socket.recv(ZMQ::NOBLOCK)
80
+ #rescue Exception => e
81
+ #if e.errno == ZMQ::EAGAIN
82
+ #break
83
+ #else
84
+ #assert self.reply_socket.rcvmore(), "Unexpected missing message part."
85
+ #msg = self.reply_socket.recv_json()
86
+ #end
87
+ #end
88
+ msg_type = msg['header']['msg_type']
89
+ reply_type = msg_type.split('_')[0] + '_reply'
90
+ @session.send(@reply_socket, reply_type, {status: 'aborted'}, msg)
91
+ # reply_msg = @session.msg(reply_type, {status: 'aborted'}, msg)
92
+ # @reply_socket.send(ident,ZMQ::SNDMORE)
93
+ # @reply_socket.send(reply_msg.to_json)
94
+ # We need to wait a bit for requests to come in. This can probably
95
+ # be set shorter for true asynchronous clients.
96
+ sleep(0.1)
97
+ end
98
+ end
99
+
100
+ def kernel_info_request(ident, parent)
101
+ reply_content = {
102
+ protocol_version: [4, 0],
103
+
104
+ # Language version number (mandatory).
105
+ # It is Python version number (e.g., [2, 7, 3]) for the kernel
106
+ # included in IPython.
107
+ language_version: RUBY_VERSION.split('.').map { |x| x.to_i },
108
+
109
+ # Programming language in which kernel is implemented (mandatory).
110
+ # Kernel included in IPython returns 'python'.
111
+ language: "ruby"
112
+ }
113
+ reply_msg = @session.send(@reply_socket, 'kernel_info_reply',
114
+ reply_content, parent, ident
115
+ )
116
+ end
117
+
118
+ def send_status(status, parent)
119
+ @session.send(@pub_socket, "status", {execution_state: status}, parent)
120
+ end
121
+
122
+ def execute_request(ident, parent)
123
+ begin
124
+ code = parent['content']['code']
125
+ rescue
126
+ STDERR.puts "Got bad msg: "
127
+ STDERR.puts parent
128
+ return
129
+ end
130
+ # pyin_msg = @session.msg()
131
+ if ! parent['content'].fetch('silent', false)
132
+ @execution_count += 1
133
+ end
134
+ self.send_status("busy", parent)
135
+ @session.send(@pub_socket, 'pyin', {code: code}, parent)
136
+ reply_content = {status: 'ok',
137
+ payload: [],
138
+ user_variables: {},
139
+ user_expressions: {},
140
+ }
141
+ result = nil
142
+ begin
143
+ $displayhook.set_parent(parent)
144
+ $stdout.set_parent(parent)
145
+
146
+ eval("@mime_type=nil",@user_ns)
147
+ result = eval(code, @user_ns)
148
+ rescue Exception => e
149
+ # $stderr.puts e.inspect
150
+ #etype, evalue, tb = sys.exc_info()
151
+ ename, evalue, tb = e.class.to_s, e.message, e.backtrace
152
+ tb = format_exception(ename, evalue, tb)
153
+ #tb = "1, 2, 3"
154
+ exc_content = {
155
+ ename: ename,
156
+ evalue: evalue,
157
+ traceback: tb,
158
+ #etype: etype,
159
+ #status: 'error',
160
+ }
161
+ # STDERR.puts exc_content
162
+ @session.send(@pub_socket, 'pyerr', exc_content, parent)
163
+
164
+ reply_content = exc_content
165
+ end
166
+ reply_content['execution_count'] = @execution_count
167
+
168
+ # reply_msg = @session.msg('execute_reply', reply_content, parent)
169
+ #$stdout.puts reply_msg
170
+ #$stderr.puts reply_msg
171
+ #@session.send(@reply_socket, ident + reply_msg)
172
+ reply_msg = @session.send(@reply_socket, 'execute_reply', reply_content, parent, ident)
173
+ if reply_msg['content']['status'] == 'error'
174
+ abort_queue
175
+ end
176
+ if ! result.nil? and ! parent['content']['silent']
177
+ output = ResponseWithMime.new(result, eval("@mime_type",@user_ns))
178
+ $displayhook.display(output)
179
+ end
180
+ self.send_status("idle", parent)
181
+ end
182
+
183
+ def complete_request(ident, parent)
184
+ matches = {
185
+ matches: @completer.complete(parent['content']['line'], parent['content']['text']),
186
+ status: 'ok',
187
+ matched_text: parent['content']['line'],
188
+ }
189
+ completion_msg = @session.send(@reply_socket, 'complete_reply',
190
+ matches, parent, ident)
191
+ return nil
192
+ end
193
+
194
+ def start()
195
+ self.send_status("starting", nil)
196
+ while true
197
+ ident, msg = @session.recv(@reply_socket, 0)
198
+ begin
199
+ handler = @handlers[msg['header']['msg_type']]
200
+ rescue
201
+ handler = nil
202
+ end
203
+ if handler.nil?
204
+ STDERR.puts "UNKNOWN MESSAGE TYPE: #{msg['header']['msg_type']} #{msg}"
205
+ else
206
+ # STDERR.puts 'handling ' + omsg.inspect
207
+ send(handler, ident, msg)
208
+ end
209
+ end
210
+ end
211
+
212
+ private
213
+ def format_exception(name, value, backtrace)
214
+ tb = []
215
+ tb << "#{name.red}: #{value}"
216
+ tb.concat(backtrace.map { |l| l.white })
217
+ tb
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,30 @@
1
+ require 'bond'
2
+
3
+ module IRuby
4
+ class KernelCompleter
5
+ class FakeReadline
6
+ def self.setup(arg)
7
+ end
8
+
9
+ def self.line_buffer
10
+ @line_buffer
11
+ end
12
+ end
13
+
14
+ def initialize(ns)
15
+ @ns = ns
16
+ Bond.start(readline: FakeReadline, debug: true)
17
+ end
18
+
19
+ def complete(line, text)
20
+ tab(line)
21
+ end
22
+
23
+ private
24
+ def tab(full_line, last_word=full_line)
25
+ # TODO use @ns as binding
26
+ Bond.agent.weapon.instance_variable_set('@line_buffer', full_line)
27
+ Bond.agent.call(last_word)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ module IRuby
2
+ class Message
3
+ # A simple message object that maps dict keys to attributes.
4
+
5
+ # A Message can be created from a dict and a dict from a Message instance
6
+ # simply by calling dict(msg_obj)."""
7
+
8
+ def initialize msg_dict
9
+ @dct = {}
10
+ msg_dict.each_pair do |k, v|
11
+ if v.is_a?(Hash)
12
+ v = Message.new(v)
13
+ end
14
+ @dct[k] = v
15
+ end
16
+ end
17
+
18
+ def method_missing(m, *args, &block)
19
+ @dct[m.to_s]
20
+ end
21
+
22
+ def self.msg_header(msg_id, username, session)
23
+ return {
24
+ msg_id: msg_id,
25
+ username: username,
26
+ session: session
27
+ }
28
+ end
29
+
30
+ def self.extract_header(msg_or_header)
31
+ # Given a message or header, return the header.
32
+ if msg_or_header.nil?
33
+ return {}
34
+ end
35
+ # See if msg_or_header is the entire message.
36
+ h = msg_or_header['header']
37
+ # See if msg_or_header is just the header
38
+ #h ||= msg_or_header['msg_id']
39
+ h ||= msg_or_header
40
+
41
+ return h
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,95 @@
1
+ module IRuby
2
+ class OutStream
3
+ #A file like object that publishes the stream to a 0MQ PUB socket.
4
+
5
+ def initialize session, pub_socket, name, max_buffer=200
6
+ @session = session
7
+ @pub_socket = pub_socket
8
+ @name = name
9
+ @_buffer = []
10
+ @_buffer_len = 0
11
+ @max_buffer = max_buffer
12
+ @parent_header = {}
13
+ end
14
+
15
+ def set_parent parent
16
+ header = Message.extract_header(parent)
17
+ @parent_header = header
18
+ end
19
+
20
+ def close
21
+ @pub_socket = nil
22
+ end
23
+
24
+ def flush
25
+ # STDERR.puts("flushing, parent to follow")
26
+ # STDERR.puts @parent_header.inspect
27
+ if @pub_socket.nil?
28
+ raise 'I/O operation on closed file'
29
+ else
30
+ if @_buffer
31
+ data = @_buffer.join('')
32
+ content = { name: @name, data: data }
33
+ if ! @session
34
+ return
35
+ end
36
+ # msg = @session.msg('stream', content, @parent_header) if @session
37
+ # FIXME: Wha?
38
+ # STDERR.puts msg.to_json
39
+ #@pub_socket.send(msg.to_json)
40
+ @session.send(@pub_socket, 'stream', content, @parent_header)
41
+ @_buffer_len = 0
42
+ @_buffer = []
43
+ nil
44
+ end
45
+ end
46
+ end
47
+
48
+ def isattr
49
+ return false
50
+ end
51
+
52
+ def next
53
+ raise 'Read not supported on a write only stream.'
54
+ end
55
+
56
+ def read size=0
57
+ raise 'Read not supported on a write only stream.'
58
+ end
59
+ alias readline read
60
+
61
+ def write s
62
+ if @pub_socket.nil?
63
+ raise 'I/O operation on closed file'
64
+ else
65
+ s = s.to_s
66
+ @_buffer << s
67
+ @_buffer_len += s.length
68
+ _maybe_send
69
+ end
70
+ end
71
+
72
+ def puts str
73
+ write "#{str}\n"
74
+ end
75
+
76
+ def _maybe_send
77
+ #if self._buffer[-1].include?('\n')
78
+ flush
79
+ #end
80
+ #if @_buffer_len > @max_buffer
81
+ #flush
82
+ #end
83
+ end
84
+
85
+ def writelines sequence
86
+ if @pub_socket.nil?
87
+ raise 'I/O operation on closed file'
88
+ else
89
+ sequence.each do |s|
90
+ write(s)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,168 @@
1
+ module IRuby
2
+ module Output
3
+ module HTML
4
+ require "gruff"
5
+ require "base64"
6
+ def self.table(data)
7
+ #
8
+ # data = {a: 1, b:2}
9
+
10
+ if data.respond_to?(:keys)
11
+ d = data
12
+ else
13
+ d = data
14
+ end
15
+
16
+ r = "<table>"
17
+ if d.respond_to?(:keys) # hash
18
+ columns = [0,1]
19
+ else
20
+ columns = d.first.keys
21
+ r << "<tr>#{columns.map{|c| "<th>#{c}</th>"}.join("\n")}</tr>"
22
+ end
23
+ d.each{|row|
24
+ r << "<tr>"
25
+ columns.each{|column|
26
+ r << "<td>#{row[column]}</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
+ g.data(data[0], data[1])
47
+ end
48
+ image(g.to_blob)
49
+ end
50
+
51
+ def self.chart_bar(o)
52
+ data=o.delete(:data)
53
+ title=o.delete(:title)
54
+ size=o.delete(:size) || 300
55
+
56
+ klass = o.delete(:stacked) ? Gruff::StackedBar : Gruff::Bar
57
+ g = klass.new(size)
58
+
59
+ if labels=o.delete(:labels)
60
+ if ! labels.respond_to?(:keys)
61
+ labels = Hash[labels.map.with_index{|v,k| [k,v]}]
62
+ end
63
+ g.labels = labels
64
+ end
65
+
66
+ g.title = title if title
67
+ data.each do |data|
68
+ g.data(data[0], data[1])
69
+ end
70
+ image(g.to_blob)
71
+
72
+ end
73
+
74
+ module Gmaps
75
+ def self.points2latlng(points)
76
+ "[" + points.reject{|p| not p.lat or not p.lon}.map{|p|
77
+ " {location: new google.maps.LatLng(#{p.lat.to_f}, #{p.lon.to_f}) #{", weight: #{p.weight.to_i}" if p.respond_to?(:weight) and p.weight} } "
78
+ }.join(',') + "]"
79
+ end
80
+ def self.heatmap(o)
81
+ data = o.delete(:points)
82
+ raise "Missing :points parameter" if not data
83
+
84
+ points = points2latlng(data)
85
+ zoom = o.delete(:zoom)
86
+ center = o.delete(:center)
87
+ map_type = o.delete(:map_type)
88
+ r = <<E
89
+ <div id='map-canvas' style='width: 500px; height: 500px;'></div>
90
+ <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=visualization&callback=initialize"></script>
91
+
92
+ <script>
93
+ function initialize() {
94
+ var points = #{points};
95
+ var latlngbounds = new google.maps.LatLngBounds();
96
+ var zoom = #{zoom.to_json};
97
+ var center = #{center.to_json};
98
+ var map_type = #{map_type.to_json} || google.maps.MapTypeId.SATELLITE;
99
+
100
+ var mapOptions = {
101
+ mapTypeId: map_type
102
+ };
103
+
104
+ if (zoom){
105
+ mapOptions.zoom = zoom
106
+ }
107
+ if (center){
108
+ mapOptions.center = new google.maps.LatLng(center.lat, center.lon)
109
+ }
110
+
111
+ map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
112
+
113
+ if (! zoom){
114
+ for (var i = 0; i < points.length; i++) {
115
+ latlngbounds.extend(points[i].location);
116
+ }
117
+ map.fitBounds(latlngbounds);
118
+ }
119
+
120
+
121
+ var pointArray = new google.maps.MVCArray(points);
122
+
123
+ heatmap = new google.maps.visualization.HeatmapLayer({
124
+ data: pointArray
125
+ });
126
+
127
+ heatmap.setMap(map);
128
+ }
129
+ console.log("finished pre- init!")
130
+ </script>
131
+ E
132
+ STDERR.write("#{r}\n\n")
133
+ r
134
+ end
135
+ end
136
+ #stolen from https://github.com/Bantik/heatmap/blob/master/lib/heatmap.rb
137
+ module WordCloud
138
+
139
+ def self.wordcloud(histogram={})
140
+ html = %{<div class="wordcloud">}
141
+ histogram.keys.sort{|a,b| histogram[a] <=> histogram[b]}.reverse.each do |k|
142
+ next if histogram[k] < 1
143
+ _max = histogram_max(histogram) * 2
144
+ _size = element_size(histogram, k)
145
+ _heat = element_heat(histogram[k], _max)
146
+ html << %{
147
+ <span class="wordcloud_element" style="color: ##{_heat}#{_heat}#{_heat}; font-size: #{_size}px;">#{k}</span>
148
+ }
149
+ end
150
+ html << %{<br style="clear: both;" /></div>}
151
+ end
152
+
153
+ def self.histogram_max(histogram)
154
+ histogram.map{|k,v| histogram[k]}.max
155
+ end
156
+
157
+ def self.element_size(histogram, key)
158
+ (((histogram[key] / histogram.map{|k,v| histogram[k]}.reduce(&:+).to_f) * 100) + 5).to_i
159
+ end
160
+
161
+ def self.element_heat(val, max)
162
+ sprintf("%02x" % (200 - ((200.0 / max) * val)))
163
+ end
164
+
165
+ end
166
+ end
167
+ end
168
+ end