Skip to content

feat: add RSS feed generation support - #3

Open
raofal-msodeh wants to merge 1 commit into
Alqudimi:mainfrom
raofal-msodeh:feature/rss-feed-generator
Open

raofal-msodeh wants to merge 1 commit into
Alqudimi:mainfrom
raofal-msodeh:feature/rss-feed-generator

Conversation

@raofal-msodeh

@raofal-msodeh raofal-msodeh commented Apr 26, 2026

Copy link
Copy Markdown

This commit introduces automatic RSS 2.0 feed generation to the static site generator. It includes:

  • A new generate_rss_feed method in StaticSiteGenerator.
  • Integration of RSS generation into the build process.
  • Auto-discovery link and footer link in the base template.
  • Support for site_url, site_name, and site_description from config.yaml.

Summary by CodeRabbit

  • New Features
    • Added automatic RSS feed generation featuring the 20 most recent articles.
    • RSS subscription link now available in site header and footer.

This commit introduces automatic RSS 2.0 feed generation to the static site generator. It includes:
- A new generate_rss_feed method in StaticSiteGenerator.
- Integration of RSS generation into the build process.
- Auto-discovery link and footer link in the base template.
- Support for site_url, site_name, and site_description from config.yaml.
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The generator now generates an RSS 2.0 feed during build via a new generate_rss_feed() method. This reads site config, sorts pages by date, includes the 20 most recent items, and writes feed metadata to rss.xml. The base template adds RSS feed discovery links in the head and footer navigation.

Changes

Cohort / File(s) Summary
RSS Feed Generation
generator/generator.py
New generate_rss_feed() method added to StaticSiteGenerator class. Reads site configuration (url, name, description) with defaults, generates feed metadata including lastBuildDate and Atom self link, sorts pages by date descending, limits to 20 most recent items, formats pubDates with fallback to current time, and writes RSS 2.0 XML to rss.xml. The build() method now invokes this method.
Template Updates
generator/templates/base.html
Added RSS feed discovery via alternate link element in document head and footer navigation link to /rss.xml.

Sequence Diagram

sequenceDiagram
    participant Build as Build Process
    participant Gen as Generator
    participant Config as Configuration
    participant Pages as Pages Collection
    participant FS as File System
    
    Build->>Gen: build()
    Gen->>Gen: generate_rss_feed()
    Gen->>Config: Read site metadata
    Config-->>Gen: site_url, site_name, site_description
    Gen->>Pages: Access and sort by date
    Pages-->>Gen: Sorted pages (descending)
    Gen->>Gen: Filter top 20 items
    Gen->>Gen: Generate RSS metadata
    Gen->>Gen: Format feed with items
    Gen->>FS: Write rss.xml
    FS-->>Gen: File written
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 In burrows of code, a new feed we sow,
Twenty tales sorted, all fresh and aglow,
RSS streams flow from our warren with cheer,
Updates aplenty for all far and near! 🌾📡

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add RSS feed generation support' accurately and concisely describes the main change: adding RSS feed generation functionality to the static site generator.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
generator/templates/base.html (1)

67-67: Optional: add an accessible label to the RSS footer link.

The icon-plus-text is fine for sighted users, but consider an explicit aria-label (or title) so the link's purpose is unambiguous to assistive tech, especially since the icon is decorative.

♻️ Suggested tweak
-                    <a href="/rss.xml" class="text-white me-3"><i class="bi bi-rss-fill"></i> RSS</a>
+                    <a href="/rss.xml" class="text-white me-3" aria-label="RSS feed" title="Subscribe via RSS"><i class="bi bi-rss-fill" aria-hidden="true"></i> RSS</a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/templates/base.html` at line 67, The RSS footer link (<a
href="/rss.xml" class="text-white me-3">) should include an explicit accessible
label so screen readers understand its purpose; update that anchor to add an
aria-label (or title) such as "RSS feed" (e.g., aria-label="RSS feed") while
keeping the existing icon and text unchanged to provide clear intent for
assistive technology.
generator/generator.py (2)

248-294: Recommended: consider using feedgen (or stdlib xml.etree.ElementTree) instead of hand-built strings.

Hand-rolled XML via str.append/f-strings is the root cause of the escaping and CDATA edge cases above. feedgen is widely used for RSS/Atom and handles escaping, namespaces, and date formatting correctly. If adding a dependency isn't desirable, building the tree with xml.etree.ElementTree and calling ElementTree.tostring(...) gets you correct escaping for free.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` around lines 248 - 294, The generate_rss_feed
function currently builds RSS XML by concatenating strings (rss_lines), which
risks escaping and CDATA issues; replace the hand-rolled construction with a
proper RSS builder — either use feedgen (FeedGenerator) to create the feed and
add entries from self.pages (use metadata['title'], ['description'], ['date']
and page['url']), or use xml.etree.ElementTree to build an Element tree (root
rss -> channel -> item entries) so namespaces, escaping, and RFC-822 pubDate
formatting are handled correctly; ensure the atom:link, lastBuildDate, and only
the latest 20 pages are added, then serialize the tree/FeedGenerator output to
rss_path (self.output_dir / 'rss.xml') with UTF-8 encoding.

