Skip to content

Commit 6ecdd95

Browse files
authored
♻️ REFACTOR: package code (#55)
Essentially re-writes the most of the code, to implement a number of improvements and bug fixes, but the output LaTeX is shown (via tests) to be essentially the same. - move event functions to separate file (and rename) - add sphinx based tests, which provide comparisons of the doctrees create with/without this extension, before/after post-transforms have been applied. This also shows how the package can be used as a "standalone" sphinx extension. - use `importlib.resources` for accessing `jupyterBook.cls` - Remove enforcement of `myst-parser` `amsmath` extension - make `myst_nb` requirement optional, and check for compatibility - convert class/function nameing from pascalCase to camel_case (and enforce with pep8-naming linter): - replace `getFilenameWithSubpath` with `is_root_document` - replace `removeExtension` with `remove_suffix` - replace `H2Node` and `H3Node` by `RootHeader` node, which applies to any sub-section of the root file (i.e. also H4 etc) - added `app.add_config_value("jblatex_load_imgconverter", True, "env")` to enable loading of sphinx.ext.imgconverter to be turned off - remove reliance on _toc.yml, instead adding `app.add_config_value("jblatex_captions_to_parts", None, "env", (type(None), bool))` - If `None` and `app.env.external_site_map` is present (from sphinx-external-toc), then this will be used to "infer" `latex_toplevel_sectioning` and whether to convert captions to parts. - Move all doctree restructuring to the post-transform, so that the stored doctrees are builder agnostic (as should be the case). The transform (which is now called for all builders) only adds extra node attributes for use by the post-transform. - a lot more code documentation and added to README
1 parent c424447 commit 6ecdd95

35 files changed

+1473
-360
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Byte-compiled / optimized / DLL files
22
__pycache__/
3+
*.pyc
34

45
build/
56
dist/

.pre-commit-config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,17 @@ repos:
3939
rev: 3.8.4
4040
hooks:
4141
- id: flake8
42-
additional_dependencies: [flake8-bugbear==21.3.1]
42+
additional_dependencies:
43+
- flake8-bugbear~=21.3.1
44+
- flake8-comprehensions~=3.4.0
45+
- pep8-naming~=0.11.1
4346

4447
- repo: https://github.com/pre-commit/mirrors-mypy
4548
rev: v0.790
4649
hooks:
4750
- id: mypy
4851
additional_dependencies: ["sphinx>=3,<4"]
49-
exclude: tests/roots/test-codeCellTransforms/conf.py
52+
exclude: tests/roots/.*py
5053

5154
- repo: https://github.com/asottile/setup-cfg-fmt
5255
rev: v1.16.0

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ include README.md
1414
include CHANGELOG.md
1515

1616
include jupyterbook_latex/py.typed
17-
recursive-include jupyterbook_latex/theme *
17+
include jupyterbook_latex/theme/jupyterBook.cls

README.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# jupyterbook-latex
1+
# jupyterbook-latex [IN DEVELOPMENT]
22

3-
Supporting LaTeX infrastructure for Jupyter Book
3+
Sphinx extension to support LaTeX infrastructure for Jupyter Book.
44

55
This repository is a **development** project to improve LaTeX support
66
in `Jupyter Book`.
@@ -13,14 +13,47 @@ To get started with `jupyterbook-latex`, first install it through `pip`:
1313
pip install jupyterbook-latex
1414
```
1515

16-
then, add `jupyterbook_latex` to the `config.yml` file in your jupyterbook projects:
16+
then, add `jupyterbook_latex` to your extensions,
17+
in a Sphinx `conf.py`:
1718

19+
```python
20+
extensions = ["jupyterbook_latex"]
21+
22+
# autoload the sphinx.ext.imgconverter extension, optional (default is True)
23+
# jblatex_load_imgconverter = True
24+
# turn root level toctree captions into top-level `part` headings, optional (default is to auto-infer)
25+
# jblatex_captions_to_parts = True
1826
```
27+
28+
OR in the jupyterbook `config.yml`:
29+
30+
```yaml
1931
sphinx:
2032
extra_extensions:
21-
- jupyterbook_latex
33+
- jupyterbook_latex
34+
# config:
35+
# jblatex_load_imgconverter: true
36+
# jblatex_captions_to_parts: true
2237
```
2338

39+
## Extension Details
40+
41+
This extension does not provide an actual Sphinx LaTeX theme,
42+
instead it instantiates a number of transforms (for LaTeX builders only) that manipulate the AST into the required format:
43+
44+
1. Overrides some configuration:
45+
46+
- ``latex_engine`` -> ``xelatex``
47+
- ``latex_theme`` -> ``jupyterBook``
48+
- appends necessary LaTeX commands to the preamble
49+
50+
2. When a latex builder is specified:
51+
52+
- Set's up `sphinx.ext.imgconverter` (if `jblatex_load_imgconverter`)
53+
- Replace sub-headers in the root document
54+
- Create headings from the root-level toctree captions (if `jblatex_captions_to_parts`)
55+
- Move bibliographies to the bottom of the document
56+
2457
Issues
2558
------
2659

docs/source/conf.py renamed to docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
# This pattern also affects html_static_path and html_extra_path.
2424
exclude_patterns = ["_build"]
2525

26-
html_static_path = ["_static"]
26+
# html_static_path = ["_static"]
2727

2828
# -- Options for HTML output -------------------------------------------------
2929

File renamed without changes.
File renamed without changes.

jupyterbook_latex/__init__.py

Lines changed: 21 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,32 @@
1-
import os
2-
from pathlib import Path
1+
""" """
32

4-
from docutils import nodes as docnodes
5-
from sphinx import builders
6-
from sphinx.util import logging
7-
from sphinx.util.fileutil import copy_asset_file
83

9-
from .nodes import (
10-
H2Node,
11-
H3Node,
12-
HiddenCellNode,
13-
depart_H2Node,
14-
depart_H3Node,
15-
visit_H2Node,
16-
visit_H3Node,
17-
visit_HiddenCellNode,
18-
)
19-
from .transforms import (
20-
LatexMasterDocTransforms,
21-
ToctreeTransforms,
22-
codeCellTransforms,
23-
handleSubSections,
24-
)
4+
from typing import TYPE_CHECKING
255

26-
__version__ = "0.2.0"
27-
"""jupyterbook-latex version"""
28-
29-
logger = logging.getLogger(__name__)
30-
31-
32-
# Helper node
33-
def skip(self, node):
34-
raise docnodes.SkipNode
35-
36-
37-
def build_init_handler(app):
38-
from sphinx.util.console import bold
39-
40-
# only allow latex builder to access rest of the features
41-
if isinstance(app.builder, builders.latex.LaTeXBuilder):
42-
app.add_post_transform(codeCellTransforms)
43-
copy_static_files(app)
44-
TOC_PATH = Path(app.confdir).joinpath("_toc.yml")
45-
if not os.path.exists(TOC_PATH):
46-
logger.info(
47-
"Some features of this exetension will work only with a jupyter-book application" # noqa: E501
48-
)
49-
return
50-
app.config["myst_amsmath_enable"] = True
51-
app.setup_extension("sphinx.ext.imgconverter")
52-
app.add_transform(LatexMasterDocTransforms)
53-
app.add_post_transform(ToctreeTransforms)
54-
app.add_post_transform(handleSubSections)
55-
logger.info(
56-
bold("jupyterbook-latex v%s:") + "(latex_engine='%s')",
57-
__version__,
58-
app.config["latex_engine"],
59-
)
6+
if TYPE_CHECKING:
7+
from sphinx.application import Sphinx
608

9+
__version__ = "0.2.1a1"
10+
"""jupyterbook-latex version"""
6111

62-
def add_necessary_config(app, config):
63-
# only allow latex builder to access rest of the features
64-
config["latex_engine"] = "xelatex"
65-
config["latex_theme"] = "jupyterBook"
6612

67-
# preamble to overwrite things from sphinx latex writer
68-
configPreamble = ""
69-
if "preamble" in config["latex_elements"]:
70-
configPreamble = config["latex_elements"]["preamble"]
13+
def setup(app: "Sphinx") -> None:
14+
"""The sphinx entry-point for the extension."""
7115

72-
config["latex_elements"]["preamble"] = (
73-
configPreamble
74-
+ r"""
75-
\usepackage[Latin,Greek]{ucharclasses}
76-
\usepackage{unicode-math}
77-
% fixing title of the toc
78-
\addto\captionsenglish{\renewcommand{\contentsname}{Contents}}
79-
"""
80-
)
16+
from .events import override_latex_config, setup_latex_transforms
17+
from .nodes import HiddenCellNode, RootHeader
18+
from .transforms import LatexRootDocTransforms
8119

20+
# autoload the sphinx.ext.imgconverter extension
21+
app.add_config_value("jblatex_load_imgconverter", True, "env")
22+
# turn root level toctree captions into top-level `part` headings
23+
# If None, auto-infer whether to do this, or specifically specify
24+
app.add_config_value("jblatex_captions_to_parts", None, "env", (type(None), bool))
8225

83-
def copy_static_files(app):
84-
themePath = Path(__file__).parent.joinpath("theme")
85-
clsFile = themePath.joinpath("jupyterBook.cls")
86-
copy_asset_file(str(clsFile), app.outdir)
26+
HiddenCellNode.add_node(app)
27+
RootHeader.add_node(app)
8728

29+
app.add_transform(LatexRootDocTransforms)
8830

89-
def setup(app):
90-
app.add_node(
91-
HiddenCellNode,
92-
override=True,
93-
html=(visit_HiddenCellNode, None),
94-
latex=(visit_HiddenCellNode, None),
95-
textinfo=(visit_HiddenCellNode, None),
96-
text=(visit_HiddenCellNode, None),
97-
man=(visit_HiddenCellNode, None),
98-
)
99-
app.add_node(
100-
H2Node,
101-
override=True,
102-
latex=(visit_H2Node, depart_H2Node),
103-
html=(visit_H2Node, depart_H2Node),
104-
textinfo=(skip, None),
105-
text=(skip, None),
106-
man=(skip, None),
107-
)
108-
app.add_node(
109-
H3Node,
110-
override=True,
111-
latex=(visit_H3Node, depart_H3Node),
112-
html=(visit_H3Node, depart_H3Node),
113-
textinfo=(skip, None),
114-
text=(skip, None),
115-
man=(skip, None),
116-
)
117-
app.connect("config-inited", add_necessary_config)
118-
app.connect("builder-inited", build_init_handler)
31+
app.connect("config-inited", override_latex_config)
32+
app.connect("builder-inited", setup_latex_transforms)

jupyterbook_latex/events.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import sys
2+
from typing import cast
3+
4+
from sphinx.application import Sphinx
5+
from sphinx.builders.latex import LaTeXBuilder
6+
from sphinx.config import Config
7+
from sphinx.util import logging
8+
from sphinx.util.fileutil import copy_asset_file
9+
10+
from . import __version__, theme
11+
from .transforms import LatexRootDocPostTransforms, MystNbPostTransform
12+
13+
if sys.version_info < (3, 9):
14+
import importlib_resources as resources
15+
else:
16+
import importlib.resources as resources
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
def override_latex_config(app: Sphinx, config: Config) -> None:
22+
"""This ``config-inited`` event overrides aspects of the sphinx latex config.
23+
24+
- ``latex_engine`` -> ``xelatex``
25+
- ``latex_theme`` -> ``jupyterBook``
26+
- appends necessary LaTeX commands to the preamble
27+
28+
"""
29+
# only allow latex builder to access rest of the features
30+
config["latex_engine"] = "xelatex"
31+
config["latex_theme"] = "jupyterBook"
32+
33+
latex_elements = cast(dict, config["latex_elements"])
34+
35+
# preamble to overwrite things from sphinx latex writer
36+
config_preamble = (
37+
latex_elements["preamble"] if "preamble" in config["latex_elements"] else ""
38+
)
39+
40+
latex_elements["preamble"] = (
41+
config_preamble
42+
+ r"""
43+
\usepackage[Latin,Greek]{ucharclasses}
44+
\usepackage{unicode-math}
45+
% fixing title of the toc
46+
\addto\captionsenglish{\renewcommand{\contentsname}{Contents}}
47+
"""
48+
)
49+
50+
51+
def setup_latex_transforms(app: Sphinx) -> None:
52+
"""This ``builder-inited`` event sets up aspects of the extension,
53+
reserved only for when a LaTeX builder is specified.
54+
"""
55+
56+
if not isinstance(app.builder, LaTeXBuilder):
57+
return
58+
59+
# note: bold is a dynamically created function
60+
from sphinx.util.console import bold # type: ignore[attr-defined]
61+
62+
# decide whether we will convert top-level toctree captions to parts
63+
app.env.jblatex_captions_to_parts = False # type: ignore[attr-defined]
64+
if app.config["jblatex_captions_to_parts"] is True: # type: ignore[comparison-overlap]
65+
app.config["latex_toplevel_sectioning"] = "part"
66+
app.env.jblatex_captions_to_parts = True
67+
elif app.config["jblatex_captions_to_parts"] is None:
68+
# if using the sphinx-external-toc, we can look if parts are being specified
69+
# TODO this should probably be made more robust
70+
sitemap = getattr(app.config, "external_site_map", None)
71+
if sitemap is not None:
72+
if sitemap.file_format == "jb-book" and len(sitemap.root.subtrees) > 1:
73+
app.config["latex_toplevel_sectioning"] = "part"
74+
app.env.jblatex_captions_to_parts = True
75+
elif sitemap.file_format == "jb-book":
76+
app.config["latex_toplevel_sectioning"] = "chapter"
77+
elif sitemap.file_format == "jb-article":
78+
app.config["latex_toplevel_sectioning"] = "section"
79+
80+
logger.info(
81+
bold("jupyterbook-latex v%s:") + "engine='%s', toplevel_section='%s'",
82+
__version__,
83+
app.config["latex_engine"],
84+
app.config["latex_toplevel_sectioning"],
85+
)
86+
87+
# Copy the class theme to the output directory.
88+
# note: importlib.resources is the formal method to access files within packages
89+
with resources.as_file(resources.files(theme).joinpath("jupyterBook.cls")) as path:
90+
copy_asset_file(str(path), app.outdir)
91+
92+
# only load when myst-nb is present
93+
if MystNbPostTransform.check_dependency():
94+
app.add_post_transform(MystNbPostTransform)
95+
96+
if app.config["jblatex_load_imgconverter"]:
97+
app.setup_extension("sphinx.ext.imgconverter")
98+
app.add_post_transform(LatexRootDocPostTransforms)

0 commit comments

Comments
 (0)