diff --git a/README.md b/README.md index e877330..c835085 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Quick Start -`sphinx-ape` is a documenation plugin for the Sphinx framework. +`sphinx-ape` is a documentation plugin for the Sphinx framework. The purpose of this plugin to share code for generating documentation across all ApeWorX repositories. ## Dependencies diff --git a/sphinx_ape/_base.py b/sphinx_ape/_base.py index c6b27cd..773e7fe 100644 --- a/sphinx_ape/_base.py +++ b/sphinx_ape/_base.py @@ -41,6 +41,10 @@ def methoddocs_path(self) -> Path: def conf_file(self) -> Path: return self.docs_path / "conf.py" + @property + def index_file(self) -> Path: + return self.build_path / "index.html" + def init(self): if not self.docs_path.is_dir(): self.docs_path.mkdir() diff --git a/sphinx_ape/build.py b/sphinx_ape/build.py index 9d6dbed..2c19b59 100644 --- a/sphinx_ape/build.py +++ b/sphinx_ape/build.py @@ -20,7 +20,10 @@ class BuildMode(Enum): LATEST = 0 """Build and then push to 'latest/'""" - RELEASE = 1 + MERGE_TO_MAIN = 1 + """Build and then push to 'stable/'""" + + RELEASE = 2 """Build and then push to 'stable/', 'latest/', and the version's release tag folder""" @classmethod @@ -40,8 +43,13 @@ def init(cls, identifier: Optional[Union[str, "BuildMode"]] = None) -> "BuildMod # Click being weird, value like "buildmode.release". identifier = identifier.split(".")[-1].upper() - # GitHub event name. - return BuildMode.RELEASE if identifier.lower() == "release" else BuildMode.LATEST + identifier = identifier.lower() + if identifier == "release": + return BuildMode.RELEASE + elif identifier in ("push", "merge_to_main"): + return BuildMode.MERGE_TO_MAIN + else: + return BuildMode.LATEST # Unexpected. raise TypeError(identifier) @@ -83,8 +91,9 @@ def build(self): building fails. """ - if self.mode is BuildMode.LATEST: + if self.mode in (BuildMode.LATEST, BuildMode.MERGE_TO_MAIN): # TRIGGER: Push to 'main' branch. Only builds latest. + # And on PRs / local. self._sphinx_build(self.latest_path) elif self.mode is BuildMode.RELEASE: @@ -122,7 +131,7 @@ def _publish(self, repository: Optional[str] = None, push: bool = True): else: repo_url = extract_source_url() - gh_pages_path = Path.cwd() / "gh-pages" + gh_pages_path = self._base_path / "gh-pages" git( "clone", repo_url, @@ -135,14 +144,11 @@ def _publish(self, repository: Optional[str] = None, push: bool = True): # Any built docs get added; the docs that got built are based on # the mode parameter. for path in self.build_path.iterdir(): - if not path.is_dir() or path.name.startswith(".") or path.name == "doctest": - continue - - elif path.name == "index.html": - (gh_pages_path / "index.html").write_text(path.read_text()) - - else: + if path.is_dir() and not path.name.startswith(".") and path.name != "doctest": shutil.copytree(path, gh_pages_path / path.name, dirs_exist_ok=True) + elif (path.name == "index.html") and path.is_file(): + gh_pages_path.mkdir(exist_ok=True) + (gh_pages_path / "index.html").write_text(path.read_text()) no_jykell_file = gh_pages_path / ".nojekyll" no_jykell_file.touch(exist_ok=True) @@ -195,14 +201,10 @@ def _build_release(self): def _setup_redirect(self): self.build_path.mkdir(exist_ok=True, parents=True) - - # In the case for local dev (or a new docs-site), the 'stable/' - # path will not exist yet, so use 'latest/' instead. redirect = "stable" if self.stable_path.is_dir() else "latest" - - index_file = self.build_path / "index.html" - index_file.unlink(missing_ok=True) - index_file.write_text(REDIRECT_HTML.format(redirect)) + # We replace it to handle the case when stable has joined the chat. + self.index_file.unlink(missing_ok=True) + self.index_file.write_text(REDIRECT_HTML.format(redirect)) def _sphinx_build(self, dst_path): sphinx_build(dst_path, self.docs_path) diff --git a/tests/test_build.py b/tests/test_build.py index 71631da..a6f9cf5 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -2,21 +2,33 @@ import pytest -from sphinx_ape.build import BuildMode, DocumentationBuilder +from sphinx_ape.build import REDIRECT_HTML, BuildMode, DocumentationBuilder class TestBuildMode: @pytest.mark.parametrize("val", ("latest", 0, "pull_request", "buildmode.latest")) def test_init_latest(self, val): - mode = BuildMode.init("val") + mode = BuildMode.init(val) assert mode is BuildMode.LATEST - @pytest.mark.parametrize("val", ("release", 1, "buildmode.release")) + @pytest.mark.parametrize("val", ("merge_to_main", 1, "push", "buildmode.merge_to_main")) + def test_init_merge_to_main(self, val): + mode = BuildMode.init(val) + assert mode is BuildMode.MERGE_TO_MAIN, val + + @pytest.mark.parametrize("val", ("release", 2, "buildmode.release")) def test_init_release(self, val): mode = BuildMode.init(val) assert mode is BuildMode.RELEASE +def assert_build_path(path: Path, expected: str): + assert path.name == expected # Tag, latest, or stable + assert path.parent.name == "sphinx-ape" # Project name, happens to be this lib + assert path.parent.parent.name == "_build" # Sphinx-ism + assert path.parent.parent.parent.name == "docs" # Sphinx-ism + + class TestDocumentationBuilder: @pytest.fixture(autouse=True) def mock_sphinx(self, mocker): @@ -37,7 +49,11 @@ def test_build_latest(self, mock_sphinx, temp_path): builder = DocumentationBuilder(mode=BuildMode.LATEST, base_path=temp_path) builder.build() call_path = mock_sphinx.call_args[0][0] - self.assert_build_path(call_path, "latest") + assert_build_path(call_path, "latest") + # Ensure re-direct exists and points to latest/. + assert builder.index_file.is_file() + expected_content = REDIRECT_HTML.format("latest") + assert builder.index_file.read_text() == expected_content def test_build_release(self, mock_sphinx, mock_git, temp_path): tag = "v1.0.0" @@ -45,10 +61,14 @@ def test_build_release(self, mock_sphinx, mock_git, temp_path): builder = DocumentationBuilder(mode=BuildMode.RELEASE, base_path=temp_path) builder.build() call_path = mock_sphinx.call_args[0][0] - self.assert_build_path(call_path, tag) + assert_build_path(call_path, tag) # Latest and Stable should also have been created! - self.assert_build_path(call_path.parent / "latest", "latest") - self.assert_build_path(call_path.parent / "stable", "stable") + assert_build_path(call_path.parent / "latest", "latest") + assert_build_path(call_path.parent / "stable", "stable") + # Ensure re-direct exists and points to stable/. + assert builder.index_file.is_file() + expected_content = REDIRECT_HTML.format("stable") + assert builder.index_file.read_text() == expected_content @pytest.mark.parametrize("sub_tag", ("alpha", "beta")) def test_build_alpha_release(self, sub_tag, mock_sphinx, mock_git, temp_path): @@ -61,12 +81,48 @@ def test_build_alpha_release(self, sub_tag, mock_sphinx, mock_git, temp_path): builder = DocumentationBuilder(mode=BuildMode.RELEASE, base_path=temp_path) builder.build() call_path = mock_sphinx.call_args[0][0] - self.assert_build_path(call_path, "stable") + assert_build_path(call_path, "stable") # Latest should also have been created! - self.assert_build_path(call_path.parent / "latest", "latest") + assert_build_path(call_path.parent / "latest", "latest") - def assert_build_path(self, path: Path, expected: str): - assert path.name == expected # Tag, latest, or stable - assert path.parent.name == "sphinx-ape" # Project name, happens to be this lib - assert path.parent.parent.name == "_build" # Sphinx-ism - assert path.parent.parent.parent.name == "docs" # Sphinx-ism + def test_publish_merge_to_main(self, temp_path, mock_git): + tag = "v1.0.0" + mock_git.return_value = tag + builder = DocumentationBuilder(mode=BuildMode.MERGE_TO_MAIN, base_path=temp_path) + # Ensure built first. + builder.build() + builder.publish(push=False) + gh_pages_path = temp_path / "gh-pages" + nojekyll_file = gh_pages_path / ".nojekyll" + stable_dir = gh_pages_path / "stable" + latest_dir = gh_pages_path / "latest" + tag_dir = gh_pages_path / tag + index_file = gh_pages_path / builder.index_file.name + assert gh_pages_path.is_dir() + assert nojekyll_file.is_file() + assert latest_dir.is_dir() + assert index_file.is_file() + # Not tag-release should have gotten created on merge-to-main. + assert not tag_dir.is_dir() + # Stable only gets built on releases. + assert not stable_dir.is_dir() + + def test_publish_release(self, temp_path, mock_git): + tag = "v1.0.0" + mock_git.return_value = tag + builder = DocumentationBuilder(mode=BuildMode.RELEASE, base_path=temp_path) + # Ensure built first. + builder.build() + builder.publish(push=False) + gh_pages_path = temp_path / "gh-pages" + nojekyll_file = gh_pages_path / ".nojekyll" + stable_dir = gh_pages_path / "stable" + latest_dir = gh_pages_path / "latest" + tag_dir = gh_pages_path / tag + index_file = gh_pages_path / builder.index_file.name + assert gh_pages_path.is_dir() + assert nojekyll_file.is_file() + assert stable_dir.is_dir() + assert latest_dir.is_dir() + assert tag_dir.is_dir() + assert index_file.is_file()