passagemath-repl 10.4.62__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.4.62.data/scripts/sage-cachegrind +25 -0
  2. passagemath_repl-10.4.62.data/scripts/sage-callgrind +16 -0
  3. passagemath_repl-10.4.62.data/scripts/sage-cleaner +230 -0
  4. passagemath_repl-10.4.62.data/scripts/sage-coverage +327 -0
  5. passagemath_repl-10.4.62.data/scripts/sage-eval +14 -0
  6. passagemath_repl-10.4.62.data/scripts/sage-fixdoctests +708 -0
  7. passagemath_repl-10.4.62.data/scripts/sage-inline-fortran +12 -0
  8. passagemath_repl-10.4.62.data/scripts/sage-ipynb2rst +50 -0
  9. passagemath_repl-10.4.62.data/scripts/sage-ipython +16 -0
  10. passagemath_repl-10.4.62.data/scripts/sage-massif +25 -0
  11. passagemath_repl-10.4.62.data/scripts/sage-notebook +267 -0
  12. passagemath_repl-10.4.62.data/scripts/sage-omega +25 -0
  13. passagemath_repl-10.4.62.data/scripts/sage-preparse +302 -0
  14. passagemath_repl-10.4.62.data/scripts/sage-run +27 -0
  15. passagemath_repl-10.4.62.data/scripts/sage-run-cython +10 -0
  16. passagemath_repl-10.4.62.data/scripts/sage-runtests +9 -0
  17. passagemath_repl-10.4.62.data/scripts/sage-startuptime.py +163 -0
  18. passagemath_repl-10.4.62.data/scripts/sage-valgrind +34 -0
  19. passagemath_repl-10.4.62.dist-info/METADATA +77 -0
  20. passagemath_repl-10.4.62.dist-info/RECORD +162 -0
  21. passagemath_repl-10.4.62.dist-info/WHEEL +5 -0
  22. passagemath_repl-10.4.62.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 +131 -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 +246 -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/__init__.py +1 -0
  143. sage/tests/all.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 +1923 -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 +790 -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