260-260: Minor: drop the f prefix — no placeholders (Ruff F541).

-        rss_lines.append(f'  <language>en-us</language>')
+        language = self.config.get('language', 'en-us')
+        rss_lines.append(f'  <language>{language}</language>')

Bonus: sourcing the language from config lets non-English sites produce a correct feed instead of always advertising en-us.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` at line 260, The line using rss_lines.append
currently uses an unnecessary f-string and hardcodes "en-us"; change
rss_lines.append to use a regular string (remove the f prefix) and instead
source the language from your configuration with a fallback to "en-us" so
non-English sites get correct feeds (use the existing config object, e.g.
config.get('language', 'en-us') or self.config.language with a default) and call
rss_lines.append with that value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@generator/generator.py`:
- Line 261: The code uses datetime.now() and hard-codes "+0000" when building
RSS timestamps (e.g., the rss_lines.append call that writes <lastBuildDate> and
the similar fallback <pubDate> construction), which mixes local time with a UTC
offset; replace datetime.now() with a UTC-aware datetime (e.g.,
datetime.utcnow() or datetime.now(timezone.utc)) and either format with a real
offset via %z or remove the hard-coded "+0000" so the timestamp is correct;
update both the <lastBuildDate> append and the pubDate fallback construction in
generator.py accordingly.
- Around line 257-262: Escape XML-special characters before interpolating into
RSS/ sitemap strings: instead of directly appending f-strings with site_name,
site_description, page["metadata"]["title"], page["url"], and base_url in
generator.py (the rss_lines.append calls and the generate_sitemap block at the
earlier sitemap lines), run those values through xml.sax.saxutils.escape (or
equivalent) and use the escaped values in the f-strings; for descriptions that
may include "]]>" either escape them or replace "]]>" with "]]&gt;" (or
consistently use escaped text rather than CDATA), and ensure atom:link href and
any URLs are escaped as well so generated RSS and sitemap XML are always valid.
- Around line 278-279: The bare except: should be replaced with a specific
exception handler to avoid swallowing SystemExit/KeyboardInterrupt; change it to
catch the exceptions that datetime parsing can raise (e.g., except (ValueError,
TypeError) as e) and keep the fallback assignment to formatted_date =
datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000"); reference the existing
formatted_date assignment and ensure the except targets ValueError/TypeError
rather than a bare except.
- Around line 265-279: The YAML-parsed `date` fields are datetime.date objects
causing sorting and strptime to fail; normalize dates when you parse files so
downstream code can assume a consistent string format: in parse_markdown_file
detect metadata['date'] (use isinstance(..., date) or datetime.date), convert it
to an ISO string like 'YYYY-MM-DD' (or store None/'') and replace the original
value, and ensure metadata always contains a string or empty string for date;
then you can safely use sorted(self.pages, key=lambda x:
x['metadata'].get('date',''), reverse=True) in
generate_index_page/generate_sitemap and keep the pub_date handling around
pub_date/datetime.strptime without TypeError.

---

