Cybrkyd's git repositories

page-with-py • commit: 2d2519a

commit 2d2519a2220181ff94e7b5081bccf8ac2e4acb38cd96263b45dbd85599304ac0
author cybrkyd <vii@cybrkyd.com> 2025-09-28 18:31:49 +0100
committer cybrkyd <vii@cybrkyd.com> 2025-09-28 18:31:49 +0100
v1.2

Commit Message

- v1.2
- Removed archive template dependency on main template as fallback.
- Moved archive template HTML to the template, where it belongs.
- Removed year grouping on archive.
- Added search capability using lunr.js (optional).
- One extra line at the end of files after html closes.
- Tagging improvements: All tags can now be displayed in mixed case. For example, #HashTag will render as such on-page with the URL rendered in lowercase.

📊 Diffstat

pg-py/pa.py 131
1 files changed, 64 insertions(+), 67 deletions(-)

Diff

diff --git a/pg-py/pa.py b/pg-py/pa.py
index 8b5987f..8f3e455 100644
--- a/pg-py/pa.py
+++ b/pg-py/pa.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
PaPy (Page with Python): a Python static site generator
- v1.1
+ v1.2
"""
import re
@@ -21,6 +21,7 @@ CONTENT_LANGUAGE = "en"
SITE_LOGO = "/img/logo.png"
THEME = "basic"
PAGINATION = "numbers" # "numbers" or "direction"
+ ENABLE_SEARCH = False
GENERATE_ARCHIVE = "yes" # "yes" or "no"
ARCHIVE_URL = "archive" # the URL of the archive
@@ -129,8 +130,8 @@ def load_template_parts(theme_dir: Path):
if archive_file.exists():
templates['archive'] = archive_file.read_text(encoding="utf-8")
else:
- templates['archive'] = templates['main']
- print("Warning: archive.html not found in theme directory, using main.html")
+ templates['archive'] = '<main>\n{content}\n</main>'
+ print("Warning: archive.html not found in theme directory")
if tags_file.exists():
templates['tags'] = tags_file.read_text(encoding="utf-8")
@@ -162,6 +163,8 @@ def render_template(template_content, context):
item_content = item_content.replace(placeholder, str(value))
else:
item_content = item_content.replace(f'{{{item_var}}}', str(item))
+
+ item_content = process_filters(item_content, item if isinstance(item, dict) else {item_var: item})
rendered_loop += item_content
content = content.replace(for_match.group(0), rendered_loop)
else:
@@ -171,29 +174,6 @@ def render_template(template_content, context):
if isinstance(value, str):
content = content.replace(f'{{{key}}}', value)
- # Handle simple year grouping for archive template
- if 'years' in context and '{% for year, posts in years %}' in content:
-
- if 'YEAR_GROUPING_PROCESSED' not in content:
- start_pattern = '{% for year, posts in years %}'
- end_pattern = '{% endfor %}'
- content = content.replace(start_pattern, '').replace(end_pattern, '')
- content = content.replace('{{ year }}', '')
-
- years_content = ''
- for year, posts in context['years'].items():
- years_content += f'<h2>{year}</h2>\n'
- for post in posts:
- date_str = apply_filter(post.get('date', ''), 'format', '%d %b %Y')
- years_content += f'<li class="archive-item">\n<span class="archive-title"><a href="{post.get("url", "")}">{post.get("title", "")}</a></span>\n<span class="archive-date">{date_str}</span>\n</li>\n'
-
- ul_start = content.find('<ul class="archive-list">') + len('<ul class="archive-list">')
- ul_end = content.find('</ul>')
- if ul_start != -1 and ul_end != -1:
- content = content[:ul_start] + years_content + content[ul_end:]
-
- content += '<!-- YEAR_GROUPING_PROCESSED -->' # Hacky AF!
-
content = process_filters(content, context)
return content
@@ -246,7 +226,7 @@ def generate_html_page(templates, title, content, description="", page_url="", i
html = f"{templates['header']}\n{content}\n{templates['footer']}"
if '<head>' not in html:
- html = f"<!DOCTYPE html>\n<html lang=\"{CONTENT_LANGUAGE}\" itemscope itemtype=\"https://schema.org/WebPage\">\n{head}\n<body>\n{html}\n</body>\n</html>"
+ html = f"<!DOCTYPE html>\n<html lang=\"{CONTENT_LANGUAGE}\" itemscope itemtype=\"https://schema.org/WebPage\">\n{head}\n<body>\n{html}\n</body>\n</html>\n"
elif '</head>' in html:
html = HEAD_TAG_PATTERN.sub(head, html)
else:
@@ -275,18 +255,18 @@ def process_markdown_file(md_path: Path, templates, output_dir: Path, is_post=Fa
front_matter_tags = []
if tags_str:
- front_matter_tags = [tag.strip().lower() for tag in tags_str.split(',') if tag.strip()]
+ front_matter_tags = [tag.strip() for tag in tags_str.split(',') if tag.strip()]
inline_tags = []
cleaned_content = body_content
for match in HASHTAG_PATTERN.finditer(body_content):
tag_name = match.group(1).strip()
- inline_tags.append(tag_name.lower())
+ inline_tags.append(tag_name)
cleaned_content = cleaned_content.replace(
match.group(0),
f'<a href="/tags/{tag_name.lower()}/">#{tag_name}</a>'
)
-
+
all_tags = list(set(front_matter_tags + inline_tags))
image_data = []
@@ -301,10 +281,10 @@ def process_markdown_file(md_path: Path, templates, output_dir: Path, is_post=Fa
markdown_content = md_converter.reset().convert(cleaned_content)
caption_data = [caption] if caption else []
-
+
tags_html = ''
if front_matter_tags:
- tags_links = [f'<a href="/tags/{tag}/">{tag}</a>' for tag in front_matter_tags]
+ tags_links = [f'<a href="/tags/{tag.lower()}/">{tag}</a>' for tag in front_matter_tags]
tags_html = f'\n<div class="post-tags"><span>Tags:</span> {", ".join(tags_links)}</div>'
html_content = f'{markdown_content}{tags_html}'
@@ -396,13 +376,14 @@ def generate_index_page(templates, posts, output_dir: Path, page_num=1, is_pagin
title = f"Page {page_num} - {SITE_TITLE}"
total_pages = (len(posts) + POSTS_PER_PAGE - 1) // POSTS_PER_PAGE
-
+
pagination_html = ''
+
if total_pages > 1:
pagination_html = '<div class="pagination">'
if page_num > 1:
if tag_name:
- prev_url = f"/tags/{tag_name}/page/{page_num-1}/" if page_num > 2 else f"/tags/{tag_name}/"
+ prev_url = f"/tags/{tag_name.lower()}/page/{page_num-1}/" if page_num > 2 else f"/tags/{tag_name.lower()}/"
else:
prev_url = f"/page/{page_num-1}/" if page_num > 2 else "/"
pagination_html += f'<span class="newer"><a href="{prev_url}">&#8617; Newer</a></span> '
@@ -413,14 +394,14 @@ def generate_index_page(templates, posts, output_dir: Path, page_num=1, is_pagin
pagination_html += f'<span>{i}</span> '
else:
if tag_name:
- page_url = f"/tags/{tag_name}/page/{i}/" if i > 1 else f"/tags/{tag_name}/"
+ page_url = f"/tags/{tag_name.lower()}/page/{i}/" if i > 1 else f"/tags/{tag_name.lower()}/"
else:
page_url = f"/page/{i}/" if i > 1 else "/"
pagination_html += f'<a href="{page_url}">{i}</a> '
if page_num < total_pages:
if tag_name:
- pagination_html += f'<a href="/tags/{tag_name}/page/{page_num+1}/">Next Post &#8618;</a>'
+ pagination_html += f'<a href="/tags/{tag_name.lower()}/page/{page_num+1}/">Next Post &#8618;</a>'
else:
pagination_html += f'<span class="older"><a href="/page/{page_num+1}/">Older &#8618;</a></span>'
pagination_html += '</div>'
@@ -453,17 +434,17 @@ def generate_index_page(templates, posts, output_dir: Path, page_num=1, is_pagin
if is_pagination and page_num > 1:
page_url = f"{SITE_URL}/page/{page_num}/"
elif tag_name:
- page_url = f"{SITE_URL}/tags/{tag_name}/"
+ page_url = f"{SITE_URL}/tags/{tag_name.lower()}/"
if is_pagination and page_num > 1:
- page_url = f"{SITE_URL}/tags/{tag_name}/page/{page_num}/"
+ page_url = f"{SITE_URL}/tags/{tag_name.lower()}/page/{page_num}/"
full_html = generate_html_page(templates, title, content, description, page_url, "", "website")
-
+
if tag_name:
if is_pagination:
- output_path = output_dir / 'tags' / tag_name / 'page' / str(page_num) / 'index.html'
+ output_path = output_dir / 'tags' / tag_name.lower() / 'page' / str(page_num) / 'index.html'
else:
- output_path = output_dir / 'tags' / tag_name / 'index.html'
+ output_path = output_dir / 'tags' / tag_name.lower() / 'index.html'
elif is_pagination:
output_path = output_dir / 'page' / str(page_num) / 'index.html'
else:
@@ -473,29 +454,16 @@ def generate_index_page(templates, posts, output_dir: Path, page_num=1, is_pagin
return output_path
- def group_posts_by_year(posts):
- years = {}
- for post in posts:
- year = post['date'][:4]
- if year not in years:
- years[year] = []
- years[year].append(post)
-
- return dict(sorted(years.items(), key=lambda x: x[0], reverse=True))
-
-
def generate_archive_page(templates, posts, output_dir: Path):
title = f"Archive - {SITE_TITLE}"
description = f"Complete post archive - {SITE_DESC}"
- # Fallback to main if archive not available
- archive_template = templates.get('archive', templates['main'])
+ archive_template = templates['archive']
context = {
'title': title,
'description': description,
'pagination': '',
- 'years': group_posts_by_year(posts),
'posts': [{
'title': post['title'],
'url': '/' + post['url'].replace(SITE_URL, '').lstrip('/'),
@@ -515,11 +483,11 @@ def generate_archive_page(templates, posts, output_dir: Path):
def generate_tags_index(templates, all_tags, output_dir: Path):
- lines = ['<h1>Tags</h1>', '<ul>']
- for tag, count in sorted(all_tags.items()):
- lines.append(f'<li><a href="/tags/{tag}/">{tag}</a> ({count})</li>')
- lines.append('</ul>')
-
+ lines = ['<main>\n<h1>Tags</h1>', '<ul>']
+ for tag, count in sorted(all_tags.items(), key=lambda x: x[0].lower()):
+ lines.append(f'<li><a href="/tags/{tag.lower()}/">{tag}</a> ({count})</li>')
+ lines.append('</ul>\n</main>\n')
+
content = "\n".join(lines)
title = f"Tags - {SITE_TITLE}"
description = f"All tags on {SITE_TITLE}"
@@ -591,6 +559,29 @@ def generate_sitemap(pages, output_dir: Path):
tree.write(output_dir / 'sitemap.xml', encoding='utf-8', xml_declaration=True)
+ def generate_search_index(posts, output_dir: Path):
+ # Optional feature - search with lunr.js
+ if not ENABLE_SEARCH:
+ return
+
+ search_data = []
+ for post in posts:
+ import re
+ plain_content = re.sub('<[^<]+?>', '', post.get('html_content', ''))
+ plain_content = plain_content.replace('&nbsp;', ' ').replace('&amp;', '&')
+
+ search_data.append({
+ 'title': post['title'],
+ 'content': plain_content,
+ 'url': post['url']
+ })
+
+ import json
+ json_output = json.dumps(search_data, indent=2)
+ (output_dir / 'search-index.json').write_text(json_output, encoding='utf-8')
+ print("Search index generated")
+
+
def main():
try:
base_dir = Path.cwd()
@@ -636,14 +627,19 @@ def main():
for i, post in enumerate(post_pages):
post['post_navigation'] = generate_post_navigation(post, post_pages)
-
+
for post in post_pages:
+ tags_with_links = [
+ {"url": tag.lower(), "label": tag}
+ for tag in post.get('front_matter_tags', [])
+ ]
+
full_html = generate_html_page(
templates, post['title'], post['html_content'],
post['description'], post['url'], post.get('image', ''),
"article", post['date'], post.get('image_data', []),
post.get('caption_data', []), post['post_navigation'],
- tags=post.get('front_matter_tags', [])
+ tags=tags_with_links
)
write_html_file(Path(post['path']), full_html)
@@ -651,7 +647,6 @@ def main():
all_pages.append({'url': f"{SITE_URL}/", 'path': str(index_page), 'date': datetime.now().replace(microsecond=0).isoformat()})
total_pages = (len(post_pages) + POSTS_PER_PAGE - 1) // POSTS_PER_PAGE
-
for page_num in range(2, total_pages + 1):
page_path = generate_index_page(templates, post_pages, public_dir, page_num=page_num, is_pagination=True)
all_pages.append({'url': f"{SITE_URL}/page/{page_num}/", 'path': str(page_path), 'date': datetime.now().replace(microsecond=0).isoformat()})
@@ -667,14 +662,14 @@ def main():
for tag in all_tags_set:
tagged_posts = [post for post in post_pages if tag in post['tags']]
tagged_posts.sort(key=lambda x: x.get('date', ''), reverse=True)
-
+
tag_page_path = generate_index_page(templates, tagged_posts, public_dir, tag_name=tag)
- all_pages.append({'url': f"{SITE_URL}/tags/{tag}/", 'path': str(tag_page_path), 'date': datetime.now().replace(microsecond=0).isoformat()})
-
+ all_pages.append({'url': f"{SITE_URL}/tags/{tag.lower()}/", 'path': str(tag_page_path), 'date': datetime.now().replace(microsecond=0).isoformat()})
+
tag_total_pages = (len(tagged_posts) + POSTS_PER_PAGE - 1) // POSTS_PER_PAGE
for page_num in range(2, tag_total_pages + 1):
tag_pagination_path = generate_index_page(templates, tagged_posts, public_dir, page_num=page_num, is_pagination=True, tag_name=tag)
- all_pages.append({'url': f"{SITE_URL}/tags/{tag}/page/{page_num}/", 'path': str(tag_pagination_path), 'date': datetime.now().replace(microsecond=0).isoformat()})
+ all_pages.append({'url': f"{SITE_URL}/tags/{tag.lower()}/page/{page_num}/", 'path': str(tag_pagination_path), 'date': datetime.now().replace(microsecond=0).isoformat()})
static_dir = base_dir / 'static'
if static_dir.exists():
@@ -689,6 +684,8 @@ def main():
generate_rss_feed(post_pages, public_dir)
generate_sitemap(all_pages, public_dir)
+ if ENABLE_SEARCH:
+ generate_search_index(post_pages, public_dir)
print("Site generated successfully")
except Exception as e: