mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-13 08:52:13 -05:00
Add `app.add_static_dir()` for copying extension static files (#14219)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
1 parent
20f1c46790
commit
b127b9478a
13 files changed
+130
-1
No files matched your search
@@ -17,6 +17,10 @@ Deprecated
|
||||
Features added
|
||||
--------------
|
||||
|
||||
* Add :meth:`~sphinx.application.Sphinx.add_static_dir` for copying static
|
||||
assets from extensions to the build output.
|
||||
Patch by Jared Dillard
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ package.
|
||||
|
||||
.. automethod:: Sphinx.add_css_file
|
||||
|
||||
.. automethod:: Sphinx.add_static_dir
|
||||
|
||||
.. automethod:: Sphinx.add_latex_package
|
||||
|
||||
.. automethod:: Sphinx.add_lexer
|
||||
|
||||
@@ -1739,6 +1739,11 @@ and also make use of these options.
|
||||
An alternative approach is to use :confval:`html_extra_path`,
|
||||
which allows copying dotfiles under the directories.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`~sphinx.application.Sphinx.add_static_dir`, for use in extensions
|
||||
to copy static files to the output directory.
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
The paths in :confval:`html_static_path` can now contain subdirectories.
|
||||
|
||||
|
||||
+43
-1
@@ -10,6 +10,7 @@ import pickle
|
||||
import sys
|
||||
from collections import deque
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, overload
|
||||
|
||||
from docutils.parsers.rst import roles
|
||||
@@ -37,7 +38,6 @@ from sphinx.util.tags import Tags
|
||||
if TYPE_CHECKING:
|
||||
import os
|
||||
from collections.abc import Callable, Collection, Iterable, Sequence, Set
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, Final, Literal
|
||||
|
||||
from docutils import nodes
|
||||
@@ -1484,6 +1484,9 @@ class Sphinx:
|
||||
A JavaScript file can be added to the specific HTML page when an extension
|
||||
calls this method on :event:`html-page-context` event.
|
||||
|
||||
.. seealso::
|
||||
:meth:`add_static_dir` for copying static files to the output directory
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
.. versionchanged:: 1.8
|
||||
@@ -1549,6 +1552,9 @@ class Sphinx:
|
||||
A CSS file can be added to the specific HTML page when an extension calls
|
||||
this method on :event:`html-page-context` event.
|
||||
|
||||
.. seealso::
|
||||
:meth:`add_static_dir` for copying static files to the output directory
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
@@ -1572,6 +1578,42 @@ class Sphinx:
|
||||
filename, priority=priority, **kwargs
|
||||
)
|
||||
|
||||
def add_static_dir(self, path: str | os.PathLike[str]) -> None:
|
||||
"""Register a static directory to include in HTML output.
|
||||
|
||||
The given directory's contents will be copied to the ``_static``
|
||||
directory during an HTML build. Files from extension static directories
|
||||
are copied after theme static files and before any directories from
|
||||
the user-configured ``html_static_path`` setting.
|
||||
|
||||
Sphinx has built-in support for ``static/`` directories in themes;
|
||||
theme developers should only use this method to register further
|
||||
directories to be copied.
|
||||
|
||||
:param path: The path to a directory containing static files.
|
||||
This is typically relative to the extension's package
|
||||
directory.
|
||||
|
||||
Example::
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
def setup(app):
|
||||
# All files in this directory are copied to _static/,
|
||||
# preserving the subdirectory structure
|
||||
app.add_static_dir(Path(__file__).parent / 'static')
|
||||
|
||||
# Add JavaScript and CSS files to HTML pages,
|
||||
# the paths are relative to _static/
|
||||
app.add_js_file('js/my_extension.js')
|
||||
app.add_css_file('css/my_extension.css')
|
||||
|
||||
.. versionadded:: 9.1
|
||||
"""
|
||||
path = Path(path)
|
||||
logger.debug("[app] adding static_dir: '%s'", path)
|
||||
self.registry.add_static_dir(path)
|
||||
|
||||
def add_latex_package(
|
||||
self, packagename: str, options: str | None = None, after_hyperref: bool = False
|
||||
) -> None:
|
||||
|
||||
@@ -865,6 +865,15 @@ class StandaloneHTMLBuilder(Builder):
|
||||
force=True,
|
||||
)
|
||||
|
||||
def copy_static_dirs(self) -> None:
|
||||
"""Copy static files registered by extensions."""
|
||||
for static_dir in self._registry.static_dirs:
|
||||
if static_dir.is_dir():
|
||||
shutil.copytree(static_dir, self._static_dir, dirs_exist_ok=True)
|
||||
else:
|
||||
msg = __("extension static directory '%s' does not exist")
|
||||
logger.warning(msg, static_dir)
|
||||
|
||||
def copy_html_static_files(self, context: dict[str, Any]) -> None:
|
||||
def onerror(filename: str, error: Exception) -> None:
|
||||
logger.warning(
|
||||
@@ -916,6 +925,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.copy_translation_js()
|
||||
self.copy_stemmer_js()
|
||||
self.copy_theme_static_files(context)
|
||||
self.copy_static_dirs()
|
||||
self.copy_html_static_files(context)
|
||||
self.copy_html_logo()
|
||||
self.copy_html_favicon()
|
||||
|
||||
@@ -22,6 +22,7 @@ from sphinx.util.logging import prefixed_warnings
|
||||
if TYPE_CHECKING:
|
||||
import os
|
||||
from collections.abc import Callable, Iterator, Mapping, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from docutils import nodes
|
||||
@@ -125,6 +126,9 @@ class SphinxComponentRegistry:
|
||||
#: js_files; list of JS paths or URLs
|
||||
self.js_files: list[tuple[str | None, dict[str, Any]]] = []
|
||||
|
||||
#: static directories registered by extensions
|
||||
self.static_dirs: list[Path] = []
|
||||
|
||||
#: LaTeX packages; list of package names and its options
|
||||
self.latex_packages: list[tuple[str, str | None]] = []
|
||||
|
||||
@@ -466,6 +470,11 @@ class SphinxComponentRegistry:
|
||||
logger.debug('[app] adding js_file: %r, %r', filename, attributes)
|
||||
self.js_files.append((filename, attributes))
|
||||
|
||||
def add_static_dir(self, path: Path) -> None:
|
||||
"""Register a static directory for extensions."""
|
||||
logger.debug("[app] adding static_dir: '%s'", path)
|
||||
self.static_dirs.append(path)
|
||||
|
||||
def has_latex_package(self, name: str) -> bool:
|
||||
packages = self.latex_packages + self.latex_packages_after_hyperref
|
||||
return bool([x for x in packages if x[0] == name])
|
||||
|
||||
@@ -115,6 +115,9 @@ def copy_asset(
|
||||
|
||||
Use ``copy_asset_file`` instead to copy a single file.
|
||||
|
||||
Use ``app.add_static_dir()`` instead to copy static files to
|
||||
the output directory.
|
||||
|
||||
:param source: The path to source file or directory
|
||||
:param destination: The path to destination directory
|
||||
:param excluded: The matcher to determine the given path should be copied or not
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path.cwd().resolve()))
|
||||
|
||||
project = 'Test Extension Static Dir'
|
||||
extensions = ['staticdirext']
|
||||
@@ -0,0 +1,4 @@
|
||||
Test
|
||||
====
|
||||
|
||||
Content for testing extension static directories.
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Test extension that registers a static directory."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_static_dir(HERE / 'static')
|
||||
app.add_js_file('js/myext.js')
|
||||
app.add_css_file('css/myext.css')
|
||||
return {
|
||||
'version': '1.0',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Extension CSS file */
|
||||
.myext { color: blue; }
|
||||
@@ -0,0 +1,2 @@
|
||||
// Extension JavaScript file
|
||||
console.log('myext loaded');
|
||||
@@ -180,3 +180,26 @@ def test_javscript_loading_method(app: SphinxTestApp) -> None:
|
||||
assert '<script src="_static/normal.js"></script>' in content
|
||||
assert '<script async="async" src="_static/early.js"></script>' in content
|
||||
assert '<script defer="defer" src="_static/late.js"></script>' in content
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext_static_dir')
|
||||
def test_extension_static_dir(app: SphinxTestApp) -> None:
|
||||
"""Test that extension static directories are copied."""
|
||||
app.build(force_all=True)
|
||||
|
||||
# Verify extension static files were copied to _static
|
||||
assert (app.outdir / '_static' / 'js' / 'myext.js').is_file()
|
||||
assert (app.outdir / '_static' / 'css' / 'myext.css').is_file()
|
||||
|
||||
# Verify JS and CSS are included in the HTML output
|
||||
content = (app.outdir / 'index.html').read_text(encoding='utf8')
|
||||
assert '_static/js/myext.js' in content
|
||||
assert '_static/css/myext.css' in content
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext_static_dir')
|
||||
def test_extension_static_dir_registry(app: SphinxTestApp) -> None:
|
||||
"""Test that extension static directories are registered in the registry."""
|
||||
# Verify the static dir is registered
|
||||
assert len(app.registry.static_dirs) == 1
|
||||
assert app.registry.static_dirs[0].name == 'static'
|
||||
Reference in new issue
Block a user