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 Alias |
|
|
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 = |
|
|
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 |
|
|
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 |
|
|
|
53 | return msg; | |
|
51 | 54 | } |
|
52 | 55 | }; |
|
53 | 56 | |
|
54 |
var deserialize = function (data |
|
|
57 | var deserialize = function (data) { | |
|
55 | 58 | /** |
|
56 |
* deserialize a message and |
|
|
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 |
|
|
|
63 | return Promise.resolve(JSON.parse(data)); | |
|
61 | 64 | } else { |
|
62 | 65 | // binary message |
|
63 |
_deserialize_binary(data |
|
|
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 |
|
|
|
63 | this.$checkbox.prop('disabled', disabled); | |
|
62 | this.$checkbox.prop("disabled", this.model.get("disabled")); | |
|
64 | 63 | |
|
65 |
var description = this.model.get( |
|
|
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. |
|
|
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(" "); // 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. |
|
|
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(" "); // 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 = " "; |
|
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=' |
|
|
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 > |
|
|
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, |
|
|
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[ |
|
|
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(buf |
|
|
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) == |
|
|
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')== |
|
|
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(' |
|
|
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'], |
|
|
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'], |
|
|
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. |
|
|
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, |
|
|
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 |
|
|
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