passagemath-repl 10.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. passagemath_repl-10.5.1.data/scripts/sage-cachegrind +25 -0
  2. passagemath_repl-10.5.1.data/scripts/sage-callgrind +16 -0
  3. passagemath_repl-10.5.1.data/scripts/sage-cleaner +230 -0
  4. passagemath_repl-10.5.1.data/scripts/sage-coverage +327 -0
  5. passagemath_repl-10.5.1.data/scripts/sage-eval +14 -0
  6. passagemath_repl-10.5.1.data/scripts/sage-fixdoctests +710 -0
  7. passagemath_repl-10.5.1.data/scripts/sage-inline-fortran +12 -0
  8. passagemath_repl-10.5.1.data/scripts/sage-ipynb2rst +50 -0
  9. passagemath_repl-10.5.1.data/scripts/sage-ipython +16 -0
  10. passagemath_repl-10.5.1.data/scripts/sage-massif +25 -0
  11. passagemath_repl-10.5.1.data/scripts/sage-notebook +267 -0
  12. passagemath_repl-10.5.1.data/scripts/sage-omega +25 -0
  13. passagemath_repl-10.5.1.data/scripts/sage-preparse +302 -0
  14. passagemath_repl-10.5.1.data/scripts/sage-run +27 -0
  15. passagemath_repl-10.5.1.data/scripts/sage-run-cython +10 -0
  16. passagemath_repl-10.5.1.data/scripts/sage-runtests +9 -0
  17. passagemath_repl-10.5.1.data/scripts/sage-startuptime.py +163 -0
  18. passagemath_repl-10.5.1.data/scripts/sage-valgrind +34 -0
  19. passagemath_repl-10.5.1.dist-info/METADATA +77 -0
  20. passagemath_repl-10.5.1.dist-info/RECORD +162 -0
  21. passagemath_repl-10.5.1.dist-info/WHEEL +5 -0
  22. passagemath_repl-10.5.1.dist-info/top_level.txt +1 -0
  23. sage/all__sagemath_repl.py +119 -0
  24. sage/doctest/__init__.py +4 -0
  25. sage/doctest/__main__.py +236 -0
  26. sage/doctest/all.py +4 -0
  27. sage/doctest/check_tolerance.py +261 -0
  28. sage/doctest/control.py +1727 -0
  29. sage/doctest/external.py +534 -0
  30. sage/doctest/fixtures.py +383 -0
  31. sage/doctest/forker.py +2665 -0
  32. sage/doctest/marked_output.py +102 -0
  33. sage/doctest/parsing.py +1708 -0
  34. sage/doctest/parsing_test.py +79 -0
  35. sage/doctest/reporting.py +733 -0
  36. sage/doctest/rif_tol.py +124 -0
  37. sage/doctest/sources.py +1657 -0
  38. sage/doctest/test.py +584 -0
  39. sage/doctest/tests/1second.rst +4 -0
  40. sage/doctest/tests/99seconds.rst +4 -0
  41. sage/doctest/tests/abort.rst +5 -0
  42. sage/doctest/tests/atexit.rst +7 -0
  43. sage/doctest/tests/fail_and_die.rst +6 -0
  44. sage/doctest/tests/initial.rst +15 -0
  45. sage/doctest/tests/interrupt.rst +7 -0
  46. sage/doctest/tests/interrupt_diehard.rst +14 -0
  47. sage/doctest/tests/keyboardinterrupt.rst +11 -0
  48. sage/doctest/tests/longtime.rst +5 -0
  49. sage/doctest/tests/nodoctest +5 -0
  50. sage/doctest/tests/random_seed.rst +4 -0
  51. sage/doctest/tests/show_skipped.rst +18 -0
  52. sage/doctest/tests/sig_on.rst +9 -0
  53. sage/doctest/tests/simple_failure.rst +8 -0
  54. sage/doctest/tests/sleep_and_raise.rst +106 -0
  55. sage/doctest/tests/tolerance.rst +31 -0
  56. sage/doctest/util.py +750 -0
  57. sage/interfaces/cleaner.py +48 -0
  58. sage/interfaces/quit.py +163 -0
  59. sage/misc/all__sagemath_repl.py +51 -0
  60. sage/misc/banner.py +235 -0
  61. sage/misc/benchmark.py +221 -0
  62. sage/misc/classgraph.py +134 -0
  63. sage/misc/copying.py +22 -0
  64. sage/misc/cython.py +694 -0
  65. sage/misc/dev_tools.py +745 -0
  66. sage/misc/edit_module.py +304 -0
  67. sage/misc/explain_pickle.py +3079 -0
  68. sage/misc/gperftools.py +361 -0
  69. sage/misc/inline_fortran.py +212 -0
  70. sage/misc/messaging.py +86 -0
  71. sage/misc/pager.py +21 -0
  72. sage/misc/profiler.py +179 -0
  73. sage/misc/python.py +70 -0
  74. sage/misc/remote_file.py +53 -0
  75. sage/misc/sage_eval.py +249 -0
  76. sage/misc/sage_input.py +3621 -0
  77. sage/misc/sagedoc.py +1742 -0
  78. sage/misc/sh.py +38 -0
  79. sage/misc/trace.py +90 -0
  80. sage/repl/__init__.py +16 -0
  81. sage/repl/all.py +15 -0
  82. sage/repl/attach.py +625 -0
  83. sage/repl/configuration.py +186 -0
  84. sage/repl/display/__init__.py +1 -0
  85. sage/repl/display/fancy_repr.py +354 -0
  86. sage/repl/display/formatter.py +318 -0
  87. sage/repl/display/jsmol_iframe.py +290 -0
  88. sage/repl/display/pretty_print.py +153 -0
  89. sage/repl/display/util.py +163 -0
  90. sage/repl/image.py +302 -0
  91. sage/repl/inputhook.py +91 -0
  92. sage/repl/interface_magic.py +298 -0
  93. sage/repl/interpreter.py +854 -0
  94. sage/repl/ipython_extension.py +593 -0
  95. sage/repl/ipython_kernel/__init__.py +1 -0
  96. sage/repl/ipython_kernel/__main__.py +4 -0
  97. sage/repl/ipython_kernel/all_jupyter.py +10 -0
  98. sage/repl/ipython_kernel/install.py +301 -0
  99. sage/repl/ipython_kernel/interact.py +278 -0
  100. sage/repl/ipython_kernel/kernel.py +217 -0
  101. sage/repl/ipython_kernel/widgets.py +466 -0
  102. sage/repl/ipython_kernel/widgets_sagenb.py +587 -0
  103. sage/repl/ipython_tests.py +163 -0
  104. sage/repl/load.py +326 -0
  105. sage/repl/preparse.py +2218 -0
  106. sage/repl/prompts.py +90 -0
  107. sage/repl/rich_output/__init__.py +4 -0
  108. sage/repl/rich_output/backend_base.py +648 -0
  109. sage/repl/rich_output/backend_doctest.py +316 -0
  110. sage/repl/rich_output/backend_emacs.py +151 -0
  111. sage/repl/rich_output/backend_ipython.py +596 -0
  112. sage/repl/rich_output/buffer.py +311 -0
  113. sage/repl/rich_output/display_manager.py +829 -0
  114. sage/repl/rich_output/example.avi +0 -0
  115. sage/repl/rich_output/example.canvas3d +1 -0
  116. sage/repl/rich_output/example.dvi +0 -0
  117. sage/repl/rich_output/example.flv +0 -0
  118. sage/repl/rich_output/example.gif +0 -0
  119. sage/repl/rich_output/example.jpg +0 -0
  120. sage/repl/rich_output/example.mkv +0 -0
  121. sage/repl/rich_output/example.mov +0 -0
  122. sage/repl/rich_output/example.mp4 +0 -0
  123. sage/repl/rich_output/example.ogv +0 -0
  124. sage/repl/rich_output/example.pdf +0 -0
  125. sage/repl/rich_output/example.png +0 -0
  126. sage/repl/rich_output/example.svg +54 -0
  127. sage/repl/rich_output/example.webm +0 -0
  128. sage/repl/rich_output/example.wmv +0 -0
  129. sage/repl/rich_output/example_jmol.spt.zip +0 -0
  130. sage/repl/rich_output/example_wavefront_scene.mtl +7 -0
  131. sage/repl/rich_output/example_wavefront_scene.obj +17 -0
  132. sage/repl/rich_output/output_basic.py +391 -0
  133. sage/repl/rich_output/output_browser.py +103 -0
  134. sage/repl/rich_output/output_catalog.py +54 -0
  135. sage/repl/rich_output/output_graphics.py +320 -0
  136. sage/repl/rich_output/output_graphics3d.py +345 -0
  137. sage/repl/rich_output/output_video.py +231 -0
  138. sage/repl/rich_output/preferences.py +432 -0
  139. sage/repl/rich_output/pretty_print.py +339 -0
  140. sage/repl/rich_output/test_backend.py +201 -0
  141. sage/repl/user_globals.py +214 -0
  142. sage/tests/all.py +0 -0
  143. sage/tests/all__sagemath_repl.py +3 -0
  144. sage/tests/article_heuberger_krenn_kropf_fsm-in-sage.py +630 -0
  145. sage/tests/arxiv_0812_2725.py +351 -0
  146. sage/tests/benchmark.py +1925 -0
  147. sage/tests/book_schilling_zabrocki_kschur_primer.py +795 -0
  148. sage/tests/book_stein_ent.py +651 -0
  149. sage/tests/book_stein_modform.py +558 -0
  150. sage/tests/cmdline.py +796 -0
  151. sage/tests/combinatorial_hopf_algebras.py +52 -0
  152. sage/tests/finite_poset.py +623 -0
  153. sage/tests/functools_partial_src.py +27 -0
  154. sage/tests/gosper-sum.py +218 -0
  155. sage/tests/lazy_imports.py +28 -0
  156. sage/tests/modular_group_cohomology.py +80 -0
  157. sage/tests/numpy.py +21 -0
  158. sage/tests/parigp.py +76 -0
  159. sage/tests/startup.py +27 -0
  160. sage/tests/symbolic-series.py +76 -0
  161. sage/tests/sympy.py +16 -0
  162. sage/tests/test_deprecation.py +31 -0
