diff --git a/README.md b/README.md
index 0189f37..1099c56 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,20 @@
-A Sphinx plugin for deployment documentation.
+A Sphinx plugin for deployment documentation. It provides a command
+`sphinx-deployment` to manage and facilitate versioned and customizable
+documentation deployment.
+
+## Features
+
+- Versioned documentation deployment management.
+- Customizable list of deployment view.
+- Deployments are managed based on Git.
+
+## License
+
+Apache License, for more details, see the
+[LICENSE](https://github.com/msclock/sphinx-deployment/blob/master/LICENSE)
+file.
diff --git a/docs/_templates/versioning.html b/docs/_templates/versioning.html
deleted file mode 100644
index 799444b..0000000
--- a/docs/_templates/versioning.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
- {{ _('Versions') }}
-
-
-
diff --git a/docs/conf.py b/docs/conf.py
index 63b9ec4..55af482 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,3 +51,10 @@
]
always_document_param_types = True
+
+sphinx_deployment_dll = {
+ "Links": {
+ "Repository": "https://github.com/msclock/sphinx-deployment/",
+ "PYPI": "https://pypi.org/project/sphinx-deployment/",
+ }
+}
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 0000000..f7217b5
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,36 @@
+# Getting Started
+
+## Installation
+
+The `sphinx-deployment` package can be installed with the following command:
+
+```bash
+pip install sphixx-deployment
+```
+
+## Usage
+
+Add the extension to your `conf.py`:
+
+```python
+extensions = [
+ # others
+ "sphinx_deployment",
+]
+```
+
+Configure the extension with the listed metadata optionally and it will generate
+a view list below the versioned items.
+
+```python
+sphinx_deployment_dll = {
+ "Links": {
+ "Repository": "set-the-repository-url",
+ "Pypi": "set-the-pypi-url",
+ "Another 1": "another-url-1",
+ },
+ "Another Section": {
+ "Another 2": "another-url-2",
+ },
+}
+```
diff --git a/docs/index.md b/docs/index.md
index 2113506..0a55622 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -21,6 +21,7 @@
:glob:
Overview
+getting_started
contributing
changelog
```
diff --git a/src/sphinx_deployment/templates/redirect.html b/src/sphinx_deployment/_static/templates/redirect.html
similarity index 100%
rename from src/sphinx_deployment/templates/redirect.html
rename to src/sphinx_deployment/_static/templates/redirect.html
diff --git a/src/sphinx_deployment/_static/templates/rtd.html b/src/sphinx_deployment/_static/templates/rtd.html
new file mode 100644
index 0000000..f1d6992
--- /dev/null
+++ b/src/sphinx_deployment/_static/templates/rtd.html
@@ -0,0 +1,12 @@
+{% if sphinx_deployment_dll %}
+
+{% for dll_key, dll in sphinx_deployment_dll.items() %}
+
+ - {{ dll_key }}
+ {% for dll_item_key, dll_item in dll.items() %}
+ -
+ {{ dll_item_key }}
+
+ {% endfor %}
+
+{% endfor %} {% endif %}
diff --git a/src/sphinx_deployment/versioning/css/rtd.css b/src/sphinx_deployment/_static/theme/rtd/rtd.css
similarity index 100%
rename from src/sphinx_deployment/versioning/css/rtd.css
rename to src/sphinx_deployment/_static/theme/rtd/rtd.css
diff --git a/src/sphinx_deployment/versioning/js/rtd.js b/src/sphinx_deployment/_static/theme/rtd/rtd.js_t
similarity index 82%
rename from src/sphinx_deployment/versioning/js/rtd.js
rename to src/sphinx_deployment/_static/theme/rtd/rtd.js_t
index 8b636fc..6f98d91 100644
--- a/src/sphinx_deployment/versioning/js/rtd.js
+++ b/src/sphinx_deployment/_static/theme/rtd/rtd.js_t
@@ -1,15 +1,3 @@
-/**
- * Handles the click event.
- *
- * @param {Event} event - The click event object.
- * @return {undefined} This function does not return a value.
- */
-function handleClick(event) {
- if (event.currentTarget.classList.contains("shift-up"))
- event.currentTarget.classList.remove("shift-up");
- else event.currentTarget.classList.add("shift-up");
-}
-
/**
* Locates the URL of the versions.json file by recursively checking parent directories.
*
@@ -37,19 +25,19 @@ async function locateVersionsJsonUrl(url) {
}
window.addEventListener("DOMContentLoaded", async function () {
- try {
- var versionsJsonUrl = await locateVersionsJsonUrl(
- this.window.location.href.substring(
- 0,
- this.window.location.href.lastIndexOf("/"),
- ),
- );
- } catch (error) {
- console.error("Failed to find versions.json");
- }
- if (versionsJsonUrl === null) {
- console.error("Failed to find versions.json");
- return;
+ var cur_href_path = this.window.location.href.substring(0, this.window.location.href.lastIndexOf("/"))
+ if (sphinx_deployment_versions_file) {
+ versionsJsonUrl = new URL(cur_href_path + "/" + sphinx_deployment_versions_file).toString();
+ } else {
+ try {
+ var versionsJsonUrl = await locateVersionsJsonUrl(cur_href_path);
+ } catch (error) {
+ console.error("Failed to find versions.json");
+ }
+ if (versionsJsonUrl === null) {
+ console.error("Failed to find versions.json");
+ return;
+ }
}
var rootUrl = versionsJsonUrl.slice(0, versionsJsonUrl.lastIndexOf("/"));
var currentVersionPath = this.window.location.href.substring(rootUrl.length);
@@ -83,7 +71,11 @@ window.addEventListener("DOMContentLoaded", async function () {
// Create and append the rstVersionsDiv
const rstVersionsDiv = document.createElement("div");
rstVersionsDiv.setAttribute("class", "rst-versions rst-badge");
- rstVersionsDiv.addEventListener("click", handleClick);
+ rstVersionsDiv.addEventListener("click", (e) => {
+ if (e.currentTarget.classList.contains("shift-up"))
+ e.currentTarget.classList.remove("shift-up");
+ else e.currentTarget.classList.add("shift-up");
+ });
injectedDiv.appendChild(rstVersionsDiv);
// Current version
@@ -137,6 +129,14 @@ window.addEventListener("DOMContentLoaded", async function () {
dl.appendChild(dd);
});
+ // Append customized items to Versions
+ var customizedItems = `{{ customizedItems }}`;
+
+ if (customizedItems) {
+ rstOtherVersionsDiv.innerHTML =
+ rstOtherVersionsDiv.innerHTML + customizedItems;
+ }
+
// Append an hr element as a separator
rstOtherVersionsDiv.appendChild(document.createElement("hr"));
diff --git a/src/sphinx_deployment/cli.py b/src/sphinx_deployment/cli.py
index 38d4df4..d50e88f 100644
--- a/src/sphinx_deployment/cli.py
+++ b/src/sphinx_deployment/cli.py
@@ -141,8 +141,8 @@ def sync_remote(remote: str, branch: str) -> bool:
rp = Repo(".")
rp.remote(remote).fetch(f"{branch}:{branch}")
return True
- except Exception as e:
- logger.warning(f"Sync failed with {remote}/{branch}:{e}")
+ except Exception:
+ logger.warning(f"Sync failed with {remote}/{branch}")
return False
@@ -178,8 +178,8 @@ def list_versions(branch: str, version_path: str) -> Versions:
)
version_dict = json.loads(versions_json_content)
return Versions(**version_dict)
- except Exception as e:
- logger.warning(f"unable to checkout branch: {branch} \n{e}")
+ except Exception:
+ logger.warning(f"No versions found in branch: {branch} and creating new one")
return Versions()
@@ -340,7 +340,7 @@ def create_command(
)
_ = sync_remote(remote, branch)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
v = versions.add(version)
if versions.default == "":
@@ -359,7 +359,7 @@ def create_command(
f"with sphinx-deployment {__version__}"
)
- t = redirect_impl(DIR / "templates")
+ t = redirect_impl(DIR.joinpath("_static", "templates"))
redirect_render = t.render(href_to_ver=version + "/index.html")
with prepare_commit() as repo:
@@ -370,11 +370,11 @@ def create_command(
else:
rp.heads[branch].checkout()
- dest_dir = Path(output_path) / v.name
+ dest_dir = Path(output_path).joinpath(v.name)
shutil.rmtree(str(dest_dir), ignore_errors=True)
shutil.copytree(tmp, str(dest_dir), dirs_exist_ok=True)
- redirect_html = Path(output_path) / "index.html"
+ redirect_html = Path(output_path).joinpath("index.html")
if not redirect_html.exists():
with redirect_html.open(
mode="w",
@@ -424,7 +424,7 @@ def delete_command(
)
_ = sync_remote(remote, branch)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
if message == "":
@@ -441,7 +441,7 @@ def delete_command(
logger.warning(f"Version {del_ver} not found in {all_keys}")
continue
versions.versions.pop(del_ver)
- dest_dir = Path(output_path) / del_ver
+ dest_dir = Path(output_path).joinpath(del_ver)
rp.index.remove(str(dest_dir), working_tree=True, r=True)
if del_ver == versions.default:
rp.index.remove(output_path + "/index.html", working_tree=True)
@@ -484,7 +484,7 @@ def default_command(
f"default args: {input_path} {output_path} {remote} {branch} {message} {push} {version}"
)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
if version not in versions.versions:
@@ -493,7 +493,7 @@ def default_command(
versions.default = version
- t = redirect_impl(DIR / "templates")
+ t = redirect_impl(DIR.joinpath("_static", "templates"))
redirect_render = t.render(href_to_ver=version + "/index.html")
if message == "":
@@ -506,7 +506,7 @@ def default_command(
rp: Repo = repo
rp.heads[branch].checkout()
- root_redirect = Path(output_path) / "index.html"
+ root_redirect = Path(output_path).joinpath("index.html")
with root_redirect.open(
mode="w",
encoding="utf-8",
@@ -556,7 +556,7 @@ def rename_command(
_ = sync_remote(remote, branch)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
if src not in versions.versions:
@@ -573,7 +573,7 @@ def rename_command(
f"with sphinx-deployment {__version__}"
)
- t = redirect_impl(DIR / "templates")
+ t = redirect_impl(DIR.joinpath("_static", "templates"))
redirect_render = t.render(href_to_ver=dst + "/index.html")
with prepare_commit() as repo:
@@ -597,7 +597,7 @@ def rename_command(
rp.index.move([rename_src_path, rename_dest_path], skip_errors=True)
- root_redirect = Path(output_path) / "index.html"
+ root_redirect = Path(output_path).joinpath("index.html")
if versions.default == src:
versions.default = dst
with Path(root_redirect).open(
@@ -631,7 +631,7 @@ def list_command(input_path: str, output_path: str, remote: str, branch: str) ->
logger.debug(f"list args: {input_path} {output_path} {remote} {branch}")
_ = sync_remote(remote, branch)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
logger.debug("\n" + json.dumps(asdict(versions), indent=4, separators=(",", ": ")))
@@ -648,7 +648,7 @@ def serve(
logger.debug(f"serve args: {input_path} {output_path} {remote} {branch} {port}")
_ = sync_remote(remote, branch)
- version_path = Path(output_path) / "versions.json"
+ version_path = Path(output_path).joinpath("versions.json")
versions = list_versions(branch, str(version_path))
with TemporaryDirectory() as tmp:
diff --git a/src/sphinx_deployment/sphinx_ext.py b/src/sphinx_deployment/sphinx_ext.py
index 7205b23..905674b 100644
--- a/src/sphinx_deployment/sphinx_ext.py
+++ b/src/sphinx_deployment/sphinx_ext.py
@@ -5,6 +5,7 @@
from pathlib import Path
from typing import Any
+from jinja2 import Template
from loguru import logger
from sphinx.application import Sphinx
from sphinx.config import Config
@@ -13,9 +14,10 @@
from ._version import version
-def _copy_custom_files(app: Sphinx, exc: Any) -> None:
+def _generate_deployment_assets(app: Sphinx, exc: Any) -> None:
"""
- Copy custom files to the Sphinx output directory if the builder format is HTML and no exception occurred.
+ Generate the deployment assets to the Sphinx output directory
+ if the builder format is HTML and no exception occurred.
Parameters:
app (Sphinx): The Sphinx application object.
@@ -25,28 +27,24 @@ def _copy_custom_files(app: Sphinx, exc: Any) -> None:
None
"""
if app.builder.format == "html" and not exc:
- dest_static_dir = Path(app.builder.outdir, "_static")
- rc_dir = Path(__file__).parent.resolve()
- _copy_assset_dir_impl(
- dest_asset_dir=dest_static_dir.joinpath("versioning"),
- src_assets_dir=rc_dir.joinpath("versioning"),
- )
-
-
-def _copy_assset_dir_impl(dest_asset_dir: Path, src_assets_dir: Path) -> None:
- """
- Copy the contents of the source assets directory to the destination assets directory.
-
- Args:
- dest_asset_dir (Path): The path to the destination assets directory.
- src_assets_dir (Path): The path to the source assets directory.
-
- Returns:
- None: This function does not return anything.
- """
- if Path(dest_asset_dir).exists():
- shutil.rmtree(dest_asset_dir)
- copy_asset(src_assets_dir, dest_asset_dir)
+ dst_static_dir = Path(app.builder.outdir, "_static")
+ src_static_dir = Path(__file__).parent.resolve().joinpath("_static")
+ dst_theme_dir = dst_static_dir.joinpath("theme", "rtd")
+ src_theme_dir = src_static_dir.joinpath("theme", "rtd")
+
+ if dst_theme_dir.exists():
+ shutil.rmtree(dst_theme_dir)
+
+ customized_tpl = src_static_dir.joinpath("templates", "rtd.html")
+ with customized_tpl.open("r", encoding="utf-8") as f:
+ t = Template(f.read(), autoescape=True, keep_trailing_newline=True)
+ rdr = t.render(sphinx_deployment_dll=app.config.sphinx_deployment_dll)
+ copy_asset(
+ src_theme_dir,
+ dst_theme_dir,
+ context={"customizedItems": rdr},
+ onerror=lambda file, e: logger.error(f"Failed to copy {file}: {e}"),
+ )
def _html_page_context(
@@ -70,8 +68,23 @@ def _html_page_context(
None
"""
_ = (pagename, templatename, context, doctree)
- app.add_css_file("versioning/css/rtd.css", priority=100)
- app.add_js_file("versioning/js/rtd.js")
+
+ # Get the path to the versions.json file
+ context["versions_file"] = str(
+ Path(context["content_root"]) / ".." / "versions.json"
+ )
+
+ # Register css and js files
+ app.add_js_file(
+ None,
+ body="var sphinx_deployment_versions_file = '"
+ + context["versions_file"]
+ + "';",
+ priority=0,
+ )
+
+ app.add_css_file("theme/rtd/rtd.css", priority=600)
+ app.add_js_file("theme/rtd/rtd.js", priority=600)
def _config_inited(app: Sphinx, config: Config) -> None:
@@ -101,9 +114,10 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
"""
if os.environ.get("SPHINX_DEPLOYMENT_VERSION", None):
- logger.info(f"sphinx_deployment setups docs {version} from {app.confdir}")
+ logger.info(f"sphinx_deployment deploys docs {version} from {app.confdir}")
+ app.add_config_value("sphinx_deployment_dll", {}, "html")
app.connect("config-inited", _config_inited)
- app.connect("build-finished", _copy_custom_files)
+ app.connect("build-finished", _generate_deployment_assets)
return {
"version": version,
diff --git a/tests/test_versions.py b/tests/test_versions.py
new file mode 100644
index 0000000..e9f1fcc
--- /dev/null
+++ b/tests/test_versions.py
@@ -0,0 +1,40 @@
+from __future__ import annotations
+
+import pytest
+
+from sphinx_deployment.cli import Versions
+
+
+@pytest.fixture()
+def versions():
+ return Versions()
+
+
+def test_add(versions: Versions):
+ assert len(versions.versions) == 0
+
+ versions.add("v1")
+ assert len(versions.versions) == 1
+ assert "v1" in versions.versions
+ assert versions.versions["v1"].name == "v1"
+ assert versions.versions["v1"].title == "v1"
+
+ versions.add("v2", "Version 2")
+ assert len(versions.versions) == 2
+ assert "v2" in versions.versions
+ assert versions.versions["v2"].name == "v2"
+ assert versions.versions["v2"].title == "Version 2"
+
+
+def test_delete(versions: Versions):
+ versions.add("v1")
+ versions.add("v2")
+
+ assert len(versions.versions) == 2
+
+ assert versions.delete("v1") is True
+ assert len(versions.versions) == 1
+ assert "v1" not in versions.versions
+
+ assert versions.delete("v3") is False
+ assert len(versions.versions) == 1