irails 0.1.0

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,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