sage/repl/attach.py ADDED
@@ -0,0 +1,625 @@
1
+ # sage_setup: distribution = sagemath-repl
2
+ r"""
3
+ Keep track of attached files
4
+
5
+ TESTS::
6
+
7
+ sage: attach('http://wstein.org/loadtest.py')
8
+ Traceback (most recent call last):
9
+ ...
10
+ NotImplementedError: you cannot attach a URL
11
+
12
+ Check that no file clutter is produced::
13
+
14
+ sage: dir = tmp_dir()
15
+ sage: src = os.path.join(dir, 'foobar.sage')
16
+ sage: with open(src, 'w') as f:
17
+ ....: _ = f.write('print("<output from attached file>")\n')
18
+ sage: attach(src)
19
+ <output from attached file>
20
+ sage: os.listdir(dir)
21
+ ['foobar.sage']
22
+ sage: detach(src)
23
+
24
+ In debug mode backtraces contain code snippets. We need to manually
25
+ print the traceback because the python doctest module has special
26
+ support for exceptions and does not match them
27
+ character-by-character::
28
+
29
+ sage: import traceback
30
+ sage: with open(src, 'w') as f:
31
+ ....: _ = f.write('# first line\n')
32
+ ....: _ = f.write('# second line\n')
33
+ ....: _ = f.write('raise ValueError("third") # this should appear in the source snippet\n')
34
+ ....: _ = f.write('# fourth line\n')
35
+
36
+ sage: load_attach_mode(attach_debug=False)
37
+ sage: try:
38
+ ....: attach(src)
39
+ ....: except Exception:
40
+ ....: traceback.print_exc(file=sys.stdout)
41
+ Traceback (most recent call last):
42
+ ...
43
+ exec(preparse_file(f.read()) + "\n", globals)
44
+ File "<string>", line 3, in <module>
45
+ ValueError: third
46
+ sage: detach(src)
47
+
48
+ sage: load_attach_mode(attach_debug=True)
49
+ sage: try:
50
+ ....: attach(src)
51
+ ....: except Exception:
52
+ ....: traceback.print_exc(file=sys.stdout)
53
+ Traceback (most recent call last):
54
+ ...
55
+ exec(code, globals)
56
+ File ".../foobar...sage.py", line ..., in <module>
57
+ raise ValueError("third") # this should appear in the source snippet...
58
+ ValueError: third
59
+ sage: detach(src)
60
+ """
61
+
62
+ # ****************************************************************************
63
+ # Copyright (C) 2013 Volker Braun <vbraun.name@gmail.com>
64
+ #
65
+ # This program is free software: you can redistribute it and/or modify
66
+ # it under the terms of the GNU General Public License as published by
67
+ # the Free Software Foundation, either version 2 of the License, or
68
+ # (at your option) any later version.
69
+ # https://www.gnu.org/licenses/
70
+ # ****************************************************************************
71
+ from __future__ import annotations
72
+
73
+ import os
74
+ import time
75
+ from pathlib import Path
76
+ from IPython.core.getipython import get_ipython
77
+
78
+ from sage.repl.load import load, load_wrap
79
+ import sage.repl.inputhook
80
+ import sage.env
81
+
82
+ # The attached files as a dict of {Path object:mtime}
83
+ attached = {}
84
+
85
+
86
+ load_debug_mode = False
87
+ attach_debug_mode = True
88
+
89
+
90
+ def load_attach_mode(load_debug=None, attach_debug=None):
91
+ """
92
+ Get or modify the current debug mode for the behavior of
93
+ :func:`load` and :func:`attach` on ``.sage`` files.
94
+
95
+ In debug mode, loaded or attached ``.sage`` files are preparsed
96
+ through a file to make their tracebacks more informative. If not
97
+ in debug mode, then ``.sage`` files are preparsed in memory only
98
+ for performance.
99
+
100
+ At startup, debug mode is ``True`` for attaching and ``False``
101
+ for loading.
102
+
103
+ .. NOTE::
104
+
105
+ This function should really be deprecated and code executed
106
+ from memory should raise proper tracebacks.
107
+
108
+ INPUT:
109
+
110
+ - ``load_debug`` -- boolean or ``None`` (default); if not
111
+ ``None``, then set a new value for the debug mode for loading
112
+ files
113
+
114
+ - ``attach_debug`` -- boolean or ``None`` (default); same as
115
+ ``load_debug``, but for attaching files
116
+
117
+ OUTPUT:
118
+
119
+ If all input values are ``None``, returns a tuple giving the
120
+ current modes for loading and attaching.
121
+
122
+ EXAMPLES::
123
+
124
+ sage: load_attach_mode()
125
+ (False, True)
126
+ sage: load_attach_mode(attach_debug=False)
127
+ sage: load_attach_mode()
128
+ (False, False)
129
+ sage: load_attach_mode(load_debug=True)
130
+ sage: load_attach_mode()
131
+ (True, False)
132
+ sage: load_attach_mode(load_debug=False, attach_debug=True)
133
+ """
134
+ global load_debug_mode, attach_debug_mode
135
+ if load_debug is None and attach_debug is None:
136
+ return (load_debug_mode, attach_debug_mode)
137
+ if load_debug is not None:
138
+ load_debug_mode = load_debug
139
+ if attach_debug is not None:
140
+ attach_debug_mode = attach_debug
141
+
142
+
143
+ search_paths: list[Path] = []
144
+
145
+
146
+ def load_attach_path(path=None, replace=False):
147
+ """
148
+ Get or modify the current search path for :func:`load` and
149
+ :func:`attach`.
150
+
151
+ INPUT:
152
+
153
+ - ``path`` -- string or list of strings (default: ``None``);
154
+ path(s) to append to or replace the current path
155
+
156
+ - ``replace`` -- boolean (default: ``False``); if ``path`` is not
157
+ ``None``, whether to *replace* the search path instead of
158
+ *appending* to it
159
+
160
+ OUTPUT: none or a *reference* to the current search paths
161
+
162
+ EXAMPLES:
163
+
164
+ First, we extend the example given in :func:`load`'s docstring::
165
+
166
+ sage: sage.repl.attach.reset(); reset_load_attach_path()
167
+ sage: load_attach_path()
168
+ [PosixPath('.')]
169
+ sage: t_dir = tmp_dir()
170
+ sage: fullpath = os.path.join(t_dir, 'test.py')
171
+ sage: with open(fullpath, 'w') as f:
172
+ ....: _ = f.write("print(37 * 3)")
173
+
174
+ We put a new, empty directory on the attach path for testing
175
+ (otherwise this will load ``test.py`` from the current working
176
+ directory if that happens to exist)::
177
+
178
+ sage: import tempfile
179
+ sage: with tempfile.TemporaryDirectory() as d:
180
+ ....: load_attach_path(d, replace=True)
181
+ ....: attach('test.py')
182
+ Traceback (most recent call last):
183
+ ...
184
+ OSError: did not find file 'test.py' to load or attach
185
+ sage: load_attach_path(t_dir)
186
+ sage: attach('test.py')
187
+ 111
188
+ sage: af = attached_files(); len(af)
189
+ 1
190
+ sage: af == [fullpath]
191
+ True
192
+ sage: from pathlib import Path
193
+ sage: sage.repl.attach.reset(); reset_load_attach_path()
194
+ sage: load_attach_path() == [Path('.')]
195
+ True
196
+ sage: with tempfile.TemporaryDirectory() as d:
197
+ ....: load_attach_path(d, replace=True)
198
+ ....: load('test.py')
199
+ Traceback (most recent call last):
200
+ ...
201
+ OSError: did not find file 'test.py' to load or attach
202
+
203
+ The function returns a reference to the path list::
204
+
205
+ sage: reset_load_attach_path(); load_attach_path()
206
+ [PosixPath('.')]
207
+ sage: load_attach_path('/path/to/my/sage/scripts'); load_attach_path()
208
+ [PosixPath('.'), PosixPath('/path/to/my/sage/scripts')]
209
+ sage: load_attach_path(['good', 'bad', 'ugly'], replace=True)
210
+ sage: load_attach_path()
211
+ [PosixPath('good'), PosixPath('bad'), PosixPath('ugly')]
212
+ sage: p = load_attach_path(); p.pop()
213
+ PosixPath('ugly')
214
+ sage: p[0] = 'weird'; load_attach_path()
215
+ ['weird', PosixPath('bad')]
216
+ sage: reset_load_attach_path(); load_attach_path()
217
+ [PosixPath('.')]
218
+ """
219
+ global search_paths
220
+ if path is None:
221
+ return search_paths
222
+
223
+ if not isinstance(path, list):
224
+ path = [Path(path)]
225
+ if replace:
226
+ search_paths = [Path(p) for p in path]
227
+ else:
228
+ for p in path:
229
+ if not p:
230
+ continue
231
+ as_path = Path(p)
232
+ if as_path not in search_paths:
233
+ search_paths.append(as_path)
234
+
235
+
236
+ def reset_load_attach_path():
237
+ """
238
+ Reset the current search path for :func:`load` and :func:`attach`.
239
+
240
+ The default path is ``'.'`` plus any paths specified in the
241
+ environment variable ``SAGE_LOAD_ATTACH_PATH``.
242
+
243
+ EXAMPLES::
244
+
245
+ sage: load_attach_path()
246
+ [PosixPath('.')]
247
+ sage: t_dir = tmp_dir()
248
+ sage: load_attach_path(t_dir)
249
+ sage: from pathlib import Path
250
+ sage: Path(t_dir) in load_attach_path()
251
+ True
252
+ sage: reset_load_attach_path(); load_attach_path()
253
+ [PosixPath('.')]
254
+
255
+ At startup, Sage adds colon-separated paths in the environment
256
+ variable ``SAGE_LOAD_ATTACH_PATH``::
257
+
258
+ sage: reset_load_attach_path(); load_attach_path()
259
+ [PosixPath('.')]
260
+ sage: os.environ['SAGE_LOAD_ATTACH_PATH'] = '/veni/vidi:vici:'
261
+ sage: from importlib import reload
262
+ sage: reload(sage.repl.attach) # Simulate startup
263
+ <module 'sage.repl.attach' from '...'>
264
+ sage: load_attach_path()
265
+ [PosixPath('.'), PosixPath('/veni/vidi'), PosixPath('vici')]
266
+ sage: del os.environ['SAGE_LOAD_ATTACH_PATH']
267
+ sage: reload(sage.repl.preparse) # Simulate startup
268
+ <module 'sage.repl.preparse' from '...'>
269
+ sage: reset_load_attach_path(); load_attach_path()
270
+ [PosixPath('.')]
271
+ """
272
+ global search_paths
273
+ search_paths = [Path()]
274
+ for path in os.environ.get('SAGE_LOAD_ATTACH_PATH', '').split(':'):
275
+ load_attach_path(path=path)
276
+
277
+
278
+ # Set up the initial search path for loading and attaching files. A
279
+ # user can modify the path with the function load_attach_path.
280
+ reset_load_attach_path()
281
+
282
+
283
+ def attach(*files):
284
+ """
285
+ Attach a file or files to a running instance of Sage and also load
286
+ that file.
287
+
288
+ .. NOTE::
289
+
290
+ Attaching files uses the Python inputhook, which will conflict
291
+ with other inputhook users. This generally includes GUI main loop
292
+ integrations, for example tkinter. So you can only use tkinter or
293
+ attach, but not both at the same time.
294
+
295
+ INPUT:
296
+
297
+ - ``files`` -- list of filenames (strings) to attach
298
+
299
+ OUTPUT:
300
+
301
+ Each file is read in and added to an internal list of watched files.
302
+ The meaning of reading in a file depends on the file type:
303
+
304
+ - ``.py`` files are read in with no preparsing (so, e.g., ``2^3`` is 2
305
+ bit-xor 3);
306
+
307
+ - ``.sage`` files are preparsed, then the result is read in;
308
+
309
+ - ``.pyx`` files are *not* preparsed, but rather are compiled to a
310
+ module ``m`` and then ``from m import *`` is executed.
311
+
312
+ The contents of the file are then loaded, which means they are read
313
+ into the running Sage session. For example, if ``foo.sage`` contains
314
+ ``x=5``, after attaching ``foo.sage`` the variable ``x`` will be set
315
+ to 5. Moreover, any time you change ``foo.sage``, before you execute
316
+ a command, the attached file will be re-read automatically (with no
317
+ intervention on your part).
318
+
319
+ .. SEEALSO::
320
+
321
+ :meth:`~sage.repl.load.load` is the same as :func:`attach`, but
322
+ does not automatically reload a file when it changes.
323
+
324
+ EXAMPLES:
325
+
326
+ You attach a file, e.g., ``foo.sage`` or ``foo.py`` or
327
+ ``foo.pyx``, to a running Sage session by typing::
328
+
329
+ sage: attach('foo.sage') # not tested
330
+
331
+ Here we test attaching multiple files at once::
332
+
333
+ sage: sage.repl.attach.reset()
334
+ sage: t1 = tmp_filename(ext='.py')
335
+ sage: with open(t1,'w') as f: _ = f.write("print('hello world')")
336
+ sage: t2 = tmp_filename(ext='.py')
337
+ sage: with open(t2,'w') as f: _ = f.write("print('hi there xxx')")
338
+ sage: attach(t1, t2)
339
+ hello world
340
+ hi there xxx
341
+ sage: af = attached_files(); len(af)
342
+ 2
343
+ sage: t1 in af and t2 in af
344
+ True
345
+
346
+ .. SEEALSO::
347
+
348
+ - :meth:`attached_files` returns a list of
349
+ all currently attached files.
350
+
351
+ - :meth:`detach` instructs Sage to remove a
352
+ file from the internal list of watched files.
353
+
354
+ - :meth:`load_attach_path` allows you to
355
+ get or modify the current search path for loading and attaching
356
+ files.
357
+ """
358
+ try:
359
+ ipy = get_ipython()
360
+ except NameError:
361
+ ipy = None
362
+ global attached
363
+ for filename in files:
364
+ if ipy:
365
+ code = load_wrap(filename, attach=True)
366
+ ipy.run_cell(code)
367
+ else:
368
+ load(filename, globals(), attach=True)
369
+
370
+
371
+ def add_attached_file(filename):
372
+ """
373
+ Add to the list of attached files.
374
+
375
+ This is a callback to be used from
376
+ :func:`~sage.repl.load.load` after evaluating the attached
377
+ file the first time.
378
+
379
+ INPUT:
380
+
381
+ - ``filename`` -- string (the fully qualified file name)
382
+ or :class:`Path` object
383
+
384
+ EXAMPLES::
385
+
386
+ sage: import sage.repl.attach as af
387
+ sage: af.reset()
388
+ sage: t = tmp_filename(ext='.py')
389
+ sage: af.add_attached_file(t)
390
+ sage: af.attached_files()
391
+ ['/.../tmp_....py']
392
+ sage: af.detach(t)
393
+ sage: af.attached_files()
394
+ []
395
+ """
396
+ sage.repl.inputhook.install()
397
+ fpath = Path(filename).absolute()
398
+ attached[fpath] = fpath.stat().st_mtime
399
+
400
+
401
+ def attached_files() -> list:
402
+ """
403
+ Return a list of all files attached to the current session with
404
+ :meth:`attach`.
405
+
406
+ OUTPUT: the filenames in a sorted list of strings
407
+
408
+ EXAMPLES::
409
+
410
+ sage: sage.repl.attach.reset()
411
+ sage: t = tmp_filename(ext='.py')
412
+ sage: with open(t,'w') as f: _ = f.write("print('hello world')")
413
+ sage: attach(t)
414
+ hello world
415
+ sage: af = attached_files(); af
416
+ ['/....py']
417
+ sage: af == [t]
418
+ True
419
+ """
420
+ global attached
421
+ return sorted(str(f) for f in attached)
422
+
423
+
424
+ def detach(filename):
425
+ """
426
+ Detach a file.
427
+
428
+ This is the counterpart to :meth:`attach`.
429
+
430
+ INPUT:
431
+
432
+ - ``filename`` -- string, a list of strings or a tuple of strings
433
+ or a :class:`Path`, a list of :class:`Path` or a tuple of :class:`Path`
434
+
435
+ EXAMPLES::
436
+
437
+ sage: sage.repl.attach.reset()
438
+ sage: t = tmp_filename(ext='.py')
439
+ sage: with open(t,'w') as f: _ = f.write("print('hello world')")
440
+ sage: attach(t)
441
+ hello world
442
+ sage: af = attached_files(); len(af)
443
+ 1
444
+ sage: af == [t]
445
+ True
446
+ sage: detach(t)
447
+ sage: attached_files()
448
+ []
449
+
450
+ sage: sage.repl.attach.reset(); reset_load_attach_path()
451
+ sage: load_attach_path()
452
+ [PosixPath('.')]
453
+ sage: t_dir = tmp_dir()
454
+ sage: fullpath = os.path.join(t_dir, 'test.py')
455
+ sage: with open(fullpath, 'w') as f: _ = f.write("print(37 * 3)")
456
+ sage: load_attach_path(t_dir, replace=True)
457
+ sage: attach('test.py')
458
+ 111
459
+ sage: af = attached_files(); len(af)
460
+ 1
461
+ sage: af == [os.path.normpath(fullpath)]
462
+ True
463
+ sage: detach('test.py')
464
+ sage: attached_files()
465
+ []
466
+ sage: attach('test.py')
467
+ 111
468
+ sage: fullpath = os.path.join(t_dir, 'test2.py')
469
+ sage: with open(fullpath, 'w') as f: _ = f.write("print(3)")
470
+ sage: attach('test2.py')
471
+ 3
472
+ sage: detach(attached_files())
473
+ sage: attached_files()
474
+ []
475
+
476
+ TESTS::
477
+
478
+ sage: detach('/dev/null/foobar.sage')
479
+ Traceback (most recent call last):
480
+ ...
481
+ ValueError: file '/dev/null/foobar.sage' is not attached, see attached_files()
482
+ """
483
+ if isinstance(filename, str):
484
+ filelist = [Path(filename)]
485
+ else:
486
+ filelist = [Path(x) for x in filename]
487
+
488
+ global attached
489
+ for filename in filelist:
490
+ fpath = filename.expanduser()
491
+ if not fpath.is_absolute():
492
+ for path in load_attach_path():
493
+ fpath = path.expanduser() / filename
494
+ fpath = fpath.absolute()
495
+ if fpath in attached:
496
+ break
497
+ abs_fpath = fpath.absolute()
498
+ if fpath in attached:
499
+ attached.pop(fpath)
500
+ elif abs_fpath in attached:
501
+ attached.pop(abs_fpath)
502
+ else:
503
+ raise ValueError("file '{0}' is not attached, see attached_files()".format(filename))
504
+ if not attached:
505
+ sage.repl.inputhook.uninstall()
506
+
507
+
508
+ def reset():
509
+ """
510
+ Remove all the attached files from the list of attached files.
511
+
512
+ EXAMPLES::
513
+
514
+ sage: sage.repl.attach.reset()
515
+ sage: t = tmp_filename(ext='.py')
516
+ sage: with open(t,'w') as f: _ = f.write("print('hello world')")
517
+ sage: attach(t)
518
+ hello world
519
+ sage: af = attached_files(); len(af)
520
+ 1
521
+ sage: af == [t]
522
+ True
523
+ sage: sage.repl.attach.reset()
524
+ sage: attached_files()
525
+ []
526
+ """
527
+ global attached
528
+ attached = {}
529
+
530
+
531
+ def modified_file_iterator():
532
+ """
533
+ Iterate over the changed files.
534
+
535
+ As a side effect the stored time stamps are updated with the
536
+ actual time stamps. So if you iterate over the attached files in
537
+ order to reload them and you hit an error then the subsequent
538
+ files are not marked as read.
539
+
540
+ Files that are in the process of being saved are excluded.
541
+
542
+ EXAMPLES::
543
+
544
+ sage: sage.repl.attach.reset()
545
+ sage: t = tmp_filename(ext='.py')
546
+ sage: attach(t)
547
+ sage: from sage.repl.attach import modified_file_iterator
548
+ sage: list(modified_file_iterator())
549
+ []
550
+ sage: sleep(1) # filesystem mtime granularity
551
+ sage: with open(t, 'w') as f: _ = f.write('1')
552
+ sage: list(modified_file_iterator())
553
+ [(PosixPath('/.../tmp_....py'), time.struct_time(...))]
554
+ """
555
+ global attached
556
+ modified = {}
557
+ for filename in list(attached):
558
+ old_tm = attached[filename]
559
+ if not filename.exists():
560
+ print('### detaching file {0} because it does not exist (deleted?) ###'.format(filename))
561
+ detach(filename)
562
+ continue
563
+ new_tm = filename.stat().st_mtime
564
+ if new_tm > old_tm:
565
+ modified[filename] = new_tm
566
+
567
+ if not modified:
568
+ return
569
+ time.sleep(0.1) # sleep 100ms to give the editor time to finish saving
570
+
571
+ for filename in list(modified):
572
+ old_tm = modified[filename]
573
+ new_tm = filename.stat().st_mtime
574
+ if new_tm == old_tm:
575
+ # file was modified but did not change in the last 100ms
576
+ attached[filename] = new_tm
577
+ yield filename, time.gmtime(new_tm)
578
+
579
+
580
+ def reload_attached_files_if_modified():
581
+ r"""
582
+ Reload attached files that have been modified.
583
+
584
+ This is the internal implementation of the attach mechanism.
585
+
586
+ EXAMPLES::
587
+
588
+ sage: sage.repl.attach.reset()
589
+ sage: from sage.repl.interpreter import get_test_shell
590
+ sage: shell = get_test_shell()
591
+ sage: tmp = tmp_filename(ext='.py')
592
+ sage: with open(tmp, 'w') as f: _ = f.write('a = 2\n')
593
+ sage: shell.run_cell('attach({0})'.format(repr(tmp)))
594
+ sage: shell.run_cell('a')
595
+ 2
596
+ sage: sleep(1) # filesystem mtime granularity
597
+ sage: with open(tmp, 'w') as f: _ = f.write('a = 3\n')
598
+
599
+ Note that the doctests are never really at the command prompt
600
+ where the automatic reload is triggered. So we have to do it
601
+ manually::
602
+
603
+ sage: shell.run_cell('from sage.repl.attach import reload_attached_files_if_modified')
604
+ sage: shell.run_cell('reload_attached_files_if_modified()')
605
+ ### reloading attached file tmp_....py modified at ... ###
606
+
607
+ sage: shell.run_cell('a')
608
+ 3
609
+ sage: shell.run_cell('detach({0})'.format(repr(tmp)))
610
+ sage: shell.run_cell('attached_files()')
611
+ []
612
+ sage: shell.quit()
613
+ """
614
+ ip = get_ipython()
615
+ for filename, mtime in modified_file_iterator():
616
+ basename = filename.name
617
+ timestr = time.strftime('%T', mtime)
618
+ notice = '### reloading attached file {0} modified at {1} ###'.format(basename, timestr)
619
+ if ip:
620
+ print(notice)
621
+ code = load_wrap(filename, attach=True)
622
+ ip.run_cell(code)
623
+ else:
624
+ print(notice)
625
+ load(filename, globals(), attach=True)