Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/src/sage_docbuild/conf.py
4052 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
SAGE_PREPARSED_DOC = os.environ.get('SAGE_PREPARSED_DOC', 'yes')
43
44
# Add any Sphinx extension module names here, as strings. They can be extensions
45
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
46
extensions = [
47
'sage_docbuild.ext.inventory_builder',
48
'sage_docbuild.ext.multidocs',
49
'sage_docbuild.ext.sage_autodoc',
50
'sphinx.ext.todo',
51
'sphinx.ext.extlinks',
52
'sphinx.ext.mathjax',
53
'sphinx.ext.linkcode',
54
'sphinx_copybutton',
55
'sphinx_inline_tabs',
56
'IPython.sphinxext.ipython_directive',
57
'matplotlib.sphinxext.plot_directive',
58
]
59
60
if JupyterSphinx().is_present():
61
extensions.append('jupyter_sphinx')
62
63
jupyter_execute_default_kernel = 'sagemath'
64
65
if SAGE_LIVE_DOC == 'yes':
66
JupyterSphinx().require()
67
SAGE_JUPYTER_SERVER = os.environ.get('SAGE_JUPYTER_SERVER', 'binder')
68
if SAGE_JUPYTER_SERVER.startswith('binder'):
69
# format: "binder" or
70
# "binder:sagemath/sage-binder-env" or
71
# "binder:sagemath/sage-binder-env/dev"
72
if SAGE_JUPYTER_SERVER == 'binder':
73
binder_repo = "sagemath/sage-binder-env/master"
74
else:
75
binder_repo = SAGE_JUPYTER_SERVER[7:]
76
s = binder_repo.split('/', 2)
77
if len(s) > 2:
78
binder_options = {
79
'repo': s[0] + '/' + s[1],
80
'ref': s[2]
81
}
82
else:
83
binder_options = {
84
'repo': binder_repo
85
}
86
jupyter_sphinx_thebelab_config = {
87
'requestKernel': False,
88
'binderOptions': binder_options,
89
'kernelOptions': {
90
'name': "sagemath",
91
'kernelName': "sagemath",
92
'path': ".",
93
},
94
'selector': "div.live-doc"
95
}
96
else: # local jupyter server
97
SAGE_JUPYTER_SERVER_TOKEN = os.environ.get('SAGE_JUPYTER_SERVER_TOKEN', 'secret')
98
jupyter_sphinx_thebelab_config = {
99
'requestKernel': False,
100
'kernelOptions': {
101
'name': "sagemath",
102
'kernelName': "sagemath",
103
'path': ".",
104
'serverSettings': {
105
'baseUrl': SAGE_JUPYTER_SERVER,
106
'token': SAGE_JUPYTER_SERVER_TOKEN
107
},
108
},
109
'selector': "div.live-doc"
110
}
111
jupyter_sphinx_thebelab_config.update({
112
'codeMirrorConfig': {
113
'lineNumbers': True,
114
}
115
})
116
117
# This code is executed before each ".. PLOT::" directive in the Sphinx
118
# documentation. It defines a 'sphinx_plot' function that displays a Sage object
119
# through matplotlib, so that it will be displayed in the HTML doc
120
plot_html_show_source_link = False
121
plot_pre_code = r"""
122
# Set locale to prevent having commas in decimal numbers
123
# in tachyon input (see https://github.com/sagemath/sage/issues/28971)
124
import locale
125
locale.setlocale(locale.LC_NUMERIC, 'C')
126
def sphinx_plot(graphics, **kwds):
127
import matplotlib.image as mpimg
128
import matplotlib.pyplot as plt
129
from sage.misc.temporary_file import tmp_filename
130
from sage.plot.graphics import _parse_figsize
131
if os.environ.get('SAGE_SKIP_PLOT_DIRECTIVE', 'no') != 'yes':
132
## Option handling is taken from Graphics.save
133
options = dict()
134
if isinstance(graphics, sage.plot.graphics.Graphics):
135
options.update(sage.plot.graphics.Graphics.SHOW_OPTIONS)
136
options.update(graphics._extra_kwds)
137
options.update(kwds)
138
elif isinstance(graphics, sage.plot.multigraphics.MultiGraphics):
139
options.update(kwds)
140
else:
141
graphics = graphics.plot(**kwds)
142
dpi = options.pop('dpi', None)
143
transparent = options.pop('transparent', None)
144
fig_tight = options.pop('fig_tight', None)
145
figsize = options.pop('figsize', None)
146
if figsize is not None:
147
figsize = _parse_figsize(figsize)
148
plt.figure(figsize=figsize)
149
figure = plt.gcf()
150
if isinstance(graphics, (sage.plot.graphics.Graphics,
151
sage.plot.multigraphics.MultiGraphics)):
152
graphics.matplotlib(figure=figure, figsize=figsize, **options)
153
if isinstance(graphics, (sage.plot.graphics.Graphics,
154
sage.plot.multigraphics.GraphicsArray)):
155
# for Graphics and GraphicsArray, tight_layout adjusts the
156
# *subplot* parameters so ticks aren't cut off, etc.
157
figure.tight_layout()
158
else:
159
# 3d graphics via png
160
import matplotlib as mpl
161
mpl.rcParams['image.interpolation'] = 'bilinear'
162
mpl.rcParams['image.resample'] = False
163
mpl.rcParams['figure.figsize'] = [8.0, 6.0]
164
mpl.rcParams['figure.dpi'] = 80
165
mpl.rcParams['savefig.dpi'] = 100
166
fn = tmp_filename(ext=".png")
167
graphics.save(fn)
168
img = mpimg.imread(fn)
169
plt.imshow(img)
170
plt.axis("off")
171
plt.margins(0)
172
if not isinstance(graphics, sage.plot.multigraphics.MultiGraphics):
173
plt.tight_layout(pad=0)
174
175
from sage.all_cmdline import *
176
"""
177
178
plot_html_show_formats = False
179
plot_formats = ['svg', 'pdf', 'png']
180
181
# We do *not* fully initialize intersphinx since we call it by hand
182
# in find_sage_dangling_links.
183
#, 'sphinx.ext.intersphinx']
184
185
# Add any paths that contain templates here, relative to this directory.
186
templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates'), 'templates']
187
188
# The master toctree document.
189
master_doc = 'index'
190
191
# General information about the project.
192
project = ""
193
copyright = "2005--{}, The Sage Development Team".format(dateutil.parser.parse(sage.version.date).year)
194
195
# The version info for the project you're documenting, acts as replacement for
196
# |version| and |release|, also used in various other places throughout the
197
# built documents.
198
version = sage.version.version
199
release = sage.version.version
200
201
source_repository = 'https://github.com/sagemath/sage/'
202
source_branch = 'develop'
203
204
# The language for content autogenerated by Sphinx. Refer to documentation
205
# for a list of supported languages.
206
# language = None
207
208
# The LaTeX engine to build the docs.
209
# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-latex_engine
210
latex_engine = 'lualatex'
211
212
# There are two options for replacing |today|: either, you set today to some
213
# non-false value, then it is used:
214
# today = ''
215
# Else, today_fmt is used as the format for a strftime call.
216
# today_fmt = '%B %d, %Y'
217
218
# List of glob-style patterns that should be excluded when looking for
219
# source files. [1] They are matched against the source file names
220
# relative to the source directory, using slashes as directory
221
# separators on all platforms.
222
exclude_patterns = ['.build']
223
224
# If true, '()' will be appended to :func: etc. cross-reference text.
225
# add_function_parentheses = True
226
227
# If true, the current module name will be prepended to all description
228
# unit titles (such as .. function::).
229
# add_module_names = True
230
231
# If true, sectionauthor and moduleauthor directives will be shown in the
232
# output. They are ignored by default.
233
show_authors = True
234
235
# Default lexer to use when highlighting code blocks, using the IPython
236
# console lexers. 'ipycon' is the IPython console, which is what we want
237
# for most code blocks: anything with "sage:" prompts. For other IPython,
238
# like blocks which might appear in a notebook cell, use 'ipython'.
239
highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'(sage:|>>>)', in2_regex=r'([.][.][.][.]:|[.][.][.])')
240
highlighting.lexers['ipython'] = IPyLexer()
241
highlight_language = 'ipycon'
242
243
# Create table of contents entries for domain objects (e.g. functions, classes,
244
# attributes, etc.). Default is True.
245
toc_object_entries = True
246
247
# A string that determines how domain objects (e.g. functions, classes,
248
# attributes, etc.) are displayed in their table of contents entry.
249
#
250
# Use "domain" to allow the domain to determine the appropriate number of parents
251
# to show. For example, the Python domain would show Class.method() and
252
# function(), leaving out the module. level of parents. This is the default
253
# setting.
254
#
255
# Use "hide" to only show the name of the element without any parents (i.e. method()).
256
#
257
# Use "all" to show the fully-qualified name for the object (i.e. module.Class.method()),
258
# displaying all parents.
259
toc_object_entries_show_parents = 'hide'
260
261
# -----------------------
262
# Extension configuration
263
# -----------------------
264
265
# include the todos
266
todo_include_todos = True
267
268
#
269
# intersphinx: Cross-links to other projects' online or installed documentation.
270
#
271
SAGE_DOC_REMOTE_INVENTORIES = os.environ.get('SAGE_DOC_REMOTE_INVENTORIES', 'no') == 'yes'
272
273
_vendored_inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor")
274
275
276
# Run "sage -python -m sage_docbuild.vendor" to update src/doc/common/_vendor/*.inv
277
_intersphinx_targets = {
278
'cvxopt': ['https://cvxopt.org/userguide/'],
279
'cvxpy': ['https://www.cvxpy.org/'],
280
'cypari2': ['https://cypari2.readthedocs.io/en/latest/'],
281
'cysignals': ['https://cysignals.readthedocs.io/en/latest/'],
282
'flint': ['https://flintlib.org/doc/'],
283
'fpylll': ['https://fpylll.readthedocs.io/en/latest/'],
284
'gmpy2': ['https://gmpy2.readthedocs.io/en/latest/'],
285
'ipywidgets': ['https://ipywidgets.readthedocs.io/en/stable/'],
286
'matplotlib': ['https://matplotlib.org/stable/'],
287
'mpmath': ['https://mpmath.org/doc/current/'],
288
'networkx': ['https://networkx.org/documentation/stable/'],
289
'numpy': ['https://numpy.org/doc/stable/'],
290
'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'],
291
'python': ['https://docs.python.org/'],
292
'rpy2': ['https://rpy2.github.io/doc/latest/html/'],
293
'scipy': ['https://docs.scipy.org/doc/scipy/'],
294
'sympy': ['https://docs.sympy.org/latest/'],
295
}
296
297
298
def _intersphinx_mapping(key):
299
inventories = []
300
link_target = None
301
for target in _intersphinx_targets[key]:
302
if not target:
303
pass
304
elif target.startswith('http'):
305
if not link_target:
306
link_target = target
307
if SAGE_DOC_REMOTE_INVENTORIES:
308
inventories.append(None) # Try downloading inventory from link_target
309
elif os.path.exists(target):
310
if not link_target:
311
link_target = target
312
inventory = os.path.join(target, 'objects.inv')
313
if os.path.exists(inventory):
314
inventories.append(inventory)
315
break
316
else:
317
vendored_inventory = os.path.join(_vendored_inventories_dir, key + '.inv')
318
if os.path.exists(vendored_inventory):
319
inventories.append(vendored_inventory)
320
else:
321
# To avoid docbuild failures when building Sage without internet
322
# connection, we use the local python inventory file as a fallback for other
323
# projects. Cross-references will not be resolved in that case, but the
324
# docbuild will still succeed.
325
python_inventory_file = os.path.join(_vendored_inventories_dir, "python.inv")
326
inventories.append(python_inventory_file)
327
assert link_target
328
if len(inventories) == 1:
329
return link_target, inventories[0]
330
return link_target, tuple(inventories)
331
332
333
def set_intersphinx_mappings(app, config):
334
"""
335
Add precompiled inventory (the objects.inv)
336
"""
337
app.config.intersphinx_mapping = {}
338
339
refpath = os.path.join(SAGE_DOC, "html", "en", "reference")
340
invpath = os.path.join(SAGE_DOC, "inventory", "en", "reference")
341
if app.config.multidoc_first_pass == 1 or \
342
not (os.path.exists(refpath) and os.path.exists(invpath)):
343
return
344
345
app.config.intersphinx_mapping = {key: _intersphinx_mapping(key)
346
for key in _intersphinx_targets}
347
348
# Add master intersphinx mapping
349
dst = os.path.join(invpath, 'objects.inv')
350
app.config.intersphinx_mapping['sagemath'] = (refpath, dst)
351
352
# Add intersphinx mapping for subdirectories
353
for directory in os.listdir(os.path.join(invpath)):
354
if directory == 'jupyter_execute':
355
# This directory is created by jupyter-sphinx extension for
356
# internal use and should be ignored here. See Issue #33507.
357
continue
358
if os.path.isdir(os.path.join(invpath, directory)):
359
src = os.path.join(refpath, directory)
360
dst = os.path.join(invpath, directory, 'objects.inv')
361
app.config.intersphinx_mapping[directory] = (src, dst)
362
363
intersphinx.normalize_intersphinx_mapping(app, config)
364
365
366
# By default document is master.
367
multidocs_is_master = True
368
369
# https://sphinx-copybutton.readthedocs.io/en/latest/use.html
370
copybutton_prompt_text = r"sage: |[.][.][.][.]: |>>> |[.][.][.] |\$ "
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
if SAGE_PREPARSED_DOC == 'yes':
996
# Tab for preparsed version
997
from sage.repl.preparse import preparse
998
container = TabContainer("", type="tab", new_set=False)
999
textnodes = [Text('Python')]
1000
label = Label("", "", *textnodes)
1001
container += label
1002
content = Container("", is_div=True, classes=["tab-content"])
1003
example_lines = []
1004
preparsed_lines = ['>>> from sage.all import *']
1005
for line in node.rawsource.splitlines() + ['']: # one extra to process last example
1006
newline = line.lstrip()
1007
if newline.startswith('....: '):
1008
example_lines.append(newline[6:])
1009
else:
1010
if example_lines:
1011
preparsed_example = preparse('\n'.join(example_lines))
1012
prompt = '>>> '
1013
for preparsed_line in preparsed_example.splitlines():
1014
preparsed_lines.append(prompt + preparsed_line)
1015
prompt = '... '
1016
example_lines = []
1017
if newline.startswith('sage: '):
1018
example_lines.append(newline[6:])
1019
else:
1020
preparsed_lines.append(line)
1021
preparsed = '\n'.join(preparsed_lines)
1022
preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon')
1023
content += preparsed_node
1024
container += content
1025
parent.insert(index, container)
1026
index += 1
1027
if isinstance(prev_node, nodes.paragraph):
1028
prev_node['classes'].append('with-python-tab')
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
if os.environ.get('SAGE_SKIP_TESTS_BLOCKS', False):
1082
app.connect('autodoc-process-docstring', skip_TESTS_block)
1083
app.connect('autodoc-skip-member', skip_member)
1084
app.add_transform(SagemathTransform)
1085
if SAGE_LIVE_DOC == 'yes' or SAGE_PREPARSED_DOC == 'yes':
1086
app.add_transform(SagecodeTransform)
1087
if not JupyterSphinx().is_present():
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