Skip to content

Instantly share code, notes, and snippets.

@iamkeyur
Created June 23, 2016 04:48
Show Gist options
  • Save iamkeyur/f3b90222d1fb26350814fae8b0bcec89 to your computer and use it in GitHub Desktop.
Save iamkeyur/f3b90222d1fb26350814fae8b0bcec89 to your computer and use it in GitHub Desktop.
Static Site Generator
import jinja2
import sys
import markdown
import os
import datetime
import shutil
import argparse
import collections
from copy import copy
try:
import config_local as config
except ImportError:
import config
POST_SKELETON = """title: {title}
date: {date}
categories:
"""
def generate_post_file_name(title):
return ''.join(char for char in title.lower() if (
char.isalnum() or char == ' ')
).replace(' ', '-')
def generate_post_file_path(title, date):
return os.path.join(
datetime.datetime.strftime(date, '%Y/%m/%d/'),
generate_post_file_name(title))
def get_all_posts(content_dir, blog_prefix, canonical_url, blog_root=None):
input_files = os.listdir(content_dir)
all_posts = list()
for post_file_name in input_files:
if os.path.splitext(post_file_name)[1] != ".md":
continue
post = dict()
post_output_path = os.path.join(content_dir, post_file_name)
with open(post_output_path, encoding='ascii') as post_file:
post_file_buffer = post_file.read()
# Generate HTML from Markdown, splitting between the teaser (the
# content to display on the front page until <!--more--> is reached)
# and the post proper
mardown_generator = markdown.Markdown(extensions=[
'fenced_code',
'codehilite',
'tables',
'footnotes',
'meta',
])
generated_html = mardown_generator.convert(post_file_buffer)
post['body'] = generated_html
post['title'] = mardown_generator.Meta['title'][0]
(post['teaser'], _, _) = generated_html.partition('<!--more-->')
post['categories'] = mardown_generator.Meta['categories'][0].split()
post['date'] = datetime.datetime.strptime(
(mardown_generator.Meta['date'][0].strip()), '%Y-%m-%d %H:%M')
post['relative_path'] = generate_post_file_path(post['title'],
post['date'])
if blog_prefix:
post['relative_path'] = os.path.join(
blog_prefix, post['relative_path'])
if blog_root:
post['relative_url'] = os.path.join('/', blog_root,
post['relative_path'])
else:
post['relative_url'] = os.path.join('/', post['relative_path'])
post['canonical_url'] = canonical_url + post['relative_url']
all_posts.append(post)
return all_posts
def create_path_to_file(path):
if not os.path.splitext(path)[1]:
path += '/'
else:
path = os.path.dirname(path)
if not os.path.exists(path):
os.makedirs(path)
def generate_post(post, template_variables, template_environment):
output_path = os.path.join(template_variables['output_dir'],
post['relative_path'], 'index.html')
if not post['body']:
raise EnvironmentError('No content for post [{post}] found.'.format(
post=post['relative_path']))
post_vars = {'post': post}
template_variables.update(post_vars)
template = template_environment.get_template('post_index.html')
create_path_to_file(output_path)
with open(output_path, 'w') as output:
output.write(template.render(template_variables))
def generate_static_page(template_variables, output_dir, template,
filename='index.html'):
create_path_to_file(output_dir)
with open(
os.path.join(
output_dir, filename), 'w', encoding='ascii') as output_file:
output_file.write(template.render(template_variables))
def generate_static_files(site_config, posts, categories, template_environment):
list_template = template_environment.get_template('list.html')
archives_template = template_environment.get_template('archives.html')
atom_template = template_environment.get_template('atom.xml')
about_template = template_environment.get_template('about.html')
if 'additional_pages' in site_config:
for entry_name in site_config['additional_pages']:
entry = site_config['additional_pages'][entry_name]
template_path = entry['template']
try:
path = os.path.join(site_config['output_dir'], entry['path'])
except KeyError:
path = os.path.join(site_config['output_dir'], entry_name)
template = template_environment.get_template(template_path)
generate_static_page(site_config, path, template)
canonical_url_base = site_config['url']
canonical_blog_base = '{url}/{blog_prefix}/'.format(
url=canonical_url_base,
blog_prefix=site_config['blog_prefix'])
template_variables = copy(site_config)
template_variables['next_page'] = 1
template_variables['canonical_url'] = template_variables['url']
template_variables['current_posts'] = posts[:5]
generate_static_page(template_variables,
site_config['output_dir'], list_template)
template_variables['canonical_url'] = canonical_blog_base
generate_static_page(template_variables,
site_config['blog_dir'], list_template)
template_variables['canonical_url'] = canonical_url_base + '/about-me/'
generate_static_page(template_variables,
os.path.join(site_config['output_dir'], 'about-me'),
about_template)
template_variables['all_posts'] = posts
template_variables['canonical_url'] = canonical_blog_base + 'archives/'
generate_static_page(template_variables,
os.path.join(site_config['blog_dir'],
'archives'), archives_template)
template_variables['now'] = datetime.datetime.now().isoformat()
generate_static_page(template_variables, site_config['output_dir'],
atom_template, 'atom.xml')
for category, posts in categories.items():
template_variables['all_posts'] = posts
generate_static_page(template_variables, os.path.join(
site_config['blog_dir'],
'categories', category), archives_template)
generate_static_page(template_variables, os.path.join(
site_config['blog_dir'],
'categories', category), atom_template, 'atom.xml')
def generate_pagination_pages(site_config, all_posts, template):
template_variables = copy(site_config)
num_posts = len(all_posts)
for index, page in enumerate(
[all_posts[index:index + 5] for index in range(5, num_posts, 5)]):
current_page = index + 1
template_variables['current_posts'] = page
template_variables['next_page'] = current_page + 1
if (current_page * 5) >= num_posts - 5:
template_variables['next_page'] = None
output_dir = os.path.join(site_config['blog_dir'],
'page', str(current_page))
generate_static_page(template_variables, output_dir, template)
def generate_all_files(site_config):
all_posts = get_all_posts(site_config['content_dir'],
site_config['blog_prefix'],
site_config['url'],
site_config['blog_root'])
all_posts.sort(key=lambda i: i['date'], reverse=True)
categories = collections.defaultdict(list)
for post in all_posts:
for category in post['categories']:
categories[category].append(post)
template_environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
site_config['template_dir']))
generate_static_files(
site_config,
all_posts,
categories,
template_environment)
generate_pagination_pages(
site_config,
all_posts,
template_environment.get_template('list.html'))
for index, post in enumerate(all_posts):
try:
post['post_previous'] = all_posts[index + 1]
except IndexError:
post['post_previous'] = all_posts[0]
generate_post(post, site_config, template_environment)
def copy_static_content(output_dir, root_dir):
if os.path.exists(output_dir):
print ('Removing old content...')
shutil.rmtree(output_dir)
shutil.copytree(os.path.join(root_dir, 'static'), output_dir)
def create_post(title, content_dir):
post_date_time = datetime.datetime.strftime(
datetime.datetime.now(), '%Y-%m-%d %H:%M')
post_date = datetime.datetime.strftime(
datetime.datetime.now(), '%Y-%m-%d')
post_file_name = '{}-{}.md'.format(
post_date,
generate_post_file_name(title))
post_file_path = os.path.join(content_dir, post_file_name)
if os.path.exists(post_file_path):
raise EnvironmentError('[{post}] already exists.'.format(
post=post_file_path))
with open(post_file_path, 'w') as post_file:
post_file.write(POST_SKELETON.format(date=post_date_time, title=title))
def serve(**kwargs):
root = kwargs['root']
os.chdir(root)
if kwargs['simple']:
import http.server
handler = http.server.SimpleHTTPRequestHandler
handler.protocol_version = "HTTP/1.0"
httpd = http.server.HTTPServer((kwargs['host'],
int(kwargs['port'])), handler)
else:
handler = blug_server.FileCacheRequestHandler
httpd = blug_server.BlugHttpServer(root, (kwargs['host'],
int(kwargs['port'])), handler)
print("serving from {path} on port {port}".format(path=root,
port=kwargs['port']))
httpd.serve_forever()
def create_new_post(**kwargs):
site_config = config.CONFIG
create_post(kwargs['title'], site_config['content_dir'])
def generate_site():
site_config = config.CONFIG
site_config['blog_dir'] = os.path.join(
site_config['output_dir'],
site_config['blog_prefix'])
print ('Generating...')
copy_static_content(site_config['output_dir'], os.getcwd())
generate_all_files(site_config)
return True
def main():
argument_parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Generate a static HTML blog from Markdown blog entries')
subparser = argument_parser.add_subparsers(help='help for sub-commands')
post_parser = subparser.add_parser(
'post', help='Create a blank blog post',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
post_parser.add_argument(
'title', help='Title for the blog post to be generated')
post_parser.set_defaults(func=create_new_post)
generate_parser = subparser.add_parser(
'generate',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
help='Generate the complete static site using the posts\
in the \'content\' directory')
generate_parser.set_defaults(func=generate_site)
serve_parser = subparser.add_parser(
'serve',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
help='Start an HTTP server that serves the files under the \
<content-dir> directory')
serve_parser.add_argument('-p', '--port', default=8080,
help='Port for HTTP server to listen to')
serve_parser.add_argument('-s', '--host',
action='store',
default='localhost',
help='Hostname for HTTP server to serve on')
serve_parser.add_argument('-r', '--root',
action='store',
default=os.path.join(os.getcwd(), 'generated'),
help='Root path to serve files from')
serve_parser.add_argument('--simple', action='store_true',
help='Use SimpleHTTPServer instead of Blug\'s web server')
serve_parser.set_defaults(func=serve)
parsed_arguments = argument_parser.parse_args()
arguments = vars(parsed_arguments)
function = arguments.pop('func')
if function == generate_site:
function()
else:
function(**arguments)
print ('Complete')
if __name__ == '__main__':
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment