##// END OF EJS Templates
Initial reply handling implemented along with css fixes.
Brian Granger -
Show More
@@ -33,7 +33,7 b' class KernelManager(object):'
33 def start_kernel(self, kernel_id):
33 def start_kernel(self, kernel_id):
34 if kernel_id in self._kernels:
34 if kernel_id in self._kernels:
35 raise DuplicateKernelError("Kernel already exists: %s" % kernel_id)
35 raise DuplicateKernelError("Kernel already exists: %s" % kernel_id)
36 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel()
36 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(pylab='inline')
37 d = dict(
37 d = dict(
38 process = process,
38 process = process,
39 stdin_port = stdin_port,
39 stdin_port = stdin_port,
@@ -80,15 +80,17 b' class ZMQStreamHandler(websocket.WebSocketHandler, BaseKernelHandler):'
80 self.zmq_stream.on_recv(self._on_zmq_reply)
80 self.zmq_stream.on_recv(self._on_zmq_reply)
81
81
82 def on_message(self, msg):
82 def on_message(self, msg):
83 logging.info("Message received: %r" % msg)
83 logging.info("Message received: %r, %r" % (msg, self.__class__))
84 logging.info(self.zmq_stream)
84 self.zmq_stream.send_unicode(msg)
85 self.zmq_stream.send_unicode(msg)
85
86
86 def on_close(self):
87 def on_close(self):
87 self.zmq_stream.close()
88 self.zmq_stream.close()
88
89
89 def _on_zmq_reply(self, msg):
90 def _on_zmq_reply(self, msg_list):
90 logging.info("Message reply: %r" % msg)
91 for msg in msg_list:
91 self.write_message(msg)
92 logging.info("Message reply: %r" % msg)
93 self.write_message(msg)
92
94
93
95
94 class IOPubStreamHandler(ZMQStreamHandler):
96 class IOPubStreamHandler(ZMQStreamHandler):
@@ -33,6 +33,7 b' class SessionManager(object):'
33 session_id = str(uuid.uuid4())
33 session_id = str(uuid.uuid4())
34 ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
34 ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
35 iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
35 iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
36 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
36 shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
37 shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
37 self._sessions[session_id] = dict(
38 self._sessions[session_id] = dict(
38 iopub_stream = iopub_stream,
39 iopub_stream = iopub_stream,
@@ -54,7 +55,7 b' class SessionManager(object):'
54 def create_connected_stream(self, port, socket_type):
55 def create_connected_stream(self, port, socket_type):
55 sock = self.context.socket(socket_type)
56 sock = self.context.socket(socket_type)
56 addr = "tcp://%s:%i" % (self.kernel_manager.ip, port)
57 addr = "tcp://%s:%i" % (self.kernel_manager.ip, port)
57 logging.info("Connecting to: %s" % addr)
58 logging.info("Connecting to: %s, %r" % (addr, socket_type))
58 sock.connect(addr)
59 sock.connect(addr)
59 return ZMQStream(sock)
60 return ZMQStream(sock)
60
61
@@ -1,24 +1,77 b''
1 html, body, div, span, applet, object, iframe,
1 /**
2 * HTML5 ✰ Boilerplate
3 *
4 * style.css contains a reset, font normalization and some base styles.
5 *
6 * Credit is left where credit is due.
7 * Much inspiration was taken from these projects:
8 * - yui.yahooapis.com/2.8.1/build/base/base.css
9 * - camendesign.com/design/
10 * - praegnanz.de/weblog/htmlcssjs-kickstart
11 */
12
13
14 /**
15 * html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
16 * v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
17 * html5doctor.com/html-5-reset-stylesheet/
18 */
19
20 html, body, div, span, object, iframe,
2 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
21 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 a, abbr, acronym, address, big, cite, code,
22 abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
4 del, dfn, em, img, ins, kbd, q, s, samp,
23 small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
5 small, strike, strong, sub, sup, tt, var,
6 b, u, i, center,
7 dl, dt, dd, ol, ul, li,
8 fieldset, form, label, legend,
24 fieldset, form, label, legend,
9 table, caption, tbody, tfoot, thead, tr, th, td,
25 table, caption, tbody, tfoot, thead, tr, th, td,
10 article, aside, canvas, details, embed,
26 article, aside, canvas, details, figcaption, figure,
11 figure, figcaption, footer, header, hgroup,
27 footer, header, hgroup, menu, nav, section, summary,
12 menu, nav, output, ruby, section, summary,
13 time, mark, audio, video {
28 time, mark, audio, video {
14 margin: 0;
29 margin: 0;
15 padding: 0;
30 padding: 0;
16 border: 0;
31 border: 0;
17 font-size: 100%;
32 font-size: 100%;
18 font: inherit;
33 font: inherit;
19 vertical-align: baseline;
34 vertical-align: baseline;
35 }
36
37 article, aside, details, figcaption, figure,
38 footer, header, hgroup, menu, nav, section {
39 display: block;
20 }
40 }
21
41
42 blockquote, q { quotes: none; }
43
44 blockquote:before, blockquote:after,
45 q:before, q:after { content: ""; content: none; }
46
47 ins { background-color: #ff9; color: #000; text-decoration: none; }
48
49 mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
50
51 del { text-decoration: line-through; }
52
53 abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
54
55 table { border-collapse: collapse; border-spacing: 0; }
56
57 hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
58
59 input, select { vertical-align: middle; }
60
61
62 /**
63 * Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
64 */
65
66 body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
67 select, input, textarea, button { font:99% sans-serif; }
68
69 /* Normalize monospace sizing:
70 en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
71 pre, code, kbd, samp { font-family: monospace, sans-serif; }
72
73
74
22 body {
75 body {
23 background-color: white;
76 background-color: white;
24 }
77 }
@@ -37,45 +90,33 b' div#toolbar {'
37 border-bottom-width: 2px;
90 border-bottom-width: 2px;
38 border-bottom-style: solid;
91 border-bottom-style: solid;
39 border-bottom-color: black;
92 border-bottom-color: black;
40 padding: 5px;
93 padding: 5px;
41 }
94 }
42
95
43 #main_toolbar {
44 }
45
96
46 #main_toolbar button {
97 /*#main_toolbar button {
47 font-size: 0.9em;
98 font-size: 0.9em;
48 }
99 }*/
49
100
50 div.notebook {
101 div.notebook {
51 width: 760px;
102 width: 790px;
52 height: 100%;
103 height: 100%;
53 margin-left: auto;
104 margin-left: auto;
54 margin-right: auto;
105 margin-right: auto;
55 padding-top: 5px;
106 padding-top: 5px;
56 padding-bottom: 5px;
107 padding-bottom: 5px;
57 background-color: white;
108 background-color: white;
58
59 /* Uncomment this block for help in debugging the padding and margins
60 /* border-left-width: 1px;
61 border-left-style: solid;
62 border-left-color: black;
63 border-right-width: 1px;
64 border-right-style: solid;
65 border-right-color: black;
66 border-bottom-width: 1px;
67 border-bottom-style: solid;
68 border-bottom-color: black;*/
69 }
109 }
70
110
71 div.cell {
111 div.cell {
72 width: 740px;
112 width: 740px;
73 margin: 5px 5px 5px 5px;
113 margin: 5px auto 5px 5px;
74 padding: 5px;
114 padding: 5px;
75 font-size: 11pt;
76 position: relative;
115 position: relative;
116 display: table;
77 }
117 }
78
118
119
79 div.code_cell {
120 div.code_cell {
80 background-color: white;
121 background-color: white;
81 }
122 }
@@ -83,16 +124,18 b' div.code_cell {'
83 div.prompt {
124 div.prompt {
84 vertical-align: top;
125 vertical-align: top;
85 display: table-cell;
126 display: table-cell;
86 width: 85px;
127 width: 80px;
87 min-width 80px !important;
128 padding: 0px;
129 margin: 0px;
88 font-family: Menlo, "Courier New", Courier, mono;
130 font-family: Menlo, "Courier New", Courier, mono;
89 font-weight: normal;
131 font-weight: normal;
90 font-style: normal;
132 font-style: normal;
91 }
133 }
92
134
93 div.input {
135 div.input {
94 display: table;
136 display: table-row;
95 height: auto;
137 padding: 0px;
138 margin: 0px;
96 }
139 }
97
140
98 div.input_prompt {
141 div.input_prompt {
@@ -106,19 +149,21 b' textarea.input_area {'
106 font-size: inherit;
149 font-size: inherit;
107 border-style: none;
150 border-style: none;
108 display: table-cell;
151 display: table-cell;
109 margin: 0;
152 padding: 0px;
110 padding: 0;
153 margin: 0px;
111 overflow: auto;
154 overflow: auto;
112 font-weight: normal;
155 font-weight: normal;
113 font-style: normal;
156 font-style: normal;
114 width: 665px;
157 width: 650px;
115 outline: none;
158 outline: none;
116 resize: none;
159 resize: none;
117 }
160 }
118
161
119
162
120 div.output {
163 div.output {
121 display: table;
164 display: table-row;
165 padding: 0px;
166 margin: 0px;
122 }
167 }
123
168
124 div.output_prompt {
169 div.output_prompt {
@@ -128,11 +173,10 b' div.output_prompt {'
128 div.output_area {
173 div.output_area {
129 text-align: left;
174 text-align: left;
130 font-family: Menlo, "Courier New", Courier, mono;
175 font-family: Menlo, "Courier New", Courier, mono;
131 font-size: inherit;
176 padding: 0px;
132 margin: 0;
177 margin: 0px;
133 padding: 0;
134 display: table-cell;
178 display: table-cell;
135 width: 665px;
179 width: 650px;
136 }
180 }
137
181
138 div.text_cell {
182 div.text_cell {
@@ -2,6 +2,67 b' var IPYTHON = {};'
2
2
3
3
4 //============================================================================
4 //============================================================================
5 // Utilities
6 //============================================================================
7
8
9 var uuid = function () {
10 // http://www.ietf.org/rfc/rfc4122.txt
11 var s = [];
12 var hexDigits = "0123456789ABCDEF";
13 for (var i = 0; i < 32; i++) {
14 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
15 }
16 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
17 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
18
19 var uuid = s.join("");
20 return uuid;
21 };
22
23
24 //Fix raw text to parse correctly in crazy XML
25 function xmlencode(string) {
26 return string.replace(/\&/g,'&'+'amp;')
27 .replace(/</g,'&'+'lt;')
28 .replace(/>/g,'&'+'gt;')
29 .replace(/\'/g,'&'+'apos;')
30 .replace(/\"/g,'&'+'quot;')
31 .replace(/`/g,'&'+'#96;')
32 }
33
34 //Map from terminal commands to CSS classes
35 attrib = {
36 "30":"cblack", "31":"cred",
37 "32":"cgreen", "33":"cyellow",
38 "34":"cblue", "36":"ccyan",
39 "37":"cwhite", "01":"cbold"}
40
41 //Fixes escaped console commands, IE colors. Turns them into HTML
42 function fixConsole(txt) {
43 txt = xmlencode(txt)
44 var re = /\033\[([\d;]*?)m/
45 var opened = false
46 var cmds = []
47 var opener = ""
48 var closer = ""
49
50 while (re.test(txt)) {
51 var cmds = txt.match(re)[1].split(";")
52 closer = opened?"</span>":""
53 opened = cmds.length > 1 || cmds[0] != 0
54 var rep = []
55 for (var i in cmds)
56 if (typeof(attrib[cmds[i]]) != "undefined")
57 rep.push(attrib[cmds[i]])
58 opener = rep.length > 0?"<span class=\""+rep.join(" ")+"\">":""
59 txt = txt.replace(re, closer + opener)
60 }
61 if (opened) txt += "</span>"
62 return txt.trim()
63 }
64
65 //============================================================================
5 // Notebook
66 // Notebook
6 //============================================================================
67 //============================================================================
7
68
@@ -11,14 +72,18 b' var Notebook = function (selector) {'
11 this.element.scroll();
72 this.element.scroll();
12 this.element.data("notebook", this);
73 this.element.data("notebook", this);
13 this.next_prompt_number = 1;
74 this.next_prompt_number = 1;
75 this.next_kernel_number = 0;
76 this.kernel = null;
77 this.msg_cell_map = {};
14 this.bind_events();
78 this.bind_events();
79 this.start_kernel();
15 };
80 };
16
81
17
82
18 Notebook.prototype.bind_events = function () {
83 Notebook.prototype.bind_events = function () {
19 var that = this;
84 var that = this;
20 $(document).keydown(function (event) {
85 $(document).keydown(function (event) {
21 console.log(event);
86 // console.log(event);
22 if (event.which == 38 && event.shiftKey) {
87 if (event.which == 38 && event.shiftKey) {
23 event.preventDefault();
88 event.preventDefault();
24 that.select_prev();
89 that.select_prev();
@@ -27,9 +92,27 b' Notebook.prototype.bind_events = function () {'
27 that.select_next();
92 that.select_next();
28 } else if (event.which == 13 && event.shiftKey) {
93 } else if (event.which == 13 && event.shiftKey) {
29 // The focus is not quite working here.
94 // The focus is not quite working here.
95 var cell = that.selected_cell();
96 var cell_index = that.find_cell_index(cell);
97 if (cell instanceof CodeCell) {
98 event.preventDefault();
99 cell.clear_output();
100 var msg_id = that.kernel.execute(cell.get_code());
101 that.msg_cell_map[msg_id] = cell.cell_id;
102 if (cell_index === (that.ncells()-1)) {
103 that.insert_code_cell_after();
104 } else {
105 that.select(cell_index+1);
106 };
107 }
108 } else if (event.which == 9) {
30 event.preventDefault();
109 event.preventDefault();
31 that.insert_code_cell_after();
110 var cell = that.selected_cell();
32 }
111 if (cell instanceof CodeCell) {
112 var ta = cell.element.find("textarea.input_area");
113 ta.val(ta.val() + " ");
114 };
115 };
33 });
116 });
34 };
117 };
35
118
@@ -112,6 +195,19 b' Notebook.prototype.selected_index = function () {'
112 };
195 };
113
196
114
197
198 Notebook.prototype.cell_for_msg = function (msg_id) {
199 var cell_id = this.msg_cell_map[msg_id];
200 var result = null;
201 this.cell_elements().filter(function (index) {
202 cell = $(this).data("cell");
203 if (cell.cell_id === cell_id) {
204 result = cell;
205 };
206 });
207 return result;
208 };
209
210
115 Notebook.prototype.selected_cell = function () {
211 Notebook.prototype.selected_cell = function () {
116 return this.cell_elements().eq(this.selected_index()).data("cell");
212 return this.cell_elements().eq(this.selected_index()).data("cell");
117 }
213 }
@@ -301,13 +397,63 b' Notebook.prototype.code_to_text = function (index) {'
301 Notebook.prototype.collapse = function (index) {
397 Notebook.prototype.collapse = function (index) {
302 var i = this.index_or_selected(index);
398 var i = this.index_or_selected(index);
303 this.cells()[i].collapse();
399 this.cells()[i].collapse();
304 }
400 };
305
401
306
402
307 Notebook.prototype.expand = function (index) {
403 Notebook.prototype.expand = function (index) {
308 var i = this.index_or_selected(index);
404 var i = this.index_or_selected(index);
309 this.cells()[i].expand();
405 this.cells()[i].expand();
310 }
406 };
407
408
409 // Kernel related things
410
411 Notebook.prototype.start_kernel = function () {
412 this.kernel = new Kernel("kernel" + this.next_kernel_number);
413 this.next_kernel_number = this.next_kernel_number + 1;
414 this.kernel.start_kernel(this._kernel_started, this);
415 };
416
417
418 Notebook.prototype._kernel_started = function () {
419 console.log("Kernel started: ", this.kernel.kernel_id);
420 this.kernel.start_session(this._session_started, this);
421 };
422
423
424 Notebook.prototype._session_started = function () {
425 console.log("Session started: ", this.kernel.session_id);
426 var that = this;
427
428 this.kernel.shell_channel.onmessage = function (e) {
429 reply = $.parseJSON(e.data);
430 console.log(reply);
431 var msg_type = reply.msg_type;
432 var cell = that.cell_for_msg(reply.parent_header.msg_id);
433 if (msg_type === "execute_reply") {
434 cell.set_prompt(reply.content.execution_count);
435 };
436 };
437
438 this.kernel.iopub_channel.onmessage = function (e) {
439 reply = $.parseJSON(e.data);
440 console.log(reply);
441 var msg_type = reply.msg_type;
442 var cell = that.cell_for_msg(reply.parent_header.msg_id);
443 if (msg_type === "stream") {
444 cell.expand();
445 cell.append_stream(reply.content.data + "\n");
446 } else if (msg_type === "pyout" || msg_type === "display_data") {
447 cell.expand();
448 cell.append_display_data(reply.content.data);
449 };
450 };
451 };
452
453
454 Notebook.prototype._handle_execute_reply = function (reply, cell) {
455 cell.set_prompt(reply.content.execution_count);
456 };
311
457
312
458
313 //============================================================================
459 //============================================================================
@@ -324,6 +470,7 b' var Cell = function (notebook) {'
324 this.element.data("cell", this);
470 this.element.data("cell", this);
325 this.bind_events();
471 this.bind_events();
326 }
472 }
473 this.cell_id = uuid();
327 };
474 };
328
475
329
476
@@ -394,9 +541,41 b' CodeCell.prototype.create_element = function () {'
394 ).append(
541 ).append(
395 $('<div/>').addClass('output_area')
542 $('<div/>').addClass('output_area')
396 );
543 );
397 output.hide();
398 cell.append(input).append(output);
544 cell.append(input).append(output);
399 this.element = cell;
545 this.element = cell;
546 this.collapse()
547 };
548
549
550 CodeCell.prototype.append_stream = function (data) {
551 var data_list = data.split("\n");
552 console.log(data_list);
553 if (data_list.length > 0) {
554 for (var i=0; i<data_list.length; i++) {
555 console.log(i, data_list[i]);
556 var toinsert = fixConsole(data_list[i]);
557 this.element.find("div.output_area").append($("<p>").append(toinsert));
558 };
559 }
560 };
561
562
563 CodeCell.prototype.append_display_data = function (data) {
564 if (data["image/svg+xml"] !== undefined) {
565 this.append_svg(data["image/svg+xml"]);
566 } else if (data["text/plain"] !== undefined) {
567 console.log(data["text/plain"]);
568 this.append_stream(data["text/plain"]);
569 };
570 };
571
572 CodeCell.prototype.append_svg = function (svg) {
573 this.element.find("div.output_area").append(svg);
574 };
575
576
577 CodeCell.prototype.clear_output = function () {
578 this.element.find("div.output_area").html("");
400 };
579 };
401
580
402
581
@@ -429,6 +608,10 b' CodeCell.prototype.set_output_prompt = function (number) {'
429 };
608 };
430
609
431
610
611 CodeCell.prototype.get_code = function () {
612 return this.element.find("textarea.input_area").val();
613 };
614
432 //============================================================================
615 //============================================================================
433 // TextCell
616 // TextCell
434 //============================================================================
617 //============================================================================
@@ -508,34 +691,71 b' TextCell.prototype.config_mathjax = function () {'
508 //============================================================================
691 //============================================================================
509
692
510
693
511 var KernelManager = function () {
694 var Kernel = function (kernel_id) {
512 this.kernelid = null;
695 this.kernel_id = kernel_id;
513 this.baseurl = "/kernels";
696 this.base_url = "/kernels";
697 this.kernel_url = this.base_url + "/" + this.kernel_id
698 this.session_id = null;
699 };
700
701
702 Kernel.prototype.get_msg = function (msg_type, content) {
703 var msg = {
704 header : {
705 msg_id : uuid(),
706 username : "bgranger",
707 session: this.session_id
708 },
709 msg_type : msg_type,
710 content : content,
711 parent_header : {}
712 };
713 return msg;
714 }
715
716 Kernel.prototype.start_kernel = function (callback, context) {
717 $.post(this.kernel_url, function () {
718 callback.call(context);
719 });
514 };
720 };
515
721
516
722
517 KernelManager.prototype.create_kernel = function () {
723 Kernel.prototype.start_session = function (callback, context) {
518 var that = this;
724 var that = this;
519 $.post(this.baseurl, function (data) {
725 $.post(this.kernel_url + "/sessions",
520 that.kernelid = data;
726 function (session_id) {
521 }, 'json');
727 that._handle_start_session(session_id, callback, context);
728 },
729 'json');
522 }
730 }
523
731
524
732
525 KernelManager.prototype.execute = function (code, callback) {
733 Kernel.prototype._handle_start_session = function (session_id, callback, context) {
526 var msg = {
734 this.session_id = session_id;
527 header : {msg_id : 0, username : "bgranger", session: 0},
735 this.session_url = this.kernel_url + "/sessions/" + this.session_id;
528 msg_type : "execute_request",
736 this._start_channels();
529 content : {code : code}
737 callback.call(context);
738 };
739
740
741 Kernel.prototype._start_channels = function () {
742 var ws_url = "ws://127.0.0.1:8888" + this.session_url;
743 this.shell_channel = new WebSocket(ws_url + "/shell");
744 this.iopub_channel = new WebSocket(ws_url + "/iopub");
745 }
746
747
748 Kernel.prototype.execute = function (code) {
749 var content = {
750 code : code,
751 silent : false,
752 user_variables : [],
753 user_expressions : {}
530 };
754 };
531 var settings = {
755 var msg = this.get_msg("execute_request", content);
532 data : JSON.stringify(msg),
756
533 processData : false,
757 this.shell_channel.send(JSON.stringify(msg));
534 contentType : "application/json",
758 return msg.header.msg_id;
535 success : callback,
536 type : "POST"
537 }
538 var url = this.baseurl + "/" + this.kernelid + "/" + ""
539 }
759 }
540
760
541
761
@@ -579,4 +799,8 b' $(document).ready(function () {'
579 $("#sort").buttonset();
799 $("#sort").buttonset();
580 $("#sort_cells").click(function () {IPYTHON.notebook.sort_cells();});
800 $("#sort_cells").click(function () {IPYTHON.notebook.sort_cells();});
581
801
802 $("#toggle").buttonset();
803 $("#collapse").click(function () {IPYTHON.notebook.collapse();});
804 $("#expand").click(function () {IPYTHON.notebook.expand();});
805
582 }); No newline at end of file
806 });
@@ -67,6 +67,10 b''
67 <span id="sort">
67 <span id="sort">
68 <button id="sort_cells">Sort</button>
68 <button id="sort_cells">Sort</button>
69 </span>
69 </span>
70 <span id="toggle">
71 <button id="collapse">Collapse</button>
72 <button id="expand">Expand</button>
73 </span>
70 </span>
74 </span>
71 </div>
75 </div>
72
76
@@ -46,7 +46,6 b' from session import Session, Message'
46 from zmqshell import ZMQInteractiveShell
46 from zmqshell import ZMQInteractiveShell
47
47
48
48
49
50 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
51 # Main kernel class
50 # Main kernel class
52 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
General Comments 0
You need to be logged in to leave comments. Login now