Nitpick comments:
In `@generator/generator.py`:
- Around line 248-294: The generate_rss_feed function currently builds RSS XML
by concatenating strings (rss_lines), which risks escaping and CDATA issues;
replace the hand-rolled construction with a proper RSS builder — either use
feedgen (FeedGenerator) to create the feed and add entries from self.pages (use
metadata['title'], ['description'], ['date'] and page['url']), or use
xml.etree.ElementTree to build an Element tree (root rss -> channel -> item
entries) so namespaces, escaping, and RFC-822 pubDate formatting are handled
correctly; ensure the atom:link, lastBuildDate, and only the latest 20 pages are
added, then serialize the tree/FeedGenerator output to rss_path (self.output_dir
/ 'rss.xml') with UTF-8 encoding.
- Line 260: The line using rss_lines.append currently uses an unnecessary
f-string and hardcodes "en-us"; change rss_lines.append to use a regular string
(remove the f prefix) and instead source the language from your configuration
with a fallback to "en-us" so non-English sites get correct feeds (use the
existing config object, e.g. config.get('language', 'en-us') or
self.config.language with a default) and call rss_lines.append with that value.

In `@generator/templates/base.html`:
- Line 67: The RSS footer link (<a href="/rss.xml" class="text-white me-3">)
should include an explicit accessible label so screen readers understand its
purpose; update that anchor to add an aria-label (or title) such as "RSS feed"
(e.g., aria-label="RSS feed") while keeping the existing icon and text unchanged
to provide clear intent for assistive technology.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72f56ce5-884f-4004-8407-e2cf971d46aa

📥 Commits

Reviewing files that changed from the base of the PR and between f485e51 and 3636ce9.

📒 Files selected for processing (2)
  • generator/generator.py
  • generator/templates/base.html

Comment thread generator/generator.py
Comment on lines +257 to +262
rss_lines.append(f' <title>{site_name}</title>')
rss_lines.append(f' <link>{base_url}</link>')
rss_lines.append(f' <description>{site_description}</description>')
rss_lines.append(f' <language>en-us</language>')
rss_lines.append(f' <lastBuildDate>{datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")}</lastBuildDate>')
rss_lines.append(f' <atom:link href="{base_url}/rss.xml" rel="self" type="application/rss+xml" />')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: RSS output is not XML-escaped — special characters will produce invalid feeds.

site_name, site_description, page["metadata"]["title"], page["url"], and base_url are interpolated directly into XML via f-strings without escaping. Any &, <, >, or quote in a title/description (e.g., a post titled Tips & Tricks or a URL with ?a=1&b=2) will yield a malformed RSS document that feed readers and validators will reject. The <![CDATA[...]]> wrapper also breaks if the description ever contains the sequence ]]>.

This is the same class of issue as generate_sitemap at lines 230–246, which should also be fixed for consistency.

🛡️ Suggested fix using xml.sax.saxutils
@@
-import os
+import os
+from xml.sax.saxutils import escape as xml_escape, quoteattr
@@
     def generate_rss_feed(self):
         """Generate RSS 2.0 feed for the site"""
         base_url = self.config.get('site_url', 'https://example.com').rstrip('/')
         site_name = self.config.get('site_name', 'My Static Site')
         site_description = self.config.get('site_description', 'A modern static site')
@@
-        rss_lines.append(f'  <title>{site_name}</title>')
-        rss_lines.append(f'  <link>{base_url}</link>')
-        rss_lines.append(f'  <description>{site_description}</description>')
+        rss_lines.append(f'  <title>{xml_escape(site_name)}</title>')
+        rss_lines.append(f'  <link>{xml_escape(base_url)}</link>')
+        rss_lines.append(f'  <description>{xml_escape(site_description)}</description>')
@@
-            rss_lines.append('  <item>')
-            rss_lines.append(f'    <title>{page["metadata"].get("title", "Untitled")}</title>')
-            rss_lines.append(f'    <link>{base_url}{page["url"]}</link>')
-            rss_lines.append(f'    <guid isPermaLink="true">{base_url}{page["url"]}</guid>')
-            rss_lines.append(f'    <pubDate>{formatted_date}</pubDate>')
-            rss_lines.append(f'    <description><![CDATA[{page["metadata"].get("description", "")}]]></description>')
-            rss_lines.append('  </item>')
+            title = xml_escape(str(page["metadata"].get("title", "Untitled")))
+            item_link = xml_escape(f'{base_url}{page["url"]}')
+            description = str(page["metadata"].get("description", "")).replace(']]>', ']]]]><![CDATA[>')
+            rss_lines.append('  <item>')
+            rss_lines.append(f'    <title>{title}</title>')
+            rss_lines.append(f'    <link>{item_link}</link>')
+            rss_lines.append(f'    <guid isPermaLink="true">{item_link}</guid>')
+            rss_lines.append(f'    <pubDate>{formatted_date}</pubDate>')
+            rss_lines.append(f'    <description><![CDATA[{description}]]></description>')
+            rss_lines.append('  </item>')

