Skip to content

Commit 2682651

Browse files
committed
Add a feature for buuilding docs sitemap on sphinx build
1 parent 6bc6c62 commit 2682651

3 files changed

Lines changed: 110 additions & 4 deletions

File tree

docs/source/conf.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99

1010
# METADATA
11-
DUCK_HOMEPAGE = "https://duckframework.xyz"
12-
DUCK_DOCS_URL = "https://docs.duckframework.xyz"
11+
DUCK_HOMEPAGE = "https://duckframework.com"
12+
DUCK_DOCS_URL = "https://docs.duckframework.com"
1313
DUCK_PACKAGE_RELATIVE_PATH = "../../duck"
1414

1515
# Path to the duck package's __init__.py
@@ -24,6 +24,29 @@ def on_html_page_context(app, template_name, template, context, _):
2424
context["DUCK_HOMEPAGE"] = DUCK_HOMEPAGE
2525
context["DUCK_DOCS_URL"] = DUCK_DOCS_URL
2626
app.connect("html-page-context", on_html_page_context)
27+
app.connect("build-finished", on_build_finished)
28+
29+
30+
def on_build_finished(app, exception):
31+
"""
32+
Called when Sphinx finishes building the documentation.
33+
34+
Args:
35+
app: The Sphinx application object.
36+
exception: Exception raised during build, if any.
37+
"""
38+
from sitemap import generate_sitemap
39+
from duck.logging import console
40+
41+
if exception is not None:
42+
console.log("Build failed, skipping custom post-build task, no sitemap will be generated.", level=console.WARNING)
43+
return
44+
45+
console.log("Build completed successfully. Running post-build task...", level=console.WARNING)
46+
47+
# Build the sitemap
48+
generate_sitemap()
49+
2750

2851
def read_metadata_from_init(init_path):
2952
"""
@@ -37,6 +60,7 @@ def read_metadata_from_init(init_path):
3760
dict: A dictionary containing metadata like __version__, __author__, and __email__.
3861
"""
3962
metadata = {}
63+
4064
with open(init_path, "r", encoding="utf-8") as f:
4165
for line in f:
4266
# Look for __<name>__ = '<value>'
@@ -167,7 +191,7 @@ def read_metadata_from_init(init_path):
167191
html_static_path = ["_static"]
168192
html_css_files = ["css/custom.css"]
169193
html_title = "Duck Framework — Python Web Framework | Build Web Apps Without JavaScript"
170-
html_baseurl = "https://docs.duckframework.xyz/main/"
194+
html_baseurl = "https://docs.duckframework.com/main/"
171195
html_theme_options = {
172196
"logo_light": "_static/images/duck-logo.png",
173197
"logo_dark": "_static/images/duck-logo.png",

docs/source/sitemap.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Retrieve documentation URLs to the sitemap.
2+
import os
3+
4+
from typing import List
5+
from pathlib import Path
6+
7+
8+
DOCS_URL = "https://docs.duckframework.com"
9+
DOCS_DIR = Path(__file__).parent.parent
10+
DOCS_SOURCE_DIRS = ( "source", "source/api")
11+
12+
# Set a testing directory as Duck settings to avoid SettingsError
13+
os.environ["DUCK_SETTINGS_MODULE"] = "duck.etc.structures.projects.testing.web.settings"
14+
15+
16+
def generate_sitemap():
17+
"""
18+
This must be called after sphinx build.
19+
20+
The sitemap.xml is placed in `build/html`.
21+
"""
22+
from duck.logging import console
23+
from duck.contrib.sitemap import SitemapBuilder
24+
25+
urls = set()
26+
27+
for source_dir in DOCS_SOURCE_DIRS:
28+
try:
29+
abs_dir = DOCS_DIR / source_dir
30+
for entry in os.scandir(abs_dir):
31+
if entry.is_file():
32+
filename = entry.name
33+
docname = None
34+
35+
if filename == "index.rst":
36+
docname = ""
37+
38+
elif filename.endswith(".md"):
39+
docname = filename.replace(".md", "")
40+
41+
elif filename.endswith(".html"):
42+
docname = filename.replace(".html", "")
43+
44+
elif filename.endswith(".rst"):
45+
docname = filename.replace(".rst", "")
46+
47+
if source_dir == "source":
48+
# This is the root source directory for main docs.
49+
# Check if docname is set.
50+
if docname is not None:
51+
urls.add(f"{DOCS_URL}/{docname}")
52+
53+
elif source_dir == "source/api":
54+
# This is the source directory for API docs.
55+
# This directory contains only html files.
56+
# Check if docname is set.
57+
if docname is not None:
58+
urls.add(f"{DOCS_URL}/api/{docname}")
59+
60+
else:
61+
raise ValueError(f"Unknown source directory '{source_dir}', expected 'source' or 'source/api'.")
62+
63+
except FileNotFoundError as e:
64+
console.log(f"Caught an error whilst scanning source dirs: {e}", level=console.WARNING)
65+
66+
# Build the sitemap.
67+
sitemap_filepath = DOCS_DIR / "build/html/sitemap.xml"
68+
builder = SitemapBuilder(
69+
server_url=DOCS_URL, # Parsing None will automatically resolve server URL
70+
save_to_file=True,
71+
filepath=sitemap_filepath,
72+
extra_urls=urls,
73+
)
74+
builder.build()
75+
console.log(f"Sitemap has been saved at {sitemap_filepath}", level=console.DEBUG)
76+

duck/utils/email/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(
8181
recipients: Optional[List[str]] = None,
8282
use_bcc: bool = True,
8383
use_ssl: bool = True,
84+
reply_to: Optional[str] = None,
8485
):
8586
"""
8687
Initialize a generic Email instance.
@@ -98,6 +99,7 @@ def __init__(
9899
recipients: List of additional recipient emails for CC/BCC (optional).
99100
use_bcc: If True, use BCC for recipients; otherwise, use CC.
100101
use_ssl: Whether to use SSL for SMTP (default True).
102+
reply_to: An email for users to reply to when they hit 'Reply'. Defaults to None, uses default from_addr.
101103
"""
102104
self.smtp_host = smtp_host
103105
self.smtp_port = smtp_port
@@ -112,6 +114,7 @@ def __init__(
112114
self.use_bcc = use_bcc
113115
self.use_ssl = use_ssl
114116
self.is_sent = False
117+
self.reply_to = reply_to
115118

116119
def _build_message(self) -> Tuple[MIMEMultipart, List[str]]:
117120
"""
@@ -124,7 +127,10 @@ def _build_message(self) -> Tuple[MIMEMultipart, List[str]]:
124127
msg['From'] = formataddr((self.name, self.from_addr))
125128
msg['To'] = self.to
126129
msg['Subject'] = self.subject
127-
130+
131+
if self.reply_to:
132+
msg["Reply-To"] = self.reply_to
133+
128134
if self.recipients:
129135
if self.use_bcc:
130136
all_recipients = [self.to] + self.recipients

0 commit comments

Comments
 (0)