irails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,286 @@
1
+ module IRails
2
+ module Display
3
+ class << self
4
+ def convert(obj, options)
5
+ Representation.new(obj, options)
6
+ end
7
+
8
+ def display(obj, options = {})
9
+ obj = convert(obj, options)
10
+ options = obj.options
11
+ obj = obj.object
12
+
13
+ fuzzy_mime = options[:format] # Treated like a fuzzy mime type
14
+ raise 'Invalid argument :format' unless !fuzzy_mime || String === fuzzy_mime
15
+ if exact_mime = options[:mime]
16
+ raise 'Invalid argument :mime' unless String === exact_mime
17
+ raise 'Invalid mime type' unless exact_mime.include?('/')
18
+ end
19
+
20
+ data = {}
21
+
22
+ # Render additional representation
23
+ render(data, obj, exact_mime, fuzzy_mime)
24
+
25
+ # IPython always requires a text representation
26
+ render(data, obj, 'text/plain', nil) unless data['text/plain']
27
+
28
+ # As a last resort, interpret string representation of the object
29
+ # as the given mime type.
30
+ data[exact_mime] = protect(exact_mime, obj) if exact_mime && !data.any? {|m,_| exact_mime == m }
31
+
32
+ data
33
+ end
34
+
35
+ private
36
+
37
+ def protect(mime, data)
38
+ MimeMagic.new(mime).text? ? data.to_s : [data.to_s].pack('m0')
39
+ end
40
+
41
+ def render(data, obj, exact_mime, fuzzy_mime)
42
+ # Filter matching renderer by object type
43
+ renderer = Registry.renderer.select {|r| r.match?(obj) }
44
+
45
+ matching_renderer = nil
46
+
47
+ # Find exactly matching display by exact_mime
48
+ matching_renderer = renderer.find {|r| exact_mime == r.mime } if exact_mime
49
+
50
+ # Find fuzzy matching display by fuzzy_mime
51
+ matching_renderer ||= renderer.find {|r| r.mime && r.mime.include?(fuzzy_mime) } if fuzzy_mime
52
+
53
+ renderer.unshift matching_renderer if matching_renderer
54
+
55
+ # Return first render result which has the right mime type
56
+ renderer.each do |r|
57
+ mime, result = r.render(obj)
58
+ if mime && result && (!exact_mime || exact_mime == mime) && (!fuzzy_mime || mime.include?(fuzzy_mime))
59
+ data[mime] = protect(mime, result)
60
+ break
61
+ end
62
+ end
63
+
64
+ nil
65
+ end
66
+ end
67
+
68
+ class Representation
69
+ attr_reader :object, :options
70
+
71
+ def initialize(object, options)
72
+ @object, @options = object, options
73
+ end
74
+
75
+ class << self
76
+ alias old_new new
77
+
78
+ def new(obj, options)
79
+ options = { format: options } if String === options
80
+ if Representation === obj
81
+ options = obj.options.merge(options)
82
+ obj = obj.object
83
+ end
84
+ old_new(obj, options)
85
+ end
86
+ end
87
+ end
88
+
89
+ class Renderer
90
+ attr_reader :match, :mime, :render, :priority
91
+
92
+ def initialize(match, mime, render, priority)
93
+ @match, @mime, @render, @priority = match, mime, render, priority
94
+ end
95
+
96
+ def match?(obj)
97
+ @match.call(obj)
98
+ end
99
+
100
+ def render(obj)
101
+ result = @render.call(obj)
102
+ Array === result ? result : [@mime, result]
103
+ end
104
+ end
105
+
106
+ module Registry
107
+ extend self
108
+
109
+ def renderer
110
+ @renderer ||= []
111
+ end
112
+
113
+ SUPPORTED_MIMES = %w(
114
+ text/plain
115
+ text/html
116
+ text/latex
117
+ application/json
118
+ application/javascript
119
+ image/png
120
+ image/jpeg
121
+ image/svg+xml)
122
+
123
+ def match(&block)
124
+ @match = block
125
+ priority 0
126
+ nil
127
+ end
128
+
129
+ def respond_to(name)
130
+ match {|obj| obj.respond_to?(name) }
131
+ end
132
+
133
+ def type(&block)
134
+ match do |obj|
135
+ begin
136
+ block.call === obj
137
+ # We have to rescue all exceptions since constant autoloading could fail with a different error
138
+ rescue Exception
139
+ rescue #NameError
140
+ false
141
+ end
142
+ end
143
+ end
144
+
145
+ def priority(p)
146
+ @priority = p
147
+ nil
148
+ end
149
+
150
+ def format(mime = nil, &block)
151
+ renderer << Renderer.new(@match, mime, block, @priority)
152
+ renderer.sort_by! {|r| -r.priority }
153
+
154
+ # Decrease priority implicitly for all formats
155
+ # which are added later for a type.
156
+ # Overwrite with the `priority` method!
157
+ @priority -= 1
158
+ nil
159
+ end
160
+
161
+ type { NMatrix }
162
+ format 'text/latex' do |obj|
163
+ obj.dim == 2 ?
164
+ LaTeX.matrix(obj, obj.shape[0], obj.shape[1]) :
165
+ LaTeX.vector(obj.to_a)
166
+ end
167
+
168
+ type { NArray }
169
+ format 'text/latex' do |obj|
170
+ obj.dim == 2 ?
171
+ LaTeX.matrix(obj.transpose(1, 0), obj.shape[1], obj.shape[0]) :
172
+ LaTeX.vector(obj.to_a)
173
+ end
174
+ format 'text/html' do |obj|
175
+ HTML.table(obj.to_a)
176
+ end
177
+
178
+ type { Matrix }
179
+ format 'text/latex' do |obj|
180
+ LaTeX.matrix(obj, obj.row_size, obj.column_size)
181
+ end
182
+ format 'text/html' do |obj|
183
+ HTML.table(obj.to_a)
184
+ end
185
+
186
+ type { GSL::Matrix }
187
+ format 'text/latex' do |obj|
188
+ LaTeX.matrix(obj, obj.size1, obj.size2)
189
+ end
190
+ format 'text/html' do |obj|
191
+ HTML.table(obj.to_a)
192
+ end
193
+
194
+ type { GSL::Vector }
195
+ format 'text/latex' do |obj|
196
+ LaTeX.vector(obj.to_a)
197
+ end
198
+ format 'text/html' do |obj|
199
+ HTML.table(obj.to_a)
200
+ end
201
+
202
+ type { GSL::Complex }
203
+ format 'text/latex' do |obj|
204
+ "$#{obj.re}+#{obj.im}\\imath$"
205
+ end
206
+
207
+ type { Complex }
208
+ format 'text/latex' do |obj|
209
+ "$#{obj.real}+#{obj.imag}\\imath$"
210
+ end
211
+
212
+ type { Gnuplot::Plot }
213
+ format 'image/svg+xml' do |obj|
214
+ Tempfile.open('plot') do |f|
215
+ terminal = obj['terminal'].to_s.split(' ')
216
+ terminal[0] = 'svg'
217
+ terminal << 'enhanced' unless terminal.include?('noenhanced')
218
+ obj.terminal terminal.join(' ')
219
+ obj.output f.path
220
+ Gnuplot.open do |io|
221
+ io << obj.to_gplot
222
+ io << obj.store_datasets
223
+ end
224
+ File.read(f.path)
225
+ end
226
+ end
227
+
228
+ match do |obj|
229
+ defined?(Magick::Image) && Magick::Image === obj ||
230
+ defined?(MiniMagick::Image) && MiniMagick::Image === obj
231
+ end
232
+ format 'image' do |obj|
233
+ format = obj.format || 'PNG'
234
+ [format == 'PNG' ? 'image/png' : 'image/jpeg', obj.to_blob {|i| i.format = format }]
235
+ end
236
+
237
+ type { Gruff::Base }
238
+ format 'image/png' do |obj|
239
+ obj.to_blob
240
+ end
241
+
242
+ respond_to :to_html
243
+ format 'text/html' do |obj|
244
+ obj.to_html
245
+ end
246
+
247
+ respond_to :to_latex
248
+ format 'text/latex' do |obj|
249
+ obj.to_latex
250
+ end
251
+
252
+ respond_to :to_tex
253
+ format 'text/latex' do |obj|
254
+ obj.to_tex
255
+ end
256
+
257
+ respond_to :to_javascript
258
+ format 'text/javascript' do |obj|
259
+ obj.to_javascript
260
+ end
261
+
262
+ respond_to :to_svg
263
+ format 'image/svg+xml' do |obj|
264
+ obj.render if defined?(Rubyvis) && Rubyvis::Mark === obj
265
+ obj.to_svg
266
+ end
267
+
268
+ respond_to :to_irails
269
+ format do |obj|
270
+ obj.to_irails
271
+ end
272
+
273
+ match {|obj| obj.respond_to?(:path) && File.readable?(obj.path) }
274
+ format do |obj|
275
+ mime = MimeMagic.by_path(obj.path).to_s
276
+ [mime, File.read(obj.path)] if SUPPORTED_MIMES.include?(mime)
277
+ end
278
+
279
+ type { Object }
280
+ priority -1000
281
+ format 'text/plain' do |obj|
282
+ obj.inspect
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,146 @@
1
+ module IRails
2
+ module LaTeX
3
+ extend self
4
+
5
+ def vector(v)
6
+ x = 'c' * v.size
7
+ y = v.map(&:to_s).join(' & ')
8
+ "$$\\left(\\begin{array}{#{x}} #{y} \\end{array}\\right)$$"
9
+ end
10
+
11
+ def matrix(m, row_count, column_count)
12
+ s = "$$\\left(\\begin{array}{#{'c' * column_count}}\n"
13
+ (0...row_count).each do |i|
14
+ s << ' ' << m[i,0].to_s
15
+ (1...column_count).each do |j|
16
+ s << '&' << m[i,j].to_s
17
+ end
18
+ s << "\\\\\n"
19
+ end
20
+ s << "\\end{array}\\right)$$"
21
+ end
22
+ end
23
+
24
+ module HTML
25
+ extend self
26
+
27
+ def table(obj, maxrows: 15, maxcols: 15, **options)
28
+ raise ArgumentError, 'Invalid :maxrows' if maxrows && maxrows < 3
29
+ raise ArgumentError, 'Invalid :maxcols' if maxcols && maxcols < 3
30
+
31
+ return obj unless obj.respond_to?(:each)
32
+
33
+ rows = []
34
+
35
+ if obj.respond_to?(:keys)
36
+ # Hash of Arrays
37
+ header = obj.keys
38
+ keys = (0...obj.keys.size).to_a
39
+ cols = obj.values.map {|x| [x].flatten(1) }
40
+ num_rows = cols.map(&:size).max
41
+ rows = []
42
+ (0...num_rows).each do |i|
43
+ rows << []
44
+ (0...cols.size).each do |j|
45
+ rows[i][j] = cols[j][i]
46
+ end
47
+ end
48
+ else
49
+ keys = nil
50
+ array_size = 0
51
+
52
+ obj.each do |row|
53
+ if row.respond_to?(:keys)
54
+ # Array of Hashes
55
+ keys ||= Set.new
56
+ keys.merge(row.keys)
57
+ elsif row.respond_to?(:map)
58
+ # Array of Arrays
59
+ array_size = row.size if array_size < row.size
60
+ end
61
+ rows << row
62
+ end
63
+
64
+ if header = keys
65
+ keys.merge(0...array_size)
66
+ else
67
+ keys = 0...array_size
68
+ end
69
+ keys = keys.to_a
70
+ end
71
+
72
+ header ||= keys if options[:header]
73
+
74
+ rows1, rows2 = rows, nil
75
+ keys1, keys2 = keys, nil
76
+ header1, header2 = header, nil
77
+
78
+ if maxcols && keys.size > maxcols
79
+ keys1 = keys[0...maxcols / 2]
80
+ keys2 = keys[-maxcols / 2...-1]
81
+ if header
82
+ header1 = header[0...maxcols / 2]
83
+ header2 = header[-maxcols / 2...-1]
84
+ end
85
+ end
86
+
87
+ if maxrows && rows.size > maxrows
88
+ rows1 = rows[0...maxrows / 2]
89
+ rows2 = rows[-maxrows / 2...-1]
90
+ end
91
+
92
+ table = '<table>'
93
+
94
+ if header1 && options[:header] != false
95
+ table << '<tr>' << header1.map {|k| "<th>#{cell k}</th>" }.join
96
+ table << "<th>&#8230;</th>" << header2.map {|k| "<th>#{cell k}</th>" }.join if keys2
97
+ table << '</tr>'
98
+ end
99
+
100
+ row_block(table, rows1, keys1, keys2)
101
+
102
+ if rows2
103
+ table << "<tr><td#{keys1.size > 1 ? " colspan='#{keys1.size}'" : ''}>&#8942;</td>"
104
+ table << "<td>&#8945;</td><td#{keys2.size > 1 ? " colspan='#{keys2.size}'" : ''}>&#8942;</td>" if keys2
105
+ table << '</tr>'
106
+
107
+ row_block(table, rows2, keys1, keys2)
108
+ end
109
+
110
+ table << '</table>'
111
+ end
112
+
113
+ private
114
+
115
+ def cell(obj)
116
+ obj.respond_to?(:to_html) ? obj.to_html : obj
117
+ end
118
+
119
+ def elem(row, k)
120
+ cell((row[k] rescue nil))
121
+ end
122
+
123
+ def row_block(table, rows, keys1, keys2)
124
+ cols = keys1.size
125
+ cols += keys2.size + 1 if keys2
126
+ rows.each_with_index do |row, i|
127
+ table << '<tr>'
128
+ if row.respond_to?(:map)
129
+ row_html = keys1.map {|k| "<td>#{elem row, k}</td>" }.join
130
+ if keys2
131
+ row_html << "<td#{rows.size > 1 ? " rowspan='#{rows.size}'" : ''}>&#8230;</td>" if i == 0
132
+ row_html << keys2.map {|k| "<td>#{elem row, k}</td>" }.join
133
+ end
134
+ if row_html.empty?
135
+ table << "<td#{cols > 1 ? " colspan='#{cols}'" : ''}></td>"
136
+ else
137
+ table << row_html
138
+ end
139
+ else
140
+ table << "<td#{cols > 1 ? " colspan='#{cols}'" : ''}>#{cell row}</td>"
141
+ end
142
+ table << '</tr>'
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,165 @@
1
+ module IRails
2
+ class Kernel
3
+ RED = "\e[31m"
4
+ WHITE = "\e[37m"
5
+ RESET = "\e[0m"
6
+
7
+ class<< self
8
+ attr_accessor :instance
9
+ end
10
+
11
+ attr_reader :session
12
+
13
+ def initialize(config_file)
14
+ @config = MultiJson.load(File.read(config_file))
15
+ IRails.logger.debug("IRails kernel start with config #{@config}")
16
+ Kernel.instance = self
17
+
18
+ @session = Session.new(@config)
19
+ $stdout = OStream.new(@session, :stdout)
20
+ $stderr = OStream.new(@session, :stderr)
21
+
22
+ @execution_count = 0
23
+ @backend = create_backend
24
+ @running = true
25
+ end
26
+
27
+ def create_backend
28
+ PryBackend.new
29
+ rescue Exception => e
30
+ IRails.logger.warn "Could not load PryBackend: #{e.message}\n#{e.backtrace.join("\n")}" unless LoadError === e
31
+ PlainBackend.new
32
+ end
33
+
34
+ def run
35
+ send_status :starting
36
+ while @running
37
+ dispatch
38
+ end
39
+ end
40
+
41
+ def dispatch
42
+ msg = @session.recv(:reply)
43
+ type = msg[:header]['msg_type']
44
+ raise "Unknown message type: #{msg.inspect}" unless type =~ /comm_|_request\Z/ && respond_to?(type)
45
+ begin
46
+ send_status :busy
47
+ send(type, msg)
48
+ ensure
49
+ send_status :idle
50
+ end
51
+ rescue Exception => e
52
+ IRails.logger.debug "Kernel error: #{e.message}\n#{e.backtrace.join("\n")}"
53
+ @session.send(:publish, :error, error_message(e))
54
+ end
55
+
56
+ def kernel_info_request(msg)
57
+ @session.send(:reply, :kernel_info_reply,
58
+ protocol_version: '5.0',
59
+ implementation: 'irails',
60
+ banner: "IRails #{IRails::VERSION}",
61
+ implementation_version: IRails::VERSION,
62
+ language_info: {
63
+ name: 'ruby',
64
+ version: RUBY_VERSION,
65
+ mimetype: 'application/x-ruby',
66
+ file_extension: '.rb'
67
+ })
68
+ end
69
+
70
+ def send_status(status)
71
+ @session.send(:publish, :status, execution_state: status)
72
+ end
73
+
74
+ def execute_request(msg)
75
+ code = msg[:content]['code']
76
+ @execution_count += 1 if msg[:content]['store_history']
77
+ @session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
78
+
79
+ content = {
80
+ status: :ok,
81
+ payload: [],
82
+ user_expressions: {},
83
+ execution_count: @execution_count
84
+ }
85
+ result = nil
86
+ begin
87
+ result = @backend.eval(code, msg[:content]['store_history'])
88
+ rescue SystemExit
89
+ content[:payload] << { source: :ask_exit }
90
+ rescue Exception => e
91
+ content = error_message(e)
92
+ @session.send(:publish, :error, content)
93
+ end
94
+ @session.send(:reply, :execute_reply, content)
95
+ @session.send(:publish, :execute_result,
96
+ data: Display.display(result),
97
+ metadata: {},
98
+ execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
99
+ end
100
+
101
+ def error_message(e)
102
+ { status: :error,
103
+ ename: e.class.to_s,
104
+ evalue: e.message,
105
+ traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *e.backtrace.map { |l| "#{WHITE}#{l}#{RESET}" }],
106
+ execution_count: @execution_count }
107
+ end
108
+
109
+ def complete_request(msg)
110
+ # HACK for #26, only complete last line
111
+ code = msg[:content]['code']
112
+ if start = code.rindex("\n")
113
+ code = code[start+1..-1]
114
+ start += 1
115
+ end
116
+ @session.send(:reply, :complete_reply,
117
+ matches: @backend.complete(code),
118
+ status: :ok,
119
+ cursor_start: start.to_i,
120
+ cursor_end: msg[:content]['cursor_pos'])
121
+ end
122
+
123
+ def connect_request(msg)
124
+ @session.send(:reply, :connect_reply, Hash[%w(shell_port iopub_port stdin_port hb_port).map {|k| [k, @config[k]] }])
125
+ end
126
+
127
+ def shutdown_request(msg)
128
+ @session.send(:reply, :shutdown_reply, msg[:content])
129
+ @running = false
130
+ end
131
+
132
+ def history_request(msg)
133
+ # we will just send back empty history for now, pending clarification
134
+ # as requested in ipython/ipython#3806
135
+ @session.send(:reply, :history_reply, history: [])
136
+ end
137
+
138
+ def inspect_request(msg)
139
+ result = @backend.eval(msg[:content]['code'])
140
+ @session.send(:reply, :inspect_reply,
141
+ status: :ok,
142
+ data: Display.display(result),
143
+ metadata: {})
144
+ rescue Exception => e
145
+ IRails.logger.warn "Inspection error: #{e.message}\n#{e.backtrace.join("\n")}"
146
+ @session.send(:reply, :inspect_reply, status: :error)
147
+ end
148
+
149
+ def comm_open(msg)
150
+ comm_id = msg[:content]['comm_id']
151
+ target_name = msg[:content]['target_name']
152
+ Comm.comm[comm_id] = Comm.target[target_name].new(target_name, comm_id)
153
+ end
154
+
155
+ def comm_msg(msg)
156
+ Comm.comm[msg[:content]['comm_id']].handle_msg(msg[:content]['data'])
157
+ end
158
+
159
+ def comm_close(msg)
160
+ comm_id = msg[:content]['comm_id']
161
+ Comm.comm[comm_id].handle_close(msg[:content]['data'])
162
+ Comm.comm.delete(comm_id)
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,19 @@
1
+ require 'logger'
2
+
3
+ module IRails
4
+ class << self
5
+ attr_accessor :logger
6
+ end
7
+
8
+ class MultiLogger < BasicObject
9
+ attr_reader :loggers
10
+
11
+ def initialize(*loggers)
12
+ @loggers = loggers
13
+ end
14
+
15
+ def method_missing(name, *args, &b)
16
+ @loggers.map {|x| x.respond_to?(name) && x.public_send(name, *args, &b) }.any?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ module IRails
2
+ # IO-like object that publishes to 0MQ socket.
3
+ class OStream
4
+ attr_accessor :sync
5
+
6
+ def initialize(session, name)
7
+ @session, @name = session, name
8
+ end
9
+
10
+ def close
11
+ @session = nil
12
+ end
13
+
14
+ def flush
15
+ end
16
+
17
+ def isatty
18
+ false
19
+ end
20
+ alias_method :tty?, :isatty
21
+
22
+ def read(*args)
23
+ raise IOError, 'not opened for reading'
24
+ end
25
+ alias_method :next, :read
26
+ alias_method :readline, :read
27
+
28
+ def write(s)
29
+ raise 'I/O operation on closed file' unless @session
30
+ @session.send(:publish, :stream, name: @name, text: s.to_s)
31
+ nil
32
+ end
33
+ alias_method :<<, :write
34
+ alias_method :print, :write
35
+
36
+ def puts(*lines)
37
+ lines = [''] if lines.empty?
38
+ lines.each { |s| write("#{s}\n")}
39
+ nil
40
+ end
41
+
42
+ def writelines(lines)
43
+ lines.each { |s| write(s) }
44
+ end
45
+ end
46
+ end