From 584904bbf9a10c15f0d96865e9fd43293a0b1a16 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 7 Jan 2026 13:10:26 +0100 Subject: [PATCH 1/4] Add type checking support with mypy Added mypy and type stubs for external dependencies (requests, Pygments) to dev dependencies. Type checking now passes cleanly for makesite.py and monitor.py. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f554044..8c14c0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,4 +16,7 @@ dependencies = [ [dependency-groups] dev = [ "black>=24.10.0", + "mypy>=1.19.1", + "types-pygments>=2.19.0.20251121", + "types-requests>=2.32.4.20260107", ] From f5df515d2b8dd8be152f542cb3a91be94806b678 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 7 Jan 2026 13:19:35 +0100 Subject: [PATCH 2/4] Add comprehensive docstrings to all functions Added Google-style docstrings to all functions and classes in makesite.py and monitor.py with descriptions, argument types, and return values. --- makesite.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++- monitor.py | 6 ++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/makesite.py b/makesite.py index 4b61410..fd3e520 100755 --- a/makesite.py +++ b/makesite.py @@ -28,7 +28,18 @@ FRENCH_MONTHS = ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', class HighlightRenderer(mistune.HTMLRenderer): + """Custom Mistune renderer that adds syntax highlighting to code blocks using Pygments.""" + def block_code(self, code, info=None): + """Render code blocks with syntax highlighting. + + Args: + code: The code content to render + info: Optional language identifier for syntax highlighting + + Returns: + str: HTML with syntax-highlighted code or plain pre/code tags + """ if info: lexer = get_lexer_by_name(info, stripall=True) formatter = html.HtmlFormatter() @@ -171,6 +182,15 @@ def render(template, **params): def get_header_list_value(header_name, page_params): + """Extract and parse a space-separated list from a header value. + + Args: + header_name: Name of the header to extract (e.g., 'category', 'tag') + page_params: Dict containing page parameters + + Returns: + list: List of stripped string values from the header + """ header_list = [] if header_name in page_params: for s in page_params[header_name].split(" "): @@ -266,6 +286,14 @@ def _process_comments(page_params, stacosys_url, comment_layout, def get_friendly_date(date_str): + """Convert date string to French-formatted readable date. + + Args: + date_str: Date string in YYYY-MM-DD format + + Returns: + str: French-formatted date (e.g., "15 janv. 2024") + """ dt = datetime.datetime.strptime(date_str, "%Y-%m-%d") french_month = FRENCH_MONTHS[dt.month - 1] return f"{dt.day:02d} {french_month} {dt.year}" @@ -407,7 +435,17 @@ def make_list( posts, dst, list_layout, item_layout, header_layout, footer_layout, **params ): - """Generate list page for a blog.""" + """Generate list page for a blog. + + Args: + posts: List of post dictionaries to include in the list + dst: Destination path for the generated HTML file + list_layout: Template for the overall list page + item_layout: Template for individual list items + header_layout: Template for page header (None to skip) + footer_layout: Template for page footer (None to skip) + **params: Additional parameters for template rendering + """ # header if header_layout is None: @@ -447,6 +485,16 @@ def make_list( def create_blog(page_layout, list_in_page_layout, params): + """Create blog posts and paginated index pages. + + Args: + page_layout: Template for individual pages + list_in_page_layout: Template for list pages wrapped in page layout + params: Global site parameters + + Returns: + list: Sorted list of all post dictionaries (newest first) + """ banner_layout = fread("layout/banner.html") paging_layout = fread("layout/paging.html") post_layout = fread("layout/post.html") @@ -509,6 +557,14 @@ def create_blog(page_layout, list_in_page_layout, params): def generate_categories(list_in_page_layout, item_nosummary_layout, posts, params): + """Generate category pages grouping posts by category. + + Args: + list_in_page_layout: Template for list pages + item_nosummary_layout: Template for list items without summaries + posts: List of all blog posts + params: Global site parameters + """ category_title_layout = fread("layout/category_title.html") cat_post = {} for post in posts: @@ -532,6 +588,15 @@ def generate_categories(list_in_page_layout, item_nosummary_layout, def generate_archives(blog_posts, list_in_page_layout, item_nosummary_layout, archive_title_layout, params): + """Generate archives page with all blog posts. + + Args: + blog_posts: List of all blog posts + list_in_page_layout: Template for list pages + item_nosummary_layout: Template for list items without summaries + archive_title_layout: Template for archive page header + params: Global site parameters + """ make_list( blog_posts, "_site/archives/index.html", @@ -545,6 +610,14 @@ def generate_archives(blog_posts, list_in_page_layout, item_nosummary_layout, def generate_notes(page_layout, archive_title_layout, list_in_page_layout, params): + """Generate notes pages and notes index. + + Args: + page_layout: Template for individual pages + archive_title_layout: Template for notes index header + list_in_page_layout: Template for list pages + params: Global site parameters + """ note_layout = fread("layout/note.html") item_note_layout = fread("layout/item_note.html") note_layout = render(page_layout, content=note_layout) @@ -569,6 +642,12 @@ def generate_notes(page_layout, archive_title_layout, def generate_rss_feeds(posts, params): + """Generate RSS feeds: main feed and per-tag feeds. + + Args: + posts: List of all blog posts + params: Global site parameters + """ rss_xml = fread("layout/rss.xml") rss_item_xml = fread("layout/rss_item.xml") @@ -606,6 +685,12 @@ def generate_rss_feeds(posts, params): def generate_sitemap(posts, params): + """Generate XML sitemap for all posts. + + Args: + posts: List of all blog posts + params: Global site parameters + """ sitemap_xml = fread("layout/sitemap.xml") sitemap_item_xml = fread("layout/sitemap_item.xml") make_list( @@ -620,6 +705,14 @@ def generate_sitemap(posts, params): def get_params(param_file): + """Load site parameters from JSON file with defaults. + + Args: + param_file: Path to JSON parameters file + + Returns: + dict: Site parameters with defaults and loaded values + """ # Default parameters. params = { "title": "Blog", @@ -637,12 +730,18 @@ def get_params(param_file): def clean_site(): + """Remove existing _site directory and copy static files.""" if os.path.isdir("_site"): shutil.rmtree("_site") shutil.copytree("static", "_site") def main(param_file): + """Main entry point for static site generation. + + Args: + param_file: Path to JSON parameters file + """ params = get_params(param_file) diff --git a/monitor.py b/monitor.py index bcc3d44..2bd526a 100755 --- a/monitor.py +++ b/monitor.py @@ -15,6 +15,11 @@ def fread(filename): def get_nb_of_comments(): + """Fetch the total number of comments from Stacosys API. + + Returns: + int: Total comment count, or 0 if request fails + """ req_url = params["stacosys_url"] + "/comments/count" query_params = dict( token=params["stacosys_token"] @@ -24,6 +29,7 @@ def get_nb_of_comments(): def exit_program(): + """Exit the program with status code 0.""" sys.exit(0) From 424ea20bcfc1376a17bc62aa6a0b54e8467aa83c Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 7 Jan 2026 13:31:25 +0100 Subject: [PATCH 3/4] Refactor function names for clarity and accuracy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed functions to better reflect their actual behavior: - truncate → strip_tags_and_truncate (does both operations) - fix_relative_links → make_links_absolute (converts, not fixes) - clean_html_tag → strip_html_tags (more explicit) - clean_site → rebuild_site_directory (removes and recreates) - read_headers → parse_headers (parses, not just reads) - read_content → parse_post_file (full processing pipeline) - get_nb_of_comments → get_comment_count (removes French abbreviation) --- makesite.py | 40 ++++++++++++++++++++-------------------- monitor.py | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/makesite.py b/makesite.py index fd3e520..051d227 100755 --- a/makesite.py +++ b/makesite.py @@ -71,13 +71,13 @@ def log(msg, *args): sys.stderr.write(msg.format(*args) + "\n") -def truncate(text, words=25): - """Remove tags and truncate text to the specified number of words.""" +def strip_tags_and_truncate(text, words=25): + """Remove HTML tags and truncate text to the specified number of words.""" return " ".join(re.sub(r"(?s)<.*?>", " ", text).split()[:words]) -def read_headers(text): - """Parse headers in text and yield (key, value, end-index) tuples.""" +def parse_headers(text): + """Parse HTML comment headers and yield (key, value, end-index) tuples.""" for match in re.finditer(r"\s*\s*|.+", text): if not match.group(1): break @@ -107,8 +107,8 @@ def slugify(value): return re.sub(r"[-\s]+", "-", value) -def read_content(filename, params): - """Read content and metadata from file into a dictionary.""" +def parse_post_file(filename, params): + """Parse post file: read, extract metadata, convert markdown, and generate summary.""" # Read file content. text = fread(filename) @@ -119,7 +119,7 @@ def read_content(filename, params): # Read headers. end = 0 - for key, val, end in read_headers(text): + for key, val, end in parse_headers(text): content[key] = val # slugify post title @@ -132,19 +132,19 @@ def read_content(filename, params): if filename.endswith((".md", ".mkd", ".mkdn", ".mdown", ".markdown")): summary_index = text.find("", "") text = markdown(clean_text) else: - summary = truncate(text) + summary = strip_tags_and_truncate(text) # Update the dictionary with content and RFC 2822 date. content.update( { "content": text, - "content_rss": fix_relative_links(params["site_url"], text), + "content_rss": make_links_absolute(params["site_url"], text), "rfc_2822_date": rfc_2822_format(content["date"]), "summary": summary, } @@ -153,16 +153,16 @@ def read_content(filename, params): return content -def fix_relative_links(site_url, text): - """Absolute links needed in RSS feed""" +def make_links_absolute(site_url, text): + """Convert relative links to absolute URLs for RSS feed.""" # TODO externalize links replacement configuration return text \ .replace("src=\"/images/20", "src=\"" + site_url + "/images/20") \ .replace("href=\"/20", "href=\"" + site_url + "/20") -def clean_html_tag(text): - """Remove HTML tags.""" +def strip_html_tags(text): + """Remove HTML tags from text.""" while True: original_text = text text = re.sub(r"<\w+.*?>", "", text) @@ -351,7 +351,7 @@ def make_posts( for posix_path in Path(src).glob(src_pattern): src_path = str(posix_path) - content = read_content(src_path, params) + content = parse_post_file(src_path, params) # render text / summary for basic fields content["content"] = render(content["content"], **params) @@ -404,7 +404,7 @@ def make_notes( for posix_path in Path(src).glob(src_pattern): src_path = str(posix_path) - content = read_content(src_path, params) + content = parse_post_file(src_path, params) # render text / summary for basic fields content["content"] = render(content["content"], **params) @@ -729,8 +729,8 @@ def get_params(param_file): return params -def clean_site(): - """Remove existing _site directory and copy static files.""" +def rebuild_site_directory(): + """Remove existing _site directory and recreate from static files.""" if os.path.isdir("_site"): shutil.rmtree("_site") shutil.copytree("static", "_site") @@ -746,7 +746,7 @@ def main(param_file): params = get_params(param_file) # Create a new _site directory from scratch. - clean_site() + rebuild_site_directory() # Load layouts. page_layout = fread("layout/page.html") diff --git a/monitor.py b/monitor.py index 2bd526a..7918ee1 100755 --- a/monitor.py +++ b/monitor.py @@ -14,7 +14,7 @@ def fread(filename): return f.read() -def get_nb_of_comments(): +def get_comment_count(): """Fetch the total number of comments from Stacosys API. Returns: @@ -45,13 +45,13 @@ if os.path.isfile("params.json"): params.update(json.loads(fread("params.json"))) external_check_cmd = params["external_check"] -initial_count = get_nb_of_comments() +initial_count = get_comment_count() print(f"Comments = {initial_count}") while True: # check number of comments every 60 seconds for _ in range(15): time.sleep(60) - if initial_count != get_nb_of_comments(): + if initial_count != get_comment_count(): exit_program() # check if git repo changed every 15 minutes if external_check_cmd and os.system(external_check_cmd): From 13d3d653de0b0961f4258c8698328ef991cf10ea Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 7 Jan 2026 13:37:55 +0100 Subject: [PATCH 4/4] Make internal helper functions private Added underscore prefix to functions used only internally: - _strip_tags_and_truncate (used only in parse_post_file) - _parse_headers (used only in parse_post_file) - _rfc_2822_format (used only in parse_post_file) - _make_links_absolute (used only in parse_post_file) - _strip_html_tags (used only in parse_post_file) - _get_friendly_date (used only in _setup_page_params) - _exit_program (used only in monitor.py main loop) This improves encapsulation by clearly marking implementation details. --- makesite.py | 26 +++++++++++++------------- monitor.py | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/makesite.py b/makesite.py index 051d227..3a61b10 100755 --- a/makesite.py +++ b/makesite.py @@ -71,12 +71,12 @@ def log(msg, *args): sys.stderr.write(msg.format(*args) + "\n") -def strip_tags_and_truncate(text, words=25): +def _strip_tags_and_truncate(text, words=25): """Remove HTML tags and truncate text to the specified number of words.""" return " ".join(re.sub(r"(?s)<.*?>", " ", text).split()[:words]) -def parse_headers(text): +def _parse_headers(text): """Parse HTML comment headers and yield (key, value, end-index) tuples.""" for match in re.finditer(r"\s*\s*|.+", text): if not match.group(1): @@ -84,7 +84,7 @@ def parse_headers(text): yield match.group(1), match.group(2), match.end() -def rfc_2822_format(date_str): +def _rfc_2822_format(date_str): """Convert yyyy-mm-dd date string to RFC 2822 format date string.""" d = datetime.datetime.strptime(date_str, "%Y-%m-%d") return d \ @@ -119,7 +119,7 @@ def parse_post_file(filename, params): # Read headers. end = 0 - for key, val, end in parse_headers(text): + for key, val, end in _parse_headers(text): content[key] = val # slugify post title @@ -132,20 +132,20 @@ def parse_post_file(filename, params): if filename.endswith((".md", ".mkd", ".mkdn", ".mdown", ".markdown")): summary_index = text.find("", "") text = markdown(clean_text) else: - summary = strip_tags_and_truncate(text) + summary = _strip_tags_and_truncate(text) # Update the dictionary with content and RFC 2822 date. content.update( { "content": text, - "content_rss": make_links_absolute(params["site_url"], text), - "rfc_2822_date": rfc_2822_format(content["date"]), + "content_rss": _make_links_absolute(params["site_url"], text), + "rfc_2822_date": _rfc_2822_format(content["date"]), "summary": summary, } ) @@ -153,7 +153,7 @@ def parse_post_file(filename, params): return content -def make_links_absolute(site_url, text): +def _make_links_absolute(site_url, text): """Convert relative links to absolute URLs for RSS feed.""" # TODO externalize links replacement configuration return text \ @@ -161,7 +161,7 @@ def make_links_absolute(site_url, text): .replace("href=\"/20", "href=\"" + site_url + "/20") -def strip_html_tags(text): +def _strip_html_tags(text): """Remove HTML tags from text.""" while True: original_text = text @@ -285,7 +285,7 @@ def _process_comments(page_params, stacosys_url, comment_layout, return len(comments), comments_html, comment_section_html -def get_friendly_date(date_str): +def _get_friendly_date(date_str): """Convert date string to French-formatted readable date. Args: @@ -334,7 +334,7 @@ def _setup_page_params(content, params): page_params["header"] = "" page_params["footer"] = "" page_params["date_path"] = page_params["date"].replace("-", "/") - page_params["friendly_date"] = get_friendly_date(page_params["date"]) + page_params["friendly_date"] = _get_friendly_date(page_params["date"]) page_params["year"] = page_params["date"].split("-")[0] page_params["post_url"] = ( page_params["year"] + "/" + page_params["slug"] + "/" diff --git a/monitor.py b/monitor.py index 7918ee1..bad2367 100755 --- a/monitor.py +++ b/monitor.py @@ -28,7 +28,7 @@ def get_comment_count(): return 0 if not resp.ok else int(resp.json()["count"]) -def exit_program(): +def _exit_program(): """Exit the program with status code 0.""" sys.exit(0) @@ -52,7 +52,7 @@ while True: for _ in range(15): time.sleep(60) if initial_count != get_comment_count(): - exit_program() + _exit_program() # check if git repo changed every 15 minutes if external_check_cmd and os.system(external_check_cmd): - exit_program() + _exit_program()