#!/usr/bin/env python # MIT License (MIT) # # Copyright (C) 2018-2023 # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import os import sys import regex as re import urllib import datetime import dateutil.parser import shutil import functools import markdown2 import PyRSS2Gen # ================================== # Main config # ================================== PROJECT_DIR = "project" POSTS_DIR = "posts" PAGES_DIR = "pages" TEMPLATE_DIR = "templates" WWW_DIR = "www" COPY_DIRS = [] SITE_TITLE = "turblog.py blog" SITE_DESC = "Blog generated using turblog.py" SITE_AUTHOR = "turblog.py" MAX_POSTS_IN_INDEX = 3 SITE_DEV = True if SITE_DEV: SITE_URL = os.path.join(os.path.dirname(os.path.realpath(__file__)), WWW_DIR) BASE_URL = SITE_URL else: SITE_URL = "http://localhost" BASE_URL = "/" VERBOSE_OUT = True REPLACE_WWW = True MARKDOWN2_EXTRAS = [ "tables", "fenced-code-blocks", "cuddled-lists", "strike", "markdown-in-html" ] # ================================== # Helpers # ================================== # Print if verbose mode on def printv(text): if VERBOSE_OUT: print(text) # Concatenate all paths to root def pathcat(root, *paths): return functools.reduce(os.path.join, paths, root) # Replace tags with text def template(template, values): result = template for key, val in values.items(): result = re.sub(re.escape(r"<%" + key + "%>"), val, result) return result # ================================== # Implementation # ================================== # Load templates index_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "index.html")).read() index_page_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "index_page.html")).read() index_ending_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "index_ending.html")).read() post_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "post.html")).read() page_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "page.html")).read() archive_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "archive.html")).read() archive_entry_template = open(pathcat(PROJECT_DIR, TEMPLATE_DIR, "archive_entry.html")).read() # Create WWW dir if not os.path.exists(WWW_DIR): os.makedirs(WWW_DIR) else: if REPLACE_WWW: shutil.rmtree(WWW_DIR) os.makedirs(WWW_DIR) else: sys.exit(f"{WWW_DIR} exists. Aborting...") # Open html files to write index_html = open(pathcat(WWW_DIR, "index.html"), 'w') archive_html = open(pathcat(WWW_DIR, "archive.html"), 'w') # Create RSS feed rss = PyRSS2Gen.RSS2(title = SITE_TITLE, link = SITE_URL, description = SITE_DESC) # Default index.html template INDEX_DEFAULT_VALS= { "BASE_URL": BASE_URL, "CONTENT": "NO CONTENT PROVIDED", "PAGES": "", "SITE_TITLE": SITE_TITLE, "ENDING": "", "CURRENT_YEAR": str(datetime.datetime.now().year), "AUTHOR": SITE_AUTHOR, "DESCRIPTION": SITE_DESC, "TURBLOG_URL": "https://bitbucket.org/redpandaua/turblog-py" } LINK_PAGE_DEFAULT_VALS = { "BASE_URL": BASE_URL, "SITE_URL": SITE_URL, "URL": "404" } # Copy directories to www root for cpy in COPY_DIRS: shutil.copytree(pathcat(PROJECT_DIR, cpy), WWW_DIR + "/" + cpy) # Go through each post file and add it to the list. posts = [] for root, dirs, files in os.walk(pathcat(PROJECT_DIR, POSTS_DIR), topdown=False): for filename in files: filepath = pathcat(root, filename) file = open(filepath) title = file.readline().strip() date = dateutil.parser.parse(file.readline().strip()) www_path = pathcat("post", str(date.year), str(date.month), str(date.day), os.path.splitext(filename)[0]) post_entry = {"file": file, "filepath": filepath, "wwwpath": www_path, "title": title, "date": date} posts.append(post_entry) # Go through each page file and add it to the list. Collect and construct # template for usage in index pages = [] index_pages = "" for root, dirs, files in os.walk(pathcat(PROJECT_DIR, PAGES_DIR), topdown=False): for filename in files: filepath = pathcat(root, filename) file = open(filepath) title = file.readline().strip() page_type = file.readline().strip() base_url = "" www_path = "" url = "" if page_type == "link": www_path = template(file.read().strip(), LINK_PAGE_DEFAULT_VALS) url = www_path else: base_url = BASE_URL + "/" www_path = pathcat("page", os.path.splitext(filename)[0]) url = urllib.parse.quote(www_path) page = {"file": file, "filepath": filepath, "wwwpath": www_path, "title": title, "type": page_type} pages.append(page) index_pages += template(index_page_template, {"BASE_URL": base_url, "URL": url, "TITLE":title}) # Write the pages down to html files sorted_posts = sorted(posts, key = lambda k: k['title']) for page in pages: if page["type"] == "link": continue # Parse markdown if they require so page_content = page["file"].read() if page["type"] == "markdown": page_content = markdown2.markdown(page_content, extras=MARKDOWN2_EXTRAS) page_content = template(page_template, {"CONTENT":page_content, "BASE_URL": BASE_URL}) # Create the date directories www_filepath = pathcat(WWW_DIR, page["wwwpath"]) + ".html" www_dirpath = os.path.dirname(www_filepath) if not os.path.exists(www_dirpath): os.makedirs(www_dirpath) with open(www_filepath, 'w') as post_html: printv("Writing page: " + page["title"] + " - " + page["wwwpath"]) vals = INDEX_DEFAULT_VALS vals["CONTENT"] = page_content vals["SITE_TITLE"] = SITE_TITLE + " - " + page["title"] vals["ENDING"] = "" vals["PAGES"] = index_pages full_post = template(index_template, vals) post_html.write(full_post) # Sort the posts, and write them to html files index_post_count = 0 index_content = "" archive_content = "" sorted_posts = sorted(posts, key = lambda k: k['date'], reverse=True) for post in sorted_posts: content = template(post["file"].read(), {"BASE_URL":BASE_URL}) content = markdown2.markdown(content, extras=MARKDOWN2_EXTRAS) # Create the date directories www_filepath = pathcat(WWW_DIR, post["wwwpath"]) + ".html" www_dirpath = os.path.dirname(www_filepath) if not os.path.exists(www_dirpath): os.makedirs(www_dirpath) # Sanitize the URL url = urllib.parse.quote(post["wwwpath"]) vals = { "TITLE": post["title"], "DATE": post["date"].strftime("%a %d of %B, %Y"), "BASE_URL": BASE_URL, "URL": url, "POST": content } post_content = template(post_template, vals) archive_content += template(archive_entry_template, vals) # Add post to index if it can fit it if index_post_count < MAX_POSTS_IN_INDEX: index_content += template(post_template, vals) index_post_count = index_post_count + 1 # Add RSS item rss.items.append(PyRSS2Gen.RSSItem( title = post["title"], link = url, description = post["title"], guid = PyRSS2Gen.Guid(url), pubDate = post["date"], )) # Write post to a file with open(www_filepath, 'w') as post_html: printv("Writing post: " + post["title"] + " - " + post["wwwpath"]) vals = INDEX_DEFAULT_VALS vals["CONTENT"] = post_content vals["SITE_TITLE"] = SITE_TITLE + " - " + post["title"] vals["ENDING"] = "" vals["PAGES"] = index_pages full_post = template(index_template, vals) post_html.write(full_post) # Write index to a file printv("Writing index") vals = INDEX_DEFAULT_VALS vals["SITE_TITLE"] = SITE_TITLE vals["CONTENT"] = index_content vals["ENDING"] = template(index_ending_template, {"BASE_URL": BASE_URL}) vals["PAGES"] = index_pages index_html.write(template(index_template, vals)) # Write archive to a file printv("Writing archive") vals = INDEX_DEFAULT_VALS vals["CONTENT"] = archive_content vals["SITE_TITLE"] = SITE_TITLE + " - Archive" vals["ENDING"] = "" archive_inner_html = template(archive_template, vals) vals["CONTENT"] = archive_inner_html archive_html.write(template(index_template, vals)) # Write RSS feed to a file printv("Writing RSS feed") rss.write_xml(open(pathcat(WWW_DIR, "feed.xml"), "w"))