##// END OF EJS Templates
Use pathlib in packaging.py, io.py, and gen_latex_symbols.py...
Justin Palmer -
Show More
@@ -1,104 +1,105 b''
1 """Implementation of packaging-related magic functions.
1 """Implementation of packaging-related magic functions.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2018 The IPython Development Team.
4 # Copyright (c) 2018 The IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 import re
11 import re
12 import shlex
12 import shlex
13 import sys
13 import sys
14 from Pathlib import Path
14
15
15 from pathlib import Path
16 from pathlib import Path
16 from IPython.core.magic import Magics, magics_class, line_magic
17 from IPython.core.magic import Magics, magics_class, line_magic
17
18
18
19
19 def _is_conda_environment():
20 def _is_conda_environment():
20 """Return True if the current Python executable is in a conda env"""
21 """Return True if the current Python executable is in a conda env"""
21 # TODO: does this need to change on windows?
22 # TODO: does this need to change on windows?
22 return Path(sys.prefix, "conda-meta", "history").exists()
23 return Path(sys.prefix, "conda-meta", "history").exists()
23
24
24
25
25 def _get_conda_executable():
26 def _get_conda_executable():
26 """Find the path to the conda executable"""
27 """Find the path to the conda executable"""
27 # Check if there is a conda executable in the same directory as the Python executable.
28 # Check if there is a conda executable in the same directory as the Python executable.
28 # This is the case within conda's root environment.
29 # This is the case within conda's root environment.
29 conda = Path(sys.executable).parent / "conda"
30 conda = Path(sys.executable).parent / "conda"
30 if conda.isfile():
31 if conda.isfile():
31 return str(conda)
32 return str(conda)
32
33
33 # Otherwise, attempt to extract the executable from conda history.
34 # Otherwise, attempt to extract the executable from conda history.
34 # This applies in any conda environment.
35 # This applies in any conda environment.
35 history = Path(sys.prefix, "conda-meta", "history").read_text()
36 history = Path(sys.prefix, "conda-meta", "history").read_text()
36 match = re.search(
37 match = re.search(
37 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
38 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
38 history,
39 history,
39 flags=re.MULTILINE,
40 flags=re.MULTILINE,
40 )
41 )
41 if match:
42 if match:
42 return match.groupdict()["command"]
43 return match.groupdict()["command"]
43
44
44 # Fallback: assume conda is available on the system path.
45 # Fallback: assume conda is available on the system path.
45 return "conda"
46 return "conda"
46
47
47
48
48 CONDA_COMMANDS_REQUIRING_PREFIX = {
49 CONDA_COMMANDS_REQUIRING_PREFIX = {
49 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
50 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
50 }
51 }
51 CONDA_COMMANDS_REQUIRING_YES = {
52 CONDA_COMMANDS_REQUIRING_YES = {
52 'install', 'remove', 'uninstall', 'update', 'upgrade',
53 'install', 'remove', 'uninstall', 'update', 'upgrade',
53 }
54 }
54 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
55 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
55 CONDA_YES_FLAGS = {'-y', '--y'}
56 CONDA_YES_FLAGS = {'-y', '--y'}
56
57
57
58
58 @magics_class
59 @magics_class
59 class PackagingMagics(Magics):
60 class PackagingMagics(Magics):
60 """Magics related to packaging & installation"""
61 """Magics related to packaging & installation"""
61
62
62 @line_magic
63 @line_magic
63 def pip(self, line):
64 def pip(self, line):
64 """Run the pip package manager within the current kernel.
65 """Run the pip package manager within the current kernel.
65
66
66 Usage:
67 Usage:
67 %pip install [pkgs]
68 %pip install [pkgs]
68 """
69 """
69 self.shell.system(' '.join([sys.executable, '-m', 'pip', line]))
70 self.shell.system(' '.join([sys.executable, '-m', 'pip', line]))
70 print("Note: you may need to restart the kernel to use updated packages.")
71 print("Note: you may need to restart the kernel to use updated packages.")
71
72
72 @line_magic
73 @line_magic
73 def conda(self, line):
74 def conda(self, line):
74 """Run the conda package manager within the current kernel.
75 """Run the conda package manager within the current kernel.
75
76
76 Usage:
77 Usage:
77 %conda install [pkgs]
78 %conda install [pkgs]
78 """
79 """
79 if not _is_conda_environment():
80 if not _is_conda_environment():
80 raise ValueError("The python kernel does not appear to be a conda environment. "
81 raise ValueError("The python kernel does not appear to be a conda environment. "
81 "Please use ``%pip install`` instead.")
82 "Please use ``%pip install`` instead.")
82
83
83 conda = _get_conda_executable()
84 conda = _get_conda_executable()
84 args = shlex.split(line)
85 args = shlex.split(line)
85 command = args[0]
86 command = args[0]
86 args = args[1:]
87 args = args[1:]
87 extra_args = []
88 extra_args = []
88
89
89 # When the subprocess does not allow us to respond "yes" during the installation,
90 # When the subprocess does not allow us to respond "yes" during the installation,
90 # we need to insert --yes in the argument list for some commands
91 # we need to insert --yes in the argument list for some commands
91 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
92 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
92 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
93 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
93 has_yes = set(args).intersection(CONDA_YES_FLAGS)
94 has_yes = set(args).intersection(CONDA_YES_FLAGS)
94 if stdin_disabled and needs_yes and not has_yes:
95 if stdin_disabled and needs_yes and not has_yes:
95 extra_args.append("--yes")
96 extra_args.append("--yes")
96
97
97 # Add --prefix to point conda installation to the current environment
98 # Add --prefix to point conda installation to the current environment
98 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
99 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
99 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
100 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
100 if needs_prefix and not has_prefix:
101 if needs_prefix and not has_prefix:
101 extra_args.extend(["--prefix", sys.prefix])
102 extra_args.extend(["--prefix", sys.prefix])
102
103
103 self.shell.system(' '.join([conda, command] + extra_args + args))
104 self.shell.system(' '.join([conda, command] + extra_args + args))
104 print("\nNote: you may need to restart the kernel to use updated packages.")
105 print("\nNote: you may need to restart the kernel to use updated packages.")
@@ -1,248 +1,249 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10
10
11 import atexit
11 import atexit
12 import os
12 import os
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import warnings
15 import warnings
16 from warnings import warn
16 from warnings import warn
17 from pathlib import Path
17
18
18 from IPython.utils.decorators import undoc
19 from IPython.utils.decorators import undoc
19 from .capture import CapturedIO, capture_output
20 from .capture import CapturedIO, capture_output
20
21
21 @undoc
22 @undoc
22 class IOStream:
23 class IOStream:
23
24
24 def __init__(self, stream, fallback=None):
25 def __init__(self, stream, fallback=None):
25 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26 DeprecationWarning, stacklevel=2)
27 DeprecationWarning, stacklevel=2)
27 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if fallback is not None:
29 if fallback is not None:
29 stream = fallback
30 stream = fallback
30 else:
31 else:
31 raise ValueError("fallback required, but not specified")
32 raise ValueError("fallback required, but not specified")
32 self.stream = stream
33 self.stream = stream
33 self._swrite = stream.write
34 self._swrite = stream.write
34
35
35 # clone all methods not overridden:
36 # clone all methods not overridden:
36 def clone(meth):
37 def clone(meth):
37 return not hasattr(self, meth) and not meth.startswith('_')
38 return not hasattr(self, meth) and not meth.startswith('_')
38 for meth in filter(clone, dir(stream)):
39 for meth in filter(clone, dir(stream)):
39 try:
40 try:
40 val = getattr(stream, meth)
41 val = getattr(stream, meth)
41 except AttributeError:
42 except AttributeError:
42 pass
43 pass
43 else:
44 else:
44 setattr(self, meth, val)
45 setattr(self, meth, val)
45
46
46 def __repr__(self):
47 def __repr__(self):
47 cls = self.__class__
48 cls = self.__class__
48 tpl = '{mod}.{cls}({args})'
49 tpl = '{mod}.{cls}({args})'
49 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50
51
51 def write(self,data):
52 def write(self,data):
52 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53 DeprecationWarning, stacklevel=2)
54 DeprecationWarning, stacklevel=2)
54 try:
55 try:
55 self._swrite(data)
56 self._swrite(data)
56 except:
57 except:
57 try:
58 try:
58 # print handles some unicode issues which may trip a plain
59 # print handles some unicode issues which may trip a plain
59 # write() call. Emulate write() by using an empty end
60 # write() call. Emulate write() by using an empty end
60 # argument.
61 # argument.
61 print(data, end='', file=self.stream)
62 print(data, end='', file=self.stream)
62 except:
63 except:
63 # if we get here, something is seriously broken.
64 # if we get here, something is seriously broken.
64 print('ERROR - failed to write data to stream:', self.stream,
65 print('ERROR - failed to write data to stream:', self.stream,
65 file=sys.stderr)
66 file=sys.stderr)
66
67
67 def writelines(self, lines):
68 def writelines(self, lines):
68 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69 DeprecationWarning, stacklevel=2)
70 DeprecationWarning, stacklevel=2)
70 if isinstance(lines, str):
71 if isinstance(lines, str):
71 lines = [lines]
72 lines = [lines]
72 for line in lines:
73 for line in lines:
73 self.write(line)
74 self.write(line)
74
75
75 # This class used to have a writeln method, but regular files and streams
76 # This class used to have a writeln method, but regular files and streams
76 # in Python don't have this method. We need to keep this completely
77 # in Python don't have this method. We need to keep this completely
77 # compatible so we removed it.
78 # compatible so we removed it.
78
79
79 @property
80 @property
80 def closed(self):
81 def closed(self):
81 return self.stream.closed
82 return self.stream.closed
82
83
83 def close(self):
84 def close(self):
84 pass
85 pass
85
86
86 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87 devnull = open(os.devnull, 'w')
88 devnull = open(os.devnull, 'w')
88 atexit.register(devnull.close)
89 atexit.register(devnull.close)
89
90
90 # io.std* are deprecated, but don't show our own deprecation warnings
91 # io.std* are deprecated, but don't show our own deprecation warnings
91 # during initialization of the deprecated API.
92 # during initialization of the deprecated API.
92 with warnings.catch_warnings():
93 with warnings.catch_warnings():
93 warnings.simplefilter('ignore', DeprecationWarning)
94 warnings.simplefilter('ignore', DeprecationWarning)
94 stdin = IOStream(sys.stdin, fallback=devnull)
95 stdin = IOStream(sys.stdin, fallback=devnull)
95 stdout = IOStream(sys.stdout, fallback=devnull)
96 stdout = IOStream(sys.stdout, fallback=devnull)
96 stderr = IOStream(sys.stderr, fallback=devnull)
97 stderr = IOStream(sys.stderr, fallback=devnull)
97
98
98 class Tee(object):
99 class Tee(object):
99 """A class to duplicate an output stream to stdout/err.
100 """A class to duplicate an output stream to stdout/err.
100
101
101 This works in a manner very similar to the Unix 'tee' command.
102 This works in a manner very similar to the Unix 'tee' command.
102
103
103 When the object is closed or deleted, it closes the original file given to
104 When the object is closed or deleted, it closes the original file given to
104 it for duplication.
105 it for duplication.
105 """
106 """
106 # Inspired by:
107 # Inspired by:
107 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108
109
109 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 """Construct a new Tee object.
111 """Construct a new Tee object.
111
112
112 Parameters
113 Parameters
113 ----------
114 ----------
114 file_or_name : filename or open filehandle (writable)
115 file_or_name : filename or open filehandle (writable)
115 File that will be duplicated
116 File that will be duplicated
116
117
117 mode : optional, valid mode for open().
118 mode : optional, valid mode for open().
118 If a filename was give, open with this mode.
119 If a filename was give, open with this mode.
119
120
120 channel : str, one of ['stdout', 'stderr']
121 channel : str, one of ['stdout', 'stderr']
121 """
122 """
122 if channel not in ['stdout', 'stderr']:
123 if channel not in ['stdout', 'stderr']:
123 raise ValueError('Invalid channel spec %s' % channel)
124 raise ValueError('Invalid channel spec %s' % channel)
124
125
125 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126 self.file = file_or_name
127 self.file = file_or_name
127 else:
128 else:
128 self.file = open(file_or_name, mode)
129 self.file = open(file_or_name, mode)
129 self.channel = channel
130 self.channel = channel
130 self.ostream = getattr(sys, channel)
131 self.ostream = getattr(sys, channel)
131 setattr(sys, channel, self)
132 setattr(sys, channel, self)
132 self._closed = False
133 self._closed = False
133
134
134 def close(self):
135 def close(self):
135 """Close the file and restore the channel."""
136 """Close the file and restore the channel."""
136 self.flush()
137 self.flush()
137 setattr(sys, self.channel, self.ostream)
138 setattr(sys, self.channel, self.ostream)
138 self.file.close()
139 self.file.close()
139 self._closed = True
140 self._closed = True
140
141
141 def write(self, data):
142 def write(self, data):
142 """Write data to both channels."""
143 """Write data to both channels."""
143 self.file.write(data)
144 self.file.write(data)
144 self.ostream.write(data)
145 self.ostream.write(data)
145 self.ostream.flush()
146 self.ostream.flush()
146
147
147 def flush(self):
148 def flush(self):
148 """Flush both channels."""
149 """Flush both channels."""
149 self.file.flush()
150 self.file.flush()
150 self.ostream.flush()
151 self.ostream.flush()
151
152
152 def __del__(self):
153 def __del__(self):
153 if not self._closed:
154 if not self._closed:
154 self.close()
155 self.close()
155
156
156
157
157 def ask_yes_no(prompt, default=None, interrupt=None):
158 def ask_yes_no(prompt, default=None, interrupt=None):
158 """Asks a question and returns a boolean (y/n) answer.
159 """Asks a question and returns a boolean (y/n) answer.
159
160
160 If default is given (one of 'y','n'), it is used if the user input is
161 If default is given (one of 'y','n'), it is used if the user input is
161 empty. If interrupt is given (one of 'y','n'), it is used if the user
162 empty. If interrupt is given (one of 'y','n'), it is used if the user
162 presses Ctrl-C. Otherwise the question is repeated until an answer is
163 presses Ctrl-C. Otherwise the question is repeated until an answer is
163 given.
164 given.
164
165
165 An EOF is treated as the default answer. If there is no default, an
166 An EOF is treated as the default answer. If there is no default, an
166 exception is raised to prevent infinite loops.
167 exception is raised to prevent infinite loops.
167
168
168 Valid answers are: y/yes/n/no (match is not case sensitive)."""
169 Valid answers are: y/yes/n/no (match is not case sensitive)."""
169
170
170 answers = {'y':True,'n':False,'yes':True,'no':False}
171 answers = {'y':True,'n':False,'yes':True,'no':False}
171 ans = None
172 ans = None
172 while ans not in answers.keys():
173 while ans not in answers.keys():
173 try:
174 try:
174 ans = input(prompt+' ').lower()
175 ans = input(prompt+' ').lower()
175 if not ans: # response was an empty string
176 if not ans: # response was an empty string
176 ans = default
177 ans = default
177 except KeyboardInterrupt:
178 except KeyboardInterrupt:
178 if interrupt:
179 if interrupt:
179 ans = interrupt
180 ans = interrupt
180 print("\r")
181 print("\r")
181 except EOFError:
182 except EOFError:
182 if default in answers.keys():
183 if default in answers.keys():
183 ans = default
184 ans = default
184 print()
185 print()
185 else:
186 else:
186 raise
187 raise
187
188
188 return answers[ans]
189 return answers[ans]
189
190
190
191
191 def temp_pyfile(src, ext='.py'):
192 def temp_pyfile(src, ext='.py'):
192 """Make a temporary python file, return filename and filehandle.
193 """Make a temporary python file, return filename and filehandle.
193
194
194 Parameters
195 Parameters
195 ----------
196 ----------
196 src : string or list of strings (no need for ending newlines if list)
197 src : string or list of strings (no need for ending newlines if list)
197 Source code to be written to the file.
198 Source code to be written to the file.
198
199
199 ext : optional, string
200 ext : optional, string
200 Extension for the generated file.
201 Extension for the generated file.
201
202
202 Returns
203 Returns
203 -------
204 -------
204 (filename, open filehandle)
205 (filename, open filehandle)
205 It is the caller's responsibility to close the open file and unlink it.
206 It is the caller's responsibility to close the open file and unlink it.
206 """
207 """
207 fname = tempfile.mkstemp(ext)[1]
208 fname = Path(tempfile.mkstemp(ext)[1])
208 with open(fname,'w') as f:
209 with fname.open('w') as f:
209 f.write(src)
210 f.write(src)
210 f.flush()
211 f.flush()
211 return fname
212 return fname
212
213
213 @undoc
214 @undoc
214 def atomic_writing(*args, **kwargs):
215 def atomic_writing(*args, **kwargs):
215 """DEPRECATED: moved to notebook.services.contents.fileio"""
216 """DEPRECATED: moved to notebook.services.contents.fileio"""
216 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
217 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
217 from notebook.services.contents.fileio import atomic_writing
218 from notebook.services.contents.fileio import atomic_writing
218 return atomic_writing(*args, **kwargs)
219 return atomic_writing(*args, **kwargs)
219
220
220 @undoc
221 @undoc
221 def raw_print(*args, **kw):
222 def raw_print(*args, **kw):
222 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
223 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
223 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
224 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
224
225
225 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
226 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
226 file=sys.__stdout__)
227 file=sys.__stdout__)
227 sys.__stdout__.flush()
228 sys.__stdout__.flush()
228
229
229 @undoc
230 @undoc
230 def raw_print_err(*args, **kw):
231 def raw_print_err(*args, **kw):
231 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
232 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
232 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
233 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
233
234
234 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
235 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
235 file=sys.__stderr__)
236 file=sys.__stderr__)
236 sys.__stderr__.flush()
237 sys.__stderr__.flush()
237
238
238 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
239 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
239 # Keep for a version or two then should remove
240 # Keep for a version or two then should remove
240 rprint = raw_print
241 rprint = raw_print
241 rprinte = raw_print_err
242 rprinte = raw_print_err
242
243
243 @undoc
244 @undoc
244 def unicode_std_stream(stream='stdout'):
245 def unicode_std_stream(stream='stdout'):
245 """DEPRECATED, moved to nbconvert.utils.io"""
246 """DEPRECATED, moved to nbconvert.utils.io"""
246 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
247 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
247 from nbconvert.utils.io import unicode_std_stream
248 from nbconvert.utils.io import unicode_std_stream
248 return unicode_std_stream(stream)
249 return unicode_std_stream(stream)
@@ -1,88 +1,87 b''
1 # coding: utf-8
1 # coding: utf-8
2
2
3 # This script autogenerates `IPython.core.latex_symbols.py`, which contains a
3 # This script autogenerates `IPython.core.latex_symbols.py`, which contains a
4 # single dict , named `latex_symbols`. The keys in this dict are latex symbols,
4 # single dict , named `latex_symbols`. The keys in this dict are latex symbols,
5 # such as `\\alpha` and the values in the dict are the unicode equivalents for
5 # such as `\\alpha` and the values in the dict are the unicode equivalents for
6 # those. Most importantly, only unicode symbols that are valid identifiers in
6 # those. Most importantly, only unicode symbols that are valid identifiers in
7 # Python 3 are included.
7 # Python 3 are included.
8
8
9 #
9 #
10 # The original mapping of latex symbols to unicode comes from the `latex_symbols.jl` files from Julia.
10 # The original mapping of latex symbols to unicode comes from the `latex_symbols.jl` files from Julia.
11
11
12 import os, sys
12 from pathlib import Path
13
13
14 # Import the Julia LaTeX symbols
14 # Import the Julia LaTeX symbols
15 print('Importing latex_symbols.js from Julia...')
15 print('Importing latex_symbols.js from Julia...')
16 import requests
16 import requests
17 url = 'https://raw.githubusercontent.com/JuliaLang/julia/master/stdlib/REPL/src/latex_symbols.jl'
17 url = 'https://raw.githubusercontent.com/JuliaLang/julia/master/stdlib/REPL/src/latex_symbols.jl'
18 r = requests.get(url)
18 r = requests.get(url)
19
19
20
20
21 # Build a list of key, value pairs
21 # Build a list of key, value pairs
22 print('Building a list of (latex, unicode) key-value pairs...')
22 print('Building a list of (latex, unicode) key-value pairs...')
23 lines = r.text.splitlines()
23 lines = r.text.splitlines()
24
24
25 prefixes_line = lines.index('# "font" prefixes')
25 prefixes_line = lines.index('# "font" prefixes')
26 symbols_line = lines.index('# manual additions:')
26 symbols_line = lines.index('# manual additions:')
27
27
28 prefix_dict = {}
28 prefix_dict = {}
29 for l in lines[prefixes_line + 1: symbols_line]:
29 for l in lines[prefixes_line + 1: symbols_line]:
30 p = l.split()
30 p = l.split()
31 if not p or p[1] == 'latex_symbols': continue
31 if not p or p[1] == 'latex_symbols': continue
32 prefix_dict[p[1]] = p[3]
32 prefix_dict[p[1]] = p[3]
33
33
34 idents = []
34 idents = []
35 for l in lines[symbols_line:]:
35 for l in lines[symbols_line:]:
36 if not '=>' in l: continue #Β if it's not a def, skip
36 if not '=>' in l: continue #Β if it's not a def, skip
37 if '#' in l: l = l[:l.index('#')] #Β get rid of eol comments
37 if '#' in l: l = l[:l.index('#')] #Β get rid of eol comments
38 x, y = l.strip().split('=>')
38 x, y = l.strip().split('=>')
39 if '*' in x: #Β if a prefix is present substitute it with its value
39 if '*' in x: #Β if a prefix is present substitute it with its value
40 p, x = x.split('*')
40 p, x = x.split('*')
41 x = prefix_dict[p][:-1] + x[1:]
41 x = prefix_dict[p][:-1] + x[1:]
42 x, y = x.split('"')[1], y.split('"')[1] #Β get the values in quotes
42 x, y = x.split('"')[1], y.split('"')[1] #Β get the values in quotes
43 idents.append((x, y))
43 idents.append((x, y))
44
44
45 # Filter out non-valid identifiers
45 # Filter out non-valid identifiers
46 print('Filtering out characters that are not valid Python 3 identifiers')
46 print('Filtering out characters that are not valid Python 3 identifiers')
47
47
48 def test_ident(i):
48 def test_ident(i):
49 """Is the unicode string valid in a Python 3 identifier."""
49 """Is the unicode string valid in a Python 3 identifier."""
50 # Some characters are not valid at the start of a name, but we still want to
50 # Some characters are not valid at the start of a name, but we still want to
51 # include them. So prefix with 'a', which is valid at the start.
51 # include them. So prefix with 'a', which is valid at the start.
52 return ('a' + i).isidentifier()
52 return ('a' + i).isidentifier()
53
53
54 assert test_ident("Ξ±")
54 assert test_ident("Ξ±")
55 assert not test_ident('‴')
55 assert not test_ident('‴')
56
56
57 valid_idents = [line for line in idents if test_ident(line[1])]
57 valid_idents = [line for line in idents if test_ident(line[1])]
58
58
59 # Write the `latex_symbols.py` module in the cwd
59 # Write the `latex_symbols.py` module in the cwd
60
60
61 s = """# encoding: utf-8
61 s = """# encoding: utf-8
62
62
63 # DO NOT EDIT THIS FILE BY HAND.
63 # DO NOT EDIT THIS FILE BY HAND.
64
64
65 # To update this file, run the script /tools/gen_latex_symbols.py using Python 3
65 # To update this file, run the script /tools/gen_latex_symbols.py using Python 3
66
66
67 # This file is autogenerated from the file:
67 # This file is autogenerated from the file:
68 # https://raw.githubusercontent.com/JuliaLang/julia/master/base/latex_symbols.jl
68 # https://raw.githubusercontent.com/JuliaLang/julia/master/base/latex_symbols.jl
69 # This original list is filtered to remove any unicode characters that are not valid
69 # This original list is filtered to remove any unicode characters that are not valid
70 # Python identifiers.
70 # Python identifiers.
71
71
72 latex_symbols = {\n
72 latex_symbols = {\n
73 """
73 """
74 for line in valid_idents:
74 for line in valid_idents:
75 s += ' "%s" : "%s",\n' % (line[0], line[1])
75 s += ' "%s" : "%s",\n' % (line[0], line[1])
76 s += "}\n"
76 s += "}\n"
77
77
78 s += """
78 s += """
79
79
80 reverse_latex_symbol = { v:k for k,v in latex_symbols.items()}
80 reverse_latex_symbol = { v:k for k,v in latex_symbols.items()}
81 """
81 """
82
82
83 fn = os.path.join('..','IPython','core','latex_symbols.py')
83 fn = Path('..', 'IPython', 'core', 'latex_symbols.py')
84 print("Writing the file: %s" % fn)
84 print("Writing the file: %s" % str(fn))
85 with open(fn, 'w', encoding='utf-8') as f:
85 fn.write_text(s, encoding='utf-8')
86 f.write(s)
87
86
88
87
General Comments 0
You need to be logged in to leave comments. Login now