Show More
@@ -0,0 +1,64 b'' | |||||
|
1 | """ | |||
|
2 | Inputhook for running the original asyncio event loop while we're waiting for | |||
|
3 | input. | |||
|
4 | ||||
|
5 | By default, in IPython, we run the prompt with a different asyncio event loop, | |||
|
6 | because otherwise we risk that people are freezing the prompt by scheduling bad | |||
|
7 | coroutines. E.g., a coroutine that does a while/true and never yield back | |||
|
8 | control to the loop. We can't cancel that. | |||
|
9 | ||||
|
10 | However, sometimes we want the asyncio loop to keep running while waiting for | |||
|
11 | a prompt. | |||
|
12 | ||||
|
13 | The following example will print the numbers from 1 to 10 above the prompt, | |||
|
14 | while we are waiting for input. (This works also because we use | |||
|
15 | prompt_toolkit`s `patch_stdout`):: | |||
|
16 | ||||
|
17 | In [1]: import asyncio | |||
|
18 | ||||
|
19 | In [2]: %gui asyncio | |||
|
20 | ||||
|
21 | In [3]: async def f(): | |||
|
22 | ...: for i in range(10): | |||
|
23 | ...: await asyncio.sleep(1) | |||
|
24 | ...: print(i) | |||
|
25 | ||||
|
26 | ||||
|
27 | In [4]: asyncio.ensure_future(f()) | |||
|
28 | ||||
|
29 | """ | |||
|
30 | import asyncio | |||
|
31 | from prompt_toolkit import __version__ as ptk_version | |||
|
32 | ||||
|
33 | PTK3 = ptk_version.startswith('3.') | |||
|
34 | ||||
|
35 | ||||
|
36 | # Keep reference to the original asyncio loop, because getting the event loop | |||
|
37 | # within the input hook would return the other loop. | |||
|
38 | loop = asyncio.get_event_loop() | |||
|
39 | ||||
|
40 | ||||
|
41 | def inputhook(context): | |||
|
42 | """ | |||
|
43 | Inputhook for asyncio event loop integration. | |||
|
44 | """ | |||
|
45 | # For prompt_toolkit 3.0, this input hook literally doesn't do anything. | |||
|
46 | # The event loop integration here is implemented in `interactiveshell.py` | |||
|
47 | # by running the prompt itself in the current asyncio loop. The main reason | |||
|
48 | # for this is that nesting asyncio event loops is unreliable. | |||
|
49 | if PTK3: | |||
|
50 | return | |||
|
51 | ||||
|
52 | # For prompt_toolkit 2.0, we can run the current asyncio event loop, | |||
|
53 | # because prompt_toolkit 2.0 uses a different event loop internally. | |||
|
54 | ||||
|
55 | def stop(): | |||
|
56 | loop.stop() | |||
|
57 | ||||
|
58 | fileno = context.fileno() | |||
|
59 | loop.add_reader(fileno, stop) | |||
|
60 | try: | |||
|
61 | loop.run_forever() | |||
|
62 | finally: | |||
|
63 | loop.remove_reader(fileno) | |||
|
64 |
@@ -0,0 +1,60 b'' | |||||
|
1 | ||||
|
2 | .. _shell_mimerenderer: | |||
|
3 | ||||
|
4 | ||||
|
5 | Mime Renderer Extensions | |||
|
6 | ======================== | |||
|
7 | ||||
|
8 | Like it's cousins, Jupyter Notebooks and JupyterLab, Terminal IPython can be | |||
|
9 | thought to render a number of mimetypes in the shell. This can be used to either | |||
|
10 | display inline images if your terminal emulator supports it; or open some | |||
|
11 | display results with external file viewers. | |||
|
12 | ||||
|
13 | Registering new mimetype handlers can so far only be done my extensions and | |||
|
14 | requires 4 steps: | |||
|
15 | ||||
|
16 | - Define a callable that takes 2 parameters:``data`` and ``metadata``; return | |||
|
17 | value of the callable is so far ignored. This callable is responsible for | |||
|
18 | "displaying" the given mimetype. Which can be sending the right escape | |||
|
19 | sequences and bytes to the current terminal; or open an external program. - | |||
|
20 | - Appending the right mimetype to ``ipython.display_formatter.active_types`` | |||
|
21 | for IPython to know it should not ignore those mimetypes. | |||
|
22 | - Enabling the given mimetype: ``ipython.display_formatter.formatters[mime].enabled = True`` | |||
|
23 | - Registering above callable with mimetype handler: | |||
|
24 | ``ipython.mime_renderers[mime] = handler`` | |||
|
25 | ||||
|
26 | ||||
|
27 | Here is a complete IPython extension to display images inline and convert math | |||
|
28 | to png, before displaying it inline for iterm2 on macOS :: | |||
|
29 | ||||
|
30 | ||||
|
31 | from base64 import encodebytes | |||
|
32 | from IPython.lib.latextools import latex_to_png | |||
|
33 | ||||
|
34 | ||||
|
35 | def mathcat(data, meta): | |||
|
36 | png = latex_to_png(f'$${data}$$'.replace('\displaystyle', '').replace('$$$', '$$')) | |||
|
37 | imcat(png, meta) | |||
|
38 | ||||
|
39 | IMAGE_CODE = '\033]1337;File=name=name;inline=true;:{}\a' | |||
|
40 | ||||
|
41 | def imcat(image_data, metadata): | |||
|
42 | try: | |||
|
43 | print(IMAGE_CODE.format(encodebytes(image_data).decode())) | |||
|
44 | # bug workaround | |||
|
45 | except: | |||
|
46 | print(IMAGE_CODE.format(image_data)) | |||
|
47 | ||||
|
48 | def register_mimerenderer(ipython, mime, handler): | |||
|
49 | ipython.display_formatter.active_types.append(mime) | |||
|
50 | ipython.display_formatter.formatters[mime].enabled = True | |||
|
51 | ipython.mime_renderers[mime] = handler | |||
|
52 | ||||
|
53 | def load_ipython_extension(ipython): | |||
|
54 | register_mimerenderer(ipython, 'image/png', imcat) | |||
|
55 | register_mimerenderer(ipython, 'image/jpeg', imcat) | |||
|
56 | register_mimerenderer(ipython, 'text/latex', mathcat) | |||
|
57 | ||||
|
58 | This example only work for iterm2 on macOS and skip error handling for brevity. | |||
|
59 | One could also invoke an external viewer with ``subprocess.run()`` and a | |||
|
60 | temporary file, which is left as an exercise. |
@@ -29,6 +29,8 b' from IPython.core.release import author_email' | |||||
29 | from IPython.utils.sysinfo import sys_info |
|
29 | from IPython.utils.sysinfo import sys_info | |
30 | from IPython.utils.py3compat import input |
|
30 | from IPython.utils.py3compat import input | |
31 |
|
31 | |||
|
32 | from IPython.core.release import __version__ as version | |||
|
33 | ||||
32 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
33 | # Code |
|
35 | # Code | |
34 | #----------------------------------------------------------------------------- |
|
36 | #----------------------------------------------------------------------------- | |
@@ -68,7 +70,7 b' To ensure accurate tracking of this issue, please file a report about it at:' | |||||
68 | """ |
|
70 | """ | |
69 |
|
71 | |||
70 | _lite_message_template = """ |
|
72 | _lite_message_template = """ | |
71 | If you suspect this is an IPython bug, please report it at: |
|
73 | If you suspect this is an IPython {version} bug, please report it at: | |
72 | https://github.com/ipython/ipython/issues |
|
74 | https://github.com/ipython/ipython/issues | |
73 | or send an email to the mailing list at {email} |
|
75 | or send an email to the mailing list at {email} | |
74 |
|
76 | |||
@@ -222,5 +224,5 b' def crash_handler_lite(etype, evalue, tb):' | |||||
222 | else: |
|
224 | else: | |
223 | # we are not in a shell, show generic config |
|
225 | # we are not in a shell, show generic config | |
224 | config = "c." |
|
226 | config = "c." | |
225 | print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) |
|
227 | print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) | |
226 |
|
228 |
@@ -153,7 +153,7 b' class DisplayHook(Configurable):' | |||||
153 | # This can be set to True by the write_output_prompt method in a subclass |
|
153 | # This can be set to True by the write_output_prompt method in a subclass | |
154 | prompt_end_newline = False |
|
154 | prompt_end_newline = False | |
155 |
|
155 | |||
156 | def write_format_data(self, format_dict, md_dict=None): |
|
156 | def write_format_data(self, format_dict, md_dict=None) -> None: | |
157 | """Write the format data dict to the frontend. |
|
157 | """Write the format data dict to the frontend. | |
158 |
|
158 | |||
159 | This default version of this method simply writes the plain text |
|
159 | This default version of this method simply writes the plain text |
@@ -19,7 +19,7 b' spec.' | |||||
19 | import sys |
|
19 | import sys | |
20 |
|
20 | |||
21 | from traitlets.config.configurable import Configurable |
|
21 | from traitlets.config.configurable import Configurable | |
22 | from traitlets import List |
|
22 | from traitlets import List, Dict | |
23 |
|
23 | |||
24 | # This used to be defined here - it is imported for backwards compatibility |
|
24 | # This used to be defined here - it is imported for backwards compatibility | |
25 | from .display import publish_display_data |
|
25 | from .display import publish_display_data | |
@@ -28,6 +28,7 b' from .display import publish_display_data' | |||||
28 | # Main payload class |
|
28 | # Main payload class | |
29 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
30 |
|
30 | |||
|
31 | ||||
31 | class DisplayPublisher(Configurable): |
|
32 | class DisplayPublisher(Configurable): | |
32 | """A traited class that publishes display data to frontends. |
|
33 | """A traited class that publishes display data to frontends. | |
33 |
|
34 | |||
@@ -35,6 +36,10 b' class DisplayPublisher(Configurable):' | |||||
35 | be accessed there. |
|
36 | be accessed there. | |
36 | """ |
|
37 | """ | |
37 |
|
38 | |||
|
39 | def __init__(self, shell=None, *args, **kwargs): | |||
|
40 | self.shell = shell | |||
|
41 | super().__init__(*args, **kwargs) | |||
|
42 | ||||
38 | def _validate_data(self, data, metadata=None): |
|
43 | def _validate_data(self, data, metadata=None): | |
39 | """Validate the display data. |
|
44 | """Validate the display data. | |
40 |
|
45 | |||
@@ -53,7 +58,7 b' class DisplayPublisher(Configurable):' | |||||
53 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
58 | raise TypeError('metadata must be a dict, got: %r' % data) | |
54 |
|
59 | |||
55 | # use * to indicate transient, update are keyword-only |
|
60 | # use * to indicate transient, update are keyword-only | |
56 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs): |
|
61 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None: | |
57 | """Publish data and metadata to all frontends. |
|
62 | """Publish data and metadata to all frontends. | |
58 |
|
63 | |||
59 | See the ``display_data`` message in the messaging documentation for |
|
64 | See the ``display_data`` message in the messaging documentation for | |
@@ -98,7 +103,15 b' class DisplayPublisher(Configurable):' | |||||
98 | rather than creating a new output. |
|
103 | rather than creating a new output. | |
99 | """ |
|
104 | """ | |
100 |
|
105 | |||
101 | # The default is to simply write the plain text data using sys.stdout. |
|
106 | handlers = {} | |
|
107 | if self.shell is not None: | |||
|
108 | handlers = getattr(self.shell, 'mime_renderers', {}) | |||
|
109 | ||||
|
110 | for mime, handler in handlers.items(): | |||
|
111 | if mime in data: | |||
|
112 | handler(data[mime], metadata.get(mime, None)) | |||
|
113 | return | |||
|
114 | ||||
102 | if 'text/plain' in data: |
|
115 | if 'text/plain' in data: | |
103 | print(data['text/plain']) |
|
116 | print(data['text/plain']) | |
104 |
|
117 |
@@ -857,7 +857,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
857 | self.configurables.append(self.display_formatter) |
|
857 | self.configurables.append(self.display_formatter) | |
858 |
|
858 | |||
859 | def init_display_pub(self): |
|
859 | def init_display_pub(self): | |
860 | self.display_pub = self.display_pub_class(parent=self) |
|
860 | self.display_pub = self.display_pub_class(parent=self, shell=self) | |
861 | self.configurables.append(self.display_pub) |
|
861 | self.configurables.append(self.display_pub) | |
862 |
|
862 | |||
863 | def init_data_pub(self): |
|
863 | def init_data_pub(self): |
@@ -853,6 +853,8 b' python-profiler package from non-free.""")' | |||||
853 | sys.argv = save_argv |
|
853 | sys.argv = save_argv | |
854 | if restore_main: |
|
854 | if restore_main: | |
855 | sys.modules['__main__'] = restore_main |
|
855 | sys.modules['__main__'] = restore_main | |
|
856 | if '__mp_main__' in sys.modules: | |||
|
857 | sys.modules['__mp_main__'] = restore_main | |||
856 | else: |
|
858 | else: | |
857 | # Remove from sys.modules the reference to main_mod we'd |
|
859 | # Remove from sys.modules the reference to main_mod we'd | |
858 | # added. Otherwise it will trap references to objects |
|
860 | # added. Otherwise it will trap references to objects |
@@ -76,7 +76,7 b" info_fields = ['type_name', 'base_class', 'string_form', 'namespace'," | |||||
76 | 'call_def', 'call_docstring', |
|
76 | 'call_def', 'call_docstring', | |
77 | # These won't be printed but will be used to determine how to |
|
77 | # These won't be printed but will be used to determine how to | |
78 | # format the object |
|
78 | # format the object | |
79 |
'ismagic', 'isalias', 'isclass', ' |
|
79 | 'ismagic', 'isalias', 'isclass', 'found', 'name' | |
80 | ] |
|
80 | ] | |
81 |
|
81 | |||
82 |
|
82 | |||
@@ -200,26 +200,39 b' def is_simple_callable(obj):' | |||||
200 | return (inspect.isfunction(obj) or inspect.ismethod(obj) or \ |
|
200 | return (inspect.isfunction(obj) or inspect.ismethod(obj) or \ | |
201 | isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type)) |
|
201 | isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type)) | |
202 |
|
202 | |||
203 |
|
203 | @undoc | ||
204 | def getargspec(obj): |
|
204 | def getargspec(obj): | |
205 | """Wrapper around :func:`inspect.getfullargspec` on Python 3, and |
|
205 | """Wrapper around :func:`inspect.getfullargspec` on Python 3, and | |
206 | :func:inspect.getargspec` on Python 2. |
|
206 | :func:inspect.getargspec` on Python 2. | |
207 |
|
207 | |||
208 | In addition to functions and methods, this can also handle objects with a |
|
208 | In addition to functions and methods, this can also handle objects with a | |
209 | ``__call__`` attribute. |
|
209 | ``__call__`` attribute. | |
|
210 | ||||
|
211 | DEPRECATED: Deprecated since 7.10. Do not use, will be removed. | |||
210 | """ |
|
212 | """ | |
|
213 | ||||
|
214 | warnings.warn('`getargspec` function is deprecated as of IPython 7.10' | |||
|
215 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) | |||
|
216 | ||||
211 | if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): |
|
217 | if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): | |
212 | obj = obj.__call__ |
|
218 | obj = obj.__call__ | |
213 |
|
219 | |||
214 | return inspect.getfullargspec(obj) |
|
220 | return inspect.getfullargspec(obj) | |
215 |
|
221 | |||
216 |
|
222 | @undoc | ||
217 | def format_argspec(argspec): |
|
223 | def format_argspec(argspec): | |
218 | """Format argspect, convenience wrapper around inspect's. |
|
224 | """Format argspect, convenience wrapper around inspect's. | |
219 |
|
225 | |||
220 | This takes a dict instead of ordered arguments and calls |
|
226 | This takes a dict instead of ordered arguments and calls | |
221 | inspect.format_argspec with the arguments in the necessary order. |
|
227 | inspect.format_argspec with the arguments in the necessary order. | |
|
228 | ||||
|
229 | DEPRECATED: Do not use; will be removed in future versions. | |||
222 | """ |
|
230 | """ | |
|
231 | ||||
|
232 | warnings.warn('`format_argspec` function is deprecated as of IPython 7.10' | |||
|
233 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) | |||
|
234 | ||||
|
235 | ||||
223 | return inspect.formatargspec(argspec['args'], argspec['varargs'], |
|
236 | return inspect.formatargspec(argspec['args'], argspec['varargs'], | |
224 | argspec['varkw'], argspec['defaults']) |
|
237 | argspec['varkw'], argspec['defaults']) | |
225 |
|
238 | |||
@@ -916,33 +929,6 b' class Inspector(Colorable):' | |||||
916 | if call_ds: |
|
929 | if call_ds: | |
917 | out['call_docstring'] = call_ds |
|
930 | out['call_docstring'] = call_ds | |
918 |
|
931 | |||
919 | # Compute the object's argspec as a callable. The key is to decide |
|
|||
920 | # whether to pull it from the object itself, from its __init__ or |
|
|||
921 | # from its __call__ method. |
|
|||
922 |
|
||||
923 | if inspect.isclass(obj): |
|
|||
924 | # Old-style classes need not have an __init__ |
|
|||
925 | callable_obj = getattr(obj, "__init__", None) |
|
|||
926 | elif callable(obj): |
|
|||
927 | callable_obj = obj |
|
|||
928 | else: |
|
|||
929 | callable_obj = None |
|
|||
930 |
|
||||
931 | if callable_obj is not None: |
|
|||
932 | try: |
|
|||
933 | argspec = getargspec(callable_obj) |
|
|||
934 | except Exception: |
|
|||
935 | # For extensions/builtins we can't retrieve the argspec |
|
|||
936 | pass |
|
|||
937 | else: |
|
|||
938 | # named tuples' _asdict() method returns an OrderedDict, but we |
|
|||
939 | # we want a normal |
|
|||
940 | out['argspec'] = argspec_dict = dict(argspec._asdict()) |
|
|||
941 | # We called this varkw before argspec became a named tuple. |
|
|||
942 | # With getfullargspec it's also called varkw. |
|
|||
943 | if 'varkw' not in argspec_dict: |
|
|||
944 | argspec_dict['varkw'] = argspec_dict.pop('keywords') |
|
|||
945 |
|
||||
946 | return object_info(**out) |
|
932 | return object_info(**out) | |
947 |
|
933 | |||
948 | @staticmethod |
|
934 | @staticmethod |
@@ -15,9 +15,11 b' rid of that dependency, we could move it there.' | |||||
15 |
|
15 | |||
16 |
|
16 | |||
17 | import os |
|
17 | import os | |
|
18 | import io | |||
18 | import re |
|
19 | import re | |
19 | import sys |
|
20 | import sys | |
20 | import tempfile |
|
21 | import tempfile | |
|
22 | import subprocess | |||
21 |
|
23 | |||
22 | from io import UnsupportedOperation |
|
24 | from io import UnsupportedOperation | |
23 |
|
25 | |||
@@ -208,9 +210,13 b' def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):' | |||||
208 | else: |
|
210 | else: | |
209 | try: |
|
211 | try: | |
210 | retval = None |
|
212 | retval = None | |
211 | # if I use popen4, things hang. No idea why. |
|
213 | # Emulate os.popen, but redirect stderr | |
212 |
|
|
214 | proc = subprocess.Popen(pager_cmd, | |
213 | pager = os.popen(pager_cmd, 'w') |
|
215 | shell=True, | |
|
216 | stdin=subprocess.PIPE, | |||
|
217 | stderr=subprocess.DEVNULL | |||
|
218 | ) | |||
|
219 | pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc) | |||
214 | try: |
|
220 | try: | |
215 | pager_encoding = pager.encoding or sys.stdout.encoding |
|
221 | pager_encoding = pager.encoding or sys.stdout.encoding | |
216 | pager.write(strng) |
|
222 | pager.write(strng) |
@@ -20,7 +20,7 b" name = 'ipython'" | |||||
20 | # release. 'dev' as a _version_extra string means this is a development |
|
20 | # release. 'dev' as a _version_extra string means this is a development | |
21 | # version |
|
21 | # version | |
22 | _version_major = 7 |
|
22 | _version_major = 7 | |
23 |
_version_minor = 1 |
|
23 | _version_minor = 11 | |
24 | _version_patch = 0 |
|
24 | _version_patch = 0 | |
25 | _version_extra = '.dev' |
|
25 | _version_extra = '.dev' | |
26 | # _version_extra = 'b1' |
|
26 | # _version_extra = 'b1' |
@@ -7,7 +7,6 b" with better-isolated tests that don't rely on the global instance in iptest." | |||||
7 | """ |
|
7 | """ | |
8 | from IPython.core.splitinput import LineInfo |
|
8 | from IPython.core.splitinput import LineInfo | |
9 | from IPython.core.prefilter import AutocallChecker |
|
9 | from IPython.core.prefilter import AutocallChecker | |
10 | from IPython.utils import py3compat |
|
|||
11 |
|
10 | |||
12 | def doctest_autocall(): |
|
11 | def doctest_autocall(): | |
13 | """ |
|
12 | """ |
@@ -49,11 +49,11 b' def test_handlers():' | |||||
49 | # For many of the below, we're also checking that leading whitespace |
|
49 | # For many of the below, we're also checking that leading whitespace | |
50 | # turns off the esc char, which it should unless there is a continuation |
|
50 | # turns off the esc char, which it should unless there is a continuation | |
51 | # line. |
|
51 | # line. | |
52 | run([(i,py3compat.u_format(o)) for i,o in \ |
|
52 | run( | |
53 | [('"no change"', '"no change"'), # normal |
|
53 | [('"no change"', '"no change"'), # normal | |
54 | (u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic |
|
54 | (u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic | |
55 | #("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache |
|
55 | #("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache | |
56 |
] |
|
56 | ]) | |
57 |
|
57 | |||
58 | # Objects which are instances of IPyAutocall are *always* autocalled |
|
58 | # Objects which are instances of IPyAutocall are *always* autocalled | |
59 | autocallable = Autocallable() |
|
59 | autocallable = Autocallable() |
@@ -539,6 +539,35 b' def test_run_tb():' | |||||
539 | del ip.user_ns['bar'] |
|
539 | del ip.user_ns['bar'] | |
540 | del ip.user_ns['foo'] |
|
540 | del ip.user_ns['foo'] | |
541 |
|
541 | |||
|
542 | ||||
|
543 | def test_multiprocessing_run(): | |||
|
544 | """Set we can run mutiprocesgin without messing up up main namespace | |||
|
545 | ||||
|
546 | Note that import `nose.tools as nt` mdify the value s | |||
|
547 | sys.module['__mp_main__'] so wee need to temporarily set it to None to test | |||
|
548 | the issue. | |||
|
549 | """ | |||
|
550 | with TemporaryDirectory() as td: | |||
|
551 | mpm = sys.modules.get('__mp_main__') | |||
|
552 | assert mpm is not None | |||
|
553 | sys.modules['__mp_main__'] = None | |||
|
554 | try: | |||
|
555 | path = pjoin(td, 'test.py') | |||
|
556 | with open(path, 'w') as f: | |||
|
557 | f.write("import multiprocessing\nprint('hoy')") | |||
|
558 | with capture_output() as io: | |||
|
559 | _ip.run_line_magic('run', path) | |||
|
560 | _ip.run_cell("i_m_undefined") | |||
|
561 | out = io.stdout | |||
|
562 | nt.assert_in("hoy", out) | |||
|
563 | nt.assert_not_in("AttributeError", out) | |||
|
564 | nt.assert_in("NameError", out) | |||
|
565 | nt.assert_equal(out.count("---->"), 1) | |||
|
566 | except: | |||
|
567 | raise | |||
|
568 | finally: | |||
|
569 | sys.modules['__mp_main__'] = mpm | |||
|
570 | ||||
542 | @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") |
|
571 | @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") | |
543 | def test_script_tb(): |
|
572 | def test_script_tb(): | |
544 | """Test traceback offset in `ipython script.py`""" |
|
573 | """Test traceback offset in `ipython script.py`""" |
@@ -296,6 +296,25 b' except Exception:' | |||||
296 | tt.AssertPrints("ValueError", suppress=False): |
|
296 | tt.AssertPrints("ValueError", suppress=False): | |
297 | ip.run_cell(self.SUPPRESS_CHAINING_CODE) |
|
297 | ip.run_cell(self.SUPPRESS_CHAINING_CODE) | |
298 |
|
298 | |||
|
299 | def test_plain_direct_cause_error(self): | |||
|
300 | with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): | |||
|
301 | ip.run_cell("%xmode Plain") | |||
|
302 | ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) | |||
|
303 | ip.run_cell("%xmode Verbose") | |||
|
304 | ||||
|
305 | def test_plain_exception_during_handling_error(self): | |||
|
306 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | |||
|
307 | ip.run_cell("%xmode Plain") | |||
|
308 | ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) | |||
|
309 | ip.run_cell("%xmode Verbose") | |||
|
310 | ||||
|
311 | def test_plain_suppress_exception_chaining(self): | |||
|
312 | with tt.AssertNotPrints("ZeroDivisionError"), \ | |||
|
313 | tt.AssertPrints("ValueError", suppress=False): | |||
|
314 | ip.run_cell("%xmode Plain") | |||
|
315 | ip.run_cell(self.SUPPRESS_CHAINING_CODE) | |||
|
316 | ip.run_cell("%xmode Verbose") | |||
|
317 | ||||
299 |
|
318 | |||
300 | class RecursionTest(unittest.TestCase): |
|
319 | class RecursionTest(unittest.TestCase): | |
301 | DEFINITIONS = """ |
|
320 | DEFINITIONS = """ |
@@ -530,6 +530,30 b' class TBTools(colorable.Colorable):' | |||||
530 |
|
530 | |||
531 | ostream = property(_get_ostream, _set_ostream) |
|
531 | ostream = property(_get_ostream, _set_ostream) | |
532 |
|
532 | |||
|
533 | def get_parts_of_chained_exception(self, evalue): | |||
|
534 | def get_chained_exception(exception_value): | |||
|
535 | cause = getattr(exception_value, '__cause__', None) | |||
|
536 | if cause: | |||
|
537 | return cause | |||
|
538 | if getattr(exception_value, '__suppress_context__', False): | |||
|
539 | return None | |||
|
540 | return getattr(exception_value, '__context__', None) | |||
|
541 | ||||
|
542 | chained_evalue = get_chained_exception(evalue) | |||
|
543 | ||||
|
544 | if chained_evalue: | |||
|
545 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | |||
|
546 | ||||
|
547 | def prepare_chained_exception_message(self, cause): | |||
|
548 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | |||
|
549 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | |||
|
550 | ||||
|
551 | if cause: | |||
|
552 | message = [[direct_cause]] | |||
|
553 | else: | |||
|
554 | message = [[exception_during_handling]] | |||
|
555 | return message | |||
|
556 | ||||
533 | def set_colors(self, *args, **kw): |
|
557 | def set_colors(self, *args, **kw): | |
534 | """Shorthand access to the color table scheme selector method.""" |
|
558 | """Shorthand access to the color table scheme selector method.""" | |
535 |
|
559 | |||
@@ -603,7 +627,7 b' class ListTB(TBTools):' | |||||
603 | self.ostream.write(self.text(etype, value, elist)) |
|
627 | self.ostream.write(self.text(etype, value, elist)) | |
604 | self.ostream.write('\n') |
|
628 | self.ostream.write('\n') | |
605 |
|
629 | |||
606 |
def structured_traceback(self, etype, value, e |
|
630 | def structured_traceback(self, etype, evalue, etb=None, tb_offset=None, | |
607 | context=5): |
|
631 | context=5): | |
608 | """Return a color formatted string with the traceback info. |
|
632 | """Return a color formatted string with the traceback info. | |
609 |
|
633 | |||
@@ -612,15 +636,16 b' class ListTB(TBTools):' | |||||
612 | etype : exception type |
|
636 | etype : exception type | |
613 | Type of the exception raised. |
|
637 | Type of the exception raised. | |
614 |
|
638 | |||
615 | value : object |
|
639 | evalue : object | |
616 | Data stored in the exception |
|
640 | Data stored in the exception | |
617 |
|
641 | |||
618 |
e |
|
642 | etb : object | |
619 | List of frames, see class docstring for details. |
|
643 | If list: List of frames, see class docstring for details. | |
|
644 | If Traceback: Traceback of the exception. | |||
620 |
|
645 | |||
621 | tb_offset : int, optional |
|
646 | tb_offset : int, optional | |
622 | Number of frames in the traceback to skip. If not given, the |
|
647 | Number of frames in the traceback to skip. If not given, the | |
623 | instance value is used (set in constructor). |
|
648 | instance evalue is used (set in constructor). | |
624 |
|
649 | |||
625 | context : int, optional |
|
650 | context : int, optional | |
626 | Number of lines of context information to print. |
|
651 | Number of lines of context information to print. | |
@@ -629,6 +654,19 b' class ListTB(TBTools):' | |||||
629 | ------- |
|
654 | ------- | |
630 | String with formatted exception. |
|
655 | String with formatted exception. | |
631 | """ |
|
656 | """ | |
|
657 | # This is a workaround to get chained_exc_ids in recursive calls | |||
|
658 | # etb should not be a tuple if structured_traceback is not recursive | |||
|
659 | if isinstance(etb, tuple): | |||
|
660 | etb, chained_exc_ids = etb | |||
|
661 | else: | |||
|
662 | chained_exc_ids = set() | |||
|
663 | ||||
|
664 | if isinstance(etb, list): | |||
|
665 | elist = etb | |||
|
666 | elif etb is not None: | |||
|
667 | elist = self._extract_tb(etb) | |||
|
668 | else: | |||
|
669 | elist = [] | |||
632 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
670 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
633 | Colors = self.Colors |
|
671 | Colors = self.Colors | |
634 | out_list = [] |
|
672 | out_list = [] | |
@@ -641,9 +679,25 b' class ListTB(TBTools):' | |||||
641 | (Colors.normalEm, Colors.Normal) + '\n') |
|
679 | (Colors.normalEm, Colors.Normal) + '\n') | |
642 | out_list.extend(self._format_list(elist)) |
|
680 | out_list.extend(self._format_list(elist)) | |
643 | # The exception info should be a single entry in the list. |
|
681 | # The exception info should be a single entry in the list. | |
644 | lines = ''.join(self._format_exception_only(etype, value)) |
|
682 | lines = ''.join(self._format_exception_only(etype, evalue)) | |
645 | out_list.append(lines) |
|
683 | out_list.append(lines) | |
646 |
|
684 | |||
|
685 | exception = self.get_parts_of_chained_exception(evalue) | |||
|
686 | ||||
|
687 | if exception and not id(exception[1]) in chained_exc_ids: | |||
|
688 | chained_exception_message = self.prepare_chained_exception_message( | |||
|
689 | evalue.__cause__)[0] | |||
|
690 | etype, evalue, etb = exception | |||
|
691 | # Trace exception to avoid infinite 'cause' loop | |||
|
692 | chained_exc_ids.add(id(exception[1])) | |||
|
693 | chained_exceptions_tb_offset = 0 | |||
|
694 | out_list = ( | |||
|
695 | self.structured_traceback( | |||
|
696 | etype, evalue, (etb, chained_exc_ids), | |||
|
697 | chained_exceptions_tb_offset, context) | |||
|
698 | + chained_exception_message | |||
|
699 | + out_list) | |||
|
700 | ||||
647 | return out_list |
|
701 | return out_list | |
648 |
|
702 | |||
649 | def _format_list(self, extracted_list): |
|
703 | def _format_list(self, extracted_list): | |
@@ -763,7 +817,7 b' class ListTB(TBTools):' | |||||
763 | etype : exception type |
|
817 | etype : exception type | |
764 | value : exception value |
|
818 | value : exception value | |
765 | """ |
|
819 | """ | |
766 |
return ListTB.structured_traceback(self, etype, value |
|
820 | return ListTB.structured_traceback(self, etype, value) | |
767 |
|
821 | |||
768 | def show_exception_only(self, etype, evalue): |
|
822 | def show_exception_only(self, etype, evalue): | |
769 | """Only print the exception type and message, without a traceback. |
|
823 | """Only print the exception type and message, without a traceback. | |
@@ -1013,16 +1067,6 b' class VerboseTB(TBTools):' | |||||
1013 | _format_traceback_lines(lnum, index, lines, Colors, lvals, |
|
1067 | _format_traceback_lines(lnum, index, lines, Colors, lvals, | |
1014 | _line_format))) |
|
1068 | _line_format))) | |
1015 |
|
1069 | |||
1016 | def prepare_chained_exception_message(self, cause): |
|
|||
1017 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" |
|
|||
1018 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" |
|
|||
1019 |
|
||||
1020 | if cause: |
|
|||
1021 | message = [[direct_cause]] |
|
|||
1022 | else: |
|
|||
1023 | message = [[exception_during_handling]] |
|
|||
1024 | return message |
|
|||
1025 |
|
||||
1026 | def prepare_header(self, etype, long_version=False): |
|
1070 | def prepare_header(self, etype, long_version=False): | |
1027 | colors = self.Colors # just a shorthand + quicker name lookup |
|
1071 | colors = self.Colors # just a shorthand + quicker name lookup | |
1028 | colorsnormal = colors.Normal # used a lot |
|
1072 | colorsnormal = colors.Normal # used a lot | |
@@ -1117,20 +1161,6 b' class VerboseTB(TBTools):' | |||||
1117 | info('\nUnfortunately, your original traceback can not be constructed.\n') |
|
1161 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |
1118 | return None |
|
1162 | return None | |
1119 |
|
1163 | |||
1120 | def get_parts_of_chained_exception(self, evalue): |
|
|||
1121 | def get_chained_exception(exception_value): |
|
|||
1122 | cause = getattr(exception_value, '__cause__', None) |
|
|||
1123 | if cause: |
|
|||
1124 | return cause |
|
|||
1125 | if getattr(exception_value, '__suppress_context__', False): |
|
|||
1126 | return None |
|
|||
1127 | return getattr(exception_value, '__context__', None) |
|
|||
1128 |
|
||||
1129 | chained_evalue = get_chained_exception(evalue) |
|
|||
1130 |
|
||||
1131 | if chained_evalue: |
|
|||
1132 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ |
|
|||
1133 |
|
||||
1134 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, |
|
1164 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, | |
1135 | number_of_lines_of_context=5): |
|
1165 | number_of_lines_of_context=5): | |
1136 | """Return a nice text document describing the traceback.""" |
|
1166 | """Return a nice text document describing the traceback.""" | |
@@ -1294,9 +1324,8 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1294 | # out-of-date source code. |
|
1324 | # out-of-date source code. | |
1295 | self.check_cache() |
|
1325 | self.check_cache() | |
1296 | # Now we can extract and format the exception |
|
1326 | # Now we can extract and format the exception | |
1297 | elist = self._extract_tb(tb) |
|
|||
1298 | return ListTB.structured_traceback( |
|
1327 | return ListTB.structured_traceback( | |
1299 |
self, etype, value, |
|
1328 | self, etype, value, tb, tb_offset, number_of_lines_of_context | |
1300 | ) |
|
1329 | ) | |
1301 |
|
1330 | |||
1302 | def stb2text(self, stb): |
|
1331 | def stb2text(self, stb): |
@@ -32,15 +32,15 b' def win32_clipboard_get():' | |||||
32 | win32clipboard.CloseClipboard() |
|
32 | win32clipboard.CloseClipboard() | |
33 | return text |
|
33 | return text | |
34 |
|
34 | |||
35 | def osx_clipboard_get(): |
|
35 | def osx_clipboard_get() -> str: | |
36 | """ Get the clipboard's text on OS X. |
|
36 | """ Get the clipboard's text on OS X. | |
37 | """ |
|
37 | """ | |
38 | p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], |
|
38 | p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], | |
39 | stdout=subprocess.PIPE) |
|
39 | stdout=subprocess.PIPE) | |
40 |
|
|
40 | bytes_, stderr = p.communicate() | |
41 | # Text comes in with old Mac \r line endings. Change them to \n. |
|
41 | # Text comes in with old Mac \r line endings. Change them to \n. | |
42 |
|
|
42 | bytes_ = bytes_.replace(b'\r', b'\n') | |
43 | text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) |
|
43 | text = py3compat.decode(bytes_) | |
44 | return text |
|
44 | return text | |
45 |
|
45 | |||
46 | def tkinter_clipboard_get(): |
|
46 | def tkinter_clipboard_get(): |
@@ -97,9 +97,6 b" __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter'," | |||||
97 |
|
97 | |||
98 |
|
98 | |||
99 | MAX_SEQ_LENGTH = 1000 |
|
99 | MAX_SEQ_LENGTH = 1000 | |
100 | # The language spec says that dicts preserve order from 3.7, but CPython |
|
|||
101 | # does so from 3.6, so it seems likely that people will expect that. |
|
|||
102 | DICT_IS_ORDERED = True |
|
|||
103 | _re_pattern_type = type(re.compile('')) |
|
100 | _re_pattern_type = type(re.compile('')) | |
104 |
|
101 | |||
105 | def _safe_getattr(obj, attr, default=None): |
|
102 | def _safe_getattr(obj, attr, default=None): | |
@@ -606,11 +603,6 b' def _dict_pprinter_factory(start, end):' | |||||
606 | step = len(start) |
|
603 | step = len(start) | |
607 | p.begin_group(step, start) |
|
604 | p.begin_group(step, start) | |
608 | keys = obj.keys() |
|
605 | keys = obj.keys() | |
609 | # if dict isn't large enough to be truncated, sort keys before displaying |
|
|||
610 | # From Python 3.7, dicts preserve order by definition, so we don't sort. |
|
|||
611 | if not DICT_IS_ORDERED \ |
|
|||
612 | and not (p.max_seq_length and len(obj) >= p.max_seq_length): |
|
|||
613 | keys = _sorted_for_pprint(keys) |
|
|||
614 | for idx, key in p._enumerate(keys): |
|
606 | for idx, key in p._enumerate(keys): | |
615 | if idx: |
|
607 | if idx: | |
616 | p.text(',') |
|
608 | p.text(',') |
@@ -12,7 +12,7 b' from IPython.utils.path import (' | |||||
12 | ensure_dir_exists, fs_encoding) |
|
12 | ensure_dir_exists, fs_encoding) | |
13 | from IPython.utils import py3compat |
|
13 | from IPython.utils import py3compat | |
14 |
|
14 | |||
15 | def get_ipython_dir(): |
|
15 | def get_ipython_dir() -> str: | |
16 | """Get the IPython directory for this platform and user. |
|
16 | """Get the IPython directory for this platform and user. | |
17 |
|
17 | |||
18 | This uses the logic in `get_home_dir` to find the home directory |
|
18 | This uses the logic in `get_home_dir` to find the home directory | |
@@ -28,10 +28,9 b' def get_ipython_dir():' | |||||
28 | home_dir = get_home_dir() |
|
28 | home_dir = get_home_dir() | |
29 | xdg_dir = get_xdg_dir() |
|
29 | xdg_dir = get_xdg_dir() | |
30 |
|
30 | |||
31 | # import pdb; pdb.set_trace() # dbg |
|
|||
32 | if 'IPYTHON_DIR' in env: |
|
31 | if 'IPYTHON_DIR' in env: | |
33 | warn('The environment variable IPYTHON_DIR is deprecated. ' |
|
32 | warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. ' | |
34 | 'Please use IPYTHONDIR instead.') |
|
33 | 'Please use IPYTHONDIR instead.', DeprecationWarning) | |
35 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) |
|
34 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) | |
36 | if ipdir is None: |
|
35 | if ipdir is None: | |
37 | # not set explicitly, use ~/.ipython |
|
36 | # not set explicitly, use ~/.ipython | |
@@ -67,11 +66,11 b' def get_ipython_dir():' | |||||
67 | warn("IPython parent '{0}' is not a writable location," |
|
66 | warn("IPython parent '{0}' is not a writable location," | |
68 | " using a temp directory.".format(parent)) |
|
67 | " using a temp directory.".format(parent)) | |
69 | ipdir = tempfile.mkdtemp() |
|
68 | ipdir = tempfile.mkdtemp() | |
|
69 | assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not." | |||
|
70 | return ipdir | |||
70 |
|
71 | |||
71 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
|||
72 |
|
72 | |||
73 |
|
73 | def get_ipython_cache_dir() -> str: | ||
74 | def get_ipython_cache_dir(): |
|
|||
75 | """Get the cache directory it is created if it does not exist.""" |
|
74 | """Get the cache directory it is created if it does not exist.""" | |
76 | xdgdir = get_xdg_cache_dir() |
|
75 | xdgdir = get_xdg_cache_dir() | |
77 | if xdgdir is None: |
|
76 | if xdgdir is None: | |
@@ -82,13 +81,14 b' def get_ipython_cache_dir():' | |||||
82 | elif not _writable_dir(xdgdir): |
|
81 | elif not _writable_dir(xdgdir): | |
83 | return get_ipython_dir() |
|
82 | return get_ipython_dir() | |
84 |
|
83 | |||
85 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
84 | return ipdir | |
86 |
|
85 | |||
87 |
|
86 | |||
88 | def get_ipython_package_dir(): |
|
87 | def get_ipython_package_dir() -> str: | |
89 | """Get the base directory where IPython itself is installed.""" |
|
88 | """Get the base directory where IPython itself is installed.""" | |
90 | ipdir = os.path.dirname(IPython.__file__) |
|
89 | ipdir = os.path.dirname(IPython.__file__) | |
91 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
90 | assert isinstance(ipdir, str) | |
|
91 | return ipdir | |||
92 |
|
92 | |||
93 |
|
93 | |||
94 | def get_ipython_module_path(module_str): |
|
94 | def get_ipython_module_path(module_str): |
@@ -17,6 +17,9 b' from prompt_toolkit.shortcuts.prompt import PromptSession' | |||||
17 | from prompt_toolkit.enums import EditingMode |
|
17 | from prompt_toolkit.enums import EditingMode | |
18 | from prompt_toolkit.formatted_text import PygmentsTokens |
|
18 | from prompt_toolkit.formatted_text import PygmentsTokens | |
19 |
|
19 | |||
|
20 | from prompt_toolkit import __version__ as ptk_version | |||
|
21 | PTK3 = ptk_version.startswith('3.') | |||
|
22 | ||||
20 |
|
23 | |||
21 | class TerminalPdb(Pdb): |
|
24 | class TerminalPdb(Pdb): | |
22 | """Standalone IPython debugger.""" |
|
25 | """Standalone IPython debugger.""" | |
@@ -49,20 +52,23 b' class TerminalPdb(Pdb):' | |||||
49 | & ~cursor_in_leading_ws |
|
52 | & ~cursor_in_leading_ws | |
50 | ))(display_completions_like_readline) |
|
53 | ))(display_completions_like_readline) | |
51 |
|
54 | |||
52 | self.pt_app = PromptSession( |
|
55 | options = dict( | |
53 |
|
|
56 | message=(lambda: PygmentsTokens(get_prompt_tokens())), | |
54 |
|
|
57 | editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), | |
55 |
|
|
58 | key_bindings=kb, | |
56 |
|
|
59 | history=self.shell.debugger_history, | |
57 |
|
|
60 | completer=self._ptcomp, | |
58 |
|
|
61 | enable_history_search=True, | |
59 |
|
|
62 | mouse_support=self.shell.mouse_support, | |
60 |
|
|
63 | complete_style=self.shell.pt_complete_style, | |
61 |
|
|
64 | style=self.shell.style, | |
62 | inputhook=self.shell.inputhook, |
|
65 | color_depth=self.shell.color_depth, | |
63 | color_depth=self.shell.color_depth, |
|
|||
64 | ) |
|
66 | ) | |
65 |
|
67 | |||
|
68 | if not PTK3: | |||
|
69 | options['inputhook'] = self.shell.inputhook | |||
|
70 | self.pt_app = PromptSession(**options) | |||
|
71 | ||||
66 | def cmdloop(self, intro=None): |
|
72 | def cmdloop(self, intro=None): | |
67 | """Repeatedly issue a prompt, accept input, parse an initial prefix |
|
73 | """Repeatedly issue a prompt, accept input, parse an initial prefix | |
68 | off the received input, and dispatch to action methods, passing them |
|
74 | off the received input, and dispatch to action methods, passing them |
@@ -1,5 +1,6 b'' | |||||
1 | """IPython terminal interface using prompt_toolkit""" |
|
1 | """IPython terminal interface using prompt_toolkit""" | |
2 |
|
2 | |||
|
3 | import asyncio | |||
3 | import os |
|
4 | import os | |
4 | import sys |
|
5 | import sys | |
5 | import warnings |
|
6 | import warnings | |
@@ -25,6 +26,7 b' from prompt_toolkit.patch_stdout import patch_stdout' | |||||
25 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text |
|
26 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text | |
26 | from prompt_toolkit.styles import DynamicStyle, merge_styles |
|
27 | from prompt_toolkit.styles import DynamicStyle, merge_styles | |
27 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict |
|
28 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict | |
|
29 | from prompt_toolkit import __version__ as ptk_version | |||
28 |
|
30 | |||
29 | from pygments.styles import get_style_by_name |
|
31 | from pygments.styles import get_style_by_name | |
30 | from pygments.style import Style |
|
32 | from pygments.style import Style | |
@@ -38,6 +40,7 b' from .ptutils import IPythonPTCompleter, IPythonPTLexer' | |||||
38 | from .shortcuts import create_ipython_shortcuts |
|
40 | from .shortcuts import create_ipython_shortcuts | |
39 |
|
41 | |||
40 | DISPLAY_BANNER_DEPRECATED = object() |
|
42 | DISPLAY_BANNER_DEPRECATED = object() | |
|
43 | PTK3 = ptk_version.startswith('3.') | |||
41 |
|
44 | |||
42 |
|
45 | |||
43 | class _NoStyle(Style): pass |
|
46 | class _NoStyle(Style): pass | |
@@ -87,7 +90,17 b' else:' | |||||
87 |
|
90 | |||
88 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) |
|
91 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) | |
89 |
|
92 | |||
|
93 | def black_reformat_handler(text_before_cursor): | |||
|
94 | import black | |||
|
95 | formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) | |||
|
96 | if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'): | |||
|
97 | formatted_text = formatted_text[:-1] | |||
|
98 | return formatted_text | |||
|
99 | ||||
|
100 | ||||
90 | class TerminalInteractiveShell(InteractiveShell): |
|
101 | class TerminalInteractiveShell(InteractiveShell): | |
|
102 | mime_renderers = Dict().tag(config=True) | |||
|
103 | ||||
91 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' |
|
104 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' | |
92 | 'to reserve for the completion menu' |
|
105 | 'to reserve for the completion menu' | |
93 | ).tag(config=True) |
|
106 | ).tag(config=True) | |
@@ -120,6 +133,11 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
120 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", |
|
133 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", | |
121 | ).tag(config=True) |
|
134 | ).tag(config=True) | |
122 |
|
135 | |||
|
136 | autoformatter = Unicode(None, | |||
|
137 | help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`", | |||
|
138 | allow_none=True | |||
|
139 | ).tag(config=True) | |||
|
140 | ||||
123 | mouse_support = Bool(False, |
|
141 | mouse_support = Bool(False, | |
124 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" |
|
142 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" | |
125 | ).tag(config=True) |
|
143 | ).tag(config=True) | |
@@ -150,6 +168,16 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
150 | if self.pt_app: |
|
168 | if self.pt_app: | |
151 | self.pt_app.editing_mode = u_mode |
|
169 | self.pt_app.editing_mode = u_mode | |
152 |
|
170 | |||
|
171 | @observe('autoformatter') | |||
|
172 | def _autoformatter_changed(self, change): | |||
|
173 | formatter = change.new | |||
|
174 | if formatter is None: | |||
|
175 | self.reformat_handler = lambda x:x | |||
|
176 | elif formatter == 'black': | |||
|
177 | self.reformat_handler = black_reformat_handler | |||
|
178 | else: | |||
|
179 | raise ValueError | |||
|
180 | ||||
153 | @observe('highlighting_style') |
|
181 | @observe('highlighting_style') | |
154 | @observe('colors') |
|
182 | @observe('colors') | |
155 | def _highlighting_style_changed(self, change): |
|
183 | def _highlighting_style_changed(self, change): | |
@@ -282,6 +310,7 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
282 |
|
310 | |||
283 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) |
|
311 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) | |
284 |
|
312 | |||
|
313 | self.pt_loop = asyncio.new_event_loop() | |||
285 | self.pt_app = PromptSession( |
|
314 | self.pt_app = PromptSession( | |
286 | editing_mode=editing_mode, |
|
315 | editing_mode=editing_mode, | |
287 | key_bindings=key_bindings, |
|
316 | key_bindings=key_bindings, | |
@@ -390,7 +419,7 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
390 | # work around this. |
|
419 | # work around this. | |
391 | get_message = get_message() |
|
420 | get_message = get_message() | |
392 |
|
421 | |||
393 |
|
|
422 | options = { | |
394 | 'complete_in_thread': False, |
|
423 | 'complete_in_thread': False, | |
395 | 'lexer':IPythonPTLexer(), |
|
424 | 'lexer':IPythonPTLexer(), | |
396 | 'reserve_space_for_menu':self.space_for_menu, |
|
425 | 'reserve_space_for_menu':self.space_for_menu, | |
@@ -407,8 +436,11 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
407 | processor=HighlightMatchingBracketProcessor(chars='[](){}'), |
|
436 | processor=HighlightMatchingBracketProcessor(chars='[](){}'), | |
408 | filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & |
|
437 | filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & | |
409 | Condition(lambda: self.highlight_matching_brackets))], |
|
438 | Condition(lambda: self.highlight_matching_brackets))], | |
410 | 'inputhook': self.inputhook, |
|
|||
411 | } |
|
439 | } | |
|
440 | if not PTK3: | |||
|
441 | options['inputhook'] = self.inputhook | |||
|
442 | ||||
|
443 | return options | |||
412 |
|
444 | |||
413 | def prompt_for_code(self): |
|
445 | def prompt_for_code(self): | |
414 | if self.rl_next_input: |
|
446 | if self.rl_next_input: | |
@@ -417,11 +449,28 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
417 | else: |
|
449 | else: | |
418 | default = '' |
|
450 | default = '' | |
419 |
|
451 | |||
420 | with patch_stdout(raw=True): |
|
452 | # In order to make sure that asyncio code written in the | |
421 | text = self.pt_app.prompt( |
|
453 | # interactive shell doesn't interfere with the prompt, we run the | |
422 | default=default, |
|
454 | # prompt in a different event loop. | |
423 | # pre_run=self.pre_prompt,# reset_current_buffer=True, |
|
455 | # If we don't do this, people could spawn coroutine with a | |
424 | **self._extra_prompt_options()) |
|
456 | # while/true inside which will freeze the prompt. | |
|
457 | ||||
|
458 | try: | |||
|
459 | old_loop = asyncio.get_event_loop() | |||
|
460 | except RuntimeError: | |||
|
461 | # This happens when the user used `asyncio.run()`. | |||
|
462 | old_loop = None | |||
|
463 | ||||
|
464 | asyncio.set_event_loop(self.pt_loop) | |||
|
465 | try: | |||
|
466 | with patch_stdout(raw=True): | |||
|
467 | text = self.pt_app.prompt( | |||
|
468 | default=default, | |||
|
469 | **self._extra_prompt_options()) | |||
|
470 | finally: | |||
|
471 | # Restore the original event loop. | |||
|
472 | asyncio.set_event_loop(old_loop) | |||
|
473 | ||||
425 | return text |
|
474 | return text | |
426 |
|
475 | |||
427 | def enable_win_unicode_console(self): |
|
476 | def enable_win_unicode_console(self): | |
@@ -528,12 +577,33 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
528 |
|
577 | |||
529 | active_eventloop = None |
|
578 | active_eventloop = None | |
530 | def enable_gui(self, gui=None): |
|
579 | def enable_gui(self, gui=None): | |
531 | if gui: |
|
580 | if gui and (gui != 'inline') : | |
532 | self.active_eventloop, self._inputhook =\ |
|
581 | self.active_eventloop, self._inputhook =\ | |
533 | get_inputhook_name_and_func(gui) |
|
582 | get_inputhook_name_and_func(gui) | |
534 | else: |
|
583 | else: | |
535 | self.active_eventloop = self._inputhook = None |
|
584 | self.active_eventloop = self._inputhook = None | |
536 |
|
585 | |||
|
586 | # For prompt_toolkit 3.0. We have to create an asyncio event loop with | |||
|
587 | # this inputhook. | |||
|
588 | if PTK3: | |||
|
589 | import asyncio | |||
|
590 | from prompt_toolkit.eventloop import new_eventloop_with_inputhook | |||
|
591 | ||||
|
592 | if gui == 'asyncio': | |||
|
593 | # When we integrate the asyncio event loop, run the UI in the | |||
|
594 | # same event loop as the rest of the code. don't use an actual | |||
|
595 | # input hook. (Asyncio is not made for nesting event loops.) | |||
|
596 | self.pt_loop = asyncio.get_event_loop() | |||
|
597 | ||||
|
598 | elif self._inputhook: | |||
|
599 | # If an inputhook was set, create a new asyncio event loop with | |||
|
600 | # this inputhook for the prompt. | |||
|
601 | self.pt_loop = new_eventloop_with_inputhook(self._inputhook) | |||
|
602 | else: | |||
|
603 | # When there's no inputhook, run the prompt in a separate | |||
|
604 | # asyncio event loop. | |||
|
605 | self.pt_loop = asyncio.new_event_loop() | |||
|
606 | ||||
537 | # Run !system commands directly, not through pipes, so terminal programs |
|
607 | # Run !system commands directly, not through pipes, so terminal programs | |
538 | # work correctly. |
|
608 | # work correctly. | |
539 | system = InteractiveShell.system_raw |
|
609 | system = InteractiveShell.system_raw |
@@ -89,3 +89,14 b' class RichPromptDisplayHook(DisplayHook):' | |||||
89 | ) |
|
89 | ) | |
90 | else: |
|
90 | else: | |
91 | sys.stdout.write(prompt_txt) |
|
91 | sys.stdout.write(prompt_txt) | |
|
92 | ||||
|
93 | def write_format_data(self, format_dict, md_dict=None) -> None: | |||
|
94 | if self.shell.mime_renderers: | |||
|
95 | ||||
|
96 | for mime, handler in self.shell.mime_renderers.items(): | |||
|
97 | if mime in format_dict: | |||
|
98 | handler(format_dict[mime], None) | |||
|
99 | return | |||
|
100 | ||||
|
101 | super().write_format_data(format_dict, md_dict) | |||
|
102 |
@@ -13,6 +13,7 b' backends = [' | |||||
13 | 'wx', |
|
13 | 'wx', | |
14 | 'pyglet', 'glut', |
|
14 | 'pyglet', 'glut', | |
15 | 'osx', |
|
15 | 'osx', | |
|
16 | 'asyncio' | |||
16 | ] |
|
17 | ] | |
17 |
|
18 | |||
18 | registered = {} |
|
19 | registered = {} |
@@ -44,6 +44,15 b' def create_ipython_shortcuts(shell):' | |||||
44 | & insert_mode |
|
44 | & insert_mode | |
45 | ))(return_handler) |
|
45 | ))(return_handler) | |
46 |
|
46 | |||
|
47 | def reformat_and_execute(event): | |||
|
48 | reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell) | |||
|
49 | event.current_buffer.validate_and_handle() | |||
|
50 | ||||
|
51 | kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER) | |||
|
52 | & ~has_selection | |||
|
53 | & insert_mode | |||
|
54 | ))(reformat_and_execute) | |||
|
55 | ||||
47 | kb.add('c-\\')(force_exit) |
|
56 | kb.add('c-\\')(force_exit) | |
48 |
|
57 | |||
49 | kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) |
|
58 | kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) | |
@@ -86,7 +95,17 b' def create_ipython_shortcuts(shell):' | |||||
86 | return kb |
|
95 | return kb | |
87 |
|
96 | |||
88 |
|
97 | |||
|
98 | def reformat_text_before_cursor(buffer, document, shell): | |||
|
99 | text = buffer.delete_before_cursor(len(document.text[:document.cursor_position])) | |||
|
100 | try: | |||
|
101 | formatted_text = shell.reformat_handler(text) | |||
|
102 | buffer.insert_text(formatted_text) | |||
|
103 | except Exception as e: | |||
|
104 | buffer.insert_text(text) | |||
|
105 | ||||
|
106 | ||||
89 | def newline_or_execute_outer(shell): |
|
107 | def newline_or_execute_outer(shell): | |
|
108 | ||||
90 | def newline_or_execute(event): |
|
109 | def newline_or_execute(event): | |
91 | """When the user presses return, insert a newline or execute the code.""" |
|
110 | """When the user presses return, insert a newline or execute the code.""" | |
92 | b = event.current_buffer |
|
111 | b = event.current_buffer | |
@@ -107,7 +126,12 b' def newline_or_execute_outer(shell):' | |||||
107 | else: |
|
126 | else: | |
108 | check_text = d.text[:d.cursor_position] |
|
127 | check_text = d.text[:d.cursor_position] | |
109 | status, indent = shell.check_complete(check_text) |
|
128 | status, indent = shell.check_complete(check_text) | |
110 |
|
129 | |||
|
130 | # if all we have after the cursor is whitespace: reformat current text | |||
|
131 | # before cursor | |||
|
132 | after_cursor = d.text[d.cursor_position:] | |||
|
133 | if not after_cursor.strip(): | |||
|
134 | reformat_text_before_cursor(b, d, shell) | |||
111 | if not (d.on_last_line or |
|
135 | if not (d.on_last_line or | |
112 | d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() |
|
136 | d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() | |
113 | ): |
|
137 | ): | |
@@ -118,6 +142,7 b' def newline_or_execute_outer(shell):' | |||||
118 | return |
|
142 | return | |
119 |
|
143 | |||
120 | if (status != 'incomplete') and b.accept_handler: |
|
144 | if (status != 'incomplete') and b.accept_handler: | |
|
145 | reformat_text_before_cursor(b, d, shell) | |||
121 | b.validate_and_handle() |
|
146 | b.validate_and_handle() | |
122 | else: |
|
147 | else: | |
123 | if shell.autoindent: |
|
148 | if shell.autoindent: |
@@ -169,7 +169,7 b' class HomeDirError(Exception):' | |||||
169 | pass |
|
169 | pass | |
170 |
|
170 | |||
171 |
|
171 | |||
172 | def get_home_dir(require_writable=False): |
|
172 | def get_home_dir(require_writable=False) -> str: | |
173 | """Return the 'home' directory, as a unicode string. |
|
173 | """Return the 'home' directory, as a unicode string. | |
174 |
|
174 | |||
175 | Uses os.path.expanduser('~'), and checks for writability. |
|
175 | Uses os.path.expanduser('~'), and checks for writability. | |
@@ -197,21 +197,18 b' def get_home_dir(require_writable=False):' | |||||
197 | if not _writable_dir(homedir) and os.name == 'nt': |
|
197 | if not _writable_dir(homedir) and os.name == 'nt': | |
198 | # expanduser failed, use the registry to get the 'My Documents' folder. |
|
198 | # expanduser failed, use the registry to get the 'My Documents' folder. | |
199 | try: |
|
199 | try: | |
200 | try: |
|
200 | import winreg as wreg | |
201 | import winreg as wreg # Py 3 |
|
201 | with wreg.OpenKey( | |
202 | except ImportError: |
|
|||
203 | import _winreg as wreg # Py 2 |
|
|||
204 | key = wreg.OpenKey( |
|
|||
205 | wreg.HKEY_CURRENT_USER, |
|
202 | wreg.HKEY_CURRENT_USER, | |
206 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" |
|
203 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" | |
207 | ) |
|
204 | ) as key: | |
208 | homedir = wreg.QueryValueEx(key,'Personal')[0] |
|
205 | homedir = wreg.QueryValueEx(key,'Personal')[0] | |
209 | key.Close() |
|
|||
210 | except: |
|
206 | except: | |
211 | pass |
|
207 | pass | |
212 |
|
208 | |||
213 | if (not require_writable) or _writable_dir(homedir): |
|
209 | if (not require_writable) or _writable_dir(homedir): | |
214 | return py3compat.cast_unicode(homedir, fs_encoding) |
|
210 | assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes" | |
|
211 | return homedir | |||
215 | else: |
|
212 | else: | |
216 | raise HomeDirError('%s is not a writable dir, ' |
|
213 | raise HomeDirError('%s is not a writable dir, ' | |
217 | 'set $HOME environment variable to override' % homedir) |
|
214 | 'set $HOME environment variable to override' % homedir) | |
@@ -254,31 +251,31 b' def get_xdg_cache_dir():' | |||||
254 |
|
251 | |||
255 | @undoc |
|
252 | @undoc | |
256 | def get_ipython_dir(): |
|
253 | def get_ipython_dir(): | |
257 | warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) |
|
254 | warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
258 | from IPython.paths import get_ipython_dir |
|
255 | from IPython.paths import get_ipython_dir | |
259 | return get_ipython_dir() |
|
256 | return get_ipython_dir() | |
260 |
|
257 | |||
261 | @undoc |
|
258 | @undoc | |
262 | def get_ipython_cache_dir(): |
|
259 | def get_ipython_cache_dir(): | |
263 | warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) |
|
260 | warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
264 | from IPython.paths import get_ipython_cache_dir |
|
261 | from IPython.paths import get_ipython_cache_dir | |
265 | return get_ipython_cache_dir() |
|
262 | return get_ipython_cache_dir() | |
266 |
|
263 | |||
267 | @undoc |
|
264 | @undoc | |
268 | def get_ipython_package_dir(): |
|
265 | def get_ipython_package_dir(): | |
269 | warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) |
|
266 | warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
270 | from IPython.paths import get_ipython_package_dir |
|
267 | from IPython.paths import get_ipython_package_dir | |
271 | return get_ipython_package_dir() |
|
268 | return get_ipython_package_dir() | |
272 |
|
269 | |||
273 | @undoc |
|
270 | @undoc | |
274 | def get_ipython_module_path(module_str): |
|
271 | def get_ipython_module_path(module_str): | |
275 | warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) |
|
272 | warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
276 | from IPython.paths import get_ipython_module_path |
|
273 | from IPython.paths import get_ipython_module_path | |
277 | return get_ipython_module_path(module_str) |
|
274 | return get_ipython_module_path(module_str) | |
278 |
|
275 | |||
279 | @undoc |
|
276 | @undoc | |
280 | def locate_profile(profile='default'): |
|
277 | def locate_profile(profile='default'): | |
281 | warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) |
|
278 | warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
282 | from IPython.paths import locate_profile |
|
279 | from IPython.paths import locate_profile | |
283 | return locate_profile(profile=profile) |
|
280 | return locate_profile(profile=profile) | |
284 |
|
281 |
@@ -171,8 +171,12 b' def test_get_home_dir_8():' | |||||
171 | env.pop(key, None) |
|
171 | env.pop(key, None) | |
172 |
|
172 | |||
173 | class key: |
|
173 | class key: | |
|
174 | def __enter__(self): | |||
|
175 | pass | |||
174 | def Close(self): |
|
176 | def Close(self): | |
175 | pass |
|
177 | pass | |
|
178 | def __exit__(*args, **kwargs): | |||
|
179 | pass | |||
176 |
|
180 | |||
177 | with patch.object(wreg, 'OpenKey', return_value=key()), \ |
|
181 | with patch.object(wreg, 'OpenKey', return_value=key()), \ | |
178 | patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): |
|
182 | patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): |
@@ -117,3 +117,15 b' version::' | |||||
117 | ... |
|
117 | ... | |
118 | install_requires=install_req |
|
118 | install_requires=install_req | |
119 | ) |
|
119 | ) | |
|
120 | ||||
|
121 | Alternatives to IPython | |||
|
122 | ======================= | |||
|
123 | ||||
|
124 | IPython may not be to your taste; if that's the case there might be similar | |||
|
125 | project that you might want to use: | |||
|
126 | ||||
|
127 | - the classic Python REPL. | |||
|
128 | - `bpython <https://bpython-interpreter.org/>`_ | |||
|
129 | - `mypython <https://www.asmeurer.com/mypython/>`_ | |||
|
130 | - `ptpython and ptipython <https://pypi.org/project/ptpython/>` | |||
|
131 | - `xonsh <https://xon.sh/>` |
@@ -29,6 +29,7 b' Extending and integrating with IPython' | |||||
29 | extensions/index |
|
29 | extensions/index | |
30 | integrating |
|
30 | integrating | |
31 | custommagics |
|
31 | custommagics | |
|
32 | shell_mimerenderer | |||
32 | inputtransforms |
|
33 | inputtransforms | |
33 | callbacks |
|
34 | callbacks | |
34 | eventloops |
|
35 | eventloops |
@@ -1,13 +1,14 b'' | |||||
1 | .. _execution_semantics: |
|
1 | .. _execution_semantics: | |
2 |
|
2 | |||
3 |
Execution |
|
3 | Execution of cells in the IPython kernel | |
4 |
======================================== |
|
4 | ======================================== | |
5 |
|
5 | |||
6 | The execution of user code consists of the following phases: |
|
6 | When IPython kernel receives `execute_request <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute>`_ | |
|
7 | with user code, it processes the message in the following phases: | |||
7 |
|
8 | |||
8 | 1. Fire the ``pre_execute`` event. |
|
9 | 1. Fire the ``pre_execute`` event. | |
9 | 2. Fire the ``pre_run_cell`` event unless silent is ``True``. |
|
10 | 2. Fire the ``pre_run_cell`` event unless silent is ``True``. | |
10 |
3. Execute the ``code`` |
|
11 | 3. Execute ``run_cell`` method to preprocess ``code``, compile and run it, see below for details. | |
11 | 4. If execution succeeds, expressions in ``user_expressions`` are computed. |
|
12 | 4. If execution succeeds, expressions in ``user_expressions`` are computed. | |
12 | This ensures that any error in the expressions don't affect the main code execution. |
|
13 | This ensures that any error in the expressions don't affect the main code execution. | |
13 | 5. Fire the ``post_execute`` event. |
|
14 | 5. Fire the ``post_execute`` event. | |
@@ -18,9 +19,15 b' The execution of user code consists of the following phases:' | |||||
18 | :doc:`/config/callbacks` |
|
19 | :doc:`/config/callbacks` | |
19 |
|
20 | |||
20 |
|
21 | |||
21 | To understand how the ``code`` field is executed, one must know that Python |
|
22 | Running user ``code`` | |
22 | code can be compiled in one of three modes (controlled by the ``mode`` argument |
|
23 | ===================== | |
23 | to the :func:`compile` builtin): |
|
24 | ||
|
25 | First, the ``code`` cell is transformed to expand ``%magic`` and ``!system`` | |||
|
26 | commands by ``IPython.core.inputtransformer2``. Then expanded cell is compiled | |||
|
27 | using standard Python :func:`compile` function and executed. | |||
|
28 | ||||
|
29 | Python :func:`compile` function provides ``mode`` argument to select one | |||
|
30 | of three ways of compiling code: | |||
24 |
|
31 | |||
25 | *single* |
|
32 | *single* | |
26 | Valid for a single interactive statement (though the source can contain |
|
33 | Valid for a single interactive statement (though the source can contain | |
@@ -50,16 +57,16 b" execution in 'single' mode, and then:" | |||||
50 |
|
57 | |||
51 | - If there is more than one block: |
|
58 | - If there is more than one block: | |
52 |
|
59 | |||
53 |
* if the last |
|
60 | * if the last block is a single line long, run all but the last in 'exec' mode | |
54 | and the very last one in 'single' mode. This makes it easy to type simple |
|
61 | and the very last one in 'single' mode. This makes it easy to type simple | |
55 | expressions at the end to see computed values. |
|
62 | expressions at the end to see computed values. | |
56 |
|
63 | |||
57 |
* if the last |
|
64 | * if the last block is no more than two lines long, run all but the last in | |
58 | 'exec' mode and the very last one in 'single' mode. This makes it easy to |
|
65 | 'exec' mode and the very last one in 'single' mode. This makes it easy to | |
59 | type simple expressions at the end to see computed values. - otherwise |
|
66 | type simple expressions at the end to see computed values. - otherwise | |
60 | (last one is also multiline), run all in 'exec' mode |
|
67 | (last one is also multiline), run all in 'exec' mode | |
61 |
|
68 | |||
62 |
* otherwise (last |
|
69 | * otherwise (last block is also multiline), run all in 'exec' mode as a single | |
63 | unit. |
|
70 | unit. | |
64 |
|
71 | |||
65 |
|
72 |
@@ -11,7 +11,7 b' This document describes in-flight development work.' | |||||
11 | `docs/source/whatsnew/pr` folder |
|
11 | `docs/source/whatsnew/pr` folder | |
12 |
|
12 | |||
13 |
|
13 | |||
14 |
Released .... ...., 201 |
|
14 | Released .... ...., 2019 | |
15 |
|
15 | |||
16 |
|
16 | |||
17 | Need to be updated: |
|
17 | Need to be updated: | |
@@ -22,8 +22,6 b' Need to be updated:' | |||||
22 |
|
22 | |||
23 | pr/* |
|
23 | pr/* | |
24 |
|
24 | |||
25 |
|
||||
26 |
|
||||
27 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. |
|
25 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. | |
28 |
|
26 | |||
29 |
|
27 |
@@ -1,6 +1,48 b'' | |||||
1 | Issues closed in the 7.x development cycle |
|
1 | Issues closed in the 7.x development cycle | |
2 | ========================================== |
|
2 | ========================================== | |
3 |
|
3 | |||
|
4 | Issues closed in 7.10.1 | |||
|
5 | ----------------------- | |||
|
6 | ||||
|
7 | GitHub stats for 2019/11/27 - 2019/12/01 (tag: 7.10.0) | |||
|
8 | ||||
|
9 | These lists are automatically generated, and may be incomplete or contain duplicates. | |||
|
10 | ||||
|
11 | We closed 5 issues and merged 7 pull requests. | |||
|
12 | The full list can be seen `on GitHub <https://github.com/ipython/ipython/issues?q=milestone%3A7.10.1>`__ | |||
|
13 | ||||
|
14 | The following 2 authors contributed 14 commits. | |||
|
15 | ||||
|
16 | * Jonathan Slenders | |||
|
17 | * Matthias Bussonnier | |||
|
18 | ||||
|
19 | Issues closed in 7.10 | |||
|
20 | --------------------- | |||
|
21 | ||||
|
22 | GitHub stats for 2019/10/25 - 2019/11/27 (tag: 7.9.0) | |||
|
23 | ||||
|
24 | These lists are automatically generated, and may be incomplete or contain duplicates. | |||
|
25 | ||||
|
26 | We closed 4 issues and merged 22 pull requests. | |||
|
27 | The full list can be seen `on GitHub <https://github.com/ipython/ipython/issues?q=milestone%3A7.10>`__ | |||
|
28 | ||||
|
29 | The following 15 authors contributed 101 commits. | |||
|
30 | ||||
|
31 | * anatoly techtonik | |||
|
32 | * Ben Lewis | |||
|
33 | * Benjamin Ragan-Kelley | |||
|
34 | * Gerrit Buss | |||
|
35 | * grey275 | |||
|
36 | * GΓΆkcen Eraslan | |||
|
37 | * Jonathan Slenders | |||
|
38 | * Joris Van den Bossche | |||
|
39 | * kousik | |||
|
40 | * Matthias Bussonnier | |||
|
41 | * Nicholas Bollweg | |||
|
42 | * Paul McCarthy | |||
|
43 | * Srinivas Reddy Thatiparthy | |||
|
44 | * Timo Kaufmann | |||
|
45 | * Tony Fast | |||
4 |
|
46 | |||
5 | Issues closed in 7.9 |
|
47 | Issues closed in 7.9 | |
6 | -------------------- |
|
48 | -------------------- |
@@ -2,8 +2,156 b'' | |||||
2 | 7.x Series |
|
2 | 7.x Series | |
3 | ============ |
|
3 | ============ | |
4 |
|
4 | |||
|
5 | .. _version 7101: | |||
|
6 | ||||
|
7 | IPython 7.10.1 | |||
|
8 | ============== | |||
|
9 | ||||
|
10 | IPython 7.10.1 fix a couple of incompatibilities with Prompt toolkit 3 (please | |||
|
11 | update Prompt toolkit to 3.0.2 at least), and fixes some interaction with | |||
|
12 | headless IPython. | |||
|
13 | ||||
|
14 | .. _version 7100: | |||
|
15 | ||||
|
16 | IPython 7.10.0 | |||
|
17 | ============== | |||
|
18 | ||||
|
19 | IPython 7.10 is the first double digit minor release in the last decade, and | |||
|
20 | first since the release of IPython 1.0, previous double digit minor release was | |||
|
21 | in August 2009. | |||
|
22 | ||||
|
23 | We've been trying to give you regular release on the last Friday of every month | |||
|
24 | for a guaranty of rapid access to bug fixes and new features. | |||
|
25 | ||||
|
26 | Unlike the previous first few releases that have seen only a couple of code | |||
|
27 | changes, 7.10 bring a number of changes, new features and bugfixes. | |||
|
28 | ||||
|
29 | Stop Support for Python 3.5 β Adopt NEP 29 | |||
|
30 | ------------------------------------------ | |||
|
31 | ||||
|
32 | IPython has decided to follow the informational `NEP 29 | |||
|
33 | <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_ which layout a clear | |||
|
34 | policy as to which version of (C)Python and NumPy are supported. | |||
|
35 | ||||
|
36 | We thus dropped support for Python 3.5, and cleaned up a number of code path | |||
|
37 | that were Python-version dependant. If you are on 3.5 or earlier pip should | |||
|
38 | automatically give you the latest compatible version of IPython so you do not | |||
|
39 | need to pin to a given version. | |||
|
40 | ||||
|
41 | Support for Prompt Toolkit 3.0 | |||
|
42 | ------------------------------ | |||
|
43 | ||||
|
44 | Prompt Toolkit 3.0 was release a week before IPython 7.10 and introduces a few | |||
|
45 | breaking changes. We believe IPython 7.10 should be compatible with both Prompt | |||
|
46 | Toolkit 2.x and 3.x, though it has not been extensively tested with 3.x so | |||
|
47 | please report any issues. | |||
|
48 | ||||
|
49 | ||||
|
50 | Prompt Rendering Performance improvements | |||
|
51 | ----------------------------------------- | |||
|
52 | ||||
|
53 | Pull Request :ghpull:`11933` introduced an optimisation in the prompt rendering | |||
|
54 | logic that should decrease the resource usage of IPython when using the | |||
|
55 | _default_ configuration but could potentially introduce a regression of | |||
|
56 | functionalities if you are using a custom prompt. | |||
|
57 | ||||
|
58 | We know assume if you haven't changed the default keybindings that the prompt | |||
|
59 | **will not change** during the duration of your input β which is for example | |||
|
60 | not true when using vi insert mode that switches between `[ins]` and `[nor]` | |||
|
61 | for the current mode. | |||
|
62 | ||||
|
63 | If you are experiencing any issue let us know. | |||
|
64 | ||||
|
65 | Code autoformatting | |||
|
66 | ------------------- | |||
|
67 | ||||
|
68 | The IPython terminal can now auto format your code just before entering a new | |||
|
69 | line or executing a command. To do so use the | |||
|
70 | ``--TerminalInteractiveShell.autoformatter`` option and set it to ``'black'``; | |||
|
71 | if black is installed IPython will use black to format your code when possible. | |||
|
72 | ||||
|
73 | IPython cannot always properly format your code; in particular it will | |||
|
74 | auto formatting with *black* will only work if: | |||
|
75 | ||||
|
76 | - Your code does not contains magics or special python syntax. | |||
|
77 | ||||
|
78 | - There is no code after your cursor. | |||
|
79 | ||||
|
80 | The Black API is also still in motion; so this may not work with all versions of | |||
|
81 | black. | |||
|
82 | ||||
|
83 | It should be possible to register custom formatter, though the API is till in | |||
|
84 | flux. | |||
|
85 | ||||
|
86 | Arbitrary Mimetypes Handing in Terminal (Aka inline images in terminal) | |||
|
87 | ----------------------------------------------------------------------- | |||
|
88 | ||||
|
89 | When using IPython terminal it is now possible to register function to handle | |||
|
90 | arbitrary mimetypes. While rendering non-text based representation was possible in | |||
|
91 | many jupyter frontend; it was not possible in terminal IPython, as usually | |||
|
92 | terminal are limited to displaying text. As many terminal these days provide | |||
|
93 | escape sequences to display non-text; bringing this loved feature to IPython CLI | |||
|
94 | made a lot of sens. This functionality will not only allow inline images; but | |||
|
95 | allow opening of external program; for example ``mplayer`` to "display" sound | |||
|
96 | files. | |||
|
97 | ||||
|
98 | So far only the hooks necessary for this are in place, but no default mime | |||
|
99 | renderers added; so inline images will only be available via extensions. We will | |||
|
100 | progressively enable these features by default in the next few releases, and | |||
|
101 | contribution is welcomed. | |||
|
102 | ||||
|
103 | We welcome any feedback on the API. See :ref:`shell_mimerenderer` for more | |||
|
104 | informations. | |||
|
105 | ||||
|
106 | This is originally based on work form in :ghpull:`10610` from @stephanh42 | |||
|
107 | started over two years ago, and still a lot need to be done. | |||
|
108 | ||||
|
109 | MISC | |||
|
110 | ---- | |||
|
111 | ||||
|
112 | - Completions can define their own ordering :ghpull:`11855` | |||
|
113 | - Enable Plotting in the same cell than the one that import matplotlib | |||
|
114 | :ghpull:`11916` | |||
|
115 | - Allow to store and restore multiple variables at once :ghpull:`11930` | |||
|
116 | ||||
|
117 | You can see `all pull-requests <https://github.com/ipython/ipython/pulls?q=is%3Apr+milestone%3A7.10+is%3Aclosed>`_ for this release. | |||
|
118 | ||||
|
119 | API Changes | |||
|
120 | ----------- | |||
|
121 | ||||
|
122 | Change of API and exposed objects automatically detected using `frappuccino <https://pypi.org/project/frappuccino/>`_ (still in beta): | |||
|
123 | ||||
|
124 | The following items are new in IPython 7.10:: | |||
|
125 | ||||
|
126 | + IPython.terminal.shortcuts.reformat_text_before_cursor(buffer, document, shell) | |||
|
127 | + IPython.terminal.interactiveshell.PTK3 | |||
|
128 | + IPython.terminal.interactiveshell.black_reformat_handler(text_before_cursor) | |||
|
129 | + IPython.terminal.prompts.RichPromptDisplayHook.write_format_data(self, format_dict, md_dict='None') | |||
|
130 | ||||
|
131 | The following items have been removed in 7.10:: | |||
|
132 | ||||
|
133 | - IPython.lib.pretty.DICT_IS_ORDERED | |||
|
134 | ||||
|
135 | The following signatures differ between versions:: | |||
|
136 | ||||
|
137 | - IPython.extensions.storemagic.restore_aliases(ip) | |||
|
138 | + IPython.extensions.storemagic.restore_aliases(ip, alias='None') | |||
|
139 | ||||
|
140 | Special Thanks | |||
|
141 | -------------- | |||
|
142 | ||||
|
143 | - @stephanh42 who started the work on inline images in terminal 2 years ago | |||
|
144 | - @augustogoulart who spent a lot of time triaging issues and responding to | |||
|
145 | users. | |||
|
146 | - @con-f-use who is my (@Carreau) first sponsor on GitHub, as a reminder if you | |||
|
147 | like IPython, Jupyter and many other library of the SciPy stack you can | |||
|
148 | donate to numfocus.org non profit | |||
|
149 | ||||
5 | .. _version 790: |
|
150 | .. _version 790: | |
6 |
|
151 | |||
|
152 | IPython 7.9.0 | |||
|
153 | ============= | |||
|
154 | ||||
7 | IPython 7.9 is a small release with a couple of improvement and bug fixes. |
|
155 | IPython 7.9 is a small release with a couple of improvement and bug fixes. | |
8 |
|
156 | |||
9 | - Xterm terminal title should be restored on exit :ghpull:`11910` |
|
157 | - Xterm terminal title should be restored on exit :ghpull:`11910` | |
@@ -13,12 +161,12 b' IPython 7.9 is a small release with a couple of improvement and bug fixes.' | |||||
13 | find all objects needing reload. This should avoid large objects traversal |
|
161 | find all objects needing reload. This should avoid large objects traversal | |
14 | like pandas dataframes. :ghpull:`11876` |
|
162 | like pandas dataframes. :ghpull:`11876` | |
15 | - Get ready for Python 4. :ghpull:`11874` |
|
163 | - Get ready for Python 4. :ghpull:`11874` | |
16 |
- `%env` Magic no |
|
164 | - `%env` Magic now has heuristic to hide potentially sensitive values :ghpull:`11896` | |
17 |
|
165 | |||
18 | This is a small release despite a number of Pull Request Pending that need to |
|
166 | This is a small release despite a number of Pull Request Pending that need to | |
19 | be reviewed/worked on. Many of the core developers have been busy outside of |
|
167 | be reviewed/worked on. Many of the core developers have been busy outside of | |
20 | IPython/Jupyter and we thanks all contributor for their patience; we'll work on |
|
168 | IPython/Jupyter and we thanks all contributor for their patience; we'll work on | |
21 |
these as soon as we have time. |
|
169 | these as soon as we have time. | |
22 |
|
170 | |||
23 |
|
171 | |||
24 | .. _version780: |
|
172 | .. _version780: |
@@ -190,7 +190,7 b' install_requires = [' | |||||
190 | 'decorator', |
|
190 | 'decorator', | |
191 | 'pickleshare', |
|
191 | 'pickleshare', | |
192 | 'traitlets>=4.2', |
|
192 | 'traitlets>=4.2', | |
193 |
'prompt_toolkit>=2.0.0,< |
|
193 | 'prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1', | |
194 | 'pygments', |
|
194 | 'pygments', | |
195 | 'backcall', |
|
195 | 'backcall', | |
196 | ] |
|
196 | ] |
@@ -1,5 +1,5 b'' | |||||
1 | # Simple tool to help for release |
|
1 | # Simple tool to help for release | |
2 |
# when releasing with bash, simple |
|
2 | # when releasing with bash, simple source it to get asked questions. | |
3 |
|
3 | |||
4 | # misc check before starting |
|
4 | # misc check before starting | |
5 |
|
5 | |||
@@ -9,14 +9,6 b" python -c 'import sphinx'" | |||||
9 | python -c 'import sphinx_rtd_theme' |
|
9 | python -c 'import sphinx_rtd_theme' | |
10 | python -c 'import nose' |
|
10 | python -c 'import nose' | |
11 |
|
11 | |||
12 | echo -n 'PREV_RELEASE (X.y.z):' |
|
|||
13 | read PREV_RELEASE |
|
|||
14 | echo -n 'MILESTONE (X.y):' |
|
|||
15 | read MILESTONE |
|
|||
16 | echo -n 'VERSION (X.y.z):' |
|
|||
17 | read VERSION |
|
|||
18 | echo -n 'branch (master|X.y):' |
|
|||
19 | read branch |
|
|||
20 |
|
12 | |||
21 | BLACK=$(tput setaf 1) |
|
13 | BLACK=$(tput setaf 1) | |
22 | RED=$(tput setaf 1) |
|
14 | RED=$(tput setaf 1) | |
@@ -28,30 +20,66 b' CYAN=$(tput setaf 6)' | |||||
28 | WHITE=$(tput setaf 7) |
|
20 | WHITE=$(tput setaf 7) | |
29 | NOR=$(tput sgr0) |
|
21 | NOR=$(tput sgr0) | |
30 |
|
22 | |||
31 | echo |
|
|||
32 | echo $BLUE"Updating what's new with informations from docs/source/whatsnew/pr"$NOR |
|
|||
33 | python tools/update_whatsnew.py |
|
|||
34 |
|
23 | |||
35 | echo |
|
24 | echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: " | |
36 | echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR |
|
25 | read input | |
37 | echo $GREEN"Press enter to continue"$NOR |
|
26 | PREV_RELEASE=${input:-$PREV_RELEASE} | |
38 | read |
|
27 | echo -n "MILESTONE (X.y) [$MILESTONE]: " | |
|
28 | read input | |||
|
29 | MILESTONE=${input:-$MILESTONE} | |||
|
30 | echo -n "VERSION (X.y.z) [$VERSION]:" | |||
|
31 | read input | |||
|
32 | VERSION=${input:-$VERSION} | |||
|
33 | echo -n "BRANCH (master|X.y) [$BRANCH]:" | |||
|
34 | read input | |||
|
35 | BRANCH=${input:-$BRANCH} | |||
|
36 | ||||
|
37 | ask_section(){ | |||
|
38 | echo | |||
|
39 | echo $BLUE"$1"$NOR | |||
|
40 | echo -n $GREEN"Press Enter to continue, S to skip: "$GREEN | |||
|
41 | read -n1 value | |||
|
42 | echo | |||
|
43 | if [ -z $value ] || [ $value = 'y' ] ; then | |||
|
44 | return 0 | |||
|
45 | fi | |||
|
46 | return 1 | |||
|
47 | } | |||
|
48 | ||||
39 |
|
49 | |||
40 | echo |
|
|||
41 | echo $BLUE"here are all the authors that contributed to this release:"$NOR |
|
|||
42 | git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f |
|
|||
43 |
|
50 | |||
44 | echo |
|
51 | echo | |
45 | echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap."$GREEN"Press enter to continue:"$NOR |
|
52 | if ask_section "Updating what's new with informations from docs/source/whatsnew/pr" | |
46 | read |
|
53 | then | |
|
54 | python tools/update_whatsnew.py | |||
47 |
|
55 | |||
48 | echo $BLUE"generating stats"$NOR |
|
56 | echo | |
49 | python tools/github_stats.py --milestone $MILESTONE > stats.rst |
|
57 | echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR | |
|
58 | echo $GREEN"Press enter to continue"$NOR | |||
|
59 | read | |||
|
60 | fi | |||
50 |
|
61 | |||
51 | echo $BLUE"stats.rst files generated."$NOR |
|
62 | if ask_section "Gen Stats, and authors" | |
52 | echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR |
|
63 | then | |
53 | echo $GREEN"press enter to continue."$NOR |
|
64 | ||
54 | read |
|
65 | echo | |
|
66 | echo $BLUE"here are all the authors that contributed to this release:"$NOR | |||
|
67 | git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f | |||
|
68 | ||||
|
69 | echo | |||
|
70 | echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap." | |||
|
71 | echo $GREEN"Press enter to continue:"$NOR | |||
|
72 | read | |||
|
73 | ||||
|
74 | echo $BLUE"generating stats"$NOR | |||
|
75 | python tools/github_stats.py --milestone $MILESTONE > stats.rst | |||
|
76 | ||||
|
77 | echo $BLUE"stats.rst files generated."$NOR | |||
|
78 | echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR | |||
|
79 | echo $GREEN"press enter to continue."$NOR | |||
|
80 | read | |||
|
81 | ||||
|
82 | fi | |||
55 |
|
83 | |||
56 | echo "Cleaning repository" |
|
84 | echo "Cleaning repository" | |
57 | git clean -xfdi |
|
85 | git clean -xfdi | |
@@ -61,30 +89,81 b' echo $GREEN"please update version number in ${RED}IPython/core/release.py${NOR} ' | |||||
61 | echo $GREEN"Press enter to continue"$NOR |
|
89 | echo $GREEN"Press enter to continue"$NOR | |
62 | read |
|
90 | read | |
63 |
|
91 | |||
64 | echo |
|
92 | if ask_section "Build the documentation ?" | |
65 | echo "Attempting to build the docs.." |
|
93 | then | |
66 | make html -C docs |
|
94 | make html -C docs | |
|
95 | echo | |||
|
96 | echo $GREEN"Check the docs, press enter to continue"$NOR | |||
|
97 | read | |||
67 |
|
98 | |||
68 | echo |
|
99 | fi | |
69 | echo $GREEN"Check the docs, press enter to continue"$NOR |
|
|||
70 | read |
|
|||
71 |
|
100 | |||
72 | echo |
|
101 | echo | |
73 | echo $BLUE"Attempting to build package..."$NOR |
|
102 | echo $BLUE"Attempting to build package..."$NOR | |
74 |
|
103 | |||
75 | tools/build_release |
|
104 | tools/build_release | |
76 |
|
105 | rm dist/* | ||
77 | echo |
|
106 | ||
78 | echo "Let\'s commit : git commit -am \"release $VERSION\" -S" |
|
107 | if ask_section "Should we commit, tag, push... etc ? " | |
79 | echo $GREEN"Press enter to continue"$NOR |
|
108 | then | |
80 | read |
|
109 | echo | |
81 |
|
|
110 | echo "Let's commit : git commit -am \"release $VERSION\" -S" | |
82 |
|
111 | echo $GREEN"Press enter to commit"$NOR | ||
83 | echo |
|
112 | read | |
84 | echo "git push origin \$BRANCH ?" |
|
113 | git commit -am "release $VERSION" -S | |
85 | echo "Press enter to continue" |
|
114 | ||
86 | read |
|
115 | echo | |
87 | git push origin $BRANCH |
|
116 | echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR | |
88 | # git tag -am "release $VERSION" "$VERSION" -s |
|
117 | echo $GREEN"Make sure you can push"$NOR | |
89 | # git push origin $VERSION |
|
118 | echo $GREEN"Press enter to continue"$NOR | |
90 |
|
119 | read | ||
|
120 | git push origin $BRANCH | |||
|
121 | ||||
|
122 | echo | |||
|
123 | echo "Let's tag : git tag -am \"release $VERSION\" \"$VERSION\" -s" | |||
|
124 | echo $GREEN"Press enter to tag commit"$NOR | |||
|
125 | read | |||
|
126 | git tag -am "release $VERSION" "$VERSION" -s | |||
|
127 | ||||
|
128 | echo | |||
|
129 | echo $BLUE"And push the tag: git push origin \$VERSION ?"$NOR | |||
|
130 | echo $GREEN"Press enter to continue"$NOR | |||
|
131 | read | |||
|
132 | git push origin $VERSION | |||
|
133 | ||||
|
134 | ||||
|
135 | echo $GREEN"please update version number and back to .dev in ${RED}IPython/core/release.py" | |||
|
136 | echo ${BLUE}"Do not commit yet βΒ we'll do it later."$NOR | |||
|
137 | ||||
|
138 | echo $GREEN"Press enter to continue"$NOR | |||
|
139 | read | |||
|
140 | ||||
|
141 | echo | |||
|
142 | echo "Let's commit : git commit -am \"back to dev\" -S" | |||
|
143 | echo $GREEN"Press enter to commit"$NOR | |||
|
144 | read | |||
|
145 | git commit -am "back to dev" -S | |||
|
146 | ||||
|
147 | echo | |||
|
148 | echo $BLUE"let's : git checkout $VERSION"$NOR | |||
|
149 | echo $GREEN"Press enter to continue"$NOR | |||
|
150 | read | |||
|
151 | git checkout $VERSION | |||
|
152 | fi | |||
|
153 | ||||
|
154 | if ask_section "Should we build and release ?" | |||
|
155 | then | |||
|
156 | ||||
|
157 | echo | |||
|
158 | echo $BLUE"Attempting to build package..."$NOR | |||
|
159 | ||||
|
160 | tools/build_release | |||
|
161 | ||||
|
162 | echo '$ shasum -a 256 dist/*' | |||
|
163 | shasum -a 256 dist/* | |||
|
164 | ||||
|
165 | if ask_section "upload packages ?" | |||
|
166 | then | |||
|
167 | tools/build_release upload | |||
|
168 | fi | |||
|
169 | fi |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now