Assorted shell and Python scripts

+blog2gemlog

Changed files
+146
+146
blog2gemlog
···
···
+
#!/usr/bin/env -S uv run --script
+
# /// script
+
# dependencies = [
+
# "feedgen",
+
# "feedparser",
+
# "md2gemini",
+
# ]
+
# ///
+
+
# 1. Take a markdown blog post as input, convert it to gemtext.
+
# 2. Update gemlog index.
+
# 3. Update the gemlog Atom feed.
+
+
import sys
+
from datetime import datetime
+
from pathlib import Path
+
from zoneinfo import ZoneInfo
+
+
import feedparser
+
from feedgen.feed import FeedGenerator
+
from md2gemini import md2gemini
+
+
# This is so that Python doesn't yell at me if I forget an argument. Not
+
# likely to happen, but still.
+
if len(sys.argv) != 3:
+
print('Usage: blog2gemlog /path/to/blog/post.md "Blog Post Title"')
+
exit(1)
+
+
# Set the absolute path to the gemini content directory
+
gemini_dir = Path.home().joinpath(
+
"repos/tildegit.org/hyperreal/hyperreal.coffee/gemini"
+
)
+
+
# Get the current date in YYYY-MM-DD format
+
date_now = datetime.now().strftime("%Y-%m-%d")
+
+
# Read blog post path from sys.argv[1] and ensure it is an absolute path
+
blog_post_path = Path(sys.argv[1])
+
if not blog_post_path.is_absolute():
+
print("Supply absolute path to blog post.")
+
exit(1)
+
+
# Convert the markdown blog post to gemtext
+
with open(blog_post_path, "r") as md_f:
+
content = md2gemini(md_f.read(), frontmatter=True, links="paragraph", md_links=True)
+
+
# Set the absolute path to the gemlog post
+
gemlog_post_path = gemini_dir.joinpath(f"gemlog/{blog_post_path.stem}.gmi")
+
+
# Write the gemtext content to the gemlog post path
+
with open(gemlog_post_path, "w") as gmi_f:
+
gmi_f.write(content)
+
+
# Set the string for the END section of the gemlog post
+
gemlog_end = f"\n\n## END\nLast updated: {date_now}\n\n=> ../gemlog Gemlog archive\n=> ../ hyperreal.coffee"
+
+
# Append gemlog_end to the end of the gemlog post
+
with open(gemlog_post_path, "a") as gmi_f:
+
gmi_f.write(gemlog_end)
+
+
# Read the gemlog post file lines into a list
+
with open(gemlog_post_path, "r") as gmi_f:
+
contents = gmi_f.readlines()
+
+
# Get the gemlog post title from sys.argv[2]
+
gemlog_post_title = str(sys.argv[2])
+
+
# Insert the gemlog post title as the level 1 heading on line 1
+
contents.insert(0, f"# {gemlog_post_title}\n\n")
+
+
# Write the new contents as a string to the gemlog file
+
with open(gemlog_post_path, "w") as gmi_f:
+
contents = "".join(contents)
+
gmi_f.write(contents)
+
+
# Read the lines of the gemlog index into a list
+
with open(gemini_dir.joinpath("gemlog/index.gmi"), "r") as index_f:
+
contents = index_f.readlines()
+
+
# Set the content of the gemlog index entry line
+
gemlog_index_line = f"=> ./{gemlog_post_path.name} {date_now} {gemlog_post_title}\n"
+
+
# Insert the new gemlog index line into the list on line 6
+
contents.insert(5, gemlog_index_line)
+
+
# Write the new contents as a string to the gemlog index file
+
with open(gemini_dir.joinpath("gemlog/index.gmi"), "w") as index_f:
+
contents = "".join(contents)
+
index_f.write(contents)
+
+
# Get a timezone-aware datetime object from a timestamp of the present moment
+
aware_ts = datetime.fromtimestamp(
+
datetime.timestamp(datetime.now()), tz=ZoneInfo("America/Chicago")
+
)
+
+
# Format the timezone-aware datetime object for the <updated> element of the
+
# Atom feed
+
updated_ts = aware_ts.strftime("%Y-%m-%dT%H:%M:%S%z")
+
+
# Instantiate a FeedParserDict object
+
d = feedparser.parse(gemini_dir.joinpath("gemlog/atom.xml"))
+
+
# Update the <updated> element's value to the current timestamp
+
d["updated"] = updated_ts
+
+
# Define a dictionary for the new Atom feed entry
+
new_entry_dict = {
+
"id": f"gemini://hyperreal.coffee/gemlog/{gemlog_post_path.name}",
+
"title": gemlog_post_title,
+
"updated": updated_ts,
+
"links": [
+
{
+
"href": f"gemini://hyperreal.coffee/gemlog/{gemlog_post_path.name}",
+
"rel": "alternate",
+
"type": "text/gemini",
+
}
+
],
+
}
+
+
# Insert the new Atom feed entry into the FeedParserDict
+
d["entries"].insert(0, new_entry_dict)
+
+
# Instantiate a FeedGenerator object and set the methods for the feed
+
fg = FeedGenerator()
+
fg.id(d["feed"]["id"])
+
fg.title(d["feed"]["title"])
+
fg.updated(d["feed"]["updated"])
+
fg.link(d["feed"]["links"])
+
+
# Reverse the order of d["entries"] so that they are written to the file in
+
# the correct order
+
d["entries"].reverse()
+
+
# For each entry, add a new entry to the FeedGenerator object
+
for entry in d["entries"]:
+
fe = fg.add_entry()
+
fe.id(entry["id"])
+
fe.title(entry["title"])
+
fe.updated(entry["updated"])
+
fe.link(entry["links"])
+
+
# Finally, render the FeedGenerator object as an Atom feed and write it to
+
# the atom.xml file
+
fg.atom_file(gemini_dir.joinpath("gemlog/atom.xml"), pretty=True)
+
+
# vim: ai et ft=python sts=4 sw=4 ts=4