@@ -0,0 +1,25 @@
1
+ #!/bin/sh
2
+
3
+ # We reuse the gdb pythonstartup script.
4
+ PYTHONSTARTUP=`dirname $0`/sage-ipython
5
+ export PYTHONSTARTUP
6
+ echo $PYTHONSTARTUP
7
+ if [ ! -d "$DOT_SAGE/valgrind" ]; then
8
+ mkdir "$DOT_SAGE/valgrind"
9
+ fi
10
+
11
+ LOG="$DOT_SAGE"/valgrind/sage-cachegrind.%p
12
+ echo "Log file is $LOG"
13
+
14
+ CACHEGRIND_FLAGS="--log-file=$LOG "; export CACHEGRIND_FLAGS
15
+ if [ "$SAGE_CACHEGRIND_FLAGS" ]; then
16
+ echo "Overwriting cachegrind flags with:"
17
+ echo $SAGE_CACHEGRIND_FLAGS
18
+ CACHEGRIND_FLAGS=$SAGE_CACHEGRIND_FLAGS; export CACHEGRIND_FLAGS
19
+ else
20
+ echo "Using default flags:"
21
+ echo $CACHEGRIND_FLAGS
22
+ fi
23
+
24
+ valgrind --tool=cachegrind $CACHEGRIND_FLAGS sage-python -i
25
+
@@ -0,0 +1,16 @@
1
+ #!/bin/sh
2
+
3
+ if [ ! -d "$DOT_SAGE/valgrind" ]; then
4
+ mkdir "$DOT_SAGE/valgrind"
5
+ fi
6
+ LOG="$DOT_SAGE"/valgrind/sage-callgrind.%p
7
+
8
+ CALLGRIND_FLAGS="--callgrind-out-file=$LOG "
9
+ if [ "$SAGE_CALLGRIND_FLAGS" ]; then
10
+ echo "Overwriting callgrind flags with: $SAGE_CALLGRIND_FLAGS"
11
+ CALLGRIND_FLAGS=$SAGE_CALLGRIND_FLAGS
12
+ else
13
+ echo "Using default flags: $CALLGRIND_FLAGS"
14
+ fi
15
+
16
+ valgrind --tool=callgrind $CALLGRIND_FLAGS sage-ipython "$@" -i
@@ -0,0 +1,230 @@
1
+ #!python
2
+
3
+ #*****************************************************************************
4
+ # This is the sage monitor *daemon*, which cleans up after Sage.
5
+ # Some things that it cleans up:
6
+ # * $DOT_SAGE/temp/HOSTNAME/pid directories
7
+ # * Processes that Sage spawns. If a copy of Sage isn't
8
+ # running, then any process it spawned should have its
9
+ # process group killed
10
+ #*****************************************************************************
11
+ # Copyright (C) 2005, 2006, 2007 William Stein <wstein@gmail.com>
12
+ #
13
+ # Distributed under the terms of the GNU General Public License (GPL)
14
+ # as published by the Free Software Foundation; either version 2 of
15
+ # the License, or (at your option) any later version.
16
+ # http://www.gnu.org/licenses/
17
+ #*****************************************************************************
18
+
19
+
20
+ import os, shutil, sys, time, socket, errno, signal, atexit
21
+
22
+
23
+ HOSTNAME = os.environ.get('HOSTNAME', socket.gethostname())
24
+ DOT_SAGE = os.environ['DOT_SAGE']
25
+
26
+ SAGE_TMP_ROOT = os.path.join(DOT_SAGE, 'temp', HOSTNAME)
27
+ logfile = os.path.join(SAGE_TMP_ROOT, 'cleaner.log')
28
+ pidfile = os.path.join(SAGE_TMP_ROOT, 'cleaner.pid')
29
+ # pidfile used by earlier versions of sage-cleaner (before #15457)
30
+ old_pidfile = os.path.join(DOT_SAGE, 'temp', 'cleaner-%s.pid'%HOSTNAME)
31
+
32
+ import logging
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ def mkdir_p(path):
37
+ """
38
+ A "mkdir -p" command that makes intermediate directories if necessary
39
+ """
40
+ try:
41
+ os.makedirs(path)
42
+ except OSError:
43
+ if not os.path.isdir(path):
44
+ raise
45
+
46
+
47
+ def rm_rf(file_or_path):
48
+ """
49
+ Force recursive delete, ignoring errors.
50
+ """
51
+ try:
52
+ os.unlink(file_or_path)
53
+ except OSError as e:
54
+ if e.errno == errno.EISDIR or e.errno == errno.EPERM:
55
+ shutil.rmtree(file_or_path, ignore_errors=True)
56
+
57
+
58
+ def is_running(pid):
59
+ """
60
+ Return True if and only if there is a process with id pid running.
61
+ """
62
+ try:
63
+ os.kill(pid,0)
64
+ return True
65
+ except OSError:
66
+ return False
67
+
68
+
69
+ def cleanup():
70
+ tmp_dirs = os.listdir(SAGE_TMP_ROOT)
71
+ try:
72
+ tmp_dirs.remove('cleaner.pid')
73
+ tmp_dirs.remove('cleaner.log')
74
+ except ValueError:
75
+ logger.error('No cleaner pid/log in SAGE_TMP_ROOT')
76
+ # Convert strings to integers
77
+ pid_list = []
78
+ for dir_entry in tmp_dirs:
79
+ try:
80
+ pid_list.append(int(dir_entry))
81
+ except ValueError:
82
+ badfile = os.path.join(SAGE_TMP_ROOT, dir_entry)
83
+ logger.warning('File %s must not be in SAGE_TMP_ROOT, deleting', badfile)
84
+ rm_rf(badfile)
85
+
86
+ logger.info("Checking PIDs %s", pid_list)
87
+ for parent_pid in pid_list:
88
+ try:
89
+ if not is_running(parent_pid):
90
+ logger.info("Process %s is no longer running, so we clean up", parent_pid)
91
+ dir_name = os.path.join(SAGE_TMP_ROOT, str(parent_pid))
92
+ spawned_processes = os.path.join(dir_name, 'spawned_processes')
93
+ if not os.path.isfile(spawned_processes) \
94
+ or kill_spawned_jobs(spawned_processes, parent_pid):
95
+ logger.info('Deleting %s', dir_name)
96
+ rm_rf(dir_name)
97
+ except Exception:
98
+ logger.error("Exception while cleaning up PID %s:", parent_pid, exc_info=True)
99
+
100
+ return len(pid_list)
101
+
102
+ def cleanup_cruft():
103
+ """ remove directories leftover from improper shutdown """
104
+ tmp_dirs = os.listdir(SAGE_TMP_ROOT)
105
+ for dir_entry in tmp_dirs:
106
+ baddir = os.path.join(SAGE_TMP_ROOT, dir_entry)
107
+ if os.path.isdir(baddir):
108
+ logger.warning('Removing old directory %s from SAGE_TMP_ROOT', baddir)
109
+ rm_rf(baddir)
110
+
111
+ def kill_spawned_jobs(jobfile, parent_pid):
112
+ logger.info("Killing %s's spawned jobs", parent_pid)
113
+ killed_them_all = True
114
+ for job in open(jobfile).readlines():
115
+ try:
116
+ pid, cmd = job.strip().split(' ', 1)
117
+ pid = int(pid)
118
+ logger.info("--> Killing %r with PID %s and parent PID %s", cmd, pid, parent_pid)
119
+ except Exception:
120
+ logger.error("Exception while processing job %r from %s, ignoring", job, jobfile)
121
+ continue
122
+ try:
123
+ pgrp = os.getpgid(pid)
124
+ logger.info("--> Killing process group %s", pgrp)
125
+ os.killpg(pgrp, signal.SIGKILL)
126
+ except OSError:
127
+ pass
128
+ if is_running(pid):
129
+ logger.error("--> Failed to kill %s", pid)
130
+ # try again later
131
+ killed_them_all = False
132
+ return killed_them_all
133
+
134
+
135
+ def exit_handler():
136
+ logger.info("Removing pidfile and logfile")
137
+ rm_rf(pidfile)
138
+ rm_rf(logfile)
139
+
140
+
141
+ def setup_daemon():
142
+ mkdir_p(SAGE_TMP_ROOT)
143
+
144
+ logger.setLevel(logging.DEBUG)
145
+ h = logging.StreamHandler() # log to stderr
146
+ h.setLevel(logging.DEBUG)
147
+ logger.addHandler(h)
148
+ h = logging.FileHandler(filename=logfile) # log to logfile
149
+ h.setLevel(logging.INFO)
150
+ logger.addHandler(h)
151
+
152
+ logger.info("SAGE_TMP_ROOT = %s", SAGE_TMP_ROOT)
153
+
154
+ # Check old pidfile (to allow old and new sage-cleaners to peacefully coexist)
155
+ try:
156
+ pid = int(open(old_pidfile).read())
157
+ except (IOError, ValueError):
158
+ pass
159
+ else:
160
+ if is_running(pid):
161
+ logger.info("old sage-cleaner is already running with PID %s, exiting", pid)
162
+ sys.exit(0)
163
+
164
+ try:
165
+ pid = int(open(pidfile).read())
166
+ except (IOError, ValueError):
167
+ pass
168
+ else:
169
+ if is_running(pid):
170
+ logger.info("sage-cleaner is already running with PID %s, exiting", pid)
171
+ sys.exit(0)
172
+ with open(pidfile, 'w') as f:
173
+ f.write(str(os.getpid())) # initialize pid file
174
+ atexit.register(exit_handler)
175
+
176
+
177
+ def fix_old_mistakes():
178
+ """
179
+ Experience is simply the name we give our mistakes.
180
+ """
181
+ # inconsistently escaped hyphens with underscores (https://github.com/sagemath/sage/issues/14055)
182
+ wrong_hostname = HOSTNAME.replace('-','_').replace('/','_').replace('\\','_')
183
+ wrong_sage_tmp_root = os.path.join(DOT_SAGE, 'temp', wrong_hostname)
184
+ if wrong_sage_tmp_root != SAGE_TMP_ROOT and os.path.exists(wrong_sage_tmp_root):
185
+ logger.warning('Deleting invalid temp dir %s', wrong_sage_tmp_root)
186
+ rm_rf(wrong_sage_tmp_root)
187
+
188
+ # SAGE_TMP in DOT_SAGE/tmp instead of DOT_SAGE/temp
189
+ import glob
190
+ old_root = glob.glob(os.path.join(DOT_SAGE, 'tmp', HOSTNAME+'-*'))
191
+ if wrong_hostname != HOSTNAME:
192
+ old_root += glob.glob(os.path.join(DOT_SAGE, 'tmp', wrong_hostname+'-*'))
193
+ for old_tmp in old_root:
194
+ logger.warning('Deleting invalid temp dir %s', old_tmp)
195
+ rm_rf(old_tmp)
196
+
197
+ # PID file before it was moved into SAGE_TMP_ROOT
198
+ rm_rf(old_pidfile)
199
+
200
+
201
+ if __name__ == '__main__':
202
+ setup_daemon()
203
+ fix_old_mistakes()
204
+ logger.info("Starting sage-cleaner with PID %s", os.getpid())
205
+ cleanup_cruft()
206
+
207
+ if len(sys.argv) > 1:
208
+ wait = int(sys.argv[1])
209
+ else:
210
+ wait = 10
211
+
212
+ # Initial cleanup, ignore time
213
+ running_sages = cleanup()
214
+ cleanup_time = 0.0
215
+ count = 1
216
+
217
+ # In the first 10 iterations, continue anyway (even if there are
218
+ # no Sage processes running) because it can happen that Sage is
219
+ # not yet started.
220
+ while count < 10 or running_sages > 0:
221
+ # Time to wait = "wait" plus 20 times the time of last cleanup().
222
+ # This ensures that sage-cleaner causes a load of at most 5%.
223
+ time.sleep(wait + 20*cleanup_time)
224
+ count += 1
225
+ t0 = time.time()
226
+ running_sages = cleanup()
227
+ cleanup_time = time.time() - t0
228
+ logger.debug("cleanup() #{:d} took {:.2f}s".format(count, cleanup_time))
229
+
230
+ logger.info("sage-cleaner is finished")
@@ -0,0 +1,327 @@
1
+ #!python
2
+ import os
3
+ import sys
4
+ from tokenize import (NEWLINE, COMMENT, INDENT, DEDENT, STRING, NL,
5
+ NAME, OP, generate_tokens)
6
+ import argparse
7
+
8
+ parser = argparse.ArgumentParser(description='Look into Sage files for wrong doctests.')
9
+ parser.add_argument('filename', type=str, nargs='*', help='filename or a directory')
10
+ parser.add_argument('--all', action='store_true', help='give summary info about all files in the Sage library')
11
+ parser.add_argument('--only-bad', action='store_true', help='only print info for bad formatted files')
12
+ parser.add_argument('--summary', action='store_true', help='only print a short summary')
13
+
14
+ args = parser.parse_args()
15
+
16
+
17
+ def coverage_all(directory):
18
+ os.chdir(directory)
19
+ r = os.popen('sage-coverage * | grep SCORE').readlines()
20
+
21
+ s = []
22
+ scr = 0
23
+ total = 0
24
+ for x in r:
25
+ y = x.lstrip('SCORE ')
26
+ i = y.rfind(' of ')
27
+ j = y.rfind(')')
28
+ n = int(y[i+4:j])
29
+
30
+ i = y.rfind(':')
31
+ j = y.rfind('%')
32
+ scr += float(y[i+1:j]) * float(n)
33
+
34
+ total += n
35
+
36
+ s.append(y)
37
+
38
+ print(''.join(s))
39
+
40
+ # Issue #5859: Don't crash if there isn't anything to test.
41
+ score = 100.0
42
+ if total != 0:
43
+ score = (float(scr) / total)
44
+
45
+ print("Overall weighted coverage score: {:.1f}%".format(score))
46
+ print("Total number of functions: {}".format(total))
47
+
48
+ # Print up to 3 doctest coverage goals.
49
+ i = 0
50
+ for goal in [70, 75, 80, 85, 90, 95, 99]:
51
+ if score < goal:
52
+ i += 1
53
+ if i > 3: break
54
+ need = int((goal*total - scr)/100.0)
55
+ print("We need {:>4} more function{} to get to {}% coverage."
56
+ .format(need, "" if (need == 1) else "s", goal))
57
+
58
+ if args.all:
59
+ if not args.filename:
60
+ coverage_all(os.path.join(os.environ["SAGE_SRC"], 'sage'))
61
+ elif len(args.filename) == 1:
62
+ coverage_all(args.filename[0])
63
+ else:
64
+ print("sage-coverage: error: --all only accepts one filename argument")
65
+ sys.exit(1)
66
+ sys.exit(0)
67
+
68
+ if not args.filename:
69
+ print("sage-coverage: error: if --all is not given, at least one filename argument is expected")
70
+ sys.exit(1)
71
+
72
+ # Collect coverage results for one file
73
+ class CoverageResults:
74
+ def __init__(self, filename=""):
75
+ """
76
+ INPUT:
77
+
78
+ - ``filename`` -- name of the file, only for display purposes.
79
+ """
80
+ self.no_doc = []
81
+ self.no_test = []
82
+ self.good = []
83
+ self.possibly_wrong = []
84
+ self.filename = filename
85
+
86
+ def report(self):
87
+ """
88
+ Print coverage results.
89
+ """
90
+ num_functions = len(self.good) + len(self.no_doc) + len(self.no_test)
91
+ if not num_functions:
92
+ print("No functions in", self.filename)
93
+ return
94
+
95
+ score = (100.0 * len(self.good)) / float(num_functions)
96
+
97
+ print("SCORE {}: {:.1f}% ({} of {})".format(self.filename, score, len(self.good), num_functions))
98
+
99
+ if self.no_doc:
100
+ print("\nMissing documentation:")
101
+ for f in self.no_doc:
102
+ print(" *", f)
103
+ if self.no_test:
104
+ print("\nMissing doctests:")
105
+ for f in self.no_test:
106
+ print(" *", f)
107
+
108
+ if self.possibly_wrong:
109
+ print("\nPossibly wrong (function name doesn't occur in doctests):")
110
+ for f in self.possibly_wrong:
111
+ print(" *", f)
112
+
113
+
114
+ def handle_function(self, name, fullname, docstring):
115
+ """
116
+ Check coverage of one function and store result.
117
+
118
+ INPUT:
119
+
120
+ - ``name`` -- bare function name (e.g. "foo")
121
+
122
+ - ``fullname`` -- complete function definition (e.g. "def foo(arg=None)")
123
+
124
+ - ``docstring`` -- the docstring, or ``None`` if there is no docstring
125
+ """
126
+ # Skip certain names
127
+ if name in ['__dealloc__', '__new__', '_']:
128
+ return
129
+
130
+ if not docstring:
131
+ self.no_doc.append(fullname)
132
+ return
133
+
134
+ if "pytest" in docstring:
135
+ self.good.append(fullname)
136
+ return
137
+
138
+ if "sage: " not in docstring:
139
+ self.no_test.append(fullname)
140
+ return
141
+
142
+ # If the name is of the form _xxx_, then the doctest is always
143
+ # considered indirect.
144
+ if name[0] == "_" and name[-1] == "_":
145
+ is_indirect = True
146
+ else:
147
+ is_indirect = "indirect doctest" in docstring
148
+
149
+ if not is_indirect and not name in docstring:
150
+ self.possibly_wrong.append(fullname)
151
+ self.good.append(fullname)
152
+
153
+
154
+ def check_file(self, f):
155
+ """
156
+ Check the coverage of one file.
157
+
158
+ INPUT:
159
+
160
+ - ``f``: an open file
161
+
162
+ OUTPUT: ``self``
163
+ """
164
+ # Where are we in a function definition?
165
+ BEGINOFLINE = 0 # Beginning of new logical line
166
+ UNKNOWN = -99 # Not at all in a function definition
167
+ DEFNAMES = 1 # In function definition before first open paren
168
+ DEFARGS = 2 # In function arguments or between closing paren and final colon
169
+ DOCSTRING = -1 # Looking for docstring
170
+
171
+ state = BEGINOFLINE
172
+
173
+ # Previous token type seen
174
+ prevtyp = NEWLINE
175
+
176
+ # Indentation level
177
+ indent = 0
178
+
179
+ # Indentation level of last "def" statement
180
+ # or None if no such statement.
181
+ defindent = None
182
+
183
+ for (typ, tok, start, end, logical_line) in generate_tokens(f.readline):
184
+ # Completely ignore comments or continuation newlines
185
+ if typ == COMMENT or typ == NL:
186
+ continue
187
+
188
+ # Handle indentation
189
+ if typ == INDENT:
190
+ indent += 1
191
+ continue
192
+ elif typ == DEDENT:
193
+ indent -= 1
194
+ if (defindent is not None and indent <= defindent):
195
+ defindent = None
196
+ continue
197
+
198
+ # Check for "def" or "cpdef" ("cdef" functions don't need to be documented).
199
+ # Skip nested functions (with indent > defindent).
200
+ if state == BEGINOFLINE:
201
+ if typ == NAME and (tok in ["def", "cpdef"]) and (defindent is None or indent <= defindent):
202
+ state = DEFNAMES
203
+ deffullname = "line %s: "%start[0]
204
+ defparen = 0 # Number of open parentheses
205
+ else:
206
+ state = UNKNOWN
207
+
208
+ if state == DOCSTRING:
209
+ if typ != NEWLINE:
210
+ docstring = None
211
+ if typ == STRING:
212
+ docstring = tok
213
+ self.handle_function(defname, deffullname, docstring)
214
+ state = UNKNOWN
215
+
216
+ if state == DEFNAMES:
217
+ if typ == NAME:
218
+ if tok == "class": # Make sure that cdef classes are ignored
219
+ state = UNKNOWN
220
+ # Last NAME token before opening parenthesis is
221
+ # the function name.
222
+ defname = tok
223
+ elif tok == '(':
224
+ state = DEFARGS
225
+ else:
226
+ state = UNKNOWN
227
+
228
+ if state == DEFARGS:
229
+ if tok == '(':
230
+ defparen += 1
231
+ elif tok == ')':
232
+ defparen -= 1
233
+ elif defparen == 0 and tok == ':':
234
+ state = DOCSTRING
235
+ defindent = indent
236
+ elif typ == NEWLINE:
237
+ state = UNKNOWN
238
+
239
+ if state > 0:
240
+ # Append tok string to deffullname
241
+ if prevtyp == NAME and typ == NAME:
242
+ deffullname += ' '
243
+ elif prevtyp == OP and deffullname[-1] in ",":
244
+ deffullname += ' '
245
+ deffullname += tok
246
+
247
+ # New line?
248
+ if state == UNKNOWN and typ == NEWLINE:
249
+ state = BEGINOFLINE
250
+
251
+ prevtyp = typ
252
+
253
+ return self
254
+
255
+
256
+ # Data reported by --summary
257
+ good = 0
258
+ no_doc = 0
259
+ no_test = 0
260
+ possibly_wrong = 0
261
+ bad_files = []
262
+
263
+ first = True
264
+
265
+
266
+ def go(filename):
267
+ r"""
268
+ If ``filename`` is a file, launch the inspector on this file. If
269
+ ``filename`` is a directory then recursively launch this function on the
270
+ files it contains.
271
+ """
272
+ if os.path.isdir(filename):
273
+ for F in sorted(os.listdir(filename)):
274
+ go(os.path.join(filename, F))
275
+ if not os.path.exists(filename):
276
+ print("File %s does not exist."%filename, file=sys.stderr)
277
+ sys.exit(1)
278
+
279
+ if not (filename.endswith('.py')
280
+ or filename.endswith('.pyx')
281
+ or filename.endswith('.sage')):
282
+ return
283
+
284
+ # Filter pytest files which are not supposed to have doctests
285
+ if filename.endswith('_test.py'):
286
+ return
287
+
288
+ with open(filename, 'r') as f:
289
+ cr = CoverageResults(filename).check_file(f)
290
+ bad = cr.no_doc or cr.no_test or cr.possibly_wrong
291
+
292
+ # Update the global variables
293
+ if args.summary:
294
+ global good, no_doc, no_test, possibly_wrong, bad_files
295
+ no_doc += len(cr.no_doc)
296
+ no_test += len(cr.no_test)
297
+ possibly_wrong += len(cr.possibly_wrong)
298
+ good += len(cr.good)
299
+ if bad:
300
+ bad_files.append(filename)
301
+ return
302
+ if not bad and args.only_bad:
303
+ return
304
+
305
+ global first
306
+ if first:
307
+ print('-' * 72)
308
+ first = False
309
+
310
+ cr.report() # Print the report
311
+ print('-' * 72)
312
+
313
+
314
+ for arg in args.filename:
315
+ go(arg)
316
+
317
+ if args.summary:
318
+ num_functions = good + no_doc + no_test
319
+ score = (100.0 * good) / float(num_functions)
320
+ print("Global score: {:.1f}% ({} of {})\n".format(score, good, num_functions))
321
+ print("{} files with wrong documentation".format(len(bad_files)))
322
+ print("{} functions with no doc".format(no_doc))
323
+ print("{} functions with no test".format(no_test))
324
+ print("{} doctest are potentially wrong".format(possibly_wrong))
325
+ print("\nFiles with wrong documentation:")
326
+ print("-------------------------------")
327
+ print("\n".join(" {}".format(filename) for filename in bad_files))
@@ -0,0 +1,14 @@
1
+ #!python
2
+
3
+ import sys
4
+ from sage.all import *
5
+ try:
6
+ from sage.calculus.predefined import x
7
+ except ImportError:
8
+ pass
9
+ from sage.repl.preparse import preparse
10
+
11
+ if len(sys.argv) > 1:
12
+ s = preparse(" ".join(sys.argv[1:]))
13
+ eval(compile(s,'<cmdline>','exec'))
14
+