Skip to content

fixes for single-file doc-builder #40431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions src/sage_docbuild/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,23 +471,24 @@ def main():
print(d.as_posix())
sys.exit(0)

# Check that the docs output directory exists
if args.output_dir is None:
args.output_dir = Path(os.environ.get('SAGE_DOC', 'src/doc'))
args.output_dir = args.output_dir.absolute()
if not args.output_dir.exists():
try:
args.output_dir.mkdir(parents=True)
except Exception as e:
parser.error(f"Failed to create output directory {args.output_dir}: {e}")

# Get the name and type (target format) of the document we are
# trying to build.
name, typ = args.document, args.format
if not name or not typ:
parser.print_help()
sys.exit(1)

# Check that the docs output directory exists
if not name.startswith("file="):
if args.output_dir is None:
args.output_dir = Path(os.environ.get('SAGE_DOC', 'src/doc'))
args.output_dir = args.output_dir.absolute()
if not args.output_dir.exists():
try:
args.output_dir.mkdir(parents=True)
except Exception as e:
parser.error(f"Failed to create output directory {args.output_dir}: {e}")

# Set up module-wide logging.
setup_logger(args.verbose, args.color)

Expand Down
72 changes: 37 additions & 35 deletions src/sage_docbuild/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@

EXAMPLES::

sage: from sage_docbuild.builders import DocBuilder

Check failure on line 177 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_dir[0]>", line 1, in <module> from sage_docbuild.builders import DocBuilder ModuleNotFoundError: No module named 'sage_docbuild'
sage: from sage_docbuild.build_options import BuildOptions

Check failure on line 178 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_dir[1]>", line 1, in <module> from sage_docbuild.build_options import BuildOptions ModuleNotFoundError: No module named 'sage_docbuild'
sage: import tempfile
sage: with tempfile.TemporaryDirectory() as directory:

Check failure on line 180 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_dir[3]>", line 2, in <module> options = BuildOptions(output_dir=Path(directory), source_dir=Path('src/doc')) ^^^^^^^^^^^^ NameError: name 'BuildOptions' is not defined
....: options = BuildOptions(output_dir=Path(directory), source_dir=Path('src/doc'))
....: builder = DocBuilder('en/tutorial', options)
....: builder._output_dir('html')
Expand All @@ -196,10 +196,10 @@

EXAMPLES::

sage: from sage_docbuild.builders import DocBuilder

Check failure on line 199 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder.Path[0]>", line 1, in <module> from sage_docbuild.builders import DocBuilder ModuleNotFoundError: No module named 'sage_docbuild'
sage: from sage_docbuild.build_options import BuildOptions

Check failure on line 200 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder.Path[1]>", line 1, in <module> from sage_docbuild.build_options import BuildOptions ModuleNotFoundError: No module named 'sage_docbuild'
sage: import tempfile
sage: with tempfile.TemporaryDirectory() as directory:

Check failure on line 202 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder.Path[3]>", line 2, in <module> options = BuildOptions(output_dir=Path(directory), source_dir=Path('src/doc')) ^^^^^^^^^^^^ NameError: name 'BuildOptions' is not defined
....: options = BuildOptions(output_dir=Path(directory), source_dir=Path('src/doc'))
....: builder = DocBuilder('en/tutorial', options)
....: builder._doctrees_dir()
Expand All @@ -215,10 +215,10 @@

EXAMPLES::

sage: from sage_docbuild.builders import DocBuilder

Check failure on line 218 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_formats[0]>", line 1, in <module> from sage_docbuild.builders import DocBuilder ModuleNotFoundError: No module named 'sage_docbuild'
sage: from sage_docbuild.build_options import BuildOptions

Check failure on line 219 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_formats[1]>", line 1, in <module> from sage_docbuild.build_options import BuildOptions ModuleNotFoundError: No module named 'sage_docbuild'
sage: options = BuildOptions(source_dir=Path('src/doc'))

Check failure on line 220 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_formats[2]>", line 1, in <module> options = BuildOptions(source_dir=Path('src/doc')) ^^^^^^^^^^^^ NameError: name 'BuildOptions' is not defined
sage: builder = DocBuilder('tutorial', options)

Check failure on line 221 in src/sage_docbuild/builders.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 730, in _run self.compile_and_execute(example, compiler, test.globs) File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 1154, in compile_and_execute exec(compiled, globs) File "<doctest sage_docbuild.builders.DocBuilder._output_formats[3]>", line 1, in <module> builder = DocBuilder('tutorial', options) ^^^^^^^^^^ NameError: name 'DocBuilder' is not defined
sage: builder._output_formats()
['changes', 'html', 'htmlhelp', 'inventory', 'json', 'latex', 'linkcheck', 'pickle', 'web']
"""
Expand Down Expand Up @@ -390,7 +390,7 @@
build the top-level page and ReferenceSubBuilder for each
sub-component.
"""
def __init__(self, name:str, options: BuildOptions):
def __init__(self, name: str, options: BuildOptions):
"""
Record the reference manual's name, in case it's not
identical to 'reference'.
Expand Down Expand Up @@ -468,6 +468,7 @@
# the other documents.
self._build_top_level(format, *args, **kwds)


class ReferenceTopBuilder(DocBuilder):
"""
This class builds the top-level page of the reference manual.
Expand Down Expand Up @@ -999,50 +1000,57 @@
class SingleFileBuilder(DocBuilder):
"""
This is the class used to build the documentation for a single
user-specified file. If the file is called 'foo.py', then the
documentation is built in ``DIR/foo/`` if the user passes the
command line option "-o DIR", or in ``DOT_SAGE/docbuild/foo/``
otherwise.
user-specified file.

If the file is called 'foo.py', then the documentation is built in
``DIR/foo/`` if the user passes the command line option "-o DIR",
or in ``DOT_SAGE/docbuild/foo/`` otherwise.
"""
def __init__(self, path):
def __init__(self, path, options) -> None:
"""
INPUT:

- ``path`` -- the path to the file for which documentation
should be built

- ``options`` -- container for options
"""
from sage.env import DOT_SAGE
self.lang = 'en'
self.name = 'single_file'
path = os.path.abspath(path)
path = Path(path).absolute()

