ADD RSS feed and config - seems valid #6
This commit is contained in:
parent
375eb6fb05
commit
e31624d03a
3 changed files with 104 additions and 5 deletions
|
|
@ -1,7 +1,11 @@
|
||||||
"""Configuration file for picopaper blog"""
|
"""Configuration file for picopaper blog"""
|
||||||
|
|
||||||
|
# General site settings
|
||||||
BLOG_TITLE = "PicoPaper.com"
|
BLOG_TITLE = "PicoPaper.com"
|
||||||
BLOG_DESCRIPTION = "we like simple."
|
BLOG_DESCRIPTION = "we like simple."
|
||||||
|
BASE_URL = "https://picopaper.com" # Your site's base URL (no trailing slash)
|
||||||
|
AUTHOR_NAME = "PicoPaper"
|
||||||
|
AUTHOR_EMAIL = "hello@picopaper.com" # Optional
|
||||||
THEME = "default"
|
THEME = "default"
|
||||||
|
|
||||||
# Exclude specific feeds from the main page (they'll still have their own /feed/name/ pages)
|
# Exclude specific feeds from the main page (they'll still have their own /feed/name/ pages)
|
||||||
|
|
@ -19,3 +23,8 @@ HIDE_LOGO = False
|
||||||
HIDE_TITLE = True
|
HIDE_TITLE = True
|
||||||
LOGO_PATH = "/images/logo.png"
|
LOGO_PATH = "/images/logo.png"
|
||||||
|
|
||||||
|
# Feed settings
|
||||||
|
ENABLE_RSS_FEED = True
|
||||||
|
RSS_FEED_PATH = "rss.xml" # Path relative to site root (e.g., "rss.xml" or "feed/rss.xml")
|
||||||
|
FEED_MAX_ITEMS = 20 # Maximum number of items to include in feeds
|
||||||
|
|
||||||
|
|
|
||||||
97
picopaper.py
97
picopaper.py
|
|
@ -6,7 +6,10 @@ from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
import markdown
|
import markdown
|
||||||
from config import BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_FEEDS_FROM_MAIN, NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH
|
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_FEEDS_FROM_MAIN,
|
||||||
|
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
|
||||||
|
ENABLE_RSS_FEED, RSS_FEED_PATH,
|
||||||
|
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS)
|
||||||
|
|
||||||
class SSGGGenerator:
|
class SSGGGenerator:
|
||||||
def __init__(self, items_dir='items', output_dir='output', theme=None, blog_title=None, blog_description=None):
|
def __init__(self, items_dir='items', output_dir='output', theme=None, blog_title=None, blog_description=None):
|
||||||
|
|
@ -149,7 +152,9 @@ class SSGGGenerator:
|
||||||
all_posts=all_posts or posts,
|
all_posts=all_posts or posts,
|
||||||
hide_logo=self.hide_logo,
|
hide_logo=self.hide_logo,
|
||||||
hide_title=self.hide_title,
|
hide_title=self.hide_title,
|
||||||
logo_path=self.logo_path
|
logo_path=self.logo_path,
|
||||||
|
rss_feed_enabled=ENABLE_RSS_FEED,
|
||||||
|
rss_feed_path=RSS_FEED_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -183,7 +188,9 @@ class SSGGGenerator:
|
||||||
all_posts=all_posts or [],
|
all_posts=all_posts or [],
|
||||||
hide_logo=self.hide_logo,
|
hide_logo=self.hide_logo,
|
||||||
hide_title=self.hide_title,
|
hide_title=self.hide_title,
|
||||||
logo_path=self.logo_path
|
logo_path=self.logo_path,
|
||||||
|
rss_feed_enabled=ENABLE_RSS_FEED,
|
||||||
|
rss_feed_path=RSS_FEED_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -205,7 +212,9 @@ class SSGGGenerator:
|
||||||
all_posts=all_posts or [],
|
all_posts=all_posts or [],
|
||||||
hide_logo=self.hide_logo,
|
hide_logo=self.hide_logo,
|
||||||
hide_title=self.hide_title,
|
hide_title=self.hide_title,
|
||||||
logo_path=self.logo_path
|
logo_path=self.logo_path,
|
||||||
|
rss_feed_enabled=ENABLE_RSS_FEED,
|
||||||
|
rss_feed_path=RSS_FEED_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create directory for the post slug
|
# Create directory for the post slug
|
||||||
|
|
@ -219,6 +228,82 @@ class SSGGGenerator:
|
||||||
|
|
||||||
print(f"✓ Generated {output_path}")
|
print(f"✓ Generated {output_path}")
|
||||||
|
|
||||||
|
def generate_rss_feed(self, posts):
|
||||||
|
"""Generate RSS 2.0 feed for main feed posts"""
|
||||||
|
from xml.etree.ElementTree import Element, SubElement, tostring, register_namespace
|
||||||
|
from xml.dom import minidom
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Register atom namespace to avoid ns0 prefix
|
||||||
|
register_namespace('atom', 'http://www.w3.org/2005/Atom')
|
||||||
|
|
||||||
|
# Limit posts
|
||||||
|
posts = posts[:FEED_MAX_ITEMS]
|
||||||
|
|
||||||
|
# Build feed URL correctly - ensure no double slashes
|
||||||
|
feed_path = RSS_FEED_PATH.lstrip('/')
|
||||||
|
# Remove trailing slash from BASE_URL if present for clean URL construction
|
||||||
|
base_url_clean = BASE_URL.rstrip('/')
|
||||||
|
feed_url = f"{base_url_clean}/{feed_path}"
|
||||||
|
|
||||||
|
# Create RSS element (namespace will be added automatically when we use atom:link)
|
||||||
|
rss = Element('rss', version='2.0')
|
||||||
|
channel = SubElement(rss, 'channel')
|
||||||
|
|
||||||
|
# Channel metadata
|
||||||
|
SubElement(channel, 'title').text = self.blog_title
|
||||||
|
SubElement(channel, 'description').text = self.blog_description
|
||||||
|
SubElement(channel, 'link').text = base_url_clean
|
||||||
|
|
||||||
|
# Add atom:link with rel="self" (required by RSS best practices)
|
||||||
|
atom_link = SubElement(channel, '{http://www.w3.org/2005/Atom}link')
|
||||||
|
atom_link.set('href', feed_url)
|
||||||
|
atom_link.set('rel', 'self')
|
||||||
|
atom_link.set('type', 'application/rss+xml')
|
||||||
|
|
||||||
|
SubElement(channel, 'lastBuildDate').text = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S +0000')
|
||||||
|
|
||||||
|
# Add author information (managingEditor format: email (name))
|
||||||
|
if AUTHOR_EMAIL and AUTHOR_NAME:
|
||||||
|
SubElement(channel, 'managingEditor').text = f"{AUTHOR_EMAIL} ({AUTHOR_NAME})"
|
||||||
|
elif AUTHOR_EMAIL:
|
||||||
|
SubElement(channel, 'managingEditor').text = AUTHOR_EMAIL
|
||||||
|
|
||||||
|
# Helper function to convert relative URLs to absolute
|
||||||
|
def make_absolute_urls(html_content):
|
||||||
|
# Replace relative URLs with absolute ones
|
||||||
|
html_content = re.sub(r'href="/', f'href="{base_url_clean}/', html_content)
|
||||||
|
html_content = re.sub(r'src="/', f'src="{base_url_clean}/', html_content)
|
||||||
|
return html_content
|
||||||
|
|
||||||
|
# Add items
|
||||||
|
for post in posts:
|
||||||
|
item = SubElement(channel, 'item')
|
||||||
|
SubElement(item, 'title').text = post['title']
|
||||||
|
SubElement(item, 'link').text = f"{base_url_clean}{post['url']}"
|
||||||
|
SubElement(item, 'guid', isPermaLink='true').text = f"{base_url_clean}{post['url']}"
|
||||||
|
SubElement(item, 'pubDate').text = datetime.strptime(post['date'], '%Y-%m-%d').strftime('%a, %d %b %Y 00:00:00 +0000')
|
||||||
|
|
||||||
|
# Content type based on post type
|
||||||
|
if post['type'] == 'long':
|
||||||
|
# For long posts, just show title/summary
|
||||||
|
SubElement(item, 'description').text = f"Read more at {base_url_clean}{post['url']}"
|
||||||
|
else:
|
||||||
|
# For short posts, include full content with absolute URLs
|
||||||
|
content_absolute = make_absolute_urls(post['content'])
|
||||||
|
SubElement(item, 'description').text = content_absolute
|
||||||
|
|
||||||
|
# Pretty print XML
|
||||||
|
xml_str = minidom.parseString(tostring(rss, encoding='utf-8')).toprettyxml(indent=' ', encoding='utf-8')
|
||||||
|
|
||||||
|
# Write to file
|
||||||
|
output_path = self.output_dir / RSS_FEED_PATH
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(xml_str)
|
||||||
|
|
||||||
|
print(f"✓ Generated {output_path}")
|
||||||
|
|
||||||
def copy_assets(self):
|
def copy_assets(self):
|
||||||
"""Copy theme assets and images to output directory"""
|
"""Copy theme assets and images to output directory"""
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -290,6 +375,10 @@ class SSGGGenerator:
|
||||||
if post['type'] in ['long', 'short', 'page']:
|
if post['type'] in ['long', 'short', 'page']:
|
||||||
self.generate_post_page(post, all_posts=feed_posts)
|
self.generate_post_page(post, all_posts=feed_posts)
|
||||||
|
|
||||||
|
# Generate RSS feed
|
||||||
|
if ENABLE_RSS_FEED:
|
||||||
|
self.generate_rss_feed(feed_posts)
|
||||||
|
|
||||||
# Copy assets
|
# Copy assets
|
||||||
self.copy_assets()
|
self.copy_assets()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
|
{% if rss_feed_enabled %}<link rel="alternate" type="application/rss+xml" title="{{ blog_title }} RSS Feed" href="/{{ rss_feed_path }}">{% endif %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue