##// 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 from IPython.core import prefilter
34 from IPython.core import prefilter
35 from IPython.core import shadowns
35 from IPython.core import shadowns
36 from IPython.core import ultratb
36 from IPython.core import ultratb
37 from IPython.core.alias import AliasManager, AliasError
37 from IPython.core.alias import Alias, AliasManager
38 from IPython.core.autocall import ExitAutocall
38 from IPython.core.autocall import ExitAutocall
39 from IPython.core.builtin_trap import BuiltinTrap
39 from IPython.core.builtin_trap import BuiltinTrap
40 from IPython.core.events import EventManager, available_events
40 from IPython.core.events import EventManager, available_events
@@ -1502,6 +1502,7 b' class InteractiveShell(SingletonConfigurable):'
1502 found = True
1502 found = True
1503 ospace = 'IPython internal'
1503 ospace = 'IPython internal'
1504 ismagic = True
1504 ismagic = True
1505 isalias = isinstance(obj, Alias)
1505
1506
1506 # Last try: special-case some literals like '', [], {}, etc:
1507 # Last try: special-case some literals like '', [], {}, etc:
1507 if not found and oname_head in ["''",'""','[]','{}','()']:
1508 if not found and oname_head in ["''",'""','[]','{}','()']:
@@ -36,6 +36,7 b' from IPython.utils import io'
36 from IPython.utils import openpy
36 from IPython.utils import openpy
37 from IPython.utils import py3compat
37 from IPython.utils import py3compat
38 from IPython.utils.dir2 import safe_hasattr
38 from IPython.utils.dir2 import safe_hasattr
39 from IPython.utils.path import compress_user
39 from IPython.utils.text import indent
40 from IPython.utils.text import indent
40 from IPython.utils.wildcard import list_namespace
41 from IPython.utils.wildcard import list_namespace
41 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
42 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
@@ -554,23 +555,6 b' class Inspector:'
554 title = header((title+":").ljust(title_width))
555 title = header((title+":").ljust(title_width))
555 out.append(cast_unicode(title) + cast_unicode(content))
556 out.append(cast_unicode(title) + cast_unicode(content))
556 return "\n".join(out)
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 def _format_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
559 def _format_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
576 """Format an info dict as text"""
560 """Format an info dict as text"""
@@ -582,41 +566,62 b' class Inspector:'
582 field = info[key]
566 field = info[key]
583 if field is not None:
567 if field is not None:
584 displayfields.append((title, field.rstrip()))
568 displayfields.append((title, field.rstrip()))
585
569
586 add_fields(self.pinfo_fields1)
570 if info['isalias']:
587
571 add_fields([('Repr', "string_form")])
588 # Base class for old-style instances
572
589 if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
573 elif info['ismagic']:
590 displayfields.append(("Base Class", info['base_class'].rstrip()))
574 add_fields([("Docstring", "docstring"),
591
575 ("File", "file")
592 add_fields(self.pinfo_fields2)
576 ])
593
577
594 # Namespace
578 elif info['isclass'] or is_simple_callable(obj):
595 if info['namespace'] != 'Interactive':
579 # Functions, methods, classes
596 displayfields.append(("Namespace", info['namespace'].rstrip()))
580 add_fields([("Signature", "definition"),
597
581 ("Init signature", "init_definition"),
598 add_fields(self.pinfo_fields3)
582 ])
599 if info['isclass'] and info['init_definition']:
583 if detail_level > 0 and info['source'] is not None:
600 displayfields.append(("Init definition",
584 add_fields([("Source", "source")])
601 info['init_definition'].rstrip()))
585 else:
602
586 add_fields([("Docstring", "docstring"),
603 # Source or docstring, depending on detail level and whether
587 ("Init docstring", "init_docstring"),
604 # source found.
588 ])
605 if detail_level > 0 and info['source'] is not None:
589
606 displayfields.append(("Source",
590 add_fields([('File', 'file'),
607 self.format(cast_unicode(info['source']))))
591 ('Type', 'type_name'),
608 elif info['docstring'] is not None:
592 ])
609 displayfields.append(("Docstring", info["docstring"]))
593
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:
618 else:
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 if displayfields:
626 if displayfields:
622 return self._format_fields(displayfields)
627 return self._format_fields(displayfields)
@@ -737,7 +742,7 b' class Inspector:'
737 binary_file = True
742 binary_file = True
738 elif fname.endswith('<string>'):
743 elif fname.endswith('<string>'):
739 fname = 'Dynamically generated function. No source code available.'
744 fname = 'Dynamically generated function. No source code available.'
740 out['file'] = fname
745 out['file'] = compress_user(fname)
741
746
742 # Original source code for a callable, class or property.
747 # Original source code for a callable, class or property.
743 if detail_level:
748 if detail_level:
@@ -2,23 +2,10 b''
2 """
2 """
3 A mixin for :class:`~IPython.core.application.Application` classes that
3 A mixin for :class:`~IPython.core.application.Application` classes that
4 launch InteractiveShell instances, load extensions, etc.
4 launch InteractiveShell instances, load extensions, etc.
5
6 Authors
7 -------
8
9 * Min Ragan-Kelley
10 """
5 """
11
6
12 #-----------------------------------------------------------------------------
7 # Copyright (c) IPython Development Team.
13 # Copyright (C) 2008-2011 The IPython Development Team
8 # Distributed under the terms of the Modified BSD License.
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 #-----------------------------------------------------------------------------
22
9
23 from __future__ import absolute_import
10 from __future__ import absolute_import
24 from __future__ import print_function
11 from __future__ import print_function
@@ -152,10 +139,6 b' class InteractiveShellApp(Configurable):'
152 extra_extension = Unicode('', config=True,
139 extra_extension = Unicode('', config=True,
153 help="dotted module name of an IPython extension to load."
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 # Extensions that are always loaded (not configurable)
143 # Extensions that are always loaded (not configurable)
161 default_extensions = List(Unicode, [u'storemagic'], config=False)
144 default_extensions = List(Unicode, [u'storemagic'], config=False)
@@ -269,6 +252,8 b' class InteractiveShellApp(Configurable):'
269 try:
252 try:
270 self.log.debug("Loading IPython extensions...")
253 self.log.debug("Loading IPython extensions...")
271 extensions = self.default_extensions + self.extensions
254 extensions = self.default_extensions + self.extensions
255 if self.extra_extension:
256 extensions.append(self.extra_extension)
272 for ext in extensions:
257 for ext in extensions:
273 try:
258 try:
274 self.log.info("Loading IPython extension: %s" % ext)
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 register_line_cell_magic)
28 register_line_cell_magic)
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.testing.decorators import skipif
30 from IPython.testing.decorators import skipif
31 from IPython.utils.path import compress_user
31 from IPython.utils import py3compat
32 from IPython.utils import py3compat
32
33
33
34
@@ -46,7 +47,7 b' ip = get_ipython()'
46 # defined, if any code is inserted above, the following line will need to be
47 # defined, if any code is inserted above, the following line will need to be
47 # updated. Do NOT insert any whitespace between the next line and the function
48 # updated. Do NOT insert any whitespace between the next line and the function
48 # definition below.
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 def test_find_source_lines():
51 def test_find_source_lines():
51 nt.assert_equal(oinspect.find_source_lines(test_find_source_lines),
52 nt.assert_equal(oinspect.find_source_lines(test_find_source_lines),
52 THIS_LINE_NUMBER+1)
53 THIS_LINE_NUMBER+1)
@@ -241,7 +242,7 b' def test_info():'
241 fname = fname[:-1]
242 fname = fname[:-1]
242 # case-insensitive comparison needed on some filesystems
243 # case-insensitive comparison needed on some filesystems
243 # e.g. Windows:
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 nt.assert_equal(i['definition'], None)
246 nt.assert_equal(i['definition'], None)
246 nt.assert_equal(i['docstring'], Call.__doc__)
247 nt.assert_equal(i['docstring'], Call.__doc__)
247 nt.assert_equal(i['source'], None)
248 nt.assert_equal(i['source'], None)
@@ -117,6 +117,7 b' define(['
117 // don't return a comm, so that further .then() functions
117 // don't return a comm, so that further .then() functions
118 // get an undefined comm input
118 // get an undefined comm input
119 });
119 });
120 return this.comms[content.comm_id];
120 };
121 };
121
122
122 CommManager.prototype.comm_msg = function(msg) {
123 CommManager.prototype.comm_msg = function(msg) {
@@ -134,6 +135,7 b' define(['
134 }
135 }
135 return comm;
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 this.username = "username";
41 this.username = "username";
42 this.session_id = utils.uuid();
42 this.session_id = utils.uuid();
43 this._msg_callbacks = {};
43 this._msg_callbacks = {};
44 this._msg_queue = Promise.resolve();
44 this.info_reply = {}; // kernel_info_reply stored here after starting
45 this.info_reply = {}; // kernel_info_reply stored here after starting
45
46
46 if (typeof(WebSocket) !== 'undefined') {
47 if (typeof(WebSocket) !== 'undefined') {
@@ -854,19 +855,23 b' define(['
854 };
855 };
855
856
856 Kernel.prototype._handle_ws_message = function (e) {
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 Kernel.prototype._finish_ws_message = function (msg) {
865 Kernel.prototype._finish_ws_message = function (msg) {
861 switch (msg.channel) {
866 switch (msg.channel) {
862 case 'shell':
867 case 'shell':
863 this._handle_shell_reply(msg);
868 return this._handle_shell_reply(msg);
864 break;
869 break;
865 case 'iopub':
870 case 'iopub':
866 this._handle_iopub_message(msg);
871 return this._handle_iopub_message(msg);
867 break;
872 break;
868 case 'stdin':
873 case 'stdin':
869 this._handle_input_request(msg);
874 return this._handle_input_request(msg);
870 break;
875 break;
871 default:
876 default:
872 console.error("unrecognized message channel", msg.channel, msg);
877 console.error("unrecognized message channel", msg.channel, msg);
@@ -875,10 +880,12 b' define(['
875
880
876 Kernel.prototype._handle_shell_reply = function (reply) {
881 Kernel.prototype._handle_shell_reply = function (reply) {
877 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
882 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
883 var that = this;
878 var content = reply.content;
884 var content = reply.content;
879 var metadata = reply.metadata;
885 var metadata = reply.metadata;
880 var parent_id = reply.parent_header.msg_id;
886 var parent_id = reply.parent_header.msg_id;
881 var callbacks = this.get_callbacks_for_msg(parent_id);
887 var callbacks = this.get_callbacks_for_msg(parent_id);
888 var promise = Promise.resolve();
882 if (!callbacks || !callbacks.shell) {
889 if (!callbacks || !callbacks.shell) {
883 return;
890 return;
884 }
891 }
@@ -888,17 +895,21 b' define(['
888 this._finish_shell(parent_id);
895 this._finish_shell(parent_id);
889
896
890 if (shell_callbacks.reply !== undefined) {
897 if (shell_callbacks.reply !== undefined) {
891 shell_callbacks.reply(reply);
898 promise = promise.then(function() {return shell_callbacks.reply(reply)});
892 }
899 }
893 if (content.payload && shell_callbacks.payload) {
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 * @function _handle_payloads
909 * @function _handle_payloads
900 */
910 */
901 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
911 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
912 var promise = [];
902 var l = payloads.length;
913 var l = payloads.length;
903 // Payloads are handled by triggering events because we don't want the Kernel
914 // Payloads are handled by triggering events because we don't want the Kernel
904 // to depend on the Notebook or Pager classes.
915 // to depend on the Notebook or Pager classes.
@@ -906,9 +917,10 b' define(['
906 var payload = payloads[i];
917 var payload = payloads[i];
907 var callback = payload_callbacks[payload.source];
918 var callback = payload_callbacks[payload.source];
908 if (callback) {
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 Kernel.prototype._handle_iopub_message = function (msg) {
1033 Kernel.prototype._handle_iopub_message = function (msg) {
1022 var handler = this.get_iopub_handler(msg.header.msg_type);
1034 var handler = this.get_iopub_handler(msg.header.msg_type);
1023 if (handler !== undefined) {
1035 if (handler !== undefined) {
1024 handler(msg);
1036 return handler(msg);
1025 }
1037 }
1026 };
1038 };
1027
1039
@@ -30,7 +30,7 b' define(['
30 return msg;
30 return msg;
31 };
31 };
32
32
33 var _deserialize_binary = function(data, callback) {
33 var _deserialize_binary = function(data) {
34 /**
34 /**
35 * deserialize the binary message format
35 * deserialize the binary message format
36 * callback will be called with a message whose buffers attribute
36 * callback will be called with a message whose buffers attribute
@@ -39,28 +39,31 b' define(['
39 if (data instanceof Blob) {
39 if (data instanceof Blob) {
40 // data is Blob, have to deserialize from ArrayBuffer in reader callback
40 // data is Blob, have to deserialize from ArrayBuffer in reader callback
41 var reader = new FileReader();
41 var reader = new FileReader();
42 reader.onload = function () {
42 var promise = new Promise(function(resolve, reject) {
43 var msg = _deserialize_array_buffer(this.result);
43 reader.onload = function () {
44 callback(msg);
44 var msg = _deserialize_array_buffer(this.result);
45 };
45 resolve(msg);
46 };
47 });
46 reader.readAsArrayBuffer(data);
48 reader.readAsArrayBuffer(data);
49 return promise;
47 } else {
50 } else {
48 // data is ArrayBuffer, can deserialize directly
51 // data is ArrayBuffer, can deserialize directly
49 var msg = _deserialize_array_buffer(data);
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 if (typeof data === "string") {
61 if (typeof data === "string") {
59 // text JSON message
62 // text JSON message
60 callback(JSON.parse(data));
63 return Promise.resolve(JSON.parse(data));
61 } else {
64 } else {
62 // binary message
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 serialize: serialize
120 serialize: serialize
118 };
121 };
119 return exports;
122 return exports;
120 }); No newline at end of file
123 });
@@ -1575,8 +1575,6 b' h6:hover .anchor-link {'
1575 }
1575 }
1576 .widget-radio-box label {
1576 .widget-radio-box label {
1577 margin-top: 0px;
1577 margin-top: 0px;
1578 }
1579 .widget-radio {
1580 margin-left: 20px;
1578 margin-left: 20px;
1581 }
1579 }
1582 /*# sourceMappingURL=ipython.min.css.map */ No newline at end of file
1580 /*# sourceMappingURL=ipython.min.css.map */
@@ -10358,8 +10358,6 b' h6:hover .anchor-link {'
10358 }
10358 }
10359 .widget-radio-box label {
10359 .widget-radio-box label {
10360 margin-top: 0px;
10360 margin-top: 0px;
10361 }
10362 .widget-radio {
10363 margin-left: 20px;
10361 margin-left: 20px;
10364 }
10362 }
10365 /*!
10363 /*!
@@ -59,10 +59,9 b' define(['
59 this.$checkbox.prop('checked', this.model.get('value'));
59 this.$checkbox.prop('checked', this.model.get('value'));
60
60
61 if (options === undefined || options.updated_view != this) {
61 if (options === undefined || options.updated_view != this) {
62 var disabled = this.model.get('disabled');
62 this.$checkbox.prop("disabled", this.model.get("disabled"));
63 this.$checkbox.prop('disabled', disabled);
64
63
65 var description = this.model.get('description');
64 var description = this.model.get("description");
66 if (description.trim().length === 0) {
65 if (description.trim().length === 0) {
67 this.$label.hide();
66 this.$label.hide();
68 } else {
67 } else {
@@ -113,7 +112,7 b' define(['
113 /**
112 /**
114 * Update the contents of this view
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 * changed by another view or by a state update from the back-end.
116 * changed by another view or by a state update from the back-end.
118 */
117 */
119 if (this.model.get('value')) {
118 if (this.model.get('value')) {
@@ -123,16 +122,16 b' define(['
123 }
122 }
124
123
125 if (options === undefined || options.updated_view != this) {
124 if (options === undefined || options.updated_view != this) {
126
125 this.$el.prop("disabled", this.model.get("disabled"));
127 var disabled = this.model.get('disabled');
128 this.$el.prop('disabled', disabled);
129
130 var description = this.model.get('description');
131 this.$el.attr("title", this.model.get("tooltip"));
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 this.$el.html("&nbsp;"); // Preserve button height
131 this.$el.html("&nbsp;"); // Preserve button height
134 } else {
132 } else {
135 this.$el.text(description);
133 this.$el.text(description);
134 $('<i class="fa"></i>').prependTo(this.$el).addClass(icon);
136 }
135 }
137 }
136 }
138 return ToggleButtonView.__super__.update.apply(this);
137 return ToggleButtonView.__super__.update.apply(this);
@@ -27,21 +27,19 b' define(['
27 /**
27 /**
28 * Update the contents of this view
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 * changed by another view or by a state update from the back-end.
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 this.$el.attr("title", this.model.get("tooltip"));
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 this.$el.html("&nbsp;"); // Preserve button height
39 this.$el.html("&nbsp;"); // Preserve button height
37 } else {
40 } else {
38 this.$el.text(description);
41 this.$el.text(description);
39 }
42 $('<i class="fa"></i>').prependTo(this.$el).addClass(icon);
40
41 if (this.model.get('disabled')) {
42 this.$el.attr('disabled','disabled');
43 } else {
44 this.$el.removeAttr('disabled');
45 }
43 }
46
44
47 return ButtonView.__super__.update.apply(this);
45 return ButtonView.__super__.update.apply(this);
@@ -307,17 +307,21 b' define(['
307 if (options === undefined || options.updated_view != this) {
307 if (options === undefined || options.updated_view != this) {
308 // Add missing items to the DOM.
308 // Add missing items to the DOM.
309 var items = this.model.get('_options_labels');
309 var items = this.model.get('_options_labels');
310 var icons = this.model.get('icons');
311 var previous_icons = this.model.previous('icons') || [];
310 var disabled = this.model.get('disabled');
312 var disabled = this.model.get('disabled');
311 var that = this;
313 var that = this;
312 var item_html;
314 var item_html;
313 _.each(items, function(item, index) {
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 item_html = "&nbsp;";
318 item_html = "&nbsp;";
316 } else {
319 } else {
317 item_html = utils.escape_html(item);
320 item_html = utils.escape_html(item);
318 }
321 }
319 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
322 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
320 var $item_element = that.$buttongroup.find(item_query);
323 var $item_element = that.$buttongroup.find(item_query);
324 var $icon_element = $item_element.find('.fa');
321 if (!$item_element.length) {
325 if (!$item_element.length) {
322 $item_element = $('<button/>')
326 $item_element = $('<button/>')
323 .attr('type', 'button')
327 .attr('type', 'button')
@@ -325,16 +329,22 b' define(['
325 .html(item_html)
329 .html(item_html)
326 .appendTo(that.$buttongroup)
330 .appendTo(that.$buttongroup)
327 .attr('data-value', encodeURIComponent(item))
331 .attr('data-value', encodeURIComponent(item))
332 .attr('data-toggle', 'tooltip')
328 .attr('value', item)
333 .attr('value', item)
329 .on('click', $.proxy(that.handle_click, that));
334 .on('click', $.proxy(that.handle_click, that));
330 that.update_style_traits($item_element);
335 that.update_style_traits($item_element);
336 $icon_element = $('<i class="fa"></i>').prependTo($item_element);
331 }
337 }
332 if (that.model.get('selected_label') == item) {
338 if (that.model.get('selected_label') == item) {
333 $item_element.addClass('active');
339 $item_element.addClass('active');
334 } else {
340 } else {
335 $item_element.removeClass('active');
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 // Remove items that no longer exist.
350 // Remove items that no longer exist.
@@ -271,9 +271,6 b''
271
271
272 label {
272 label {
273 margin-top: 0px;
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 <li id="download_html"><a href="#">HTML (.html)</a></li>
108 <li id="download_html"><a href="#">HTML (.html)</a></li>
109 <li id="download_markdown"><a href="#">Markdown (.md)</a></li>
109 <li id="download_markdown"><a href="#">Markdown (.md)</a></li>
110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
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 </ul>
112 </ul>
113 </li>
113 </li>
114 <li class="divider"></li>
114 <li class="divider"></li>
@@ -51,7 +51,9 b' casper.notebook_test(function () {'
51 this.then(function () {
51 this.then(function () {
52 var index = this.append_cell([
52 var index = this.append_cell([
53 "buffers = [b'\\xFF\\x00', b'\\x00\\x01\\x02']",
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 ].join('\n'), 'code');
57 ].join('\n'), 'code');
56 this.execute_cell(index);
58 this.execute_cell(index);
57 });
59 });
@@ -59,7 +61,7 b' casper.notebook_test(function () {'
59 // wait for capture
61 // wait for capture
60 this.waitFor(function () {
62 this.waitFor(function () {
61 return this.evaluate(function () {
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 var msgs = this.evaluate(function () {
70 var msgs = this.evaluate(function () {
69 return IPython._msgs;
71 return IPython._msgs;
70 });
72 });
71 this.test.assertEquals(msgs.length, 1, "Captured comm message");
73 this.test.assertEquals(msgs.length, 3, "Captured three comm messages");
72 var buffers = msgs[0].buffers;
74
73 this.test.assertEquals(buffers.length, 2, "comm message has buffers");
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 // extract attributes to test in evaluate,
85 // extract attributes to test in evaluate,
76 // because the raw DataViews can't be passed across
86 // because the raw DataViews can't be passed across
77 var buf_info = function (index) {
87 var buf_info = function (message, index) {
78 var buf = IPython._msgs[0].buffers[index];
88 var buf = IPython._msgs[message].buffers[index];
79 var data = {};
89 var data = {};
80 data.byteLength = buf.byteLength;
90 data.byteLength = buf.byteLength;
81 data.bytes = [];
91 data.bytes = [];
@@ -85,18 +95,22 b' casper.notebook_test(function () {'
85 return data;
95 return data;
86 };
96 };
87
97
88 buf0 = this.evaluate(buf_info, 0);
98 var msgs_with_buffers = [0, 2];
89 buf1 = this.evaluate(buf_info, 1);
99 for (var i = 0; i < msgs_with_buffers.length; i++) {
90 this.test.assertEquals(buf0.byteLength, 2, 'buf[0] has correct size');
100 msg_index = msgs_with_buffers[i];
91 this.test.assertEquals(buf0.bytes, [255, 0], 'buf[0] has correct bytes');
101 buf0 = this.evaluate(buf_info, msg_index, 0);
92 this.test.assertEquals(buf1.byteLength, 3, 'buf[1] has correct size');
102 buf1 = this.evaluate(buf_info, msg_index, 1);
93 this.test.assertEquals(buf1.bytes, [0, 1, 2], 'buf[1] has correct bytes');
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 // validate captured buffers Python-side
110 // validate captured buffers Python-side
97 this.then(function () {
111 this.then(function () {
98 var index = this.append_cell([
112 var index = this.append_cell([
99 "assert len(msgs) == 1, len(msgs)",
113 "assert len(msgs) == 3, len(msgs)",
100 "bufs = msgs[0]['buffers']",
114 "bufs = msgs[0]['buffers']",
101 "assert len(bufs) == len(buffers), bufs",
115 "assert len(bufs) == len(buffers), bufs",
102 "assert bufs[0].bytes == buffers[0], bufs[0].bytes",
116 "assert bufs[0].bytes == buffers[0], bufs[0].bytes",
@@ -107,7 +121,7 b' casper.notebook_test(function () {'
107 this.wait_for_output(index);
121 this.wait_for_output(index);
108 this.then(function () {
122 this.then(function () {
109 var out = this.get_output_cell(index);
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 'Toggle button exists.');
49 'Toggle button exists.');
50
50
51 this.test.assert(this.cell_element_function(bool_index,
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 'Toggle button labeled correctly.');
53 'Toggle button labeled correctly.');
54
54
55 this.test.assert(this.cell_element_function(bool_index,
55 this.test.assert(this.cell_element_function(bool_index,
@@ -29,7 +29,7 b' casper.notebook_test(function () {'
29 'Widget button exists.');
29 'Widget button exists.');
30
30
31 this.test.assert(this.cell_element_function(button_index,
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 'Set button description.');
33 'Set button description.');
34
34
35 this.cell_element_function(button_index,
35 this.cell_element_function(button_index,
@@ -55,10 +55,15 b' class ToggleButton(_Bool):'
55 value of the toggle button: True-pressed, False-unpressed
55 value of the toggle button: True-pressed, False-unpressed
56 description : str
56 description : str
57 description displayed next to the button
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 _view_name = Unicode('ToggleButtonView', sync=True)
64 _view_name = Unicode('ToggleButtonView', sync=True)
61 tooltip = Unicode(help="Tooltip caption of the toggle button.", sync=True)
65 tooltip = Unicode(help="Tooltip caption of the toggle button.", sync=True)
66 icon = Unicode('', help= "Font-awesome icon.", sync=True)
62
67
63 button_style = CaselessStrEnum(
68 button_style = CaselessStrEnum(
64 values=['primary', 'success', 'info', 'warning', 'danger', ''],
69 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -24,15 +24,25 b' from IPython.utils.warn import DeprecatedClass'
24 @register('IPython.Button')
24 @register('IPython.Button')
25 class Button(DOMWidget):
25 class Button(DOMWidget):
26 """Button widget.
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
30 Parameters
29 user clicking on the button. The click event itself is stateless."""
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 _view_name = Unicode('ButtonView', sync=True)
39 _view_name = Unicode('ButtonView', sync=True)
31
40
32 # Keys
41 # Keys
33 description = Unicode('', help="Button label.", sync=True)
42 description = Unicode('', help="Button label.", sync=True)
34 tooltip = Unicode(help="Tooltip caption of the button.", sync=True)
43 tooltip = Unicode(help="Tooltip caption of the button.", sync=True)
35 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
44 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
45 icon = Unicode('', help= "Font-awesome icon.", sync=True)
36
46
37 button_style = CaselessStrEnum(
47 button_style = CaselessStrEnum(
38 values=['primary', 'success', 'info', 'warning', 'danger', ''],
48 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -19,7 +19,7 b' from threading import Lock'
19
19
20 from .widget import DOMWidget, register
20 from .widget import DOMWidget, register
21 from IPython.utils.traitlets import (
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 from IPython.utils.py3compat import unicode_type
24 from IPython.utils.py3compat import unicode_type
25 from IPython.utils.warn import DeprecatedClass
25 from IPython.utils.warn import DeprecatedClass
@@ -32,6 +32,12 b' class _Selection(DOMWidget):'
32
32
33 ``options`` can be specified as a list or dict. If given as a list,
33 ``options`` can be specified as a list or dict. If given as a list,
34 it will be transformed to a dict of the form ``{str(value):value}``.
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 value = Any(help="Selected value")
43 value = Any(help="Selected value")
@@ -194,6 +200,8 b' class ToggleButtons(_Selection):'
194 """Group of toggle buttons that represent an enumeration. Only one toggle
200 """Group of toggle buttons that represent an enumeration. Only one toggle
195 button can be toggled at any point in time."""
201 button can be toggled at any point in time."""
196 _view_name = Unicode('ToggleButtonsView', sync=True)
202 _view_name = Unicode('ToggleButtonsView', sync=True)
203 tooltips = List(Unicode(), sync=True)
204 icons = List(Unicode(), sync=True)
197
205
198 button_style = CaselessStrEnum(
206 button_style = CaselessStrEnum(
199 values=['primary', 'success', 'info', 'warning', 'danger', ''],
207 values=['primary', 'success', 'info', 'warning', 'danger', ''],
@@ -384,7 +384,7 b' def test_oinfo_detail():'
384 content = reply['content']
384 content = reply['content']
385 assert content['found']
385 assert content['found']
386 text = content['data']['text/plain']
386 text = content['data']['text/plain']
387 nt.assert_in('Definition:', text)
387 nt.assert_in('Signature:', text)
388 nt.assert_in('Source:', text)
388 nt.assert_in('Source:', text)
389
389
390
390
@@ -91,7 +91,7 b' class TestSession(SessionTestCase):'
91
91
92 content = msg['content']
92 content = msg['content']
93 header = msg['header']
93 header = msg['header']
94 header['date'] = datetime.now()
94 header['msg_id'] = self.session.msg_id
95 parent = msg['parent_header']
95 parent = msg['parent_header']
96 metadata = msg['metadata']
96 metadata = msg['metadata']
97 msg_type = header['msg_type']
97 msg_type = header['msg_type']
@@ -100,7 +100,7 b' class TestSession(SessionTestCase):'
100 ident, msg_list = self.session.feed_identities(B.recv_multipart())
100 ident, msg_list = self.session.feed_identities(B.recv_multipart())
101 new_msg = self.session.deserialize(msg_list)
101 new_msg = self.session.deserialize(msg_list)
102 self.assertEqual(ident[0], b'foo')
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 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
104 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
105 self.assertEqual(new_msg['header'],msg['header'])
105 self.assertEqual(new_msg['header'],msg['header'])
106 self.assertEqual(new_msg['content'],msg['content'])
106 self.assertEqual(new_msg['content'],msg['content'])
@@ -108,12 +108,12 b' class TestSession(SessionTestCase):'
108 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
108 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
109 self.assertEqual(new_msg['buffers'],[b'bar'])
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 self.session.send(A, msg, ident=b'foo', buffers=[b'bar'])
113 self.session.send(A, msg, ident=b'foo', buffers=[b'bar'])
114 ident, new_msg = self.session.recv(B)
114 ident, new_msg = self.session.recv(B)
115 self.assertEqual(ident[0], b'foo')
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 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
117 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
118 self.assertEqual(new_msg['header'],msg['header'])
118 self.assertEqual(new_msg['header'],msg['header'])
119 self.assertEqual(new_msg['content'],msg['content'])
119 self.assertEqual(new_msg['content'],msg['content'])
@@ -142,9 +142,10 b' class Exporter(LoggingConfigurable):'
142 resources = ResourcesDict()
142 resources = ResourcesDict()
143 if not 'metadata' in resources or resources['metadata'] == '':
143 if not 'metadata' in resources or resources['metadata'] == '':
144 resources['metadata'] = ResourcesDict()
144 resources['metadata'] = ResourcesDict()
145 basename = os.path.basename(filename)
145 path, basename = os.path.split(filename)
146 notebook_name = basename[:basename.rfind('.')]
146 notebook_name = basename[:basename.rfind('.')]
147 resources['metadata']['name'] = notebook_name
147 resources['metadata']['name'] = notebook_name
148 resources['metadata']['path'] = path
148
149
149 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
150 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
150 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
151 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
@@ -8,23 +8,21 b' markdown within Jinja templates.'
8
8
9 from __future__ import print_function
9 from __future__ import print_function
10
10
11 # Stdlib imports
12 import os
11 import os
13 import subprocess
12 import subprocess
14 from io import TextIOWrapper, BytesIO
13 from io import TextIOWrapper, BytesIO
15 import re
16
14
17 import mistune
15 try:
18 from pygments import highlight
16 from .markdown_mistune import markdown2html_mistune
19 from pygments.lexers import get_lexer_by_name
17 except ImportError as e:
20 from pygments.formatters import HtmlFormatter
18 # store in variable for Python 3
21 from pygments.util import ClassNotFound
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 from IPython.nbconvert.utils.pandoc import pandoc
24 from IPython.nbconvert.utils.pandoc import pandoc
26 from IPython.nbconvert.utils.exceptions import ConversionException
25 from IPython.nbconvert.utils.exceptions import ConversionException
27 from IPython.utils.decorators import undoc
28 from IPython.utils.process import get_output_error_code
26 from IPython.utils.process import get_output_error_code
29 from IPython.utils.py3compat import cast_bytes
27 from IPython.utils.py3compat import cast_bytes
30 from IPython.utils.version import check_version
28 from IPython.utils.version import check_version
@@ -46,6 +44,7 b' class NodeJSMissing(ConversionException):'
46 """Exception raised when node.js is missing."""
44 """Exception raised when node.js is missing."""
47 pass
45 pass
48
46
47
49 def markdown2latex(source, markup='markdown', extra_args=None):
48 def markdown2latex(source, markup='markdown', extra_args=None):
50 """Convert a markdown string to LaTeX via pandoc.
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 return pandoc(source, markup, 'latex', extra_args=extra_args)
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 def markdown2html_pandoc(source, extra_args=None):
71 def markdown2html_pandoc(source, extra_args=None):
169 """Convert a markdown string to HTML via pandoc"""
72 """Convert a markdown string to HTML via pandoc"""
170 extra_args = extra_args or ['--mathjax']
73 extra_args = extra_args or ['--mathjax']
171 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
74 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
172
75
76
173 def _find_nodejs():
77 def _find_nodejs():
174 global _node
78 global _node
175 if _node is None:
79 if _node is None:
@@ -25,6 +25,14 b' class FilesWriter(WriterBase):'
25 help="""Directory to write output to. Leave blank
25 help="""Directory to write output to. Leave blank
26 to output to the current directory""")
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 # Make sure that the output directory exists.
37 # Make sure that the output directory exists.
30 def _build_directory_changed(self, name, old, new):
38 def _build_directory_changed(self, name, old, new):
@@ -59,6 +67,12 b' class FilesWriter(WriterBase):'
59 # Pull the extension and subdir from the resources dict.
67 # Pull the extension and subdir from the resources dict.
60 output_extension = resources.get('output_extension', None)
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 # Write all of the extracted resources to the destination directory.
76 # Write all of the extracted resources to the destination directory.
63 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
77 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
64 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
78 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
@@ -81,8 +95,14 b' class FilesWriter(WriterBase):'
81 # Copy files that match search pattern
95 # Copy files that match search pattern
82 for matching_filename in glob.glob(filename):
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 # Make sure folder exists.
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 path = os.path.dirname(dest)
106 path = os.path.dirname(dest)
87 self._makedir(path)
107 self._makedir(path)
88
108
@@ -201,3 +201,107 b' class Testfiles(TestsBase):'
201 with open(dest, 'r') as f:
201 with open(dest, 'r') as f:
202 output = f.read()
202 output = f.read()
203 self.assertEqual(output, 'e')
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 .. _scipy: http://www.scipy.org
48 .. _scipy: http://www.scipy.org
49 .. _scipy_conference: http://conference.scipy.org
49 .. _scipy_conference: http://conference.scipy.org
50 .. _matplotlib: http://matplotlib.org
50 .. _matplotlib: http://matplotlib.org
51 .. _pythonxy: http://www.pythonxy.com
51 .. _pythonxy: https://code.google.com/p/pythonxy/
52 .. _ETS: http://code.enthought.com/projects/tool-suite.php
52 .. _ETS: http://code.enthought.com/projects/tool-suite.php
53 .. _EPD: http://www.enthought.com/products/epd.php
53 .. _EPD: http://www.enthought.com/products/epd.php
54 .. _python: http://www.python.org
54 .. _python: http://www.python.org
@@ -307,7 +307,7 b''
307 "range1, range2, range3 = widgets.IntSlider(description='Range 1'),\\\n",
307 "range1, range2, range3 = widgets.IntSlider(description='Range 1'),\\\n",
308 " widgets.IntSlider(description='Range 2'),\\\n",
308 " widgets.IntSlider(description='Range 2'),\\\n",
309 " widgets.IntSlider(description='Range 3')\n",
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 "display(caption, range1, range2, range3)"
311 "display(caption, range1, range2, range3)"
312 ]
312 ]
313 },
313 },
@@ -323,7 +323,7 b''
323 "source_range, target_range1, target_range2 = widgets.IntSlider(description='Source range'),\\\n",
323 "source_range, target_range1, target_range2 = widgets.IntSlider(description='Source range'),\\\n",
324 " widgets.IntSlider(description='Target range 1'),\\\n",
324 " widgets.IntSlider(description='Target range 1'),\\\n",
325 " widgets.IntSlider(description='Target range 2')\n",
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 "display(caption, source_range, target_range1, target_range2)"
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