Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/src/sage_docbuild/conf.py
7363 views
1
r"""
2
Sphinx build configuration
3
4
This file contains configuration needed to customize Sphinx input and output
5
behavior.
6
"""
7
8
# ****************************************************************************
9
# Copyright (C) 2022 Kwankyu Lee <[email protected]>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, either version 2 of the License, or
14
# (at your option) any later version.
15
# https://www.gnu.org/licenses/
16
# ****************************************************************************
17
18
import importlib
19
import os
20
import re
21
import sys
22
23
import dateutil.parser
24
from IPython.lib.lexers import IPyLexer, IPythonConsoleLexer
25
from sphinx import highlighting
26
from sphinx.ext import intersphinx
27
from sphinx.transforms import SphinxTransform
28
from sphinx.util.docutils import SphinxDirective
29
30
import sage.version
31
from sage.env import MATHJAX_DIR, PPLPY_DOCS, SAGE_DOC, SAGE_DOC_SRC
32
from sage.features.sphinx import JupyterSphinx
33
from sage.misc.latex_macros import sage_mathjax_macros
34
from sage.misc.sagedoc import extlinks as extlinks # noqa: PLC0414
35
from sage.misc.sagedoc_conf import * # Load configuration shared with sage.misc.sphinxify
36
37
# ---------------------
38
# General configuration
39
# ---------------------
40
41
SAGE_LIVE_DOC = os.environ.get('SAGE_LIVE_DOC', 'no')
42
43
# Add any Sphinx extension module names here, as strings. They can be extensions
44
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
45
extensions = [
46
'sage_docbuild.ext.inventory_builder',
47
'sage_docbuild.ext.multidocs',
48
'sage_docbuild.ext.sage_autodoc',
49
'sphinx.ext.todo',
50
'sphinx.ext.extlinks',
51
'sphinx.ext.mathjax',
52
'sphinx.ext.linkcode',
53
'sphinx_copybutton',
54
'sphinx_inline_tabs',
55
'IPython.sphinxext.ipython_directive',
56
'matplotlib.sphinxext.plot_directive',
57
]
58
59
if JupyterSphinx().is_present():
60
extensions.append('jupyter_sphinx')
61
62
jupyter_execute_default_kernel = 'sagemath'
63
64
if SAGE_LIVE_DOC == 'yes':
65
JupyterSphinx().require()
66
SAGE_JUPYTER_SERVER = os.environ.get('SAGE_JUPYTER_SERVER', 'binder')
67
if SAGE_JUPYTER_SERVER.startswith('binder'):
68
# format: "binder" or
69
# "binder:sagemath/sage-binder-env" or
70
# "binder:sagemath/sage-binder-env/dev"
71
if SAGE_JUPYTER_SERVER == 'binder':
72
binder_repo = "sagemath/sage-binder-env/master"
73
else:
74
binder_repo = SAGE_JUPYTER_SERVER[7:]
75
s = binder_repo.split('/', 2)
76
if len(s) > 2:
77
binder_options = {
78
'repo': s[0] + '/' + s[1],
79
'ref': s[2]
80
}
81
else:
82
binder_options = {
83
'repo': binder_repo
84
}
85
jupyter_sphinx_thebelab_config = {
86
'requestKernel': False,
87
'binderOptions': binder_options,
88
'kernelOptions': {
89
'name': "sagemath",
90
'kernelName': "sagemath",
91
'path': ".",
92
},
93
'selector': "div.live-doc"
94
}
95
else: # local jupyter server
96
SAGE_JUPYTER_SERVER_TOKEN = os.environ.get('SAGE_JUPYTER_SERVER_TOKEN', 'secret')
97
jupyter_sphinx_thebelab_config = {
98
'requestKernel': False,
99
'kernelOptions': {
100
'name': "sagemath",
101
'kernelName': "sagemath",
102
'path': ".",
103
'serverSettings': {
104
'baseUrl': SAGE_JUPYTER_SERVER,
105
'token': SAGE_JUPYTER_SERVER_TOKEN
106
},
107
},
108
'selector': "div.live-doc"
109
}
110
jupyter_sphinx_thebelab_config.update({
111
'codeMirrorConfig': {
112
'lineNumbers': True,
113
}
114
})
115
116
# This code is executed before each ".. PLOT::" directive in the Sphinx
117
# documentation. It defines a 'sphinx_plot' function that displays a Sage object
118
# through matplotlib, so that it will be displayed in the HTML doc
119
plot_html_show_source_link = False
120
plot_pre_code = r"""
121
# Set locale to prevent having commas in decimal numbers
122
# in tachyon input (see https://github.com/sagemath/sage/issues/28971)
123
import locale
124
locale.setlocale(locale.LC_NUMERIC, 'C')
125
def sphinx_plot(graphics, **kwds):
126
import matplotlib.image as mpimg
127
import matplotlib.pyplot as plt
128
from sage.misc.temporary_file import tmp_filename
129
from sage.plot.graphics import _parse_figsize
130
if os.environ.get('SAGE_SKIP_PLOT_DIRECTIVE', 'no') != 'yes':
131
## Option handling is taken from Graphics.save
132
options = dict()
133
if isinstance(graphics, sage.plot.graphics.Graphics):
134
options.update(sage.plot.graphics.Graphics.SHOW_OPTIONS)
135
options.update(graphics._extra_kwds)
136
options.update(kwds)
137
elif isinstance(graphics, sage.plot.multigraphics.MultiGraphics):
138
options.update(kwds)
139
else:
140
graphics = graphics.plot(**kwds)
141
dpi = options.pop('dpi', None)
142
transparent = options.pop('transparent', None)
143
fig_tight = options.pop('fig_tight', None)
144
figsize = options.pop('figsize', None)
145
if figsize is not None:
146
figsize = _parse_figsize(figsize)
147
plt.figure(figsize=figsize)
148
figure = plt.gcf()
149
if isinstance(graphics, (sage.plot.graphics.Graphics,
150
sage.plot.multigraphics.MultiGraphics)):
151
graphics.matplotlib(figure=figure, figsize=figsize, **options)
152
if isinstance(graphics, (sage.plot.graphics.Graphics,
153
sage.plot.multigraphics.GraphicsArray)):
154
# for Graphics and GraphicsArray, tight_layout adjusts the
155
# *subplot* parameters so ticks aren't cut off, etc.
156
figure.tight_layout()
157
else:
158
# 3d graphics via png
159
import matplotlib as mpl
160
mpl.rcParams['image.interpolation'] = 'bilinear'
161
mpl.rcParams['image.resample'] = False
162
mpl.rcParams['figure.figsize'] = [8.0, 6.0]
163
mpl.rcParams['figure.dpi'] = 80
164
mpl.rcParams['savefig.dpi'] = 100
165
fn = tmp_filename(ext=".png")
166
graphics.save(fn)
167
img = mpimg.imread(fn)
168
plt.imshow(img)
169
plt.axis("off")
170
plt.margins(0)
171
if not isinstance(graphics, sage.plot.multigraphics.MultiGraphics):
172
plt.tight_layout(pad=0)
173
174
from sage.all_cmdline import *
175
"""
176
177
plot_html_show_formats = False
178
plot_formats = ['svg', 'pdf', 'png']
179
180
# We do *not* fully initialize intersphinx since we call it by hand
181
# in find_sage_dangling_links.
182
#, 'sphinx.ext.intersphinx']
183
184
# Add any paths that contain templates here, relative to this directory.
185
templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates'), 'templates']
186
187
# The master toctree document.
188
master_doc = 'index'
189
190
# General information about the project.
191
project = ""
192
copyright = "2005--{}, The Sage Development Team".format(dateutil.parser.parse(sage.version.date).year)
193
194
# The version info for the project you're documenting, acts as replacement for
195
# |version| and |release|, also used in various other places throughout the
196
# built documents.
197
version = sage.version.version
198
release = sage.version.version
199
200
source_repository = 'https://github.com/sagemath/sage/'
201
source_branch = 'develop'
202
203
# The language for content autogenerated by Sphinx. Refer to documentation
204
# for a list of supported languages.
205
# language = None
206
207
# The LaTeX engine to build the docs.
208
# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-latex_engine
209
latex_engine = 'lualatex'
210
211
# There are two options for replacing |today|: either, you set today to some
212
# non-false value, then it is used:
213
# today = ''
214
# Else, today_fmt is used as the format for a strftime call.
215
# today_fmt = '%B %d, %Y'
216
217
# List of glob-style patterns that should be excluded when looking for
218
# source files. [1] They are matched against the source file names
219
# relative to the source directory, using slashes as directory
220
# separators on all platforms.
221
exclude_patterns = ['.build']
222
223
# If true, '()' will be appended to :func: etc. cross-reference text.
224
# add_function_parentheses = True
225
226
# If true, the current module name will be prepended to all description
227
# unit titles (such as .. function::).
228
# add_module_names = True
229
230
# If true, sectionauthor and moduleauthor directives will be shown in the
231
# output. They are ignored by default.
232
show_authors = True
233
234
# Default lexer to use when highlighting code blocks, using the IPython
235
# console lexers. 'ipycon' is the IPython console, which is what we want
236
# for most code blocks: anything with "sage:" prompts. For other IPython,
237
# like blocks which might appear in a notebook cell, use 'ipython'.
238
highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'(sage:|>>>)', in2_regex=r'([.][.][.][.]:|[.][.][.])')
239
highlighting.lexers['ipython'] = IPyLexer()
240
highlight_language = 'ipycon'
241
242
# Create table of contents entries for domain objects (e.g. functions, classes,
243
# attributes, etc.). Default is True.
244
toc_object_entries = True
245
246
# A string that determines how domain objects (e.g. functions, classes,
247
# attributes, etc.) are displayed in their table of contents entry.
248
#
249
# Use "domain" to allow the domain to determine the appropriate number of parents
250
# to show. For example, the Python domain would show Class.method() and
251
# function(), leaving out the module. level of parents. This is the default
252
# setting.
253
#
254
# Use "hide" to only show the name of the element without any parents (i.e. method()).
255
#
256
# Use "all" to show the fully-qualified name for the object (i.e. module.Class.method()),
257
# displaying all parents.
258
toc_object_entries_show_parents = 'hide'
259
260
# -----------------------
261
# Extension configuration
262
# -----------------------
263
264
# include the todos
265
todo_include_todos = True
266
267
#
268
# intersphinx: Cross-links to other projects' online or installed documentation.
269
#
270
SAGE_DOC_REMOTE_INVENTORIES = os.environ.get('SAGE_DOC_REMOTE_INVENTORIES', 'no') == 'yes'
271
272
_vendored_inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor")
273
274
275
# Run "sage -python -m sage_docbuild.vendor" to update src/doc/common/_vendor/*.inv
276
_intersphinx_targets = {
277
'cvxopt': ['https://cvxopt.org/userguide/'],
278
'cvxpy': ['https://www.cvxpy.org/'],
279
'cypari2': ['https://cypari2.readthedocs.io/en/latest/'],
280
'cysignals': ['https://cysignals.readthedocs.io/en/latest/'],
281
'flint': ['https://flintlib.org/doc/'],
282
'fpylll': ['https://fpylll.readthedocs.io/en/latest/'],
283
'gmpy2': ['https://gmpy2.readthedocs.io/en/latest/'],
284
'ipywidgets': ['https://ipywidgets.readthedocs.io/en/stable/'],
285
'matplotlib': ['https://matplotlib.org/stable/'],
286
'mpmath': ['https://mpmath.org/doc/current/'],
287
'networkx': ['https://networkx.org/documentation/stable/'],
288
'numpy': ['https://numpy.org/doc/stable/'],
289
'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'],
290
'python': ['https://docs.python.org/'],
291
'rpy2': ['https://rpy2.github.io/doc/latest/html/'],
292
'scipy': ['https://docs.scipy.org/doc/scipy/'],
293
'sympy': ['https://docs.sympy.org/latest/'],
294
}
295
296
297
def _intersphinx_mapping(key):
298
inventories = []
299
link_target = None
300
for target in _intersphinx_targets[key]:
301
if not target:
302
pass
303
elif target.startswith('http'):
304
if not link_target:
305
link_target = target
306
if SAGE_DOC_REMOTE_INVENTORIES:
307
inventories.append(None) # Try downloading inventory from link_target
308
elif os.path.exists(target):
309
if not link_target:
310
link_target = target
311
inventory = os.path.join(target, 'objects.inv')
312
if os.path.exists(inventory):
313
inventories.append(inventory)
314
break
315
else:
316
vendored_inventory = os.path.join(_vendored_inventories_dir, key + '.inv')
317
if os.path.exists(vendored_inventory):
318
inventories.append(vendored_inventory)
319
else:
320
# To avoid docbuild failures when building Sage without internet
321
# connection, we use the local python inventory file as a fallback for other
322
# projects. Cross-references will not be resolved in that case, but the
323
# docbuild will still succeed.
324
python_inventory_file = os.path.join(_vendored_inventories_dir, "python.inv")
325
inventories.append(python_inventory_file)
326
assert link_target
327
if len(inventories) == 1:
328
return link_target, inventories[0]
329
return link_target, tuple(inventories)
330
331
332
def set_intersphinx_mappings(app, config):
333
"""
334
Add precompiled inventory (the objects.inv)
335
"""
336
app.config.intersphinx_mapping = {}
337
338
refpath = os.path.join(SAGE_DOC, "html", "en", "reference")
339
invpath = os.path.join(SAGE_DOC, "inventory", "en", "reference")
340
if app.config.multidoc_first_pass == 1 or \
341
not (os.path.exists(refpath) and os.path.exists(invpath)):
342
return
343
344
app.config.intersphinx_mapping = {key: _intersphinx_mapping(key)
345
for key in _intersphinx_targets}
346
347
# Add master intersphinx mapping
348
dst = os.path.join(invpath, 'objects.inv')
349
app.config.intersphinx_mapping['sagemath'] = (refpath, dst)
350
351
# Add intersphinx mapping for subdirectories
352
for directory in os.listdir(os.path.join(invpath)):
353
if directory == 'jupyter_execute':
354
# This directory is created by jupyter-sphinx extension for
355
# internal use and should be ignored here. See Issue #33507.
356
continue
357
if os.path.isdir(os.path.join(invpath, directory)):
358
src = os.path.join(refpath, directory)
359
dst = os.path.join(invpath, directory, 'objects.inv')
360
app.config.intersphinx_mapping[directory] = (src, dst)
361
362
intersphinx.normalize_intersphinx_mapping(app, config)
363
364
365
# By default document is master.
366
multidocs_is_master = True
367
368
# https://sphinx-copybutton.readthedocs.io/en/latest/use.html
369
copybutton_prompt_text = r"sage: |[.][.][.][.]: |>>> |[.][.][.] |\$ "
370
copybutton_line_continuation_character = "\\"
371
copybutton_prompt_is_regexp = True
372
copybutton_exclude = '.linenos, .c1' # exclude single comments (in particular, # optional!)
373
copybutton_only_copy_prompt_lines = True
374
375
376
# https://www.sphinx-doc.org/en/master/usage/extensions/linkcode.html
377
def linkcode_resolve(domain, info):
378
from urllib.parse import quote
379
380
from sage.misc.sageinspect import sage_getsourcelines
381
if domain != 'py':
382
return None
383
if info['module']:
384
m = importlib.import_module(info['module'])
385
filename = quote(info['module'].replace('.', '/'))
386
if m.__file__.endswith('py'):
387
filename += '.py'
388
else:
389
filename += '.pyx'
390
if 'fullname' in info:
391
fullname = info['fullname']
392
obj = m
393
try:
394
for attr in fullname.split('.'):
395
obj = getattr(obj, attr)
396
lineno = sage_getsourcelines(obj)[-1]
397
except Exception: # catch all
398
return None
399
anchor = f'#L{lineno}'
400
else:
401
anchor = ''
402
return f"{source_repository}blob/develop/src/{filename}{anchor}"
403
return None
404
405
406
# -----------------------
407
# Options for HTML output
408
# -----------------------
409
410
# Add any paths that contain custom themes here, relative to this directory.
411
html_theme_path = [os.path.join(SAGE_DOC_SRC, "common", "themes")]
412
413
# Deprecated Sage classic theme:
414
#
415
# html_theme = "sage-classic"
416
# html_theme_options = {}
417
#
418
# See the directory doc/common/themes/sage-classic/ for theme files.
419
420
# Sphinx theme "furo" does not permit an extension. Do not attempt to make
421
# a "sage-furo" theme.
422
html_theme = "furo"
423
424
# Theme options are theme-specific and customize the look and feel of
425
# a theme further. For a list of options available for each theme,
426
# see the documentation.
427
html_theme_options = {
428
"light_css_variables": {
429
"color-brand-primary": "#0f0fff",
430
"color-brand-content": "#0f0fff",
431
},
432
"light_logo": "logo_sagemath_black.svg",
433
"dark_logo": "logo_sagemath_white.svg",
434
# Furo can add a small edit button to each document to allow visitors to
435
# easily propose changes to that document using the repository’s source
436
# control system.
437
# https://pradyunsg.me/furo/customisation/edit-button/#adding-an-edit-button
438
"source_repository": source_repository,
439
"source_branch": source_branch,
440
# "source_directory" is defined in conf.py customized for the doc
441
}
442
443
# Check the condition for announcement banner
444
github_ref = os.environ.get('GITHUB_REF', '')
445
if github_ref:
446
match = re.search(r'refs/pull/(\d+)/merge', github_ref)
447
if match:
448
pr_number = match.group(1)
449
is_for_develop = github_ref.startswith('refs/heads/develop')
450
is_for_github_pr = github_ref and match and pr_number
451
is_stable_release = version.split('.')[-1].isnumeric()
452
453
if is_for_develop or is_for_github_pr or not is_stable_release: # condition for announcement banner
454
# This URL is hardcoded in the file .github/workflows/doc-publish.yml.
455
# See NETLIFY_ALIAS of the "Deploy to Netlify" step.
456
ver = f'<a href="https://doc-develop--sagemath.netlify.app/html/en/index.html">{version}</a>'
457
if is_for_github_pr:
458
pr_url = f'https://github.com/sagemath/sage/pull/{pr_number}'
459
pr_sha = os.environ.get('PR_SHA', '')
460
pr_commit = pr_url + f'/commits/{pr_sha}'
461
ver += f' built with GitHub PR <a href="{pr_url}">#{pr_number}</a>' \
462
f' on <a href="{pr_commit}">{pr_sha[:7]}</a>' \
463
f' [<a href="/changes.html">changes</a>]'
464
banner = f'This is documentation for Sage version {ver} for development purpose.'
465
html_theme_options.update({ "announcement": banner })
466
467
# The name of the Pygments (syntax highlighting) style to use. This
468
# overrides a HTML theme's corresponding setting.
469
pygments_style = "sphinx"
470
pygments_dark_style = "monokai"
471
472
# Add siderbar/home.html to the default sidebar.
473
html_sidebars = {
474
"**": [
475
"sidebar/scroll-start.html",
476
"sidebar/brand.html",
477
"sidebar/version-selector.html",
478
"sidebar/search.html",
479
"sidebar/home.html",
480
"sidebar/navigation.html",
481
"sidebar/ethical-ads.html",
482
"sidebar/scroll-end.html",
483
"sidebar/variant-selector.html",
484
]
485
}
486
487
# These paths are either relative to html_static_path
488
# or fully qualified paths (eg. https://...)
489
html_css_files = [
490
'custom-furo.css',
491
'custom-jupyter-sphinx.css',
492
'custom-codemirror-monokai.css',
493
'custom-tabs.css',
494
]
495
496
html_js_files = [
497
'jupyter-sphinx-furo.js',
498
]
499
500
# A list of paths that contain extra templates (or templates that overwrite
501
# builtin/theme-specific templates). Relative paths are taken as relative
502
# to the configuration directory.
503
templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates-furo')] + templates_path
504
505
# HTML style sheet. This overrides a HTML theme's corresponding setting.
506
# html_style = 'default.css'
507
508
# The name for this set of Sphinx documents. If None, it defaults to
509
# "<project> v<release> documentation".
510
# html_title = None
511
512
# A shorter title for the navigation bar. Default is the same as html_title.
513
# html_short_title = None
514
515
# The name of an image file (within the static path) to place at the top of
516
# the sidebar.
517
# html_logo = 'sagelogo-word.ico'
518
519
# The name of an image file (within the static path) to use as favicon of the
520
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
521
# pixels large.
522
html_favicon = 'favicon.ico'
523
524
# html_static_path defined here and imported in the actual configuration file
525
# conf.py read by Sphinx was the cause of subtle bugs in builders (see #30418 for
526
# instance). Hence now html_common_static_path contains the common paths to static
527
# files, and is combined to html_static_path in each conf.py file read by Sphinx.
528
html_common_static_path = [os.path.join(SAGE_DOC_SRC, 'common', 'static'), 'static']
529
530
# Configure MathJax
531
# https://docs.mathjax.org/en/latest/options/input/tex.html
532
mathjax3_config = {
533
"tex": {
534
# Add custom sage macros
535
# http://docs.mathjax.org/en/latest/input/tex/macros.html
536
"macros": sage_mathjax_macros(),
537
# Add $...$ as possible inline math
538
# https://docs.mathjax.org/en/latest/input/tex/delimiters.html#tex-and-latex-math-delimiters
539
"inlineMath": [["$", "$"], ["\\(", "\\)"]],
540
# Increase the limit the size of the string to be processed
541
# https://docs.mathjax.org/en/latest/options/input/tex.html#option-descriptions
542
"maxBuffer": 50 * 1024,
543
# Use colorv2 extension instead of built-in color extension
544
# https://docs.mathjax.org/en/latest/input/tex/extensions/autoload.html#tex-autoload-options
545
# https://docs.mathjax.org/en/latest/input/tex/extensions/colorv2.html#tex-colorv2
546
"autoload": {"color": [], "colorv2": ["color"]},
547
},
548
}
549
550
if os.environ.get('SAGE_USE_CDNS', 'no') == 'yes':
551
mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
552
else:
553
mathjax_path = os.path.join(MATHJAX_DIR, 'tex-chtml.js')
554
555
# A list of glob-style patterns that should be excluded when looking for source
556
# files. They are matched against the source file names relative to the
557
# source directory, using slashes as directory separators on all platforms.
558
exclude_patterns = []
559
560
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
561
# using the given strftime format.
562
# html_last_updated_fmt = '%b %d, %Y'
563
564
# If true, SmartyPants will be used to convert quotes and dashes to
565
# typographically correct entities.
566
# html_use_smartypants = True
567
568
# Custom sidebar templates, maps document names to template names.
569
# html_sidebars = {}
570
571
# Additional templates that should be rendered to pages, maps page names to
572
# template names.
573
# html_additional_pages = {}
574
575
# If false, no module index is generated.
576
# html_use_modindex = True
577
578
# A list of prefixes that are ignored for sorting the Python module index ( if
579
# this is set to ['foo.'], then foo.bar is shown under B, not F). Works only
580
# for the HTML builder currently.
581
modindex_common_prefix = ['sage.']
582
583
# If false, no index is generated.
584
# html_use_index = True
585
586
# If true, the index is split into individual pages for each letter.
587
html_split_index = True
588
589
# If true, the reST sources are included in the HTML build as _sources/<name>.
590
# html_copy_source = True
591
592
# If true, an OpenSearch description file will be output, and all pages will
593
# contain a <link> tag referring to it. The value of this option must be the
594
# base URL from which the finished HTML is served.
595
# html_use_opensearch = ''
596
597
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
598
# html_file_suffix = ''
599
600
# Output file base name for HTML help builder.
601
# htmlhelp_basename = ''
602
603
# ------------------------
604
# Options for LaTeX output
605
# ------------------------
606
607
# See http://sphinx-doc.org/config.html#confval-latex_elements
608
latex_elements = {}
609
610
# The paper size ('letterpaper' or 'a4paper').
611
#latex_elements['papersize'] = 'letterpaper'
612
613
# The font size ('10pt', '11pt' or '12pt').
614
#latex_elements['pointsize'] = '10pt'
615
616
# Grouping the document tree into LaTeX files. List of tuples
617
# (source start file, target name, title, author, document class [howto/manual]).
618
latex_documents = []
619
620
# The name of an image file (relative to this directory) to place at the top of
621
# the title page.
622
#latex_logo = 'sagelogo-word.png'
623
624
# For "manual" documents, if this is true, then toplevel headings are parts,
625
# not chapters.
626
#latex_use_parts = False
627
628
# Additional stuff for the LaTeX preamble.
629
latex_elements['preamble'] = r"""
630
\usepackage{amsmath}
631
\usepackage{amssymb}
632
\usepackage{textcomp}
633
\usepackage{mathrsfs}
634
\usepackage{iftex}
635
636
\let\textLaTeX\LaTeX
637
\AtBeginDocument{\renewcommand*{\LaTeX}{\hbox{\textLaTeX}}}
638
639
% Workaround for a LaTeX bug -- see Issue #31397 and
640
% https://tex.stackexchange.com/questions/583391/mactex-2020-error-with-report-hyperref-mathbf-in-chapter.
641
\makeatletter
642
\pdfstringdefDisableCommands{%
643
\let\mathbf\@firstofone
644
}
645
\makeatother
646
"""
647
648
# Enable "hard wrapping" long code lines (only applies if breaking
649
# long codelines at spaces or other suitable places failed, typically
650
# this is for long decimal expansions or possibly long string identifiers)
651
latex_elements['sphinxsetup'] = "verbatimforcewraps=true"
652
653
# Documents to append as an appendix to all manuals.
654
# latex_appendices = []
655
656
# If false, no module index is generated.
657
# latex_use_modindex = True
658
659
# -------------------------
660
# add LaTeX macros for Sage
661
# -------------------------
662
663
from sage.misc.latex_macros import sage_latex_macros
664
665
try:
666
pngmath_latex_preamble # check whether this is already defined
667
except NameError:
668
pngmath_latex_preamble = ""
669
670
for macro in sage_latex_macros():
671
# used when building latex and pdf versions
672
latex_elements['preamble'] += macro + '\n'
673
# used when building html version
674
pngmath_latex_preamble += macro + '\n'
675
676
677
# ------------------------------------------
678
# add custom context variables for templates
679
# ------------------------------------------
680
681
def add_page_context(app, pagename, templatename, context, doctree):
682
# # The template function
683
# def template_function(arg):
684
# return "Your string is " + arg
685
# # Add it to the page's context
686
# context['template_function'] = template_function
687
path1 = os.path.dirname(app.builder.get_outfilename(pagename))
688
path2 = os.path.join(SAGE_DOC, 'html', 'en')
689
relpath = os.path.relpath(path2, path1)
690
context['release'] = release
691
context['documentation_title'] = f'Version {release} Documentation'
692
context['documentation_root'] = os.path.join(relpath, 'index.html')
693
if 'website' in path1:
694
context['title'] = 'Documentation'
695
context['website'] = True
696
context['documentation_root'] = 'index.html'
697
if 'reference' in path1 and not path1.endswith('reference'):
698
path2 = os.path.join(SAGE_DOC, 'html', 'en', 'reference')
699
relpath = os.path.relpath(path2, path1)
700
context['reference_title'] = f'Version {release} Reference Manual'
701
context['reference_root'] = os.path.join(relpath, 'index.html')
702
context['refsub'] = True
703
if pagename.startswith('sage/'):
704
# This is for adding small view/edit buttons using Furo's feature:
705
# https://pradyunsg.me/furo/customisation/top-of-page-buttons/
706
# This works well if the source file is '.rst' file. But the '.rst'
707
# files in the directory 'sage/' are generated by the Sphinx
708
# autodoc from the Python or Cython source files. Hence we tweak
709
# here template context variables so that links to the correct
710
# source files are generated.
711
suffix = '.py' if importlib.import_module(pagename.replace('/','.')).__file__.endswith('.py') else '.pyx'
712
context['page_source_suffix'] = suffix
713
context['theme_source_view_link'] = os.path.join(source_repository, 'blob/develop/src', '{filename}')
714
context['theme_source_edit_link'] = os.path.join(source_repository, 'edit/develop/src', '{filename}')
715
716
717
dangling_debug = False
718
719
720
def debug_inf(app, message):
721
if dangling_debug:
722
app.info(message)
723
724
725
def call_intersphinx(app, env, node, contnode):
726
r"""
727
Call intersphinx and make links between Sage manuals relative.
728
729
TESTS:
730
731
Check that the link from the thematic tutorials to the reference
732
manual is relative, see :issue:`20118`::
733
734
sage: from sage.env import SAGE_DOC
735
sage: thematic_index = os.path.join(SAGE_DOC, "html", "en", "thematic_tutorials", "index.html")
736
sage: for line in open(thematic_index).readlines(): # optional - sagemath_doc_html
737
....: if "padics" in line:
738
....: _ = sys.stdout.write(line)
739
<li><p><a class="reference external" href="../reference/padics/sage/rings/padics/tutorial.html#sage-rings-padics-tutorial" title="(in $p$-adics v...)"><span>Introduction to the p-adics</span></a></p></li>
740
"""
741
debug_inf(app, "???? Trying intersphinx for %s" % node['reftarget'])
742
builder = app.builder
743
res = intersphinx.missing_reference(
744
app, env, node, contnode)
745
if res:
746
# Replace absolute links to $SAGE_DOC by relative links: this
747
# allows to copy the whole documentation tree somewhere else
748
# without breaking links, see Issue #20118.
749
if res['refuri'].startswith(SAGE_DOC):
750
here = os.path.dirname(os.path.join(builder.outdir,
751
node['refdoc']))
752
res['refuri'] = os.path.relpath(res['refuri'], here)
753
debug_inf(app, "++++ Found at %s" % res['refuri'])
754
else:
755
debug_inf(app, "---- Intersphinx: %s not Found" % node['reftarget'])
756
return res
757
758
759
def find_sage_dangling_links(app, env, node, contnode):
760
r"""
761
Try to find dangling link in local module imports or all.py.
762
"""
763
debug_inf(app, "==================== find_sage_dangling_links ")
764
765
reftype = node['reftype']
766
reftarget = node['reftarget']
767
try:
768
doc = node['refdoc']
769
except KeyError:
770
debug_inf(app, "-- no refdoc in node %s" % node)
771
return None
772
773
debug_inf(app, "Searching %s from %s" % (reftarget, doc))
774
775
# Workaround: in Python's doc 'object', 'list', ... are documented as a
776
# function rather than a class
777
if reftarget in base_class_as_func and reftype == 'class':
778
node['reftype'] = 'func'
779
780
res = call_intersphinx(app, env, node, contnode)
781
if res:
782
debug_inf(app, "++ DONE %s" % (res['refuri']))
783
return res
784
785
if node.get('refdomain') != 'py': # not a python file
786
return None
787
788
try:
789
module = node['py:module']
790
cls = node['py:class']
791
except KeyError:
792
debug_inf(app, "-- no module or class for :%s:%s" % (reftype,
793
reftarget))
794
return None
795
796
basename = reftarget.split(".")[0]
797
try:
798
target_module = getattr(sys.modules['sage.all'], basename).__module__
799
debug_inf(app, "++ found %s using sage.all in %s" % (basename, target_module))
800
except AttributeError:
801
try:
802
target_module = getattr(sys.modules[node['py:module']], basename).__module__
803
debug_inf(app, "++ found %s in this module" % (basename,))
804
except AttributeError:
805
debug_inf(app, "-- %s not found in sage.all or this module" % (basename))
806
return None
807
except KeyError:
808
target_module = None
809
if target_module is None:
810
target_module = ""
811
debug_inf(app, "?? found in None !!!")
812
813
newtarget = target_module+'.'+reftarget
814
node['reftarget'] = newtarget
815
816
# adapted from sphinx/domains/python.py
817
builder = app.builder
818
searchmode = node.hasattr('refspecific') and 1 or 0
819
matches = builder.env.domains['py'].find_obj(
820
builder.env, module, cls, newtarget, reftype, searchmode)
821
if not matches:
822
debug_inf(app, "?? no matching doc for %s" % newtarget)
823
return call_intersphinx(app, env, node, contnode)
824
elif len(matches) > 1:
825
env.warn(target_module,
826
'more than one target found for cross-reference '
827
'%r: %s' % (newtarget,
828
', '.join(match[0] for match in matches)),
829
node.line)
830
name, obj = matches[0]
831
debug_inf(app, "++ match = %s %s" % (name, obj))
832
833
from docutils import nodes
834
newnode = nodes.reference('', '', internal=True)
835
if name == target_module:
836
newnode['refid'] = name
837
else:
838
newnode['refuri'] = builder.get_relative_uri(node['refdoc'], obj[0])
839
newnode['refuri'] += '#' + name
840
debug_inf(app, "++ DONE at URI %s" % (newnode['refuri']))
841
newnode['reftitle'] = name
842
newnode.append(contnode)
843
return newnode
844
845
846
# lists of basic Python class which are documented as functions
847
base_class_as_func = [
848
'bool', 'complex', 'dict', 'file', 'float',
849
'frozenset', 'int', 'list', 'long', 'object',
850
'set', 'slice', 'str', 'tuple', 'type', 'unicode', 'xrange']
851
852
853
# nitpicky option configuration: Put here broken links we want to ignore. For
854
# link to the Python documentation several links where broken because there
855
# where class listed as functions. Expand the list 'base_class_as_func' above
856
# instead of marking the link as broken.
857
nitpick_ignore = [
858
('py:class', 'twisted.web2.resource.Resource'),
859
('py:class', 'twisted.web2.resource.PostableResource')]
860
861
862
skip_picklability_check_modules = [
863
#'sage.misc.test_nested_class', # for test only
864
'sage.misc.latex',
865
'sage.misc.explain_pickle',
866
'__builtin__',
867
]
868
869
870
def check_nested_class_picklability(app, what, name, obj, skip, options):
871
"""
872
Print a warning if pickling is broken for nested classes.
873
"""
874
if hasattr(obj, '__dict__') and hasattr(obj, '__module__'):
875
# Check picklability of nested classes. Adapted from
876
# sage.misc.nested_class.modify_for_nested_pickle.
877
module = sys.modules[obj.__module__]
878
for (nm, v) in obj.__dict__.items():
879
if (isinstance(v, type) and
880
v.__name__ == nm and
881
v.__module__ == module.__name__ and
882
getattr(module, nm, None) is not v and
883
v.__module__ not in skip_picklability_check_modules):
884
# OK, probably this is an *unpicklable* nested class.
885
app.warn('Pickling of nested class %r is probably broken. '
886
'Please set the metaclass of the parent class to '
887
'sage.misc.nested_class.NestedClassMetaclass.' % (
888
v.__module__ + '.' + name + '.' + nm))
889
890
891
def skip_member(app, what, name, obj, skip, options):
892
"""
893
To suppress Sphinx warnings / errors, we
894
895
- Don't include [aliases of] builtins.
896
897
- Don't include the docstring for any nested class which has been
898
inserted into its module by
899
:class:`sage.misc.NestedClassMetaclass` only for pickling. The
900
class will be properly documented inside its surrounding class.
901
902
- Optionally, check whether pickling is broken for nested classes.
903
904
- Optionally, include objects whose name begins with an underscore
905
('_'), i.e., "private" or "hidden" attributes, methods, etc.
906
907
Otherwise, we abide by Sphinx's decision. Note: The object
908
``obj`` is excluded (included) if this handler returns True
909
(False).
910
"""
911
if 'SAGE_CHECK_NESTED' in os.environ:
912
check_nested_class_picklability(app, what, name, obj, skip, options)
913
914
if getattr(obj, '__module__', None) == '__builtin__':
915
return True
916
917
objname = getattr(obj, "__name__", None)
918
if objname is not None:
919
# check if name was inserted to the module by NestedClassMetaclass
920
if name.find('.') != -1 and objname.find('.') != -1:
921
if objname.split('.')[-1] == name.split('.')[-1]:
922
return True
923
924
if 'SAGE_DOC_UNDERSCORE' in os.environ:
925
if name.split('.')[-1].startswith('_'):
926
return False
927
928
return skip
929
930
931
class SagecodeTransform(SphinxTransform):
932
"""
933
Transform a code block to a live code block enabled by jupyter-sphinx.
934
935
Effectively a code block like::
936
937
EXAMPLE::
938
939
sage: 1 + 1
940
2
941
942
is transformed into::
943
944
EXAMPLE::
945
946
sage: 1 + 1
947
2
948
949
.. ONLY:: html
950
951
.. JUPYTER-EXECUTE::
952
:hide-code:
953
:hide-output:
954
:raises:
955
:stderr:
956
957
1 + 1
958
959
enabling live execution of the code.
960
"""
961
# lower than the priority of jupyer_sphinx.execute.ExecuteJupyterCells
962
default_priority = 170
963
964
def apply(self):
965
if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'):
966
for node in list(self.document.findall(nodes.literal_block)):
967
if node.get('language') is None and node.astext().startswith('sage:'):
968
from docutils.nodes import Text
969
from docutils.nodes import container as Container
970
from docutils.nodes import label as Label
971
from docutils.nodes import literal_block as LiteralBlock
972
from sphinx_inline_tabs._impl import TabContainer
973
parent = node.parent
974
index = parent.index(node)
975
prev_node = node.previous_sibling()
976
if isinstance(prev_node, TabContainer):
977
# Make sure not to merge inline tabs for adjacent literal blocks
978
parent.insert(index, nodes.paragraph())
979
prev_node = parent[index]
980
index += 1
981
parent.remove(node)
982
# Tab for Sage code
983
container = TabContainer("", type="tab", new_set=False)
984
textnodes = [Text('Sage')]
985
label = Label("", "", *textnodes)
986
container += label
987
content = Container("", is_div=True, classes=["tab-content"])
988
content += node
989
container += content
990
parent.insert(index, container)
991
index += 1
992
if isinstance(prev_node, nodes.paragraph):
993
prev_node['classes'].append('with-sage-tab')
994
995
# Tab for preparsed version
996
from sage.repl.preparse import preparse
997
container = TabContainer("", type="tab", new_set=False)
998
textnodes = [Text('Python')]
999
label = Label("", "", *textnodes)
1000
container += label
1001
content = Container("", is_div=True, classes=["tab-content"])
1002
example_lines = []
1003
preparsed_lines = ['>>> from sage.all import *']
1004
for line in node.rawsource.splitlines() + ['']: # one extra to process last example
1005
newline = line.lstrip()
1006
if newline.startswith('....: '):
1007
example_lines.append(newline[6:])
1008
else:
1009
if example_lines:
1010
preparsed_example = preparse('\n'.join(example_lines))
1011
prompt = '>>> '
1012
for preparsed_line in preparsed_example.splitlines():
1013
preparsed_lines.append(prompt + preparsed_line)
1014
prompt = '... '
1015
example_lines = []
1016
if newline.startswith('sage: '):
1017
example_lines.append(newline[6:])
1018
else:
1019
preparsed_lines.append(line)
1020
preparsed = '\n'.join(preparsed_lines)
1021
preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon')
1022
content += preparsed_node
1023
container += content
1024
parent.insert(index, container)
1025
index += 1
1026
if isinstance(prev_node, nodes.paragraph):
1027
prev_node['classes'].append('with-python-tab')
1028
1029
if SAGE_LIVE_DOC == 'yes':
1030
# Tab for Jupyter-sphinx cell
1031
from jupyter_sphinx.ast import CellInputNode, JupyterCellNode
1032
source = node.rawsource
1033
lines = []
1034
for line in source.splitlines():
1035
newline = line.lstrip()
1036
if newline.startswith('sage: ') or newline.startswith('....: '):
1037
lines.append(newline[6:])
1038
cell_node = JupyterCellNode(
1039
execute=False,
1040
hide_code=False,
1041
hide_output=True,
1042
emphasize_lines=[],
1043
raises=False,
1044
stderr=True,
1045
code_below=False,
1046
classes=["jupyter_cell"])
1047
cell_input = CellInputNode(classes=['cell_input','live-doc'])
1048
cell_input += nodes.literal_block(
1049
text='\n'.join(lines),
1050
linenos=False,
1051
linenostart=1)
1052
cell_node += cell_input
1053
container = TabContainer("", type="tab", new_set=False)
1054
textnodes = [Text('Sage Live')]
1055
label = Label("", "", *textnodes)
1056
container += label
1057
content = Container("", is_div=True, classes=["tab-content"])
1058
content += cell_node
1059
container += content
1060
parent.insert(index, container)
1061
index += 1
1062
if isinstance(prev_node, nodes.paragraph):
1063
prev_node['classes'].append('with-sage-live-tab')
1064
1065
1066
class Ignore(SphinxDirective):
1067
1068
has_content = True
1069
1070
def run(self):
1071
return []
1072
1073
1074
# This replaces the setup() in sage.misc.sagedoc_conf
1075
def setup(app):
1076
app.connect('autodoc-process-docstring', process_docstring_cython)
1077
app.connect('autodoc-process-docstring', process_directives)
1078
app.connect('autodoc-process-docstring', process_docstring_module_title)
1079
app.connect('autodoc-process-docstring', process_dollars)
1080
app.connect('autodoc-process-docstring', process_inherited)
1081
app.connect('autodoc-process-docstring', process_docstring_aliases)
1082
if os.environ.get('SAGE_SKIP_TESTS_BLOCKS', False):
1083
app.connect('autodoc-process-docstring', skip_TESTS_block)
1084
app.connect('autodoc-skip-member', skip_member)
1085
app.add_transform(SagemathTransform)
1086
app.add_transform(SagecodeTransform)
1087
if SAGE_LIVE_DOC != 'yes':
1088
app.add_directive("jupyter-execute", Ignore)
1089
app.add_directive("jupyter-kernel", Ignore)
1090
app.add_directive("jupyter-input", Ignore)
1091
app.add_directive("jupyter-output", Ignore)
1092
app.add_directive("thebe-button", Ignore)
1093
1094
# When building the standard docs, app.srcdir is set to SAGE_DOC_SRC +
1095
# 'LANGUAGE/DOCNAME'.
1096
if app.srcdir.is_relative_to(SAGE_DOC_SRC):
1097
app.add_config_value('intersphinx_resolve_self', 'sagemath', False)
1098
app.add_config_value('intersphinx_mapping', {}, False)
1099
app.add_config_value('intersphinx_cache_limit', 5, False)
1100
app.add_config_value('intersphinx_disabled_reftypes', [], False)
1101
app.add_config_value('intersphinx_timeout', None, False)
1102
app.connect('config-inited', set_intersphinx_mappings)
1103
app.connect('builder-inited', intersphinx.load_mappings)
1104
# We do *not* fully initialize intersphinx since we call it by hand
1105
# in find_sage_dangling_links.
1106
# app.connect('missing-reference', missing_reference)
1107
app.connect('missing-reference', find_sage_dangling_links)
1108
app.connect('html-page-context', add_page_context)
1109
1110