Alternatively, consider feedgen which handles all of this (and namespacing/validation) for you.

Also applies to: 282-286

🧰 Tools
🪛 Ruff (0.15.11)

[error] 260-260: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` around lines 257 - 262, Escape XML-special characters
before interpolating into RSS/ sitemap strings: instead of directly appending
f-strings with site_name, site_description, page["metadata"]["title"],
page["url"], and base_url in generator.py (the rss_lines.append calls and the
generate_sitemap block at the earlier sitemap lines), run those values through
xml.sax.saxutils.escape (or equivalent) and use the escaped values in the
f-strings; for descriptions that may include "]]>" either escape them or replace
"]]>" with "]]&gt;" (or consistently use escaped text rather than CDATA), and
ensure atom:link href and any URLs are escaped as well so generated RSS and
sitemap XML are always valid.

Comment thread generator/generator.py
rss_lines.append(f' <link>{base_url}</link>')
rss_lines.append(f' <description>{site_description}</description>')
rss_lines.append(f' <language>en-us</language>')
rss_lines.append(f' <lastBuildDate>{datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")}</lastBuildDate>')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: lastBuildDate / fallback pubDate mix local time with +0000.

datetime.now() returns local time, but the format string hard-codes +0000 (UTC). On any non-UTC host this yields incorrect timestamps in the feed. Use datetime.utcnow() (or datetime.now(timezone.utc)) — or emit a real offset via %z on a timezone-aware datetime.

♻️ Diff
-        rss_lines.append(f'  <lastBuildDate>{datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")}</lastBuildDate>')
+        rss_lines.append(f'  <lastBuildDate>{datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")}</lastBuildDate>')

Also applies to: 279-279

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` at line 261, The code uses datetime.now() and
hard-codes "+0000" when building RSS timestamps (e.g., the rss_lines.append call
that writes <lastBuildDate> and the similar fallback <pubDate> construction),
which mixes local time with a UTC offset; replace datetime.now() with a
UTC-aware datetime (e.g., datetime.utcnow() or datetime.now(timezone.utc)) and
either format with a real offset via %z or remove the hard-coded "+0000" so the
timestamp is correct; update both the <lastBuildDate> append and the pubDate
fallback construction in generator.py accordingly.

Comment thread generator/generator.py
Comment on lines +265 to +279
sorted_pages = sorted(
self.pages,
key=lambda x: x['metadata'].get('date', ''),
reverse=True
)

# Only include the latest 20 items in RSS
for page in sorted_pages[:20]:
# Skip pages without a date or index page if desired, but usually all content is fine
pub_date = page['metadata'].get('date', datetime.now().strftime('%Y-%m-%d'))
try:
dt = datetime.strptime(pub_date, '%Y-%m-%d')
formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")
except:
formatted_date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect a few content frontmatter dates to confirm whether they are quoted strings or bare YAML dates.
fd -e md . content 2>/dev/null | head -20 | xargs -I{} sh -c 'echo "=== {} ==="; sed -n "1,15p" "{}"'

Repository: Alqudimi/MarkSite

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Explore repository structure
find . -type f -name "*.py" | grep -i generator | head -10
ls -la

Repository: Alqudimi/MarkSite

Length of output: 971


🏁 Script executed:

#!/bin/bash
# Look for content files or examples
fd -e md . 2>/dev/null | head -20
find . -name "*.md" -type f | head -20

Repository: Alqudimi/MarkSite

Length of output: 463


🏁 Script executed:

#!/bin/bash
# Read the generator.py file to inspect the actual code
cat -n generator/generator.py | sed -n '1,50p'
wc -l generator/generator.py

Repository: Alqudimi/MarkSite

Length of output: 2032


🏁 Script executed:

#!/bin/bash
# Read the relevant sections of generator.py
cat -n generator/generator.py | sed -n '170,190p'
echo "---"
cat -n generator/generator.py | sed -n '230,250p'
echo "---"
cat -n generator/generator.py | sed -n '260,285p'

Repository: Alqudimi/MarkSite

Length of output: 3611


🏁 Script executed:

#!/bin/bash
# Check parse_markdown_file function
rg -n "def parse_markdown_file" generator/generator.py -A 20

Repository: Alqudimi/MarkSite

