Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
3-manifolds
GitHub Repository: 3-manifolds/Sage_macOS
Path: blob/main/Sage_framework/files/sagedoc.py
173 views
1
# sage_setup: distribution = sagemath-repl
2
r"""
3
Format Sage documentation for viewing with IPython and the notebook
4
5
AUTHORS:
6
7
- William Stein (2005): initial version.
8
- Nick Alexander (2007): nodetex functions
9
- Nick Alexander (2008): search_src, search_def improvements
10
- Martin Albrecht (2008-03-21): parse LaTeX description environments in sagedoc
11
- John Palmieri (2009-04-11): fix for #5754 plus doctests
12
- Dan Drake (2009-05-21): refactor search_* functions, use system 'find' instead of sage -grep
13
- John Palmieri (2009-06-28): don't use 'find' -- use Python (os.walk, re.search) instead.
14
- Simon King (2011-09): Use os.linesep, avoid destruction of embedding information,
15
enable nodetex in a docstring. Consequently use sage_getdoc.
16
17
- Marc Culler (2023--): modifications for Sage_macOS
18
19
TESTS:
20
21
Check that argspecs of extension function/methods appear correctly,
22
see :issue:`12849`::
23
24
sage: from sage.env import SAGE_DOC
25
sage: docfilename = os.path.join(SAGE_DOC, 'html', 'en', 'reference', 'calculus', 'sage', 'symbolic', 'expression.html')
26
sage: with open(docfilename) as fobj: # needs sagemath_doc_html
27
....: for line in fobj:
28
....: if "#sage.symbolic.expression.Expression.numerical_approx" in line:
29
....: print(line)
30
<span class="sig-name descname"><span class="pre">numerical_approx</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prec</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">digits</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">algorithm</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span>...
31
32
Check that sphinx is not imported at Sage start-up::
33
34
sage: os.system("sage -c \"if 'sphinx' in sys.modules: sys.exit(1)\"")
35
0
36
"""
37
# ****************************************************************************
38
# Copyright (C) 2005 William Stein <[email protected]>
39
#
40
# Distributed under the terms of the GNU General Public License (GPL)
41
# as published by the Free Software Foundation; either version 2 of
42
# the License, or (at your option) any later version.
43
# https://www.gnu.org/licenses/
44
# ****************************************************************************
45
import os
46
import re
47
import shutil
48
import sys
49
import pydoc
50
from sage.misc.temporary_file import tmp_dir
51
from sage.misc.viewer import browser
52
from sage.misc import sageinspect
53
import sage.version
54
from sage.env import SAGE_DOC, SAGE_SRC
55
56
app_contents = os.path.abspath(os.path.join(os.environ['SAGE_ROOT'],
57
'..', '..', '..', '..'))
58
site_root = os.path.join(app_contents, 'Resources', 'documentation')
59
try:
60
from cocoserver import StaticServer
61
doc_server = StaticServer(site_root)
62
except ImportError:
63
doc_server = None
64
65
# The detex function does two kinds of substitutions: math, which
66
# should only be done on the command line -- in the notebook, these
67
# should instead by taken care of by MathJax -- and nonmath, which
68
# should be done always.
69
70
# Math substitutions: don't forget the leading backslash '\\'. These
71
# are done using regular expressions, so it works best to also make
72
# the strings raw: r'\\blah'.
73
math_substitutes = [
74
(r'\\to', '-->'),
75
(r'\\rightarrow', '-->'),
76
(r'\\leftarrow', '<--'),
77
(r'\\leftrightarrow', '<->'),
78
(r'\\longrightarrow', '--->'),
79
(r'\\longleftarrow', '<---'),
80
(r'\\longleftrightarrow', '<-->'),
81
(r'\\Rightarrow', '==>'),
82
(r'\\Leftarrow', '<=='),
83
(r'\\Leftrightarrow', '<=>'),
84
(r'\\Longrightarrow', '===>'),
85
(r'\\Longleftarrow', '<==='),
86
(r'\\Longleftrightarrow', '<==>'),
87
(r'\\colon', ':'),
88
(r'\\left', ''),
89
(r'\\right', ''),
90
(r'\\bigl', ''),
91
(r'\\bigr', ''),
92
(r'\\leq', '<='),
93
(r'\\geq', '>='),
94
(r'\\le', '<='),
95
(r'\\ge', '>='),
96
(r'\\cdots', '...'),
97
(r'\\ldots', '...'),
98
(r'\\dots', '...'),
99
(r'\\cdot', ' *'),
100
(r'\\ast', ' *'),
101
(r' \\times', ' x'),
102
(r'\\times', ' x'),
103
(r'\\backslash', '\\'),
104
(r'\\mapsto', ' |--> '),
105
(r'\\longmapsto', ' |---> '),
106
(r'\\lvert', '|'),
107
(r'\\rvert', '|'),
108
(r'\\mid', '|'),
109
(r' \\circ', ' o'),
110
(r'\\circ', ' o')
111
]
112
nonmath_substitutes = [
113
('\\_', '_'),
114
('\\item', '* '),
115
('<BLANKLINE>', ''),
116
('\\bf', ''),
117
('\\sage', 'Sage'),
118
('\\SAGE', 'Sage'),
119
('\\Sage', 'Sage'),
120
('\\rm', ''),
121
('backslash', '\\'),
122
('begin{enumerate}', ''),
123
('end{enumerate}', ''),
124
('begin{description}', ''),
125
('end{description}', ''),
126
('begin{itemize}', ''),
127
('end{itemize}', ''),
128
('begin{verbatim}', ''),
129
('end{verbatim}', ''),
130
('note{', 'NOTE: '),
131
]
132
133
app_contents = os.path.abspath(os.path.join(os.environ['SAGE_ROOT'],
134
'..', '..', '..', '..'))
135
site_root = os.path.join(app_contents, 'Resources', 'documentation')
136
try:
137
from cocoserver import StaticServer
138
doc_server = StaticServer(site_root)
139
except ImportError:
140
doc_server = None
141
142
def _rmcmd(s, cmd, left='', right=''):
143
"""
144
Remove the LaTeX command ``cmd`` from the string ``s``. This
145
function is used by ``detex``.
146
147
INPUT:
148
149
- ``s`` -- (string) string from which to remove the command
150
151
- ``cmd`` -- (string) command to be removed. This should be a
152
command which takes a single argument, like 'emph' or 'url'; the
153
command is removed, but its argument is not.
154
155
- ``left``, ``right`` -- (string, default: '') add these
156
strings at the left and right ends of the command. See the
157
examples.
158
159
EXAMPLES::
160
161
sage: from sage.misc.sagedoc import _rmcmd
162
sage: _rmcmd('Check out \\url{http://www.sagemath.org}.', 'url')
163
'Check out http://www.sagemath.org.'
164
sage: _rmcmd('Text in \\emph{italics} looks like this.', 'emph', '*', '*')
165
'Text in *italics* looks like this.'
166
sage: _rmcmd('This is a \\very{silly} example.', 'very', right='!?')
167
'This is a silly!? example.'
168
"""
169
c = '\\%s{' % cmd
170
while True:
171
i = s.find(c)
172
if i == -1:
173
return s
174
nesting = 1
175
j = i + len(c) + 1
176
while j < len(s) and nesting > 0:
177
if s[j] == '{':
178
nesting += 1
179
elif s[j] == '}':
180
nesting -= 1
181
j += 1
182
j -= 1 # j is position of closing '}'
183
if j < len(s):
184
s = s[:i] + left + s[i + len(c):j] + right + s[j + 1:]
185
else:
186
return s
187
188
# I wanted to be cool and use regexp's, but they aren't really
189
# useful, since really this is a parsing problem, because of
190
# nesting of commands, etc. Since it doesn't have to be
191
# super super fast (it's a page of text scrolled to the user),
192
# the above works fine.
193
194
#
195
# import re
196
# def _rmcmd(s, cmd, left='', right=''):
197
# c = '\\%s{.*}'%cmd
198
# r = re.compile(c, re.DOTALL)
199
# while True:
200
# m = r.search(s)
201
# if m is None: break
202
# s = s[:m.start()] + left + s[m.start()+len(cmd)+1:m.end()-1] \
203
# + right + s[m.end():]
204
# return s
205
206
207
itempattern = re.compile(r"\\item\[?([^]]*)\]? *(.*)")
208
itemreplace = r"* \1 \2"
209
210
211
def detex(s, embedded=False):
212
r"""nodetex
213
This strips LaTeX commands from a string; it is used by the
214
``format`` function to process docstrings for display from the
215
command line interface.
216
217
INPUT:
218
219
- ``s`` -- string
220
- ``embedded`` -- boolean (default: ``False``)
221
222
If ``embedded`` is False, then do the replacements in both
223
``math_substitutes`` and ``nonmath_substitutes``. If True, then
224
only do ``nonmath_substitutes``.
225
226
OUTPUT:
227
228
string
229
230
EXAMPLES::
231
232
sage: from sage.misc.sagedoc import detex
233
sage: detex(r'Some math: `n \geq k`. A website: \url{sagemath.org}.')
234
'Some math: n >= k. A website: sagemath.org.\n'
235
sage: detex(r'More math: `x \mapsto y`. {\bf Bold face}.')
236
'More math: x |--> y. { Bold face}.\n'
237
sage: detex(r'`a, b, c, \ldots, z`')
238
'a, b, c, ..., z\n'
239
sage: detex(r'`a, b, c, \ldots, z`', embedded=True)
240
'`a, b, c, \\ldots, z`'
241
sage: detex(r'`\left(\lvert x\ast y \rvert\right]`')
242
'(| x * y |]\n'
243
sage: detex(r'`\left(\leq\le\leftarrow \rightarrow\unknownmacro\to`')
244
'(<=<=<-- -->\\unknownmacro-->\n'
245
"""
246
s = _rmcmd(s, 'url')
247
s = _rmcmd(s, 'code')
248
s = _rmcmd(s, 'class')
249
s = _rmcmd(s, 'mbox')
250
s = _rmcmd(s, 'text')
251
s = _rmcmd(s, 'section')
252
s = _rmcmd(s, 'subsection')
253
s = _rmcmd(s, 'subsubsection')
254
s = _rmcmd(s, 'note', 'NOTE: ', '')
255
s = _rmcmd(s, 'emph', '*', '*')
256
s = _rmcmd(s, 'textbf', '*', '*')
257
258
s = re.sub(itempattern, itemreplace, s)
259
260
for a, b in nonmath_substitutes:
261
s = s.replace(a, b)
262
if not embedded: # not in the notebook
263
s = _rmcmd(s, 'mathop')
264
s = _rmcmd(s, 'mathrm')
265
try:
266
from .sphinxify import sphinxify
267
except ImportError:
268
s = s.replace('``', '"').replace('`', '') + '\n'
269
else:
270
s = sphinxify(s, format='text')
271
# Do math substitutions. The strings to be replaced should be
272
# TeX commands like "\\blah". Do a regular expression
273
# replacement to replace "\\blah" but not "\\blahxyz", etc.:
274
# test to make sure the next character is not a letter.
275
for a, b in math_substitutes:
276
s = re.sub(a + '([^a-zA-Z])', b + '\\1', s)
277
return s
278
279
280
def skip_TESTS_block(docstring):
281
r"""
282
Remove blocks labeled "TESTS:" from ``docstring``.
283
284
INPUT:
285
286
- ``docstring``, a string
287
288
A "TESTS" block is a block starting "TESTS:" (or
289
the same with two colons), on a line on its own, and ending either
290
with a line indented less than "TESTS", or with a line with the
291
same level of indentation -- not more -- matching one of the
292
following:
293
294
- a Sphinx directive of the form ".. foo:", optionally followed by
295
other text.
296
297
- text of the form "UPPERCASE:", optionally followed by other
298
text.
299
300
- lines which look like a reST header: one line containing
301
anything, followed by a line consisting only of a string of
302
hyphens, equal signs, or other characters which are valid
303
markers for reST headers: ``- = ` : ' " ~ _ ^ * + # < >``.
304
However, lines only containing double colons `::` do not
305
end "TESTS" blocks.
306
307
Return the string obtained from ``docstring`` by removing these
308
blocks.
309
310
EXAMPLES::
311
312
sage: from sage.misc.sagedoc import skip_TESTS_block
313
sage: start = ' Docstring\n\n'
314
sage: test = ' TESTS: \n\n Here is a test::\n sage: 2+2 \n 5 \n\n'
315
sage: test2 = ' TESTS:: \n\n sage: 2+2 \n 6 \n\n'
316
317
Test lines starting with "REFERENCES:"::
318
319
sage: refs = ' REFERENCES: \n text text \n'
320
sage: skip_TESTS_block(start + test + refs).rstrip() == (start + refs).rstrip()
321
True
322
sage: skip_TESTS_block(start + test + test2 + refs).rstrip() == (start + refs).rstrip()
323
True
324
sage: skip_TESTS_block(start + test + refs + test2).rstrip() == (start + refs).rstrip()
325
True
326
327
Test Sphinx directives::
328
329
sage: directive = ' .. todo:: \n do some stuff \n'
330
sage: skip_TESTS_block(start + test + refs + test2 + directive).rstrip() == (start + refs + directive).rstrip()
331
True
332
333
Test unindented lines::
334
335
sage: unindented = 'NOT INDENTED\n'
336
sage: skip_TESTS_block(start + test + unindented).rstrip() == (start + unindented).rstrip()
337
True
338
sage: skip_TESTS_block(start + test + unindented + test2 + unindented).rstrip() == (start + unindented + unindented).rstrip()
339
True
340
341
Test headers::
342
343
sage: header = ' Header:\n ~~~~~~~~'
344
sage: skip_TESTS_block(start + test + header) == start + header
345
True
346
347
Not a header because the characters on the second line must all be
348
the same::
349
350
sage: fake_header = ' Header:\n -=-=-=-=-='
351
sage: skip_TESTS_block(start + test + fake_header).rstrip() == start.rstrip()
352
True
353
354
Not a header because it's indented compared to 'TEST' in the
355
string ``test``::
356
357
sage: another_fake = '\n blah\n ----'
358
sage: skip_TESTS_block(start + test + another_fake).rstrip() == start.rstrip()
359
True
360
361
Double colons ``::`` are also not considered as headers (:issue:`27896`)::
362
363
sage: colons = ' ::\n\n sage: 2+2\n 4\n\n'
364
sage: skip_TESTS_block(start + test2 + colons).rstrip() == start.rstrip()
365
True
366
"""
367
# tests_block: match a line starting with whitespace, then
368
# "TEST" or "TESTS" followed by ":" or "::", then possibly
369
# more whitespace, then the end of the line.
370
tests_block = re.compile('([ ]*)TEST[S]?:[:]?[ ]*$')
371
# end_of_block: match a line starting with whitespace, then Sphinx
372
# directives of the form ".. foo:". This will match directive
373
# names "foo" containing letters of either case, hyphens,
374
# underscores.
375
# Also match uppercase text followed by a colon, like
376
# "REFERENCES:" or "ALGORITHM:".
377
end_of_block = re.compile(r'[ ]*(\.\.[ ]+[-_A-Za-z]+|[A-Z]+):')
378
# header: match a string of hyphens, or other characters which are
379
# valid markers for reST headers: - = ` : ' " ~ _ ^ * + # < >
380
# except for double colons ::
381
header = re.compile(r'^[ ]*(?:([-=`\'"~_^*+#><])\1+|:|:::+)[ ]*$')
382
s = ''
383
skip = False
384
previous = ''
385
# indentation: amount of indentation at the start of 'TESTS:'.
386
indentation = ''
387
for l in docstring.split('\n'):
388
if not skip:
389
m = tests_block.match(l)
390
if m:
391
skip = True
392
indentation = m.group(1)
393
else:
394
s += "\n"
395
s += l
396
else:
397
if l and not l.isspace() and not l.startswith(indentation):
398
# A non-blank line indented less than 'TESTS:'
399
skip = False
400
s += "\n"
401
s += l
402
elif end_of_block.match(l) and not tests_block.match(l):
403
# A line matching end_of_block and indented the same as 'TESTS:'
404
if l.startswith(indentation + " "):
405
continue
406
skip = False
407
s += "\n"
408
s += l
409
elif header.match(l):
410
# A line matching header.
411
if l.startswith(indentation + " "):
412
continue
413
skip = False
414
if previous:
415
s += "\n"
416
s += previous
417
s += "\n"
418
s += l
419
previous = l
420
return s[1:] # Remove empty line from the beginning.
421
422
423
def process_dollars(s):
424
r"""nodetex
425
Replace dollar signs with backticks.
426
427
More precisely, do a regular expression search. Replace a plain
428
dollar sign ($) by a backtick (`). Replace an escaped dollar sign
429
(\\$) by a dollar sign ($). Don't change a dollar sign preceded or
430
followed by a backtick (\`$ or \$`), because of strings like
431
"``$HOME``". Don't make any changes on lines starting with more
432
spaces than the first nonempty line in ``s``, because those are
433
indented and hence part of a block of code or examples.
434
435
This also doesn't replaces dollar signs enclosed in curly braces,
436
to avoid nested math environments.
437
438
EXAMPLES::
439
440
sage: from sage.misc.sagedoc import process_dollars
441
sage: process_dollars('hello')
442
'hello'
443
sage: process_dollars('some math: $x=y$')
444
doctest:warning...
445
DeprecationWarning: using dollar signs to mark up math in Sage docstrings
446
is deprecated; use backticks instead
447
See https://github.com/sagemath/sage/issues/33973 for details.
448
'some math: `x=y`'
449
450
Replace \\$ with $, and don't do anything when backticks are involved::
451
452
sage: process_dollars(r'a ``$REAL`` dollar sign: \$')
453
'a ``$REAL`` dollar sign: $'
454
455
Don't make any changes on lines indented more than the first
456
nonempty line::
457
458
sage: s = '\n first line\n indented $x=y$'
459
sage: s == process_dollars(s)
460
True
461
462
Don't replace dollar signs enclosed in curly braces::
463
464
sage: process_dollars(r'f(n) = 0 \text{ if $n$ is prime}')
465
'f(n) = 0 \\text{ if $n$ is prime}'
466
467
This is not perfect::
468
469
sage: process_dollars(r'$f(n) = 0 \text{ if $n$ is prime}$')
470
'`f(n) = 0 \\text{ if $n$ is prime}$'
471
472
The regular expression search doesn't find the last $.
473
Fortunately, there don't seem to be any instances of this kind of
474
expression in the Sage library, as of this writing.
475
"""
476
if s.find("$") == -1:
477
return s
478
from sage.misc.superseded import deprecation
479
# find how much leading whitespace s has, for later comparison:
480
# ignore all $ on lines which start with more whitespace.
481
whitespace = re.match(r'\s*\S', s.lstrip('\n'))
482
whitespace = ' ' * (whitespace.end() - 1) # leading whitespace
483
# Indices will be a list of pairs of positions in s, to search between.
484
# If the following search has no matches, then indices will be (0, len(s)).
485
indices = [0]
486
# This searches for "$blah$" inside a pair of curly braces --
487
# don't change these, since they're probably coming from a nested
488
# math environment. So for each match, search to the left of its
489
# start and to the right of its end, but not in between.
490
for m in re.finditer(r"{[^{}$]*\$([^{}$]*)\$[^{}$]*}", s):
491
indices[-1] = (indices[-1], m.start())
492
indices.append(m.end())
493
indices[-1] = (indices[-1], len(s))
494
# regular expression for $ (not \$, `$, $`, and only on a line
495
# with no extra leading whitespace).
496
#
497
# in detail:
498
# re.compile("^" # beginning of line
499
# + "(%s%)?" % whitespace
500
# + r"""(\S # non whitespace
501
# .*?)? # non-greedy match any non-newline characters
502
# (?<!`|\\)\$(?!`) # $ with negative lookbehind and lookahead
503
# """, re.M | re.X)
504
#
505
# except that this doesn't work, so use the equivalent regular
506
# expression without the 're.X' option. Maybe 'whitespace' gets
507
# eaten up by re.X?
508
regexp = "^" + "(%s)?" % whitespace + r"(\S.*?)?(?<!`|\\)\$(?!`)"
509
dollar = re.compile(regexp, re.M)
510
# regular expression for \$
511
slashdollar = re.compile(r"\\\$")
512
for start, end in indices:
513
while dollar.search(s, start, end):
514
m = dollar.search(s, start, end)
515
s = s[:m.end() - 1] + "`" + s[m.end():]
516
deprecation(33973,
517
"using dollar signs to mark up math in Sage docstrings "
518
"is deprecated; use backticks instead")
519
while slashdollar.search(s, start, end):
520
m = slashdollar.search(s, start, end)
521
s = s[:m.start()] + "$" + s[m.end():]
522
return s
523
524
525
# When adding roles here, also add them to SAGE_ROOT/src/tox.ini [flake8]
526
# and document them in SAGE_ROOT/src/doc/en/developer/sage_manuals.rst
527
#
528
# Sage github issue shortcuts. For example, :issue:`7549`.
529
pythonversion = sys.version.split(' ')[0]
530
extlinks = {
531
'python': (f'https://docs.python.org/release/{pythonversion}/%s', None),
532
'issue': ('https://github.com/sagemath/sage/issues/%s', 'Issue #%s'),
533
'sage_root': ('https://github.com/sagemath/sage/tree/develop/%s', 'SAGE_ROOT/%s'),
534
'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article %s'),
535
'arxiv': ('https://arxiv.org/abs/%s', 'arXiv %s'),
536
'oeis': ('https://oeis.org/%s', 'OEIS sequence %s'),
537
'doi': ('https://doi.org/%s', 'doi:%s'),
538
'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'),
539
'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s'),
540
'common_lisp': ('https://www.lispworks.com/documentation/lw50/CLHS/Body/%s.htm', 'Common Lisp: %s'),
541
'ecl': ('https://ecl.common-lisp.dev/static/manual/%s.html', 'ECL: %s'),
542
'gap': ('https://docs.gap-system.org/doc/ref/%s_mj.html', 'GAP: %s'),
543
'gap_package': ('https://docs.gap-system.org/pkg/%s', 'GAP package %s'),
544
'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'),
545
'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'),
546
'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: %s'),
547
'meson': ('https://mesonbuild.com/%s', 'Meson: %s'),
548
'polymake': ('https://polymake.org/doku.php/documentation/latest/%s', 'polymake: %s'),
549
'ppl': ('https://www.bugseng.com/products/ppl/documentation/user/ppl-user-1.2-html/%s.html', 'PPL: %s'),
550
'qepcad': ('https://www.usna.edu/CS/qepcadweb/B/%s.html', 'QEPCAD: %s'),
551
'scip': ('https://scipopt.org/doc/html/%s.php', 'SCIP: %s'),
552
'singular': ('https://www.singular.uni-kl.de/Manual/4-3-2/%s.htm', 'Singular: %s'),
553
'soplex': ('https://soplex.zib.de/doc/html/%s.php', 'SoPlex: %s'),
554
}
555
556
557
def process_extlinks(s, embedded=False):
558
r"""nodetex
559
560
In docstrings at the command line, process markup related to the
561
Sphinx extlinks extension. For example, replace ``:issue:`NUM```
562
with ``https://github.com/sagemath/sage/issues/NUM``, and similarly with
563
``:python:TEXT`` and ``:wikipedia:TEXT``, looking up the url from
564
the dictionary ``extlinks`` in ``sage_docbuild.conf``.
565
If ``TEXT`` is of the form ``blah <LINK>``, then it uses ``LINK``
566
rather than ``TEXT`` to construct the url.
567
568
In the notebook, don't do anything: let sphinxify take care of it.
569
570
INPUT:
571
572
- ``s`` -- string, in practice a docstring
573
- ``embedded`` -- boolean (default: ``False``)
574
575
This function is called by :func:`format`, and if in the notebook,
576
it sets ``embedded`` to be ``True``, otherwise ``False``.
577
578
EXAMPLES::
579
580
sage: from sage.misc.sagedoc import process_extlinks
581
sage: process_extlinks('See :issue:`1234`, :wikipedia:`Wikipedia <Sage_(mathematics_software)>`, and :issue:`4321` ...')
582
'See https://github.com/sagemath/sage/issues/1234, https://en.wikipedia.org/wiki/Sage_(mathematics_software), and https://github.com/sagemath/sage/issues/4321 ...'
583
sage: process_extlinks('See :issue:`1234` for more information.', embedded=True)
584
'See :issue:`1234` for more information.'
585
sage: process_extlinks('see :python:`Implementing Descriptors <reference/datamodel.html#implementing-descriptors>` ...')
586
'see https://docs.python.org/release/.../reference/datamodel.html#implementing-descriptors ...'
587
"""
588
if embedded:
589
return s
590
for key in extlinks:
591
while True:
592
m = re.search(':%s:`([^`]*)`' % key, s)
593
if not m:
594
break
595
link = m.group(1)
596
m = re.search('.*<([^>]*)>', link)
597
if m:
598
link = m.group(1)
599
s = re.sub(':%s:`([^`]*)`' % key,
600
extlinks[key][0].replace('%s', link),
601
s, count=1)
602
return s
603
604
605
def process_mathtt(s):
606
r"""nodetex
607
Replace \\mathtt{BLAH} with BLAH in the command line.
608
609
INPUT:
610
611
- ``s`` -- string, in practice a docstring
612
613
This function is called by :func:`format`.
614
615
EXAMPLES::
616
617
sage: from sage.misc.sagedoc import process_mathtt
618
sage: process_mathtt(r'e^\mathtt{self}')
619
'e^self'
620
"""
621
while True:
622
start = s.find("\\mathtt{")
623
end = s.find("}", start)
624
if start == -1 or end == -1:
625
break
626
s = s[:start] + s[start + 8:end] + s[end + 1:]
627
return s
628
629
630
def process_optional_doctest_tags(s):
631
r"""
632
Remove ``# optional/needs`` doctest tags for present features from docstring ``s``.
633
634
EXAMPLES:
635
636
sage: from sage.misc.sagedoc import process_optional_doctest_tags
637
sage: process_optional_doctest_tags("sage: # needs sage.rings.finite_rings\nsage: K.<x> = FunctionField(GF(5^2,'a')); K\nRational function field in x over Finite Field in a of size 5^2") # needs sage.rings.finite_rings
638
"sage: K.<x> = FunctionField(GF(5^2,'a')); K\nRational function field in x over Finite Field in a of size 5^2"
639
"""
640
import io
641
from sage.doctest.external import available_software
642
from sage.doctest.parsing import parse_optional_tags, update_optional_tags
643
644
start = 0
645
with io.StringIO() as output:
646
for m in re.finditer('( *sage: *.*#.*)\n', s):
647
output.write(s[start:m.start(0)])
648
line = m.group(1)
649
tags = [tag for tag in parse_optional_tags(line)
650
if tag not in available_software]
651
line = update_optional_tags(line, tags=tags)
652
if not re.fullmatch(' *sage: *', line):
653
print(line, file=output)
654
start = m.end(0)
655
output.write(s[start:])
656
return output.getvalue()
657
658
659
def format(s, embedded=False):
660
r"""noreplace
661
Format Sage documentation ``s`` for viewing with IPython.
662
663
This calls :func:`detex` on ``s`` to convert LaTeX commands to plain
664
text, unless the directive ``nodetex`` is given in the first line
665
of the string.
666
667
Also, if ``s`` contains a string of the form ``<<<obj>>>``, then
668
it replaces it with the docstring for ``obj``, unless the
669
directive ``noreplace`` is given in the first line. If an error
670
occurs under the attempt to find the docstring for ``obj``, then
671
the substring ``<<<obj>>>`` is preserved.
672
673
Directives must be separated by a comma.
674
675
INPUT:
676
677
- ``s`` -- string
678
- ``embedded`` -- boolean (default: ``False``)
679
680
OUTPUT: string
681
682
Set ``embedded`` equal to ``True`` if formatting for use in the
683
notebook; this just gets passed as an argument to :func:`detex`.
684
685
.. SEEALSO::
686
687
:func:`sage.misc.sageinspect.sage_getdoc` to get the formatted
688
documentation of a given object.
689
690
EXAMPLES::
691
692
sage: from sage.misc.sagedoc import format
693
sage: identity_matrix(2).rook_vector.__doc__[191:263] # needs sage.modules
694
'Let `A` be an `m` by `n` (0,1)-matrix. We identify `A` with a chessboard'
695
696
sage: format(identity_matrix(2).rook_vector.__doc__[191:263]) # needs sage.modules
697
'Let A be an m by n (0,1)-matrix. We identify A with a chessboard\n'
698
699
If the first line of the string is 'nodetex', remove 'nodetex' but
700
don't modify any TeX commands::
701
702
sage: format("nodetex\n`x \\geq y`")
703
'`x \\geq y`'
704
705
Testing a string enclosed in triple angle brackets::
706
707
sage: format('<<<identity_matrix')
708
'<<<identity_matrix\n'
709
sage: format('identity_matrix>>>')
710
'identity_matrix>>>\n'
711
sage: format('<<<identity_matrix>>>') # needs sage.modules
712
'...Definition: identity_matrix(...'
713
sage: format('<<<identity_matrix>>>')[:28] # needs sphinx
714
'Definition: identity_matrix('
715
716
TESTS:
717
718
We check that the todo Sphinx extension is correctly activated::
719
720
sage: sage.misc.sagedoc.format(sage.combinat.ranker.on_fly.__doc__) # needs sphinx
721
" Returns ... Todo: add tests as in combinat::rankers\n"
722
723
In the following use case, the ``nodetex`` directive would have been ignored prior
724
to :issue:`11815`::
725
726
sage: cython_code = ["def testfunc(x):",
727
....: " '''",
728
....: " nodetex",
729
....: " This is a doc string with raw latex",
730
....: "",
731
....: " `x \\geq y`",
732
....: " '''",
733
....: " return -x"]
734
sage: cython('\n'.join(cython_code)) # needs sage.misc.cython
735
sage: from sage.misc.sageinspect import sage_getdoc
736
sage: print(sage_getdoc(testfunc)) # needs sage.misc.cython
737
<BLANKLINE>
738
This is a doc string with raw latex
739
<BLANKLINE>
740
`x \geq y`
741
<BLANKLINE>
742
743
We check that the ``noreplace`` directive works, even combined with
744
``nodetex`` (see :issue:`11817`)::
745
746
sage: print(format('''nodetex, noreplace\n<<<identity_matrix>>>`\\not= 0`'''))
747
<<<identity_matrix>>>`\not= 0`
748
749
If replacement is impossible, then no error is raised::
750
751
sage: print(format('<<<bla\n<<<bla>>>\n<<<identity_matrix>>>'))
752
<<<bla <<<bla>>>
753
<BLANKLINE>
754
Definition: identity_matrix(ring, n=0, sparse=False)
755
<BLANKLINE>
756
This function is available as identity_matrix(...) and
757
matrix.identity(...).
758
<BLANKLINE>
759
Return the n x n identity matrix over the given ring.
760
...
761
762
Check that backslashes are preserved in code blocks (:issue:`29140`)::
763
764
sage: format('::\n' # needs sphinx
765
....: '\n'
766
....: r' sage: print(r"\\\\.")' '\n'
767
....: r' \\\\.')
768
' sage: print(r"\\\\\\\\.")\n \\\\\\\\.\n'
769
sage: format(r'inline code ``\\\\.``')
770
'inline code "\\\\\\\\."\n'
771
"""
772
if not isinstance(s, str):
773
raise TypeError("s must be a string")
774
775
# Leading empty lines must be removed, since we search for directives
776
# in the first line.
777
s = s.lstrip(os.linesep)
778
779
# parse directives at beginning of docstring
780
# currently, only 'nodetex' and 'noreplace' are supported.
781
# 'no' + 'doctest' may be supported eventually (don't type that as
782
# one word, or the whole file will not be doctested).
783
first_newline = s.find(os.linesep)
784
if first_newline > -1:
785
first_line = s[:first_newline]
786
else:
787
first_line = s
788
# Moreover, we must strip blank space in order to get the directives
789
directives = [d.strip().lower() for d in first_line.split(',')]
790
791
if 'noreplace' in directives or 'nodetex' in directives:
792
s = s[first_newline + len(os.linesep):]
793
794
try:
795
import sage.all
796
except ImportError:
797
pass
798
799
docs = set()
800
if 'noreplace' not in directives:
801
i_0 = 0
802
while True:
803
i = s[i_0:].find("<<<")
804
if i == -1:
805
break
806
j = s[i_0 + i + 3:].find('>>>')
807
if j == -1:
808
break
809
obj = s[i_0 + i + 3:i_0 + i + 3 + j]
810
if obj in docs:
811
t = ''
812
else:
813
try:
814
x = eval('sage.all.%s' % obj, locals())
815
except AttributeError:
816
# A pair <<<...>>> has been found, but the object not.
817
i_0 += i + 6 + j
818
continue
819
except SyntaxError:
820
# This is a simple heuristics to cover the case of
821
# a non-matching set of <<< and >>>
822
i_0 += i + 3
823
continue
824
t0 = sage.misc.sageinspect.sage_getdef(x, obj)
825
t1 = sage.misc.sageinspect.sage_getdoc(x)
826
t = 'Definition: ' + t0 + '\n\n' + t1
827
docs.add(obj)
828
s = s[:i_0 + i] + '\n' + t + s[i_0 + i + 6 + j:]
829
i_0 += i
830
831
if 'nodetex' not in directives:
832
s = process_dollars(s)
833
s = skip_TESTS_block(s)
834
if not embedded:
835
s = process_mathtt(s)
836
s = process_extlinks(s, embedded=embedded)
837
s = detex(s, embedded=embedded)
838
839
if not embedded:
840
s = process_optional_doctest_tags(s)
841
842
return s
843
844
845
def format_src(s):
846
"""
847
Format Sage source code ``s`` for viewing with IPython.
848
849
If ``s`` contains a string of the form "<<<obj>>>", then it
850
replaces it with the source code for "obj".
851
852
INPUT:
853
854
- ``s`` -- string
855
856
OUTPUT: string
857
858
EXAMPLES::
859
860
sage: from sage.misc.sagedoc import format_src
861
sage: format_src('unladen swallow')
862
'unladen swallow'
863
sage: format_src('<<<Sq>>>')[5:15] # needs sage.combinat sage.modules
864
'Sq(*nums):'
865
"""
866
if not isinstance(s, str):
867
raise TypeError("s must be a string")
868
docs = set()
869
870
try:
871
import sage.all
872
except ImportError:
873
pass
874
875
while True:
876
i = s.find("<<<")
877
if i == -1:
878
break
879
j = s[i + 3:].find('>>>')
880
if j == -1:
881
break
882
obj = s[i + 3:i + 3 + j]
883
if obj in docs:
884
t = ''
885
else:
886
x = eval('sage.all.%s' % obj, locals())
887
t = my_getsource(x)
888
docs.add(obj)
889
if t is None:
890
print(x)
891
t = ''
892
s = s[:i] + '\n' + t + s[i + 6 + j:]
893
894
return s
895
896
897
###############################
898
899
def _search_src_or_doc(what, string, extra1='', extra2='', extra3='',
900
extra4='', extra5='', **kwargs):
901
r"""
902
Search the Sage library or documentation for lines containing
903
``string`` and possibly some other terms. This function is used by
904
:func:`search_src`, :func:`search_doc`, and :func:`search_def`.
905
906
INPUT:
907
908
- ``what``: either ``'src'`` or ``'doc'``, according to whether you
909
are searching the documentation or source code.
910
- the rest of the input is the same as :func:`search_src`,
911
:func:`search_doc`, and :func:`search_def`.
912
913
OUTPUT:
914
915
If ``interact`` is ``False``, a string containing the results;
916
otherwise, there is no output and the results are presented
917
according to whether you are using the notebook or command-line
918
interface. In the command-line interface, each line of the results
919
has the form ``filename:num:line of code``, where ``num`` is the
920
line number in ``filename`` and ``line of code`` is the line that
921
matched your search terms.
922
923
EXAMPLES::
924
925
sage: from sage.misc.sagedoc import _search_src_or_doc
926
sage: print(_search_src_or_doc('src', r'matrix\(', # long time random
927
....: 'incidence_structures', 'self',
928
....: '^combinat', interact=False))
929
misc/sagedoc.py: sage: _search_src_or_doc('src', 'matrix(', 'incidence_structures', 'self', '^combinat', interact=False)
930
combinat/designs/incidence_structures.py: M1 = self.incidence_matrix()
931
combinat/designs/incidence_structures.py: A = self.incidence_matrix()
932
combinat/designs/incidence_structures.py: M = transpose(self.incidence_matrix())
933
combinat/designs/incidence_structures.py: def incidence_matrix(self):
934
combinat/designs/incidence_structures.py: A = self.incidence_matrix()
935
combinat/designs/incidence_structures.py: A = self.incidence_matrix()
936
combinat/designs/incidence_structures.py: #A = self.incidence_matrix()
937
938
TESTS:
939
940
The examples are nice, but marking them "random" means we're not
941
really testing if the function works, just that it completes. These
942
tests aren't perfect, but are reasonable.
943
944
::
945
946
sage: from sage.misc.sagedoc import _search_src_or_doc
947
sage: len(_search_src_or_doc('src', r'matrix\(', 'incidence_structures', 'self', 'combinat', interact=False).splitlines()) > 1
948
True
949
sage: 'abvar/homology' in _search_src_or_doc('doc', 'homology', 'variety', interact=False) # long time (4s on sage.math, 2012), needs sagemath_doc_html
950
True
951
sage: 'divisors' in _search_src_or_doc('src', '^ *def prime', interact=False)
952
True
953
954
When passing ``interactive=True``, in a terminal session this will pass the
955
``text/plain`` output to the configured pager, while in a notebook session
956
it will display the ``text/html`` output in the notebook's pager. However,
957
in a non-interactive session (as in the doctests) it should just print the
958
results to stdout::
959
960
sage: from sage.misc.sagedoc import _search_src_or_doc
961
sage: _search_src_or_doc('src', # long time
962
....: r'def _search_src_or_doc\(',
963
....: interact=True)
964
misc/sagedoc.py:...: def _search_src_or_doc(what, string, extra1='', extra2='', extra3='',
965
"""
966
967
# process keyword arguments
968
interact = kwargs.get('interact', True)
969
path_re = kwargs.get('path_re', '')
970
module = kwargs.get('module', 'sage')
971
whole_word = kwargs.get('whole_word', False)
972
ignore_case = kwargs.get('ignore_case', True)
973
multiline = kwargs.get('multiline', False)
974
975
# done processing keywords
976
# define module, exts (file extension), title (title of search),
977
# base_path (top directory in which to search)
978
if what == 'src':
979
base_path = SAGE_SRC
980
if module.find('sage') == 0:
981
module = module[4:].lstrip(".") # remove 'sage' or 'sage.' from module
982
base_path = os.path.join(base_path, 'sage')
983
module = module.replace(".", os.sep)
984
exts = ['py', 'pyx', 'pxd']
985
title = 'Source Code'
986
else:
987
module = ''
988
exts = ['html']
989
title = 'Documentation'
990
base_path = os.path.join(SAGE_DOC, 'html')
991
if not os.path.exists(base_path):
992
print("""Warning: the Sage documentation is not available""")
993
994
strip = len(base_path)
995
results = []
996
# in regular expressions, '\bWORD\b' matches 'WORD' but not
997
# 'SWORD' or 'WORDS'. so if the user requests a whole_word
998
# search, append and prepend '\b' to each string.
999
regexp = string
1000
extra_regexps = extras = [extra1, extra2, extra3, extra4, extra5]
1001
if whole_word:
1002
regexp = r'\b' + regexp + r'\b'
1003
extra_regexps = [r'\b%s\b' % e for e in extra_regexps]
1004
if ignore_case:
1005
# 'flags' is a flag passed to re.search. use bit-wise or "|" to combine flags.
1006
flags = re.IGNORECASE
1007
else:
1008
flags = 0
1009
1010
# done with preparation; ready to start search
1011
for dirpath, dirs, files in os.walk(os.path.join(base_path, module)):
1012
try:
1013
dirs.remove('_static')
1014
except ValueError:
1015
pass
1016
for f in files:
1017
if not f.startswith('.') and re.search(r"\.(" + "|".join(exts) + ")$", f):
1018
filename = os.path.join(dirpath, f)
1019
if re.search(path_re, filename):
1020
if multiline:
1021
with open(filename) as fobj:
1022
line = fobj.read()
1023
if re.search(regexp, line, flags):
1024
match_list = line
1025
else:
1026
match_list = None
1027
for extra in extra_regexps:
1028
if extra and match_list:
1029
if not re.search(extra, match_list):
1030
match_list = None
1031
if match_list:
1032
results.append(filename[strip:].lstrip("/") + '\n')
1033
else:
1034
with open(filename) as fobj:
1035
match_list = [(lineno, line)
1036
for lineno, line in enumerate(fobj)
1037
if re.search(regexp, line, flags)]
1038
for extra in extra_regexps:
1039
if extra:
1040
match_list = [s for s in match_list
1041
if re.search(extra, s[1], re.MULTILINE | flags)]
1042
for num, line in match_list:
1043
results.append('{}:{}:{}'.format(
1044
filename[strip:].lstrip('/'), num + 1, line))
1045
1046
text_results = ''.join(results).rstrip()
1047
1048
if not interact:
1049
return text_results
1050
1051
html_results = format_search_as_html(title, results, [string] + extras)
1052
# potentially used below
1053
1054
# Pass through the IPython pager in a mime bundle
1055
from IPython.core.page import page
1056
if not isinstance(text_results, str):
1057
text_results = text_results.decode('utf-8', 'replace')
1058
1059
page({
1060
'text/plain': text_results,
1061
# 'text/html': html_results
1062
# don't return HTML results since they currently are not
1063
# correctly formatted for Jupyter use
1064
})
1065
1066
1067
def search_src(string, extra1='', extra2='', extra3='', extra4='',
1068
extra5='', **kwds):
1069
r"""
1070
Search Sage library source code for lines containing ``string``.
1071
The search is case-insensitive by default.
1072
1073
INPUT:
1074
1075
- ``string`` -- a string to find in the Sage source code.
1076
1077
- ``extra1``, ..., ``extra5`` -- additional strings to require when
1078
searching. Lines must match all of these, as well as ``string``.
1079
1080
- ``whole_word`` (default: ``False``) -- if True, search for
1081
``string`` and ``extra1`` (etc.) as whole words only. This
1082
assumes that each of these arguments is a single word, not a
1083
regular expression, and it might have unexpected results if used
1084
with regular expressions.
1085
1086
- ``ignore_case`` (default: ``True``) -- if False, perform a
1087
case-sensitive search
1088
1089
- ``multiline`` (default: ``False``) -- if True, search more
1090
than one line at a time. In this case, print any matching file
1091
names, but don't print line numbers.
1092
1093
- ``interact`` (default: ``True``) -- if ``False``, return
1094
a string with all the matches. Otherwise, this function returns
1095
``None``, and the results are displayed appropriately, according
1096
to whether you are using the notebook or the command-line
1097
interface. You should not ordinarily need to use this.
1098
1099
- ``path_re`` (default: '') -- regular expression which
1100
the filename (including the path) must match.
1101
1102
- ``module`` (default: 'sage') -- the module in which to
1103
search. The default is 'sage', the entire Sage library. If
1104
``module`` doesn't start with "sage", then the links in the
1105
notebook output may not function.
1106
1107
OUTPUT: If ``interact`` is False, then return a string with all of
1108
the matches, separated by newlines. On the other hand, if
1109
``interact`` is True (the default), there is no output. Instead:
1110
at the command line, the search results are printed on the screen
1111
in the form ``filename:line_number:line of text``, showing the
1112
filename in which each match occurs, the line number where it
1113
occurs, and the actual matching line. (If ``multiline`` is True,
1114
then only the filename is printed for each match.) The file paths
1115
in the output are relative to ``$SAGE_SRC``. In the
1116
notebook, each match produces a link to the actual file in which
1117
it occurs.
1118
1119
The ``string`` and ``extraN`` arguments are treated as regular
1120
expressions, as is ``path_re``, and errors will be raised if they
1121
are invalid. The matches will be case-insensitive unless
1122
``ignore_case`` is False.
1123
1124
.. note::
1125
1126
The ``extraN`` parameters are present only because
1127
``search_src(string, *extras, interact=False)``
1128
is not parsed correctly by Python 2.6; see http://bugs.python.org/issue1909.
1129
1130
EXAMPLES:
1131
1132
First note that without using ``interact=False``, this function
1133
produces no output, while with ``interact=False``, the output is a
1134
string. These examples almost all use this option, so that they
1135
have something to which to compare their output.
1136
1137
You can search for "matrix" by typing ``search_src("matrix")``.
1138
This particular search will produce many results::
1139
1140
sage: len(search_src("matrix", interact=False).splitlines()) # random # long time
1141
9522
1142
1143
You can restrict to the Sage calculus code with
1144
``search_src("matrix", module="sage.calculus")``, and this
1145
produces many fewer results::
1146
1147
sage: len(search_src("matrix", module="sage.calculus", interact=False).splitlines()) # random
1148
26
1149
1150
Note that you can do tab completion on the ``module`` string.
1151
Another way to accomplish a similar search::
1152
1153
sage: len(search_src("matrix", path_re="calc", # needs sage.modules
1154
....: interact=False).splitlines()) > 15
1155
True
1156
1157
The following produces an error because the string 'fetch(' is a
1158
malformed regular expression::
1159
1160
sage: print(search_src(" fetch(", "def", interact=False))
1161
Traceback (most recent call last):
1162
...
1163
error: missing ), unterminated subpattern at position 6
1164
1165
To fix this, *escape* the parenthesis with a backslash::
1166
1167
sage: print(search_src(r" fetch\(", "def", interact=False)) # random # long time
1168
matrix/matrix0.pyx: cdef fetch(self, key):
1169
matrix/matrix0.pxd: cdef fetch(self, key)
1170
1171
sage: print(search_src(r" fetch\(", "def", "pyx", interact=False)) # random # long time
1172
matrix/matrix0.pyx: cdef fetch(self, key):
1173
1174
As noted above, the search is case-insensitive, but you can make it
1175
case-sensitive with the 'ignore_case' key word::
1176
1177
sage: s = search_src('Matrix', path_re='matrix', interact=False); s.find('x') > 0
1178
True
1179
1180
sage: s = search_src('MatRiX', path_re='matrix', interact=False); s.find('x') > 0
1181
True
1182
1183
sage: s = search_src('MatRiX', path_re='matrix',
1184
....: interact=False, ignore_case=False); s.find('x') > 0
1185
False
1186
1187
Searches are by default restricted to single lines, but this can
1188
be changed by setting ``multiline`` to be True. In the following,
1189
since ``search_src(string, interact=False)`` returns a string with
1190
one line for each match, counting the length of
1191
``search_src(string, interact=False).splitlines()`` gives the
1192
number of matches. ::
1193
1194
sage: len(search_src('log', 'derivative', interact=False).splitlines()) < 40
1195
True
1196
sage: len(search_src('log', 'derivative',
1197
....: interact=False, multiline=True).splitlines()) > 70
1198
True
1199
1200
A little recursive narcissism: let's do a doctest that searches for
1201
this function's doctests. Note that you can't put "sage:" in the
1202
doctest string because it will get replaced by the Python ">>>"
1203
prompt.
1204
1205
::
1206
1207
sage: print(search_src(r'^ *sage[:] .*search_src\(', interact=False)) # long time
1208
misc/sagedoc.py:... len(search_src("matrix", interact=False).splitlines())...
1209
misc/sagedoc.py:... len(search_src("matrix", module="sage.calculus", interact=False).splitlines())...
1210
misc/sagedoc.py:... len(search_src("matrix", path_re="calc"...
1211
misc/sagedoc.py:... print(search_src(" fetch(", "def", interact=False))...
1212
misc/sagedoc.py:... print(search_src(r" fetch\(", "def", interact=False))...
1213
misc/sagedoc.py:... print(search_src(r" fetch\(", "def", "pyx", interact=False))...
1214
misc/sagedoc.py:... s = search_src('Matrix', path_re='matrix', interact=False); s.find('x') > 0...
1215
misc/sagedoc.py:... s = search_src('MatRiX', path_re='matrix', interact=False); s.find('x') > 0...
1216
misc/sagedoc.py:... s = search_src('MatRiX', path_re='matrix',...
1217
misc/sagedoc.py:... len(search_src('log', 'derivative', interact=False).splitlines()) < 40...
1218
misc/sagedoc.py:... len(search_src('log', 'derivative'...
1219
misc/sagedoc.py:... print(search_src(r'^ *sage[:] .*search_src\(', interact=False))...
1220
misc/sagedoc.py:... len(search_src("matrix", interact=False).splitlines()) > 9000...
1221
misc/sagedoc.py:... print(search_src('matrix', 'column', 'row', 'sub',...
1222
misc/sagedoc.py:... sage: results = search_src('format_search_as_html',...
1223
1224
TESTS:
1225
1226
As of this writing, there are about 9500 lines in the Sage library that
1227
contain "matrix"; it seems safe to assume we will continue to have
1228
over 9000 such lines::
1229
1230
sage: len(search_src("matrix", interact=False).splitlines()) > 9000 # long time
1231
True
1232
1233
Check that you can pass 5 parameters::
1234
1235
sage: print(search_src('matrix', 'column', 'row', 'sub', 'start', 'index', interact=False)) # random # long time
1236
matrix/matrix0.pyx:598: Get The 2 x 2 submatrix of M, starting at row index and column
1237
matrix/matrix0.pyx:607: Get the 2 x 3 submatrix of M starting at row index and column index
1238
matrix/matrix0.pyx:924: Set the 2 x 2 submatrix of M, starting at row index and column
1239
matrix/matrix0.pyx:933: Set the 2 x 3 submatrix of M starting at row index and column
1240
1241
"""
1242
return _search_src_or_doc('src', string, extra1=extra1, extra2=extra2,
1243
extra3=extra3, extra4=extra4, extra5=extra5,
1244
**kwds)
1245
1246
1247
def search_doc(string, extra1='', extra2='', extra3='', extra4='',
1248
extra5='', **kwds):
1249
r"""
1250
Search Sage HTML documentation for lines containing ``string``. The
1251
search is case-insensitive by default.
1252
1253
The file paths in the output are relative to ``$SAGE_DOC``.
1254
1255
INPUT: same as for :func:`search_src`.
1256
1257
OUTPUT: same as for :func:`search_src`.
1258
1259
EXAMPLES:
1260
1261
See the documentation for :func:`search_src` for more examples. ::
1262
1263
sage: search_doc('creates a polynomial', path_re='tutorial', interact=False) # random
1264
html/en/tutorial/tour_polynomial.html:<p>This creates a polynomial ring and tells Sage to use (the string)
1265
1266
If you search the documentation for 'tree', then you will get too
1267
many results, because many lines in the documentation contain the
1268
word 'toctree'. If you use the ``whole_word`` option, though, you
1269
can search for 'tree' without returning all of the instances of
1270
'toctree'. In the following, since ``search_doc('tree',
1271
interact=False)`` returns a string with one line for each match,
1272
counting the length of ``search_doc('tree',
1273
interact=False).splitlines()`` gives the number of matches. ::
1274
1275
sage: # long time, needs sagemath_doc_html
1276
sage: N = len(search_doc('tree', interact=False).splitlines())
1277
sage: L = search_doc('tree', whole_word=True, interact=False).splitlines()
1278
sage: len(L) < N
1279
True
1280
sage: import re
1281
sage: tree_re = re.compile(r'(^|\W)tree(\W|$)', re.I)
1282
sage: all(tree_re.search(l) for l in L)
1283
True
1284
"""
1285
return _search_src_or_doc('doc', string, extra1=extra1, extra2=extra2,
1286
extra3=extra3, extra4=extra4, extra5=extra5,
1287
**kwds)
1288
1289
1290
def search_def(name, extra1='', extra2='', extra3='', extra4='',
1291
extra5='', **kwds):
1292
r"""
1293
Search Sage library source code for function definitions containing
1294
``name``. The search is case-insensitive by default.
1295
1296
INPUT: same as for :func:`search_src`.
1297
1298
OUTPUT: same as for :func:`search_src`.
1299
1300
.. note::
1301
1302
The regular expression used by this function only finds function
1303
definitions that are preceded by spaces, so if you use tabs on a
1304
"def" line, this function will not find it. As tabs are not
1305
allowed in Sage library code, this should not be a problem.
1306
1307
EXAMPLES:
1308
1309
See the documentation for :func:`search_src` for more examples. ::
1310
1311
sage: print(search_def("fetch", interact=False)) # random # long time
1312
matrix/matrix0.pyx: cdef fetch(self, key):
1313
matrix/matrix0.pxd: cdef fetch(self, key)
1314
1315
sage: print(search_def("fetch", path_re="pyx", interact=False)) # random # long time
1316
matrix/matrix0.pyx: cdef fetch(self, key):
1317
"""
1318
# since we convert name to a regular expression, we need to do the
1319
# 'whole_word' conversion here, rather than pass it on to
1320
# _search_src_or_doc.
1321
if 'whole_word' in kwds and kwds['whole_word']:
1322
name = r'\b' + name + r'\b'
1323
if extra1:
1324
extra1 = r'\b' + extra1 + r'\b'
1325
if extra2:
1326
extra2 = r'\b' + extra2 + r'\b'
1327
if extra3:
1328
extra3 = r'\b' + extra3 + r'\b'
1329
if extra4:
1330
extra4 = r'\b' + extra4 + r'\b'
1331
if extra5:
1332
extra5 = r'\b' + extra5 + r'\b'
1333
kwds['whole_word'] = False
1334
1335
return _search_src_or_doc('src', '^ *[c]?def.*%s' % name, extra1=extra1,
1336
extra2=extra2, extra3=extra3, extra4=extra4,
1337
extra5=extra5, **kwds)
1338
1339
1340
def format_search_as_html(what, results, search):
1341
r"""
1342
Format the output from ``search_src``, ``search_def``, or
1343
``search_doc`` as html, for use in the notebook.
1344
1345
INPUT:
1346
1347
- ``what`` -- (string) what was searched (source code or
1348
documentation)
1349
- ``results`` -- (string or list) the results of the search as a string or list of
1350
search results
1351
- ``search`` -- (string or list) what was being searched for, either as a
1352
string which is taken verbatim, or a list of multiple search terms if
1353
there were more than one
1354
1355
This function parses ``results``: each line should have either the form
1356
``FILENAME`` or ``FILENAME: string`` where FILENAME is the file in which
1357
the string that matched the search was found. If FILENAME ends in '.html',
1358
then this is part of the documentation; otherwise, it is in the source
1359
code. In either case, an appropriate link is created.
1360
1361
EXAMPLES::
1362
1363
sage: from sage.misc.sagedoc import format_search_as_html
1364
sage: format_search_as_html('Source', 'algebras/steenrod_algebra_element.py: an antihomomorphism: if we call the antipode `c`, then', 'antipode antihomomorphism')
1365
'<html><font color="black"><h2>Search Source: "antipode antihomomorphism"</h2></font><font color="darkpurple"><ol><li><a href="/src/algebras/steenrod_algebra_element.py" target="_blank"><tt>algebras/steenrod_algebra_element.py</tt></a>\n</ol></font></html>'
1366
sage: format_search_as_html('Other', 'html/en/reference/sage/algebras/steenrod_algebra_element.html:an antihomomorphism: if we call the antipode <span class="math">c</span>, then', 'antipode antihomomorphism')
1367
'<html><font color="black"><h2>Search Other: "antipode antihomomorphism"</h2></font><font color="darkpurple"><ol><li><a href="/doc/live/reference/sage/algebras/steenrod_algebra_element.html" target="_blank"><tt>reference/sage/algebras/steenrod_algebra_element.html</tt></a>\n</ol></font></html>'
1368
1369
TESTS:
1370
1371
Test that results from a ``search_src`` with ``multiline=True`` works
1372
reasonably::
1373
1374
sage: results = search_src('format_search_as_html', # long time
1375
....: multiline=True, interact=False)
1376
sage: format_search_as_html('Source', results, # long time
1377
....: 'format_search_as_html')
1378
'<html><font color="black"><h2>Search Source: "format_search_as_html"</h2></font><font color="darkpurple"><ol><li><a href="/src/misc/sagedoc.py" target="_blank"><tt>misc/sagedoc.py</tt></a>\n</ol></font></html>'
1379
"""
1380
1381
if not isinstance(search, list):
1382
search = [search]
1383
1384
s = [
1385
'<html>',
1386
'<font color="black">',
1387
'<h2>Search {}: {}</h2>'.format(
1388
what, ', '.join('"{}"'.format(s) for s in search if s.strip())),
1389
'</font>',
1390
'<font color="darkpurple">',
1391
'<ol>'
1392
]
1393
1394
append = s.append
1395
1396
if not isinstance(results, list):
1397
results = results.splitlines()
1398
1399
files = set()
1400
for L in results:
1401
filename = L.strip().split(':', 1)[0]
1402
if filename:
1403
files.add(filename)
1404
files = sorted(files)
1405
for F in files:
1406
if F.endswith('.html'):
1407
F = F.split('/', 2)[2]
1408
url = '/doc/live/' + F
1409
else:
1410
# source code
1411
url = '/src/' + F
1412
append('<li><a href="%s" target="_blank"><tt>%s</tt></a>\n' % (url, F))
1413
append('</ol>')
1414
append('</font>')
1415
append('</html>')
1416
return ''.join(s)
1417
1418
1419
#######################################
1420
# Add detex'ing of documentation
1421
#######################################
1422
1423
1424
def my_getsource(obj, oname=''):
1425
"""
1426
Retrieve the source code for ``obj``.
1427
1428
INPUT:
1429
1430
- ``obj`` -- a Sage object, function, etc.
1431
1432
- ``oname`` -- str (optional). A name under which the object is
1433
known. Currently ignored by Sage.
1434
1435
OUTPUT:
1436
1437
Its documentation (string)
1438
1439
EXAMPLES::
1440
1441
sage: from sage.misc.sagedoc import my_getsource
1442
sage: s = my_getsource(identity_matrix) # needs sage.modules
1443
sage: s[15:34] # needs sage.modules
1444
'def identity_matrix'
1445
"""
1446
try:
1447
s = sageinspect.sage_getsource(obj)
1448
return format_src(s)
1449
except Exception as msg:
1450
print('Error getting source:', msg)
1451
return None
1452
1453
1454
class _sage_doc:
1455
"""
1456
Open Sage documentation in a web browser, from either the
1457
command-line or the notebook.
1458
1459
- Type "browse_sage_doc.DOCUMENT()" to open the named document --
1460
for example, "browse_sage_doc.tutorial()" opens the tutorial.
1461
Available documents are
1462
1463
- tutorial: the Sage tutorial
1464
- reference: the Sage reference manual
1465
- constructions: "how do I construct ... in Sage?"
1466
- developer: the Sage developer's guide.
1467
1468
- Type "browse_sage_doc(OBJECT, output=FORMAT, view=BOOL)" to view
1469
the documentation for OBJECT, as in
1470
"browse_sage_doc(identity_matrix, 'html'). ``output`` can be
1471
either 'html' or 'rst': the form of the output. ``view`` is
1472
only relevant if ``output`` is ``html``; in this case, if
1473
``view`` is True (its default value), then open up the
1474
documentation in a web browser. Otherwise, just output the
1475
documentation as a string.
1476
1477
EXAMPLES::
1478
1479
sage: browse_sage_doc._open("reference", testing=True)[0] # needs sagemath_doc_html
1480
'http://localhost:8000/doc/live/reference/index.html'
1481
sage: browse_sage_doc(identity_matrix, 'rst')[-107:-47] # needs sage.modules
1482
'Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring'
1483
"""
1484
def __init__(self):
1485
"""
1486
EXAMPLES::
1487
1488
sage: browse_sage_doc._base_url
1489
'http://localhost:8000/doc/live/'
1490
"""
1491
self._base_url = "http://localhost:8000/doc/live/"
1492
self._base_path = os.path.join(SAGE_DOC, "html", "en")
1493
1494
def __call__(self, obj, output='html', view=True):
1495
r"""
1496
Return the documentation for ``obj``.
1497
1498
INPUT:
1499
1500
- ``obj`` -- a Sage object
1501
- ``output`` -- 'html', 'rst', or 'text': return documentation in this form
1502
- ``view`` -- only has an effect if output is 'html': in this
1503
case, if ``view`` is ``True``, display the documentation in
1504
a web browser. Otherwise, return the documentation as a
1505
string.
1506
1507
EXAMPLES::
1508
1509
sage: browse_sage_doc(identity_matrix, 'rst') # needs sage.modules
1510
"...**File:**...**Type:**...**Definition:** identity_matrix..."
1511
sage: identity_matrix.__doc__ in browse_sage_doc(identity_matrix, 'rst') # needs sage.modules
1512
True
1513
sage: browse_sage_doc(identity_matrix, 'html', False) # needs sagemath_doc_html sphinx
1514
'...div...File:...Type:...Definition:...identity_matrix...'
1515
1516
In the 'text' version, double colons have been replaced with
1517
single ones (among other things)::
1518
1519
sage: '::' in browse_sage_doc(identity_matrix, 'rst') # needs sage.modules
1520
True
1521
sage: '::' in browse_sage_doc(identity_matrix, 'text') # needs sphinx
1522
False
1523
"""
1524
if output != 'html' and view:
1525
view = False
1526
1527
s = ''
1528
newline = "\n\n" # blank line to start new paragraph
1529
1530
try:
1531
filename = sageinspect.sage_getfile(obj)
1532
s += '**File:** %s' % filename
1533
s += newline
1534
except TypeError:
1535
pass
1536
1537
obj_name = ''
1538
locs = sys._getframe(1).f_locals
1539
for var in locs:
1540
if id(locs[var]) == id(obj):
1541
obj_name = var
1542
1543
s += '**Type:** %s' % type(obj)
1544
s += newline
1545
s += '**Definition:** %s' % sageinspect.sage_getdef(obj, obj_name)
1546
s += newline
1547
s += '**Docstring:**'
1548
s += newline
1549
s += sageinspect.sage_getdoc(obj, obj_name, embedded=True)
1550
1551
# now s should be the reST version of the docstring
1552
if output == 'html':
1553
try:
1554
from .sphinxify import sphinxify
1555
except ImportError:
1556
from html import escape
1557
html = escape(s)
1558
else:
1559
html = sphinxify(s)
1560
if view:
1561
path = os.path.join(tmp_dir(), "temp.html")
1562
filed = open(path, 'w')
1563
1564
static_path = os.path.join(SAGE_DOC, "html", "en", "_static")
1565
if os.path.exists(static_path):
1566
title = obj_name + ' - Sage ' + sage.version.version + ' Documentation'
1567
template = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1568
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1569
1570
<html xmlns="http://www.w3.org/1999/xhtml">
1571
<head>
1572
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1573
<title>%(title)s</title>
1574
<link rel="stylesheet" href="%(static_path)s/default.css" type="text/css" />
1575
<link rel="stylesheet" href="%(static_path)s/pygments.css" type="text/css" />
1576
<style type="text/css">
1577
<!--
1578
div.body {
1579
margin: 1.0em;
1580
padding: 1.0em;
1581
}
1582
div.bodywrapper {
1583
margin: 0;
1584
}
1585
-->
1586
</style>
1587
<script type="text/javascript">
1588
var DOCUMENTATION_OPTIONS = {
1589
URL_ROOT: '',
1590
VERSION: '%(version)s',
1591
COLLAPSE_MODINDEX: false,
1592
FILE_SUFFIX: '.html',
1593
HAS_SOURCE: false
1594
};
1595
</script>
1596
<script type="text/javascript" src="%(static_path)s/jquery.js"></script>
1597
<script type="text/javascript" src="%(static_path)s/doctools.js"></script>
1598
<link rel="shortcut icon" href="%(static_path)s/favicon.ico" />
1599
<link rel="icon" href="%(static_path)s/sageicon.png" type="image/x-icon" />
1600
</head>
1601
<body>
1602
<div class="document">
1603
<div class="documentwrapper">
1604
<div class="bodywrapper">
1605
<div class="body">
1606
%(html)s
1607
</div>
1608
</div>
1609
</div>
1610
</div>
1611
</body>
1612
</html>"""
1613
html = template % {'html': html,
1614
'static_path': static_path,
1615
'title': title,
1616
'version': sage.version.version}
1617
1618
filed.write(html)
1619
filed.close()
1620
os.system(browser() + " " + path)
1621
else:
1622
return html
1623
elif output == 'rst':
1624
return s
1625
elif output == 'text':
1626
try:
1627
from .sphinxify import sphinxify
1628
except ImportError:
1629
return s
1630
else:
1631
return sphinxify(s, format='text')
1632
else:
1633
raise ValueError("output type {} not recognized".format(output))
1634
1635
def _open(self, name, testing=False):
1636
"""
1637
Open the document ``name`` in a web browser. This constructs
1638
the appropriate URL and/or path name and passes it to the web
1639
browser.
1640
1641
INPUT:
1642
1643
- ``name`` -- string, name of the documentation
1644
1645
- ``testing`` -- boolean (default: ``False``): if True,
1646
then just return the URL and path-name for this document;
1647
don't open the web browser.
1648
1649
EXAMPLES::
1650
1651
sage: browse_sage_doc._open("reference", testing=True)[0] # needs sagemath_doc_html
1652
'http://localhost:8000/doc/live/reference/index.html'
1653
sage: browse_sage_doc._open("tutorial", testing=True)[1] # needs sagemath_doc_html
1654
'.../html/en/tutorial/index.html'
1655
"""
1656
url = self._base_url + os.path.join(name, "index.html")
1657
path = os.path.join(self._base_path, name, "index.html")
1658
if doc_server:
1659
doc_server.visit(name)
1660
return
1661
elif os.path.exists(path):
1662
os.system(browser() + " " + path)
1663
else:
1664
os.system(browser() + " " + "https://doc.sagemath.org/html/en/%s/index.html"%name)
1665
1666
if testing:
1667
return (url, path)
1668
1669
os.system(browser() + " " + path)
1670
1671
def tutorial(self):
1672
"""
1673
The Sage tutorial. To get started with Sage, start here.
1674
1675
EXAMPLES::
1676
1677
sage: tutorial() # indirect doctest, not tested
1678
"""
1679
self._open("tutorial")
1680
1681
def reference(self):
1682
"""
1683
The Sage reference manual.
1684
1685
EXAMPLES::
1686
1687
sage: reference() # indirect doctest, not tested
1688
sage: manual() # indirect doctest, not tested
1689
"""
1690
self._open("reference")
1691
1692
manual = reference
1693
1694
def developer(self):
1695
"""
1696
The Sage developer's guide. Learn to develop programs for Sage.
1697
1698
EXAMPLES::
1699
1700
sage: developer() # indirect doctest, not tested
1701
"""
1702
self._open("developer")
1703
1704
def constructions(self):
1705
"""
1706
Sage constructions. Attempts to answer the question "How do I
1707
construct ... in Sage?"
1708
1709
EXAMPLES::
1710
1711
sage: constructions() # indirect doctest, not tested
1712
"""
1713
self._open("constructions")
1714
1715
1716
browse_sage_doc = _sage_doc()
1717
tutorial = browse_sage_doc.tutorial
1718
reference = browse_sage_doc.reference
1719
manual = browse_sage_doc.reference
1720
developer = browse_sage_doc.developer
1721
constructions = browse_sage_doc.constructions
1722
1723
python_help = pydoc.help
1724
1725
1726
def help(module=None):
1727
"""
1728
If there is an argument ``module``, print the Python help message
1729
for ``module``. With no argument, print a help message about
1730
getting help in Sage.
1731
1732
EXAMPLES::
1733
1734
sage: help()
1735
Welcome to Sage ...
1736
"""
1737
if module is not None:
1738
python_help(module)
1739
else:
1740
print("""Welcome to Sage {}!
1741
1742
To view the Sage tutorial in your web browser, type "tutorial()", and
1743
to view the (very detailed) Sage reference manual, type "manual()".
1744
For help on any Sage function, for example "matrix_plot", type
1745
"matrix_plot?" to see a help message, type "help(matrix_plot)" to see
1746
a very similar message, type "browse_sage_doc(matrix_plot)" to view a
1747
help message in a web browser, and type "matrix_plot??" to look at the
1748
function's source code.
1749
1750
(When you type something like "matrix_plot?", "help(matrix_plot)", or
1751
"matrix_plot??", Sage may start a paging program to display the
1752
requested message. Type a space to scroll to the next page, type "h"
1753
to get help on the paging program, and type "q" to quit it and return
1754
to the "sage:" prompt.)
1755
1756
For license information for Sage and its components, read the file
1757
"COPYING.txt" in the top-level directory of the Sage installation,
1758
or type "license()".
1759
1760
To enter Python's interactive online help utility, type "python_help()".
1761
To get help on a Python function, module or package, type "help(MODULE)" or
1762
"python_help(MODULE)".""".format(sage.version.version))
1763
1764