diff --git a/src/sage_docbuild/__main__.py b/src/sage_docbuild/__main__.py index 6459596a28b..e8b156a4dca 100644 --- a/src/sage_docbuild/__main__.py +++ b/src/sage_docbuild/__main__.py @@ -471,16 +471,6 @@ 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 @@ -488,6 +478,17 @@ def main(): 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) diff --git a/src/sage_docbuild/builders.py b/src/sage_docbuild/builders.py index 91035a01f1c..0542cf936ba 100644 --- a/src/sage_docbuild/builders.py +++ b/src/sage_docbuild/builders.py @@ -390,7 +390,7 @@ class ReferenceBuilder(): 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'. @@ -468,6 +468,7 @@ def _wrapper(self, format, *args, **kwds): # the other documents. self._build_top_level(format, *args, **kwds) + class ReferenceTopBuilder(DocBuilder): """ This class builds the top-level page of the reference manual. @@ -999,50 +1000,57 @@ def print_included_modules(self): 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 {}' @@ -1051,6 +1059,7 @@ def __init__(self, path): html_title = project html_short_title = project htmlhelp_basename = name +html_favicon='' with contextlib.suppress(ValueError): extensions.remove('multidocs') # see #29651 @@ -1061,7 +1070,7 @@ def __init__(self, path): ('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""" @@ -1069,7 +1078,7 @@ 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 @@ -1084,30 +1093,22 @@ def setup(app): :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. @@ -1136,7 +1137,7 @@ def get_builder(name: str, options: BuildOptions) -> DocBuilder | ReferenceBuild 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: @@ -1176,6 +1177,7 @@ def get_all_documents(source: Path) -> list[Path]: return documents + def get_all_reference_documents(source: Path) -> list[Path]: """ Return a list of all reference manual documents to build, relative to the