Length of output: 1060


🏁 Script executed:

#!/bin/bash
# Check for sample markdown files in doc directory
cat doc/index.md | head -20

Repository: Alqudimi/MarkSite

Length of output: 946


🏁 Script executed:

#!/bin/bash
# Test frontmatter date parsing behavior
python3 << 'EOF'
import frontmatter
from datetime import datetime, date

# Test 1: Parse markdown with bare YAML date (no quotes)
test_markdown_bare = """---
title: Test Post
date: 2025-01-15
---
# Test Content
"""

post = frontmatter.loads(test_markdown_bare)
print("Test 1 - Bare YAML date (date: 2025-01-15):")
print(f"  Type: {type(post.metadata['date'])}")
print(f"  Value: {post.metadata['date']}")
print()

# Test 2: Parse markdown with quoted date
test_markdown_quoted = """---
title: Test Post
date: "2025-01-15"
---
# Test Content
"""

post2 = frontmatter.loads(test_markdown_quoted)
print("Test 2 - Quoted date (date: \"2025-01-15\"):")
print(f"  Type: {type(post2.metadata['date'])}")
print(f"  Value: {post2.metadata['date']}")
print()

# Test 3: Try sorting mixed types
print("Test 3 - Sorting with mixed types (date object vs empty string):")
try:
    items = [
        {'metadata': {'date': date(2025, 1, 15)}},
        {'metadata': {'date': ''}},
        {'metadata': {'date': date(2025, 1, 10)}}
    ]
    sorted_items = sorted(items, key=lambda x: x['metadata'].get('date', ''), reverse=True)
    print("  Sorting succeeded (unexpected)")
except TypeError as e:
    print(f"  TypeError: {e}")
print()

# Test 4: Try strptime with date object
print("Test 4 - strptime with datetime.date object:")
try:
    pub_date = date(2025, 1, 15)
    dt = datetime.strptime(pub_date, '%Y-%m-%d')
    print(f"  Success (unexpected): {dt}")
except TypeError as e:
    print(f"  TypeError: {e}")
EOF

Repository: Alqudimi/MarkSite

Length of output: 185


🏁 Script executed:

#!/bin/bash
# Find all markdown files and sample their dates
find . -name "*.md" -type f | while read file; do
  echo "=== $file ==="
  sed -n '1,10p' "$file" | grep -i "^date:"
done

Repository: Alqudimi/MarkSite

Length of output: 403


🏁 Script executed:

#!/bin/bash
# Install requirements
pip install -q -r requirements.txt

# Test frontmatter date parsing behavior
python3 << 'EOF'
import frontmatter
from datetime import datetime, date

# Test 1: Parse markdown with bare YAML date (no quotes)
test_markdown_bare = """---
title: Test Post
date: 2025-01-15
---
# Test Content
"""

post = frontmatter.loads(test_markdown_bare)
print("Test 1 - Bare YAML date (date: 2025-01-15):")
print(f"  Type: {type(post.metadata['date'])}")
print(f"  Value: {post.metadata['date']}")
print()

# Test 2: Parse markdown with quoted date
test_markdown_quoted = """---
title: Test Post
date: "2025-01-15"
---
# Test Content
"""

post2 = frontmatter.loads(test_markdown_quoted)
print("Test 2 - Quoted date (date: \"2025-01-15\"):")
print(f"  Type: {type(post2.metadata['date'])}")
print(f"  Value: {post2.metadata['date']}")
print()

# Test 3: Try sorting mixed types
print("Test 3 - Sorting with mixed types (date object vs empty string):")
try:
    items = [
        {'metadata': {'date': date(2025, 1, 15)}},
        {'metadata': {'date': ''}},
        {'metadata': {'date': date(2025, 1, 10)}}
    ]
    sorted_items = sorted(items, key=lambda x: x['metadata'].get('date', ''), reverse=True)
    print("  Sorting succeeded (unexpected)")
except TypeError as e:
    print(f"  TypeError: {e}")
print()

# Test 4: Try strptime with date object
print("Test 4 - strptime with datetime.date object:")
try:
    pub_date = date(2025, 1, 15)
    dt = datetime.strptime(pub_date, '%Y-%m-%d')
    print(f"  Success (unexpected): {dt}")
except TypeError as e:
    print(f"  TypeError: {e}")
print()

