Assorted shell and Python scripts
at main 4.6 kB view raw
1#!/usr/bin/env -S uv run --script 2# /// script 3# dependencies = [ 4# "feedgen", 5# "feedparser", 6# "md2gemini", 7# ] 8# /// 9 10# 1. Take a markdown blog post as input, convert it to gemtext. 11# 2. Update gemlog index. 12# 3. Update the gemlog Atom feed. 13 14import sys 15from datetime import datetime 16from pathlib import Path 17from zoneinfo import ZoneInfo 18 19import feedparser 20from feedgen.feed import FeedGenerator 21from md2gemini import md2gemini 22 23# This is so that Python doesn't yell at me if I forget an argument. Not 24# likely to happen, but still. 25if len(sys.argv) != 3: 26 print('Usage: blog2gemlog /path/to/blog/post.md "Blog Post Title"') 27 exit(1) 28 29# Set the absolute path to the gemini content directory 30gemini_dir = Path.home().joinpath( 31 "repos/tildegit.org/hyperreal/hyperreal.coffee/gemini" 32) 33 34# Get the current date in YYYY-MM-DD format 35date_now = datetime.now().strftime("%Y-%m-%d") 36 37# Read blog post path from sys.argv[1] and ensure it is an absolute path 38blog_post_path = Path(sys.argv[1]) 39if not blog_post_path.is_absolute(): 40 print("Supply absolute path to blog post.") 41 exit(1) 42 43# Convert the markdown blog post to gemtext 44with open(blog_post_path, "r") as md_f: 45 content = md2gemini(md_f.read(), frontmatter=True, links="paragraph", md_links=True) 46 47# Set the absolute path to the gemlog post 48gemlog_post_path = gemini_dir.joinpath(f"gemlog/{blog_post_path.stem}.gmi") 49 50# Write the gemtext content to the gemlog post path 51with open(gemlog_post_path, "w") as gmi_f: 52 gmi_f.write(content) 53 54# Set the string for the END section of the gemlog post 55gemlog_end = f"\n\n## END\nLast updated: {date_now}\n\n=> ../gemlog Gemlog archive\n=> ../ hyperreal.coffee" 56 57# Append gemlog_end to the end of the gemlog post 58with open(gemlog_post_path, "a") as gmi_f: 59 gmi_f.write(gemlog_end) 60 61# Read the gemlog post file lines into a list 62with open(gemlog_post_path, "r") as gmi_f: 63 contents = gmi_f.readlines() 64 65# Get the gemlog post title from sys.argv[2] 66gemlog_post_title = str(sys.argv[2]) 67 68# Insert the gemlog post title as the level 1 heading on line 1 69contents.insert(0, f"# {gemlog_post_title}\n\n") 70 71# Write the new contents as a string to the gemlog file 72with open(gemlog_post_path, "w") as gmi_f: 73 contents = "".join(contents) 74 gmi_f.write(contents) 75 76# Read the lines of the gemlog index into a list 77with open(gemini_dir.joinpath("gemlog/index.gmi"), "r") as index_f: 78 contents = index_f.readlines() 79 80# Set the content of the gemlog index entry line 81gemlog_index_line = f"=> ./{gemlog_post_path.name} {date_now} {gemlog_post_title}\n" 82 83# Insert the new gemlog index line into the list on line 6 84contents.insert(5, gemlog_index_line) 85 86# Write the new contents as a string to the gemlog index file 87with open(gemini_dir.joinpath("gemlog/index.gmi"), "w") as index_f: 88 contents = "".join(contents) 89 index_f.write(contents) 90 91# Get a timezone-aware datetime object from a timestamp of the present moment 92aware_ts = datetime.fromtimestamp( 93 datetime.timestamp(datetime.now()), tz=ZoneInfo("America/Chicago") 94) 95 96# Format the timezone-aware datetime object for the <updated> element of the 97# Atom feed 98updated_ts = aware_ts.strftime("%Y-%m-%dT%H:%M:%S%z") 99 100# Instantiate a FeedParserDict object 101d = feedparser.parse(gemini_dir.joinpath("gemlog/atom.xml")) 102 103# Update the <updated> element's value to the current timestamp 104d["updated"] = updated_ts 105 106# Define a dictionary for the new Atom feed entry 107new_entry_dict = { 108 "id": f"gemini://hyperreal.coffee/gemlog/{gemlog_post_path.name}", 109 "title": gemlog_post_title, 110 "updated": updated_ts, 111 "links": [ 112 { 113 "href": f"gemini://hyperreal.coffee/gemlog/{gemlog_post_path.name}", 114 "rel": "alternate", 115 "type": "text/gemini", 116 } 117 ], 118} 119 120# Insert the new Atom feed entry into the FeedParserDict 121d["entries"].insert(0, new_entry_dict) 122 123# Instantiate a FeedGenerator object and set the methods for the feed 124fg = FeedGenerator() 125fg.id(d["feed"]["id"]) 126fg.title(d["feed"]["title"]) 127fg.updated(d["feed"]["updated"]) 128fg.link(d["feed"]["links"]) 129 130# Reverse the order of d["entries"] so that they are written to the file in 131# the correct order 132d["entries"].reverse() 133 134# For each entry, add a new entry to the FeedGenerator object 135for entry in d["entries"]: 136 fe = fg.add_entry() 137 fe.id(entry["id"]) 138 fe.title(entry["title"]) 139 fe.updated(entry["updated"]) 140 fe.link(entry["links"]) 141 142# Finally, render the FeedGenerator object as an Atom feed and write it to 143# the atom.xml file 144fg.atom_file(gemini_dir.joinpath("gemlog/atom.xml"), pretty=True) 145 146# vim: ai et ft=python sts=4 sw=4 ts=4