##// END OF EJS Templates
Merge pull request #1 from ipython/master...
Gareth Elston -
r20633:d587f5ea merge
parent child Browse files
Show More
@@ -0,0 +1,118 b''
1 """Markdown filters with mistune
2
3 Used from markdown.py
4 """
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
7
8 from __future__ import print_function
9
10 import re
11
12 import mistune
13
14 from pygments import highlight
15 from pygments.lexers import get_lexer_by_name
16 from pygments.formatters import HtmlFormatter
17 from pygments.util import ClassNotFound
18
19 from IPython.nbconvert.filters.strings import add_anchor
20 from IPython.nbconvert.utils.exceptions import ConversionException
21 from IPython.utils.decorators import undoc
22
23
24 @undoc
25 class MathBlockGrammar(mistune.BlockGrammar):
26 block_math = re.compile("^\$\$(.*?)\$\$", re.DOTALL)
27 latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}",
28 re.DOTALL)
29
30 @undoc
31 class MathBlockLexer(mistune.BlockLexer):
32 default_rules = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_rules
33
34 def __init__(self, rules=None, **kwargs):
35 if rules is None:
36 rules = MathBlockGrammar()
37 super(MathBlockLexer, self).__init__(rules, **kwargs)
38
39 def parse_block_math(self, m):
40 """Parse a $$math$$ block"""
41 self.tokens.append({
42 'type': 'block_math',
43 'text': m.group(1)
44 })
45
46 def parse_latex_environment(self, m):
47 self.tokens.append({
48 'type': 'latex_environment',
49 'name': m.group(1),
50 'text': m.group(2)
51 })
52
53 @undoc
54 class MathInlineGrammar(mistune.InlineGrammar):
55 math = re.compile("^\$(.+?)\$")
56 text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)')
57
58 @undoc
59 class MathInlineLexer(mistune.InlineLexer):
60 default_rules = ['math'] + mistune.InlineLexer.default_rules
61
62 def __init__(self, renderer, rules=None, **kwargs):
63 if rules is None:
64 rules = MathInlineGrammar()
65 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
66
67 def output_math(self, m):
68 return self.renderer.inline_math(m.group(1))
69
70 @undoc
71 class MarkdownWithMath(mistune.Markdown):
72 def __init__(self, renderer, **kwargs):
73 if 'inline' not in kwargs:
74 kwargs['inline'] = MathInlineLexer
75 if 'block' not in kwargs:
76 kwargs['block'] = MathBlockLexer
77 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
78
79 def output_block_math(self):
80 return self.renderer.block_math(self.token['text'])
81
82 def output_latex_environment(self):
83 return self.renderer.latex_environment(self.token['name'], self.token['text'])
84
85 @undoc
86 class IPythonRenderer(mistune.Renderer):
87 def block_code(self, code, lang):
88 if lang:
89 try:
90 lexer = get_lexer_by_name(lang, stripall=True)
91 except ClassNotFound:
92 code = lang + '\n' + code
93 lang = None
94
95 if not lang:
96 return '\n<pre><code>%s</code></pre>\n' % \
97 mistune.escape(code)
98
99 formatter = HtmlFormatter()
100 return highlight(code, lexer, formatter)
101
102 def header(self, text, level, raw=None):
103 html = super(IPythonRenderer, self).header(text, level, raw=raw)
104 return add_anchor(html)
105
106 # Pass math through unaltered - mathjax does the rendering in the browser
107 def block_math(self, text):
108 return '$$%s$$' % text
109
110 def latex_environment(self, name, text):
111 return r'\begin{%s}%s\end{%s}' % (name, text, name)
112
113 def inline_math(self, text):
114 return '$%s$' % text
115
116 def markdown2html_mistune(source):
117 """Convert a markdown string to HTML using mistune"""
118 return MarkdownWithMath(renderer=IPythonRenderer()).render(source)
@@ -34,7 +34,7 b' from IPython.core import page'
34 34 from IPython.core import prefilter
35 35 from IPython.core import shadowns
36 36 from IPython.core import ultratb
37 from IPython.core.alias import AliasManager, AliasError
37 from IPython.core.alias import Alias, AliasManager
38 38 from IPython.core.autocall import ExitAutocall
39 39 from IPython.core.builtin_trap import BuiltinTrap
40 40 from IPython.core.events import EventManager, available_events
@@ -1502,6 +1502,7 b' class InteractiveShell(SingletonConfigurable):'
1502 1502 found = True
1503 1503 ospace = 'IPython internal'
1504 1504 ismagic = True
1505 isalias = isinstance(obj, Alias)
1505 1506
1506 1507 # Last try: special-case some literals like '', [], {}, etc:
1507 1508 if not found and oname_head in ["''",'""','[]','{}','()']:
@@ -36,6 +36,7 b' from IPython.utils import io'
36 36 from IPython.utils import openpy
37 37 from IPython.utils import py3compat
38 38 from IPython.utils.dir2 import safe_hasattr
39 from IPython.utils.path import compress_user
39 40 from IPython.utils.text import indent
40 41 from IPython.utils.wildcard import list_namespace
41 42 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
@@ -554,23 +555,6 b' class Inspector:'
554 555 title = header((title+":").ljust(title_width))
555 556 out.append(cast_unicode(title) + cast_unicode(content))
556 557 return "\n".join(out)
557
558 # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
559 pinfo_fields1 = [("Type", "type_name"),
560 ]
561
562 pinfo_fields2 = [("String form", "string_form"),
563 ]
564
565 pinfo_fields3 = [("Length", "length"),
566 ("File", "file"),
567 ("Definition", "definition"),
568 ]
569
570 pinfo_fields_obj = [("Class docstring", "class_docstring"),
571 ("Init docstring", "init_docstring"),
572 ("Call def", "call_def"),
573 ("Call docstring", "call_docstring")]
574 558
575 559 def _format_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
576 560 """Format an info dict as text"""
@@ -582,41 +566,62 b' class Inspector:'
582 566 field = info[key]
583 567 if field is not None:
584 568 displayfields.append((title, field.rstrip()))
585
586 add_fields(self.pinfo_fields1)
587
588 # Base class for old-style instances
589 if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
590 displayfields.append(("Base Class", info['base_class'].rstrip()))
591
592 add_fields(self.pinfo_fields2)
593
594 # Namespace
595 if info['namespace'] != 'Interactive':
596 displayfields.append(("Namespace", info['namespace'].rstrip()))
597
598 add_fields(self.pinfo_fields3)
599 if info['isclass'] and info['init_definition']:
600 displayfields.append(("Init definition",
601 info['init_definition'].rstrip()))
602
603 # Source or docstring, depending on detail level and whether
604 # source found.
605 if detail_level > 0 and info['source'] is not None:
606 displayfields.append(("Source",
607 self.format(cast_unicode(info['source']))))
608 elif info['docstring'] is not None:
609 displayfields.append(("Docstring", info["docstring"]))
610
611 # Constructor info for classes
612 if info['isclass']:
613 if info['init_docstring'] is not None:
614 displayfields.append(("Init docstring",
615 info['init_docstring']))
616
617 # Info for objects:
569
570 if info['isalias']:
571 add_fields([('Repr', "string_form")])
572
573 elif info['ismagic']:
574 add_fields([("Docstring", "docstring"),
575 ("File", "file")
576 ])
577
578 elif info['isclass'] or is_simple_callable(obj):
579 # Functions, methods, classes
580 add_fields([("Signature", "definition"),
581 ("Init signature", "init_definition"),
582 ])
583 if detail_level > 0 and info['source'] is not None:
584 add_fields([("Source", "source")])
585 else:
586 add_fields([("Docstring", "docstring"),
587 ("Init docstring", "init_docstring"),
588 ])
589
590 add_fields([('File', 'file'),
591 ('Type', 'type_name'),
592 ])
593
618 594 else:
619 add_fields(self.pinfo_fields_obj)
595 # General Python objects
596 add_fields([("Type", "type_name")])
597
598 # Base class for old-style instances
599 if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
600 displayfields.append(("Base Class", info['base_class'].rstrip()))
601
602 add_fields([("String form", "string_form")])
603
604 # Namespace
605 if info['namespace'] != 'Interactive':
606 displayfields.append(("Namespace", info['namespace'].rstrip()))
607
608 add_fields([("Length", "length"),
609 ("File", "file"),
610 ("Signature", "definition"),
611 ])
612
613 # Source or docstring, depending on detail level and whether
614 # source found.
615 if detail_level > 0 and info['source'] is not None:
616 displayfields.append(("Source",
617 self.format(cast_unicode(info['source']))))
618 elif info['docstring'] is not None:
619 displayfields.append(("Docstring", info["docstring"]))
620
621 add_fields([("Class docstring", "class_docstring"),
622 ("Init docstring", "init_docstring"),
623 ("Call signature", "call_def"),
624 ("Call docstring", "call_docstring")])
620 625
621 626 if displayfields:
622 627 return self._format_fields(displayfields)
@@ -737,7 +742,7 b' class Inspector:'
737 742 binary_file = True
738 743 elif fname.endswith('<string>'):
739 744 fname = 'Dynamically generated function. No source code available.'
740 out['file'] = fname
745 out['file'] = compress_user(fname)
741 746
742 747 # Original source code for a callable, class or property.
743 748 if detail_level:
@@ -2,23 +2,10 b''
2 2 """
3 3 A mixin for :class:`~IPython.core.application.Application` classes that
4 4 launch InteractiveShell instances, load extensions, etc.
5
6 Authors
7 -------
8
9 * Min Ragan-Kelley
10 5 """
11 6
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 #-----------------------------------------------------------------------------
20 # Imports
21 #-----------------------------------------------------------------------------
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
22 9
23 10 from __future__ import absolute_import
24 11 from __future__ import print_function
@@ -152,10 +139,6 b' class InteractiveShellApp(Configurable):'
152 139 extra_extension = Unicode('', config=True,
153 140 help="dotted module name of an IPython extension to load."
154 141 )
155 def _extra_extension_changed(self, name, old, new):
156 if new:
157 # add to self.extensions
158 self.extensions.append(new)
159 142
160 143 # Extensions that are always loaded (not configurable)
161 144 default_extensions = List(Unicode, [u'storemagic'], config=False)
@@ -269,6 +252,8 b' class InteractiveShellApp(Configurable):'
269 252 try:
270 253 self.log.debug("Loading IPython extensions...")
271 254 extensions = self.default_extensions + self.extensions
255 if self.extra_extension:
256 extensions.append(self.extra_extension)
272 257 for ext in extensions:
273 258 try:
274 259 self.log.info("Loading IPython extension: %s" % ext)
@@ -28,6 +28,7 b' from IPython.core.magic import (Magics, magics_class, line_magic,'
28 28 register_line_cell_magic)
29 29 from IPython.external.decorator import decorator
30 30 from IPython.testing.decorators import skipif
31 from IPython.utils.path import compress_user
31 32 from IPython.utils import py3compat
32 33
33 34
@@ -46,7 +47,7 b' ip = get_ipython()'
46 47 # defined, if any code is inserted above, the following line will need to be
47 48 # updated. Do NOT insert any whitespace between the next line and the function
48 49 # definition below.
49 THIS_LINE_NUMBER = 49 # Put here the actual number of this line
50 THIS_LINE_NUMBER = 50 # Put here the actual number of this line
50 51 def test_find_source_lines():
51 52 nt.assert_equal(oinspect.find_source_lines(test_find_source_lines),
52 53 THIS_LINE_NUMBER+1)
@@ -241,7 +242,7 b' def test_info():'
241 242 fname = fname[:-1]
242 243 # case-insensitive comparison needed on some filesystems
243 244 # e.g. Windows:
244 nt.assert_equal(i['file'].lower(), fname.lower())
245 nt.assert_equal(i['file'].lower(), compress_user(fname.lower()))
245 246 nt.assert_equal(i['definition'], None)
246 247 nt.assert_equal(i['docstring'], Call.__doc__)
247 248 nt.assert_equal(i['source'], None)
@@ -117,6 +117,7 b' define(['
117 117 // don't return a comm, so that further .then() functions
118 118 // get an undefined comm input
119 119 });
120 return this.comms[content.comm_id];
120 121 };
121 122
122 123 CommManager.prototype.comm_msg = function(msg) {
@@ -134,6 +135,7 b' define(['
134 135 }
135 136 return comm;
136 137 });
138 return this.comms[content.comm_id];
137 139 };
138 140
139 141 //-----------------------------------------------------------------------
@@ -41,6 +41,7 b' define(['
41 41 this.username = "username";
42 42 this.session_id = utils.uuid();
43 43 this._msg_callbacks = {};
44 this._msg_queue = Promise.resolve();
44 45 this.info_reply = {}; // kernel_info_reply stored here after starting
45 46
46 47 if (typeof(WebSocket) !== 'undefined') {
@@ -854,19 +855,23 b' define(['
854 855 };
855 856
856 857 Kernel.prototype._handle_ws_message = function (e) {
857 serialize.deserialize(e.data, $.proxy(this._finish_ws_message, this));
858 var that = this;
859 this._msg_queue = this._msg_queue.then(function() {
860 return serialize.deserialize(e.data);
861 }).then(function(msg) {return that._finish_ws_message(msg);})
862 .catch(utils.reject("Couldn't process kernel message", true));
858 863 };
859 864
860 865 Kernel.prototype._finish_ws_message = function (msg) {
861 866 switch (msg.channel) {
862 867 case 'shell':
863 this._handle_shell_reply(msg);
868 return this._handle_shell_reply(msg);
864 869 break;
865 870 case 'iopub':
866 this._handle_iopub_message(msg);
871 return this._handle_iopub_message(msg);
867 872 break;
868 873 case 'stdin':
869 this._handle_input_request(msg);
874 return this._handle_input_request(msg);
870 875 break;
871 876 default:
872 877 console.error("unrecognized message channel", msg.channel, msg);
@@ -875,10 +880,12 b' define(['
875 880
876 881 Kernel.prototype._handle_shell_reply = function (reply) {
877 882 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
883 var that = this;
878 884 var content = reply.content;
879 885 var metadata = reply.metadata;
880 886 var parent_id = reply.parent_header.msg_id;
881 887 var callbacks = this.get_callbacks_for_msg(parent_id);
888 var promise = Promise.resolve();
882 889 if (!callbacks || !callbacks.shell) {
883 890 return;
884 891 }
@@ -888,17 +895,21 b' define(['
888 895 this._finish_shell(parent_id);
889 896
890 897 if (shell_callbacks.reply !== undefined) {
891 shell_callbacks.reply(reply);
898 promise = promise.then(function() {return shell_callbacks.reply(reply)});
892 899 }
893 900 if (content.payload && shell_callbacks.payload) {
894 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
901 promise = promise.then(function() {
902 return that._handle_payloads(content.payload, shell_callbacks.payload, reply);
903 });
895 904 }
905 return promise;
896 906 };
897 907
898 908 /**
899 909 * @function _handle_payloads
900 910 */
901 911 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
912 var promise = [];
902 913 var l = payloads.length;
903 914 // Payloads are handled by triggering events because we don't want the Kernel
904 915 // to depend on the Notebook or Pager classes.
@@ -906,9 +917,10 b' define(['
906 917 var payload = payloads[i];
907 918 var callback = payload_callbacks[payload.source];
908 919 if (callback) {
909 callback(payload, msg);
920 promise.push(callback(payload, msg));
910 921 }
911 922 }
923 return Promise.all(promise);
912 924 };
913 925
914 926 /**
@@ -1021,7 +1033,7 b' define(['
1021 1033 Kernel.prototype._handle_iopub_message = function (msg) {
1022 1034 var handler = this.get_iopub_handler(msg.header.msg_type);
1023 1035 if (handler !== undefined) {
1024 handler(msg);
1036 return handler(msg);
1025 1037 }
1026 1038 };
1027 1039
@@ -30,7 +30,7 b' define(['
30 30 return msg;
31 31 };
32 32
33 var _deserialize_binary = function(data, callback) {
33 var _deserialize_binary = function(data) {
34 34 /**
35 35 * deserialize the binary message format
36 36 * callback will be called with a message whose buffers attribute
@@ -39,28 +39,31 b' define(['
39 39 if (data instanceof Blob) {
40 40 // data is Blob, have to deserialize from ArrayBuffer in reader callback
41 41 var reader = new FileReader();
42 reader.onload = function () {
43 var msg = _deserialize_array_buffer(this.result);
44 callback(msg);
45 };
42 var promise = new Promise(function(resolve, reject) {
43 reader.onload = function () {
44 var msg = _deserialize_array_buffer(this.result);
45 resolve(msg);
46 };
47 });
46 48 reader.readAsArrayBuffer(data);
49 return promise;
47 50 } else {
48 51 // data is ArrayBuffer, can deserialize directly
49 52 var msg = _deserialize_array_buffer(data);
50 callback(msg);
53 return msg;
51 54 }
52 55 };
53 56
54 var deserialize = function (data, callback) {
57 var deserialize = function (data) {
55 58 /**
56 * deserialize a message and pass the unpacked message object to callback
59 * deserialize a message and return a promise for the unpacked message
57 60 */
58 61 if (typeof data === "string") {
59 62 // text JSON message
60 callback(JSON.parse(data));
63 return Promise.resolve(JSON.parse(data));
61 64 } else {
62 65 // binary message
63 _deserialize_binary(data, callback);
66 return Promise.resolve(_deserialize_binary(data));
64 67 }
65 68 };
66 69
@@ -117,4 +120,4 b' define(['
117 120 serialize: serialize
118 121 };
119 122 return exports;
120 }); No newline at end of file
123 });
@@ -1575,8 +1575,6 b' h6:hover .anchor-link {'
1575 1575 }
1576 1576 .widget-radio-box label {
1577 1577 margin-top: 0px;
1578 }
1579 .widget-radio {
1580 1578 margin-left: 20px;
1581 1579 }
1582 1580 /*# sourceMappingURL=ipython.min.css.map */ No newline at end of file
@@ -10358,8 +10358,6 b' h6:hover .anchor-link {'
10358 10358 }
10359 10359 .widget-radio-box label {
10360 10360 margin-top: 0px;
10361 }
10362 .widget-radio {
10363 10361 margin-left: 20px;
10364 10362 }
10365 10363 /*!
@@ -59,10 +59,9 b' define(['
59 59 this.$checkbox.prop('checked', this.model.get('value'));
60 60
61 61 if (options === undefined || options.updated_view != this) {
62 var disabled = this.model.get('disabled');
63 this.$checkbox.prop('disabled', disabled);
62 this.$checkbox.prop("disabled", this.model.get("disabled"));
64 63
65 var description = this.model.get('description');
64 var description = this.model.get("description");
66 65 if (description.trim().length === 0) {
67 66 this.$label.hide();
68 67 } else {
@@ -113,7 +112,7 b' define(['
113 112 /**
114 113 * Update the contents of this view
115 114 *
116 * Called when the model is changed. The model may have been
115 * Called when the model is changed. The model may have been
117 116 * changed by another view or by a state update from the back-end.
118 117 */
119 118 if (this.model.get('value')) {
@@ -123,16 +122,16 b' define(['
123 122 }
124 123
125 124 if (options === undefined || options.updated_view != this) {
126
127 var disabled = this.model.get('disabled');
128 this.$el.prop('disabled', disabled);
129
130 var description = this.model.get('description');
125 this.$el.prop("disabled", this.model.get("disabled"));
131 126 this.$el.attr("title", this.model.get("tooltip"));
132 if (description.trim().length === 0) {
127
128 var description = this.model.get("description");
129 var icon = this.model.get("icon");
130 if (description.trim().length === 0 && icon.trim().length ===0) {
133 131 this.$el.html("&nbsp;"); // Preserve button height
134 132 } else {
135 133 this.$el.text(description);
134 $('<i class="fa"></i>').prependTo(this.$el).addClass(icon);
136 135 }
137 136 }
138 137 return ToggleButtonView.__super__.update.apply(this);
@@ -27,21 +27,19 b' define(['
27 27 /**
28 28 * Update the contents of this view
29 29 *
30 * Called when the model is changed. The model may have been
30 * Called when the model is changed. The model may have been
31 31 * changed by another view or by a state update from the back-end.
32 32 */
33 var description = this.model.get('description');
33 this.$el.prop("disabled", this.model.get("disabled"));
34 34 this.$el.attr("title", this.model.get("tooltip"));
35 if (description.length === 0) {
35
36 var description = this.model.get("description");
37 var icon = this.model.get("icon");
38 if (description.trim().length === 0 && icon.trim().length ===0) {
36 39 this.$el.html("&nbsp;"); // Preserve button height
37 40 } else {
38 41 this.$el.text(description);
39 }
40
41 if (this.model.get('disabled')) {
42 this.$el.attr('disabled','disabled');
43 } else {
44 this.$el.removeAttr('disabled');
42 $('<i class="fa"></i>').prependTo(this.$el).addClass(icon);
45 43 }
46 44
47 45 return ButtonView.__super__.update.apply(this);
@@ -307,17 +307,21 b' define(['
307 307 if (options === undefined || options.updated_view != this) {
308 308 // Add missing items to the DOM.
309 309 var items = this.model.get('_options_labels');
310 var icons = this.model.get('icons');
311 var previous_icons = this.model.previous('icons') || [];
310 312 var disabled = this.model.get('disabled');
311 313 var that = this;
312 314 var item_html;
313 315 _.each(items, function(item, index) {
314 if (item.trim().length === 0) {
316 if (item.trim().length === 0 && (!icons[index] ||
317 icons[index].trim().length === 0)) {
315 318 item_html = "&nbsp;";
316 319 } else {
317 320 item_html = utils.escape_html(item);
318 321 }
319 322 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
320 323 var $item_element = that.$buttongroup.find(item_query);
324 var $icon_element = $item_element.find('.fa');
321 325 if (!$item_element.length) {
322 326 $item_element = $('<button/>')
323 327 .attr('type', 'button')
@@ -325,16 +329,22 b' define(['
325 329 .html(item_html)
326 330 .appendTo(that.$buttongroup)
327 331 .attr('data-value', encodeURIComponent(item))
332 .attr('data-toggle', 'tooltip')
328 333 .attr('value', item)
329 334 .on('click', $.proxy(that.handle_click, that));
330 335 that.update_style_traits($item_element);
336 $icon_element = $('<i class="fa"></i>').prependTo($item_element);
331 337 }
332 338 if (that.model.get('selected_label') == item) {
333 339 $item_element.addClass('active');
334 340 } else {
335 341 $item_element.removeClass('active');
336 342 }
337 $item_element.prop('disabled', disabled);
343 $item_element.prop('disabled', disabled);
344 $item_element.attr('title', that.model.get('tooltips')[index]);
345 $icon_element
346 .removeClass(previous_icons[index])
347 .addClass(icons[index]);
338 348 });
339 349
340 350 // Remove items that no longer exist.
@@ -271,9 +271,6 b''
271 271
272 272 label {
273 273 margin-top: 0px;
274 margin-left: 20px;
274 275 }
275 276 }
276
277 .widget-radio {
278 margin-left: 20px;
279 }
@@ -108,7 +108,7 b' data-notebook-path="{{notebook_path}}"'
108 108 <li id="download_html"><a href="#">HTML (.html)</a></li>
109 109 <li id="download_markdown"><a href="#">Markdown (.md)</a></li>
110 110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
111 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
111 <li id="download_pdf"><a href="#">PDF via LaTeX (.pdf)</a></li>
112 112 </ul>
113 113 </li>
114 114 <li class="divider"></li>
@@ -51,7 +51,9 b' casper.notebook_test(function () {'
51 51 this.then(function () {
52 52 var index = this.append_cell([
53 53 "buffers = [b'\\xFF\\x00', b'\\x00\\x01\\x02']",
54 "comm.send(data='hi', buffers=buffers)"
54 "comm.send(data='message 0', buffers=buffers)",
55 "comm.send(data='message 1')",
56 "comm.send(data='message 2', buffers=buffers)",
55 57 ].join('\n'), 'code');
56 58 this.execute_cell(index);
57 59 });
@@ -59,7 +61,7 b' casper.notebook_test(function () {'
59 61 // wait for capture
60 62 this.waitFor(function () {
61 63 return this.evaluate(function () {
62 return IPython._msgs.length > 0;
64 return IPython._msgs.length >= 3;
63 65 });
64 66 });
65 67
@@ -68,14 +70,22 b' casper.notebook_test(function () {'
68 70 var msgs = this.evaluate(function () {
69 71 return IPython._msgs;
70 72 });
71 this.test.assertEquals(msgs.length, 1, "Captured comm message");
72 var buffers = msgs[0].buffers;
73 this.test.assertEquals(buffers.length, 2, "comm message has buffers");
73 this.test.assertEquals(msgs.length, 3, "Captured three comm messages");
74
75
76
77 // check the messages came in the right order
78 this.test.assertEquals(msgs[0].content.data, "message 0", "message 0 processed first");
79 this.test.assertEquals(msgs[0].buffers.length, 2, "comm message 0 has two buffers");
80 this.test.assertEquals(msgs[1].content.data, "message 1", "message 1 processed second");
81 this.test.assertEquals(msgs[1].buffers.length, 0, "comm message 1 has no buffers");
82 this.test.assertEquals(msgs[2].content.data, "message 2", "message 2 processed third");
83 this.test.assertEquals(msgs[2].buffers.length, 2, "comm message 2 has two buffers");
74 84
75 85 // extract attributes to test in evaluate,
76 86 // because the raw DataViews can't be passed across
77 var buf_info = function (index) {
78 var buf = IPython._msgs[0].buffers[index];
87 var buf_info = function (message, index) {
88 var buf = IPython._msgs[message].buffers[index];
79 89 var data = {};
80 90 data.byteLength = buf.byteLength;
81 91 data.bytes = [];
@@ -85,18 +95,22 b' casper.notebook_test(function () {'
85 95 return data;
86 96 };
87 97
88 buf0 = this.evaluate(buf_info, 0);
89 buf1 = this.evaluate(buf_info, 1);
90 this.test.assertEquals(buf0.byteLength, 2, 'buf[0] has correct size');
91 this.test.assertEquals(buf0.bytes, [255, 0], 'buf[0] has correct bytes');
92 this.test.assertEquals(buf1.byteLength, 3, 'buf[1] has correct size');
93 this.test.assertEquals(buf1.bytes, [0, 1, 2], 'buf[1] has correct bytes');
98 var msgs_with_buffers = [0, 2];
99 for (var i = 0; i < msgs_with_buffers.length; i++) {
100 msg_index = msgs_with_buffers[i];
101 buf0 = this.evaluate(buf_info, msg_index, 0);
102 buf1 = this.evaluate(buf_info, msg_index, 1);
103 this.test.assertEquals(buf0.byteLength, 2, 'buf[0] has correct size in message '+msg_index);
104 this.test.assertEquals(buf0.bytes, [255, 0], 'buf[0] has correct bytes in message '+msg_index);
105 this.test.assertEquals(buf1.byteLength, 3, 'buf[1] has correct size in message '+msg_index);
106 this.test.assertEquals(buf1.bytes, [0, 1, 2], 'buf[1] has correct bytes in message '+msg_index);
107 }
94 108 });
95 109
96 110 // validate captured buffers Python-side
97 111 this.then(function () {
98 112 var index = this.append_cell([
99 "assert len(msgs) == 1, len(msgs)",
113 "assert len(msgs) == 3, len(msgs)",
100 114 "bufs = msgs[0]['buffers']",
101 115 "assert len(bufs) == len(buffers), bufs",
102 116 "assert bufs[0].bytes == buffers[0], bufs[0].bytes",
@@ -107,7 +121,7 b' casper.notebook_test(function () {'
107 121 this.wait_for_output(index);
108 122 this.then(function () {
109 123 var out = this.get_output_cell(index);
110 this.test.assertEquals(out['text/plain'], '1', "Python received buffers");
124 this.test.assertEquals(out.data['text/plain'], '1', "Python received buffers");
111 125 });
112 126 });
113 127 });
@@ -49,7 +49,7 b' casper.notebook_test(function () {'
49 49 'Toggle button exists.');
50 50
51 51 this.test.assert(this.cell_element_function(bool_index,
52 widget_togglebutton_selector, 'html')=="Title",
52 widget_togglebutton_selector, 'html')=='<i class="fa"></i>Title',
53 53 'Toggle button labeled correctly.');
54 54
55 55 this.test.assert(this.cell_element_function(bool_index,
@@ -29,7 +29,7 b' casper.notebook_test(function () {'
29 29 'Widget button exists.');
30 30
31 31 this.test.assert(this.cell_element_function(button_index,
32 widget_button_selector, 'html')=='Title',
32 widget_button_selector, 'html')=='<i class="fa"></i>Title',
33 33 'Set button description.');
34 34
35 35 this.cell_element_function(button_index,
@@ -55,10 +55,15 b' class ToggleButton(_Bool):'
55 55 value of the toggle button: True-pressed, False-unpressed
56 56 description : str
57 57 description displayed next to the button
58 tooltip: str
59 tooltip caption of the toggle button
60 icon: str
61 font-awesome icon name
58 62 """
59 63
60 64 _view_name = Unicode('ToggleButtonView', sync=True)
61 65 tooltip = Unicode(help="Tooltip caption of the toggle button.", sync=True)
66 icon = Unicode('', help= "Font-awesome icon.", sync=True)
62 67
63 68 button_style = CaselessStrEnum(
64 69 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -24,15 +24,25 b' from IPython.utils.warn import DeprecatedClass'
24 24 @register('IPython.Button')
25 25 class Button(DOMWidget):
26 26 """Button widget.
27 This widget has an `on_click` method that allows you to listen for the
28 user clicking on the button. The click event itself is stateless.
27 29
28 This widget has an `on_click` method that allows you to listen for the
29 user clicking on the button. The click event itself is stateless."""
30 Parameters
31 ----------
32 description : str
33 description displayed next to the button
34 tooltip: str
35 tooltip caption of the toggle button
36 icon: str
37 font-awesome icon name
38 """
30 39 _view_name = Unicode('ButtonView', sync=True)
31 40
32 41 # Keys
33 42 description = Unicode('', help="Button label.", sync=True)
34 43 tooltip = Unicode(help="Tooltip caption of the button.", sync=True)
35 44 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
45 icon = Unicode('', help= "Font-awesome icon.", sync=True)
36 46
37 47 button_style = CaselessStrEnum(
38 48 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -19,7 +19,7 b' from threading import Lock'
19 19
20 20 from .widget import DOMWidget, register
21 21 from IPython.utils.traitlets import (
22 Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple
22 Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List
23 23 )
24 24 from IPython.utils.py3compat import unicode_type
25 25 from IPython.utils.warn import DeprecatedClass
@@ -32,6 +32,12 b' class _Selection(DOMWidget):'
32 32
33 33 ``options`` can be specified as a list or dict. If given as a list,
34 34 it will be transformed to a dict of the form ``{str(value):value}``.
35
36 When programmatically setting the value, a reverse lookup is performed
37 among the options to set the value of ``selected_label`` accordingly. The
38 reverse lookup uses the equality operator by default, but an other
39 predicate may be provided via the ``equals`` argument. For example, when
40 dealing with numpy arrays, one may set equals=np.array_equal.
35 41 """
36 42
37 43 value = Any(help="Selected value")
@@ -194,6 +200,8 b' class ToggleButtons(_Selection):'
194 200 """Group of toggle buttons that represent an enumeration. Only one toggle
195 201 button can be toggled at any point in time."""
196 202 _view_name = Unicode('ToggleButtonsView', sync=True)
203 tooltips = List(Unicode(), sync=True)
204 icons = List(Unicode(), sync=True)
197 205
198 206 button_style = CaselessStrEnum(
199 207 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -384,7 +384,7 b' def test_oinfo_detail():'
384 384 content = reply['content']
385 385 assert content['found']
386 386 text = content['data']['text/plain']
387 nt.assert_in('Definition:', text)
387 nt.assert_in('Signature:', text)
388 388 nt.assert_in('Source:', text)
389 389
390 390
@@ -91,7 +91,7 b' class TestSession(SessionTestCase):'
91 91
92 92 content = msg['content']
93 93 header = msg['header']
94 header['date'] = datetime.now()
94 header['msg_id'] = self.session.msg_id
95 95 parent = msg['parent_header']
96 96 metadata = msg['metadata']
97 97 msg_type = header['msg_type']
@@ -100,7 +100,7 b' class TestSession(SessionTestCase):'
100 100 ident, msg_list = self.session.feed_identities(B.recv_multipart())
101 101 new_msg = self.session.deserialize(msg_list)
102 102 self.assertEqual(ident[0], b'foo')
103 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
103 self.assertEqual(new_msg['msg_id'],header['msg_id'])
104 104 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
105 105 self.assertEqual(new_msg['header'],msg['header'])
106 106 self.assertEqual(new_msg['content'],msg['content'])
@@ -108,12 +108,12 b' class TestSession(SessionTestCase):'
108 108 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
109 109 self.assertEqual(new_msg['buffers'],[b'bar'])
110 110
111 header['date'] = datetime.now()
111 header['msg_id'] = self.session.msg_id
112 112
113 113 self.session.send(A, msg, ident=b'foo', buffers=[b'bar'])
114 114 ident, new_msg = self.session.recv(B)
115 115 self.assertEqual(ident[0], b'foo')
116 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
116 self.assertEqual(new_msg['msg_id'],header['msg_id'])
117 117 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
118 118 self.assertEqual(new_msg['header'],msg['header'])
119 119 self.assertEqual(new_msg['content'],msg['content'])
@@ -142,9 +142,10 b' class Exporter(LoggingConfigurable):'
142 142 resources = ResourcesDict()
143 143 if not 'metadata' in resources or resources['metadata'] == '':
144 144 resources['metadata'] = ResourcesDict()
145 basename = os.path.basename(filename)
145 path, basename = os.path.split(filename)
146 146 notebook_name = basename[:basename.rfind('.')]
147 147 resources['metadata']['name'] = notebook_name
148 resources['metadata']['path'] = path
148 149
149 150 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
150 151 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
@@ -8,23 +8,21 b' markdown within Jinja templates.'
8 8
9 9 from __future__ import print_function
10 10
11 # Stdlib imports
12 11 import os
13 12 import subprocess
14 13 from io import TextIOWrapper, BytesIO
15 import re
16 14
17 import mistune
18 from pygments import highlight
19 from pygments.lexers import get_lexer_by_name
20 from pygments.formatters import HtmlFormatter
21 from pygments.util import ClassNotFound
15 try:
16 from .markdown_mistune import markdown2html_mistune
17 except ImportError as e:
18 # store in variable for Python 3
19 _mistune_import_error = e
20 def markdown2html_mistune(source):
21 """mistune is unavailable, raise ImportError"""
22 raise ImportError("markdown2html requires mistune: %s" % _mistune_import_error)
22 23
23 # IPython imports
24 from IPython.nbconvert.filters.strings import add_anchor
25 24 from IPython.nbconvert.utils.pandoc import pandoc
26 25 from IPython.nbconvert.utils.exceptions import ConversionException
27 from IPython.utils.decorators import undoc
28 26 from IPython.utils.process import get_output_error_code
29 27 from IPython.utils.py3compat import cast_bytes
30 28 from IPython.utils.version import check_version
@@ -46,6 +44,7 b' class NodeJSMissing(ConversionException):'
46 44 """Exception raised when node.js is missing."""
47 45 pass
48 46
47
49 48 def markdown2latex(source, markup='markdown', extra_args=None):
50 49 """Convert a markdown string to LaTeX via pandoc.
51 50
@@ -69,107 +68,12 b" def markdown2latex(source, markup='markdown', extra_args=None):"
69 68 return pandoc(source, markup, 'latex', extra_args=extra_args)
70 69
71 70
72 @undoc
73 class MathBlockGrammar(mistune.BlockGrammar):
74 block_math = re.compile("^\$\$(.*?)\$\$", re.DOTALL)
75 latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}",
76 re.DOTALL)
77
78 @undoc
79 class MathBlockLexer(mistune.BlockLexer):
80 default_rules = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_rules
81
82 def __init__(self, rules=None, **kwargs):
83 if rules is None:
84 rules = MathBlockGrammar()
85 super(MathBlockLexer, self).__init__(rules, **kwargs)
86
87 def parse_block_math(self, m):
88 """Parse a $$math$$ block"""
89 self.tokens.append({
90 'type': 'block_math',
91 'text': m.group(1)
92 })
93
94 def parse_latex_environment(self, m):
95 self.tokens.append({
96 'type': 'latex_environment',
97 'name': m.group(1),
98 'text': m.group(2)
99 })
100
101 @undoc
102 class MathInlineGrammar(mistune.InlineGrammar):
103 math = re.compile("^\$(.+?)\$")
104 text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)')
105
106 @undoc
107 class MathInlineLexer(mistune.InlineLexer):
108 default_rules = ['math'] + mistune.InlineLexer.default_rules
109
110 def __init__(self, renderer, rules=None, **kwargs):
111 if rules is None:
112 rules = MathInlineGrammar()
113 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
114
115 def output_math(self, m):
116 return self.renderer.inline_math(m.group(1))
117
118 @undoc
119 class MarkdownWithMath(mistune.Markdown):
120 def __init__(self, renderer, **kwargs):
121 if 'inline' not in kwargs:
122 kwargs['inline'] = MathInlineLexer
123 if 'block' not in kwargs:
124 kwargs['block'] = MathBlockLexer
125 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
126
127 def output_block_math(self):
128 return self.renderer.block_math(self.token['text'])
129
130 def output_latex_environment(self):
131 return self.renderer.latex_environment(self.token['name'], self.token['text'])
132
133 @undoc
134 class IPythonRenderer(mistune.Renderer):
135 def block_code(self, code, lang):
136 if lang:
137 try:
138 lexer = get_lexer_by_name(lang, stripall=True)
139 except ClassNotFound:
140 code = lang + '\n' + code
141 lang = None
142
143 if not lang:
144 return '\n<pre><code>%s</code></pre>\n' % \
145 mistune.escape(code)
146
147 formatter = HtmlFormatter()
148 return highlight(code, lexer, formatter)
149
150 def header(self, text, level, raw=None):
151 html = super(IPythonRenderer, self).header(text, level, raw=raw)
152 return add_anchor(html)
153
154 # Pass math through unaltered - mathjax does the rendering in the browser
155 def block_math(self, text):
156 return '$$%s$$' % text
157
158 def latex_environment(self, name, text):
159 return r'\begin{%s}%s\end{%s}' % (name, text, name)
160
161 def inline_math(self, text):
162 return '$%s$' % text
163
164 def markdown2html_mistune(source):
165 """Convert a markdown string to HTML using mistune"""
166 return MarkdownWithMath(renderer=IPythonRenderer()).render(source)
167
168 71 def markdown2html_pandoc(source, extra_args=None):
169 72 """Convert a markdown string to HTML via pandoc"""
170 73 extra_args = extra_args or ['--mathjax']
171 74 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
172 75
76
173 77 def _find_nodejs():
174 78 global _node
175 79 if _node is None:
@@ -25,6 +25,14 b' class FilesWriter(WriterBase):'
25 25 help="""Directory to write output to. Leave blank
26 26 to output to the current directory""")
27 27
28 relpath = Unicode(
29 "", config=True,
30 help="""When copying files that the notebook depends on, copy them in
31 relation to this path, such that the destination filename will be
32 os.path.relpath(filename, relpath). If FilesWriter is operating on a
33 notebook that already exists elsewhere on disk, then the default will be
34 the directory containing that notebook.""")
35
28 36
29 37 # Make sure that the output directory exists.
30 38 def _build_directory_changed(self, name, old, new):
@@ -59,6 +67,12 b' class FilesWriter(WriterBase):'
59 67 # Pull the extension and subdir from the resources dict.
60 68 output_extension = resources.get('output_extension', None)
61 69
70 # Get the relative path for copying files
71 if self.relpath == '':
72 relpath = resources.get('metadata', {}).get('path', '')
73 else:
74 relpath = self.relpath
75
62 76 # Write all of the extracted resources to the destination directory.
63 77 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
64 78 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
@@ -81,8 +95,14 b' class FilesWriter(WriterBase):'
81 95 # Copy files that match search pattern
82 96 for matching_filename in glob.glob(filename):
83 97
98 # compute the relative path for the filename
99 if relpath != '':
100 dest_filename = os.path.relpath(matching_filename, relpath)
101 else:
102 dest_filename = matching_filename
103
84 104 # Make sure folder exists.
85 dest = os.path.join(self.build_directory, matching_filename)
105 dest = os.path.join(self.build_directory, dest_filename)
86 106 path = os.path.dirname(dest)
87 107 self._makedir(path)
88 108
@@ -201,3 +201,107 b' class Testfiles(TestsBase):'
201 201 with open(dest, 'r') as f:
202 202 output = f.read()
203 203 self.assertEqual(output, 'e')
204
205 def test_relpath(self):
206 """Can the FilesWriter handle relative paths for linked files correctly?"""
207
208 # Work in a temporary directory.
209 with self.create_temp_cwd():
210
211 # Create test file
212 os.mkdir('sub')
213 with open(os.path.join('sub', 'c'), 'w') as f:
214 f.write('d')
215
216 # Create the resoruces dictionary
217 res = {}
218
219 # Create files writer, test output
220 writer = FilesWriter()
221 writer.files = [os.path.join('sub', 'c')]
222 writer.build_directory = u'build'
223 writer.relpath = 'sub'
224 writer.write(u'y', res, notebook_name="z")
225
226 # Check the output of the file
227 assert os.path.isdir(writer.build_directory)
228 dest = os.path.join(writer.build_directory, 'z')
229 with open(dest, 'r') as f:
230 output = f.read()
231 self.assertEqual(output, u'y')
232
233 # Check to make sure the linked file was copied
234 dest = os.path.join(writer.build_directory, 'c')
235 assert os.path.isfile(dest)
236 with open(dest, 'r') as f:
237 output = f.read()
238 self.assertEqual(output, 'd')
239
240 def test_relpath_default(self):
241 """Is the FilesWriter default relative path correct?"""
242
243 # Work in a temporary directory.
244 with self.create_temp_cwd():
245
246 # Create test file
247 os.mkdir('sub')
248 with open(os.path.join('sub', 'c'), 'w') as f:
249 f.write('d')
250
251 # Create the resoruces dictionary
252 res = dict(metadata=dict(path="sub"))
253
254 # Create files writer, test output
255 writer = FilesWriter()
256 writer.files = [os.path.join('sub', 'c')]
257 writer.build_directory = u'build'
258 writer.write(u'y', res, notebook_name="z")
259
260 # Check the output of the file
261 assert os.path.isdir(writer.build_directory)
262 dest = os.path.join(writer.build_directory, 'z')
263 with open(dest, 'r') as f:
264 output = f.read()
265 self.assertEqual(output, u'y')
266
267 # Check to make sure the linked file was copied
268 dest = os.path.join(writer.build_directory, 'c')
269 assert os.path.isfile(dest)
270 with open(dest, 'r') as f:
271 output = f.read()
272 self.assertEqual(output, 'd')
273
274 def test_relpath_default(self):
275 """Does the FilesWriter relpath option take precedence over the path?"""
276
277 # Work in a temporary directory.
278 with self.create_temp_cwd():
279
280 # Create test file
281 os.mkdir('sub')
282 with open(os.path.join('sub', 'c'), 'w') as f:
283 f.write('d')
284
285 # Create the resoruces dictionary
286 res = dict(metadata=dict(path="other_sub"))
287
288 # Create files writer, test output
289 writer = FilesWriter()
290 writer.files = [os.path.join('sub', 'c')]
291 writer.build_directory = u'build'
292 writer.relpath = 'sub'
293 writer.write(u'y', res, notebook_name="z")
294
295 # Check the output of the file
296 assert os.path.isdir(writer.build_directory)
297 dest = os.path.join(writer.build_directory, 'z')
298 with open(dest, 'r') as f:
299 output = f.read()
300 self.assertEqual(output, u'y')
301
302 # Check to make sure the linked file was copied
303 dest = os.path.join(writer.build_directory, 'c')
304 assert os.path.isfile(dest)
305 with open(dest, 'r') as f:
306 output = f.read()
307 self.assertEqual(output, 'd')
@@ -48,7 +48,7 b''
48 48 .. _scipy: http://www.scipy.org
49 49 .. _scipy_conference: http://conference.scipy.org
50 50 .. _matplotlib: http://matplotlib.org
51 .. _pythonxy: http://www.pythonxy.com
51 .. _pythonxy: https://code.google.com/p/pythonxy/
52 52 .. _ETS: http://code.enthought.com/projects/tool-suite.php
53 53 .. _EPD: http://www.enthought.com/products/epd.php
54 54 .. _python: http://www.python.org
@@ -307,7 +307,7 b''
307 307 "range1, range2, range3 = widgets.IntSlider(description='Range 1'),\\\n",
308 308 " widgets.IntSlider(description='Range 2'),\\\n",
309 309 " widgets.IntSlider(description='Range 3')\n",
310 "l = widgets.link((range1, 'value'), (range2, 'value'), (range3, 'value'))\n",
310 "l = widgets.jslink((range1, 'value'), (range2, 'value'), (range3, 'value'))\n",
311 311 "display(caption, range1, range2, range3)"
312 312 ]
313 313 },
@@ -323,7 +323,7 b''
323 323 "source_range, target_range1, target_range2 = widgets.IntSlider(description='Source range'),\\\n",
324 324 " widgets.IntSlider(description='Target range 1'),\\\n",
325 325 " widgets.IntSlider(description='Target range 2')\n",
326 "widgets.dlink((source_range, 'value'), (target_range1, 'value'), (target_range2, 'value'))\n",
326 "widgets.jsdlink((source_range, 'value'), (target_range1, 'value'), (target_range2, 'value'))\n",
327 327 "display(caption, source_range, target_range1, target_range2)"
328 328 ]
329 329 },
General Comments 0
You need to be logged in to leave comments. Login now