# Test 5: Format date object directly to string
print("Test 5 - Formatting date object with strftime:")
pub_date = date(2025, 1, 15)
try:
    # This is what happens at line 239 of generator.py
    formatted = f'{pub_date}'
    print(f"  Direct string conversion: {formatted}")
except Exception as e:
    print(f"  Error: {e}")
EOF

Repository: Alqudimi/MarkSite

Length of output: 1263


Major: date from YAML frontmatter is a datetime.date object, not a string — sorting and strptime both fail.

When frontmatter parses date: 2025-01-15 (bare YAML without quotes), PyYAML auto-converts it to a datetime.date object. This causes two critical failures:

  1. sorted(..., key=lambda x: x['metadata'].get('date', '')) at lines 179 and 267 mixes date objects with '' strings, raising TypeError: '<' not supported between instances of 'str' and 'datetime.date' whenever a page lacks a date.
  2. datetime.strptime(pub_date, '%Y-%m-%d') at line 276 raises TypeError: strptime() argument 1 must be str, not datetime.date. The bare except then silently replaces every pubDate with the current timestamp, erasing real publication dates.

The same sorting issue affects generate_index_page (line 179) and generate_sitemap (line 239). Root cause: the inconsistent date type across functions. Normalize dates to strings in parse_markdown_file so downstream code handles a consistent representation.

Suggested fix inside the loop (and a normalization helper)
-        for page in sorted_pages[:20]:
-            # Skip pages without a date or index page if desired, but usually all content is fine
-            pub_date = page['metadata'].get('date', datetime.now().strftime('%Y-%m-%d'))
-            try:
-                dt = datetime.strptime(pub_date, '%Y-%m-%d')
-                formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")
-            except:
-                formatted_date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
+        for page in sorted_pages[:20]:
+            pub_date = page['metadata'].get('date')
+            dt = None
+            if isinstance(pub_date, datetime):
+                dt = pub_date
+            elif isinstance(pub_date, date):
+                dt = datetime(pub_date.year, pub_date.month, pub_date.day)
+            elif isinstance(pub_date, str):
+                try:
+                    dt = datetime.strptime(pub_date, '%Y-%m-%d')
+                except ValueError:
+                    dt = None
+            if dt is None:
+                dt = datetime.utcnow()
+            formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")

(Also import from datetime import date alongside datetime.)

🧰 Tools
🪛 Ruff (0.15.11)

[error] 278-278: Do not use bare except

(E722)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` around lines 265 - 279, The YAML-parsed `date` fields
are datetime.date objects causing sorting and strptime to fail; normalize dates
when you parse files so downstream code can assume a consistent string format:
in parse_markdown_file detect metadata['date'] (use isinstance(..., date) or
datetime.date), convert it to an ISO string like 'YYYY-MM-DD' (or store None/'')
and replace the original value, and ensure metadata always contains a string or
empty string for date; then you can safely use sorted(self.pages, key=lambda x:
x['metadata'].get('date',''), reverse=True) in
generate_index_page/generate_sitemap and keep the pub_date handling around
pub_date/datetime.strptime without TypeError.

Comment thread generator/generator.py
Comment on lines +278 to +279
except:
formatted_date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace bare except with a specific exception (Ruff E722).

A bare except: swallows KeyboardInterrupt and SystemExit. Catch only what strptime can raise.

♻️ Diff
-            try:
-                dt = datetime.strptime(pub_date, '%Y-%m-%d')
-                formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")
-            except:
-                formatted_date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
+            try:
+                dt = datetime.strptime(pub_date, '%Y-%m-%d')
+                formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")
+            except (TypeError, ValueError):
+                formatted_date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except:
formatted_date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
try:
dt = datetime.strptime(pub_date, '%Y-%m-%d')
formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S +0000")
except (TypeError, ValueError):
formatted_date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
🧰 Tools
🪛 Ruff (0.15.11)

[error] 278-278: Do not use bare except

(E722)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generator/generator.py` around lines 278 - 279, The bare except: should be
replaced with a specific exception handler to avoid swallowing
SystemExit/KeyboardInterrupt; change it to catch the exceptions that datetime
parsing can raise (e.g., except (ValueError, TypeError) as e) and keep the
fallback assignment to formatted_date = datetime.now().strftime("%a, %d %b %Y
%H:%M:%S +0000"); reference the existing formatted_date assignment and ensure
the except targets ValueError/TypeError rather than a bare except.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant