From 195e1d0ff130ff53d7915832e8403f123a3ed629 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Tue, 11 Aug 2026 19:16:03 -0500 Subject: Build with orgo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generator changes; the steps around it that orgo does not do stay. Four of them go, because it does: - the blog index groups itself by year (a template, using page.year) - the tags page is a collection - the recent-posts list on the home page comes from the blog collection - the sitemap is written once site.base_url is set What remains is minifying CSS, copying the org sources for readers who want them, and rewriting img.cleberg.net to /img/ so the onion serves images from its own origin. That last one is why this script still exists. Verified against the live site before deploying: 195 pages, 186 identical in content, and every URL in the live sitemap still produced. The nine that differ are the ones we chose — the reply footer is opt-in now, /tags/ has a page per tag, and footnotes carry aria labels. publish.el and theme/templates/ are left alone. They are weblorg's, and there is no reason to delete a fallback on the same day the replacement goes live. --- build.py | 234 +++++---------------------------------------------------------- 1 file changed, 18 insertions(+), 216 deletions(-) diff --git a/build.py b/build.py index 771c406..3bfc8fb 100755 --- a/build.py +++ b/build.py @@ -32,7 +32,6 @@ import sys from datetime import datetime from html import escape from pathlib import Path -from urllib.parse import quote SITE_TEMPLATE_VARS = { "site_name": "cleberg.net", @@ -51,66 +50,6 @@ def run_ruff(): sys.exit(1) -def update_marked_section( - html_snippet, - template_path="./.build/index.html", - begin_marker="", - end_marker="", -): - """ - Read the file at `template_path`, replace everything between `begin_marker` - and `end_marker` with the provided html_snippet, and write the updated - content back to the same file. - """ - with open(template_path, "r", encoding="utf-8") as f: - content = f.read() - - # Find the indices of the markers - begin_index = content.find(begin_marker) - end_index = content.find(end_marker) - - if begin_index == -1 or end_index == -1: - raise ValueError(f"Markers not found in {template_path}") - - # Compute insertion points: after the end of begin_marker line, before end_marker - # Include the newline after BEGIN_POSTS - insert_start = begin_index + len(begin_marker) - # Ensure we capture the newline character if present - if content[insert_start : insert_start + 1] == "\n": - insert_start += 1 - - # If there is a newline before END_POSTS, trim trailing whitespace from snippet block - # We will preserve indentation of BEGIN_POSTS line - indent = "" - # Determine the indentation by looking at characters after the newline that follows begin_marker - lines_after_begin = content[begin_index:].splitlines(True) - if len(lines_after_begin) > 1: - # The second line starts with the indentation to preserve - second_line = lines_after_begin[1] - indent = "" - for ch in second_line: - if ch.isspace(): - indent += ch - else: - break - - # Prepare the replacement block: indent each line of html_snippet - snippet_lines = html_snippet.splitlines() - indented_snippet = "\n".join(indent + line for line in snippet_lines) + "\n" - - # Compute the position just before end_marker (excluding any preceding whitespace/newline) - end_line_start = content.rfind("\n", 0, end_index) - if end_line_start == -1: - end_line_start = end_index - - # Construct the new content - new_content = content[:insert_start] + indented_snippet + content[end_line_start:] - - # Write back to index.html - with open(template_path, "w", encoding="utf-8") as f: - f.write(new_content) - - def render_base_template(main_html, subtitle="", title=None): """ Render a small subset of the site's shared templates for Python-generated @@ -257,24 +196,6 @@ def get_blog_posts(content_dir="./content/blog"): return posts -def get_recent_posts_html(content_dir="./content/blog", num_posts=3): - """ - Return an HTML snippet for the `num_posts` most recent blog posts. - """ - recent = get_blog_posts(content_dir)[:num_posts] - - lines = [] - for post in recent: - lines.append('\t
  • ') - lines.append( - f'\t\t' - ) - lines.append(f'\t\t{post["title"]}') - lines.append("\t
  • ") - - return "\n".join(lines) - - def prompt(prompt_text): try: return input(prompt_text).strip() @@ -303,20 +224,6 @@ def minify_css(src_css, dest_css): sys.exit(1) -def minify_html(src_html, dest_html): - print(f"Minifying HTML: {src_html} → {dest_html}") - result = subprocess.run( - ["minify", "-o", str(dest_html), str(src_html)], - capture_output=True, - text=True, - check=False, - ) - if result.returncode != 0: - print("Error during HTML minification:") - print(result.stderr, file=sys.stderr) - sys.exit(1) - - def rewrite_img_urls(build_dir=".build"): """ Rewrite absolute img.cleberg.net URLs to root-relative /img/ paths so the @@ -342,31 +249,32 @@ def rewrite_img_urls(build_dir=".build"): print(f"Rewrote {count} img.cleberg.net references to /img/") -def run_emacs_publish(dev_mode=True): - mode = "development" if dev_mode else "production" - print(f"Running Emacs publish script ({mode})...") +def run_orgo_build(dev_mode=True): + """ + Build the site with orgo. + + Replaces the weblorg/Emacs publish. orgo reads content/orgo.toml, so the routes, + templates and collections that used to live in publish.el live there now. Four of the + steps this script used to perform afterwards are gone with it: the blog index groups + itself by year, the tags page is a collection, the recent-posts list is generated from + the blog collection, and the sitemap is written by orgo once base_url is set. + What is left around it is what orgo does not do: minifying CSS, copying the org + sources for readers who want them, and rewriting image URLs for the onion. + """ + print("Building with orgo...") result = subprocess.run( - ["emacs", "--script", "publish.el"], + ["orgo", "build", "content", "-o", ".build", "--strict"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, check=False, ) - + print(result.stdout, end="") if result.returncode != 0: - print("Error running publish.el output:") - print(result.stdout) + print("orgo build failed", file=sys.stderr) sys.exit(1) - annoying_file = Path(".build/cleberg-net.html") - if annoying_file.exists(): - os.remove(annoying_file) - else: - print( - "Warning: .build/cleberg-net.html not found, but Emacs exited successfully." - ) - def copy_org_sources(content_dir="./content", build_dir="./.build/org"): print(f"Copying org sources: {content_dir} → {build_dir}") @@ -395,90 +303,6 @@ def copy_org_sources(content_dir="./content", build_dir="./.build/org"): print(f" {src_path.name} → {dest_name}") -def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"): - """ - Generates a sitemap.xml based on contents of the .build directory. - Only includes .html files (except 404.html). - """ - sitemap_entries = [] - for root, dirs, files in os.walk(build_dir): - for filename in files: - if filename.endswith(".html") and filename != "404.html": - full_path = os.path.join(root, filename) - rel_path = os.path.relpath(full_path, build_dir) - url_path = "/" + quote(rel_path.replace(os.sep, "/")) - # Remove index.html for cleaner URLs - if url_path.endswith("/index.html"): - url_path = url_path[:-10] or "/" - elif url_path == "/index.html": - url_path = "/" - loc = f"{base_url}{url_path}" - - # Last modified time - lastmod = datetime.fromtimestamp(os.path.getmtime(full_path)).strftime( - "%Y-%m-%d" - ) - - sitemap_entries.append(f""" - {loc} - {lastmod} - """) - - sitemap_xml = f""" - -{os.linesep.join(sitemap_entries)} - -""" - # Write to .build/sitemap.xml - sitemap_path = os.path.join(build_dir, "sitemap.xml") - with open(sitemap_path, "w", encoding="utf-8") as f: - f.write(sitemap_xml) - print(f"Sitemap generated at {sitemap_path} with {len(sitemap_entries)} entries.") - - -def inject_blog_year_separators(blog_index_path="./.build/blog/index.html"): - """ - Post-processes the rendered blog index to inject year separator
  • elements - between groups of posts. Weblorg/templatel doesn't support mutable loop state, - so this runs after the HTML is generated. - - Finds each
  • that contains a
  • YYYY
  • before the first post of each year. - """ - path = Path(blog_index_path) - if not path.exists(): - print(f"Warning: {blog_index_path} not found, skipping year separators.") - return - - content = path.read_text(encoding="utf-8") - - # Match each post list item, capturing the date and the full element - item_pattern = re.compile( - r'(
  • .*?
  • )', - re.DOTALL, - ) - date_pattern = re.compile(r"datetime=['\"]?(\d{4})-\d{2}-\d{2}['\"]?") - - current_year = None - - def replace_item(m): - nonlocal current_year - item_html = m.group(1) - date_match = date_pattern.search(item_html) - if not date_match: - return item_html - year = date_match.group(1) - if year != current_year: - current_year = year - separator = f'
  • {year}
  • ' - return f"{separator}\n{item_html}" - return item_html - - new_content = item_pattern.sub(replace_item, content) - path.write_text(new_content, encoding="utf-8") - print(f"Blog year separators injected into {blog_index_path}") - - def get_tags_html(content_dir="./content/blog"): """ Build the tag index HTML snippet for the rendered tags template. @@ -541,17 +365,6 @@ def get_tags_html(content_dir="./content/blog"): ) -def generate_tags_page(content_dir="./content/blog", build_dir="./.build"): - """ - Render the tags page using the shared template structure. - """ - tags_html = get_tags_html(content_dir) - out_path = Path(build_dir) / "tags" / "index.html" - out_path.parent.mkdir(parents=True, exist_ok=True) - out_path.write_text(render_tags_page_html(tags_html), encoding="utf-8") - print(f"Tags page written to {out_path}") - - def deploy_to_server(build_dir, server): remote_path = f"{server}:/var/www/cleberg.net/" print(f"Deploying .build/ → {remote_path}") @@ -584,7 +397,6 @@ def main(): env = os.environ.get("ENV", "").casefold() if env != "prod": run_ruff() - html_snippet = get_recent_posts_html("./content/blog", num_posts=3) build_dir = Path(".build") theme_dir = Path("theme/static") @@ -599,14 +411,9 @@ def main(): if build: remove_build_directory(build_dir) minify_css(css_src, css_min) - run_emacs_publish(dev_mode=False) + run_orgo_build(dev_mode=False) copy_org_sources() - update_marked_section(html_snippet) - inject_blog_year_separators() - generate_tags_page() rewrite_img_urls(build_dir) - # minify_html("./.build/index.html", "./.build/index.html") - generate_sitemap() if deploy: print("Deploying to production...") deploy_to_server(build_dir, "homelab") @@ -616,13 +423,8 @@ def main(): if build: remove_build_directory(build_dir) minify_css(css_src, css_min) - run_emacs_publish(dev_mode=True) + run_orgo_build(dev_mode=True) copy_org_sources() - update_marked_section(html_snippet) - inject_blog_year_separators() - generate_tags_page() - minify_html("./.build/index.html", "./.build/index.html") - generate_sitemap() if deploy: start_dev_server(build_dir) -- cgit v1.2.3