# Create docbuild and relevant subdirectories, e.g.,
# the static and templates directories in the output directory.
# By default, this is DOT_SAGE/docbuild/MODULE_NAME, but can
# also be specified at the command line.
module_name = os.path.splitext(os.path.basename(path))[0]
module_name = path.stem
latex_name = module_name.replace('_', r'\\_')

if self._options.output_dir:
base_dir = os.path.join(self._options.output_dir, module_name)
if os.path.exists(base_dir):
logger.warning('Warning: Directory %s exists. It is safer to build in a new directory.' % base_dir)
if options.output_dir:
base_dir = Path(options.output_dir) / module_name
if base_dir.exists():
logger.warning(f'Warning: Directory {base_dir} exists. It is safer to build in a new directory.')
else:
base_dir = os.path.join(DOT_SAGE, 'docbuild', module_name)
base_dir = Path(DOT_SAGE) / 'docbuild' / module_name
try:
shutil.rmtree(base_dir)
except OSError:
pass
self.dir = os.path.join(base_dir, 'source')

os.makedirs(os.path.join(self.dir, "static"), exist_ok=True)
os.makedirs(os.path.join(self.dir, "templates"), exist_ok=True)
self.dir = base_dir / 'source'
Path.mkdir(self.dir / "static", parents=True, exist_ok=True)
Path.mkdir(self.dir / "templates", parents=True, exist_ok=True)

# Write self.dir/conf.py
conf = r"""# This file is automatically generated by {}, do not edit!

import sys, os, contextlib
import contextlib
import os
import sys
sys.path.append({!r})

from sage.docs.conf import *
from sage_docbuild.conf import *
html_static_path = [] + html_common_static_path

project = 'Documentation for {}'
Expand All @@ -1051,6 +1059,7 @@
html_title = project
html_short_title = project
htmlhelp_basename = name
html_favicon=''

with contextlib.suppress(ValueError):
extensions.remove('multidocs') # see #29651
Expand All @@ -1061,15 +1070,15 @@
('index', name + '.tex', 'Documentation for {}',
'unknown', 'manual'),
]
""".format(__file__, self.dir, module_name, module_name, latex_name)
""".format(__file__, str(self.dir), module_name, module_name, latex_name)

if 'SAGE_DOC_UNDERSCORE' in os.environ:
conf += r"""
def setup(app):
app.connect('autodoc-skip-member', skip_member)
"""

with open(os.path.join(self.dir, 'conf.py'), 'w') as conffile:
with open(self.dir / 'conf.py', 'w') as conffile:
conffile.write(conf)

# Write self.dir/index.rst
Expand All @@ -1084,30 +1093,22 @@
:undoc-members:
:show-inheritance:
""".format(heading, __file__, module_name)
with open(os.path.join(self.dir, 'index.rst'), 'w') as indexfile:
indexfile.write(index)

# Create link from original file to self.dir. Note that we
# append self.dir to sys.path in conf.py. This is reasonably
# safe (but not perfect), since we just created self.dir.
try:
os.symlink(path, os.path.join(self.dir, os.path.basename(path)))
except OSError:
pass
with open(self.dir / 'index.rst', 'w') as indexfile:
indexfile.write(index)

def _output_dir(self, type):
def _output_dir(self, type: str) -> Path:
"""
Return the directory where the output of type ``type`` is stored.

If the directory does not exist, then it will automatically be
created.
"""
base_dir = os.path.split(self.dir)[0]
d = os.path.join(base_dir, "output", type)
os.makedirs(d, exist_ok=True)
d = self.dir.parent / "output" / type
Path.mkdir(d, parents=True, exist_ok=True)
return d

def _doctrees_dir(self):
def _doctrees_dir(self) -> Path:
"""
Return the directory where the doctrees are stored.

Expand Down Expand Up @@ -1136,7 +1137,7 @@
path = name[5:]
if path.endswith('.sage') or path.endswith('.pyx'):
raise NotImplementedError('Building documentation for a single file only works for Python files.')
return SingleFileBuilder(path)
return SingleFileBuilder(path, options)
elif Path(name) in get_all_documents(options.source_dir):
return DocBuilder(name, options)
else:
Expand Down Expand Up @@ -1176,6 +1177,7 @@

return documents


def get_all_reference_documents(source: Path) -> list[Path]:
"""
Return a list of all reference manual documents to build, relative to the
Expand Down
Loading