#!/usr/bin/env python # The MIT License (MIT) # # Copyright (c) 2018 Sunaina Pai # # 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. """Make static website/blog with Python.""" import os import shutil import re import glob import sys import json import datetime import optparse try: import markdown has_markdown = True except ImportError as err: print(f'Could not import markdown: {err}. Markdown cannot be rendered!') has_markdown = False def fread(filename): """Read file and close the file.""" with open(filename, 'r') as f: return f.read() def fwrite(filename, text): """Write content to file and close the file.""" basedir = os.path.dirname(filename) if not os.path.isdir(basedir): os.makedirs(basedir) with open(filename, 'w') as f: f.write(text) def log(msg, *args): """Log message with specified arguments.""" sys.stderr.write(msg.format(*args) + '\n') def truncate(text, words=25): """Remove tags and truncate text to the specified number of words.""" return ' '.join(re.sub('(?s)<.*?>', ' ', text).split()[:words]) def read_headers(text): """Parse headers in text and yield (key, value, end-index) tuples.""" for match in re.finditer(r'\s*\s*|.+', text): if not match.group(1): break yield match.group(1), match.group(2), match.end() def rfc_2822_format(date_str): """Convert yyyy-mm-dd date string to RFC 2822 format date string.""" d = datetime.datetime.strptime(date_str, '%Y-%m-%d') return d.strftime('%a, %d %b %Y %H:%M:%S +0000') def read_content(filename): """Read content and metadata from file into a dictionary.""" # Read file content. text = fread(filename) # Read metadata and save it in a dictionary. date_slug = os.path.basename(filename).split('.')[0] match = re.search(r'^(?:(\d\d\d\d-\d\d-\d\d)-)?(.+)$', date_slug) content = { 'date': match.group(1) or '1970-01-01', 'slug': match.group(2), } # Read headers. end = 0 for key, val, end in read_headers(text): content[key] = val # Separate content from headers. text = text[end:] # Convert Markdown content to HTML. if filename.endswith(('.md', '.mkd', '.mkdn', '.mdown', '.markdown')): if _test == 'ImportError': raise ImportError('Error forced by test') if has_markdown: text = markdown.markdown(text, extensions=['codehilite', 'fenced_code']) else: log('WARNING: Cannot render Markdown in {}: python-markdown not available', filename) # Update the dictionary with content and RFC 2822 date. content.update({ 'content': text, 'rfc_2822_date': rfc_2822_format(content['date']) }) return content def render(template, **params): """Replace placeholders in template with values from params.""" def handle_variable(match): var = match.group(1).strip() if match.group(1).startswith("template "): import_name = var[7:] import_ = fread(import_name) return render(import_, **params) elif var.startswith("for_each "): args = var.split(" ")[1:] attribute_name = args[0] template_name = args[1] template_ = fread(template_name) templates = [] for val in params.get(attribute_name, []): templates.append(render(template_, **params, item=val)) return "\n".join(templates) elif var.startswith("item."): return str(params["item"].get(var[5:], match.group(0))) elif var.startswith("template_if_empty "): # {{ template_if_empty