/
generator.py
executable file
·165 lines (138 loc) · 5.27 KB
/
generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os
import sys
import collections
import boto
from boto.s3.key import Key
from flask import Flask, render_template, url_for, abort, request
from flask.ext.frozen import Freezer
from werkzeug import cached_property
from werkzeug.contrib.atom import AtomFeed
import markdown
import yaml
from datetime import datetime
FREEZER_BASE_URL = 'http://mp3user.github.com'
FREEZER_DESTINATION_IGNORE = ['.git*', 'CNAME']
DOMAIN = 'mp3user.github.com'
POSTS_FILE_EXTENSION = '.md'
class SortedDict(collections.MutableMapping):
def __init__(self, items=None, key=None, reverse=False):
self._items = {}
self._keys = []
if key:
self._key_fn = lambda k: key(self._items[k])
else:
self._key_fn = lambda k: self._items[k]
self._reverse = reverse
if items is not None:
self.update(items)
def __getitem__(self, key):
return self._items[key]
def __setitem__(self, key, value):
self._items[key] = value
if key not in self._keys:
self._keys.append(key)
self._keys.sort(key=self._key_fn, reverse=self._reverse)
def __delitem__(self, key):
self._items.pop(key)
self._keys.remove(key)
def __len__(self):
return len(self._keys)
def __iter__(self):
for key in self._keys:
yield key
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._items)
class Blog(object):
def __init__(self, app, root_dir='', file_ext=None):
self.root_dir = root_dir
self.file_ext = file_ext if file_ext is not None else app.config['POSTS_FILE_EXTENSION']
self._app = app
self._cache = SortedDict(key=lambda p: p.date, reverse=True)
self._initialize_cache()
@property
def posts(self):
if self._app.debug:
return self._cache.values()
else:
return [post for post in self._cache.values() if post.published]
def get_post_or_404(self, path):
"""Returns the Post object for the given path or raises a NotFound exception
"""
try:
return self._cache[path]
except KeyError:
abort(404)
def _initialize_cache(self):
"""Walks the root directory and adds all posts to the cache
"""
for (root, dirpaths, filepaths) in os.walk(self.root_dir):
for filepath in filepaths:
filename, ext = os.path.splitext(filepath)
if ext == self.file_ext:
path = os.path.join(root, filepath).replace(self.root_dir, '')
post = Post(path, root_dir=self.root_dir)
self._cache[post.urlpath] = post
class Post(object):
def __init__(self, path, root_dir=''):
self.urlpath = os.path.splitext(path.strip('/'))[0]
self.filepath = os.path.join(root_dir, path.strip('/'))
self.published = False
self._initialize_metadata()
@cached_property
def html(self):
with open(self.filepath, 'r') as fin:
content = fin.read().split('\n\n', 1)[1].strip().decode('utf-8')
return markdown.markdown(content, extensions=['codehilite'])
def url(self, _external=False):
return url_for('post', path=self.urlpath, _external=_external)
def _initialize_metadata(self):
content = ''
with open(self.filepath, 'r') as fin:
for line in fin:
if not line.strip():
break
content += line
self.__dict__.update(yaml.load(content))
app = Flask(__name__)
app.config.from_object(__name__)
blog = Blog(app, root_dir='posts')
freezer = Freezer(app)
@app.template_filter('date')
def format_date(value, format='%B %d, %Y'):
return value.strftime(format)
@app.route('/')
def index():
return render_template('index.html', posts=blog.posts)
@app.route('/blog/<path:path>/')
def post(path):
post = blog.get_post_or_404(path)
return render_template('post.html', post=post)
@app.route('/feed.atom')
def feed():
feed = AtomFeed('KoMo',
feed_url=request.url,
url=request.url_root)
posts = blog.posts[:10]
title = lambda p: '%s: %s' % (p.title, p.subtitle) if hasattr(p, 'subtitle') else p.title
for post in posts:
feed.add(title(post),
unicode(post.html),
content_type='html',
author='Konstantin Monakhov',
url=post.url(_external=True),
updated=datetime.combine(post.date, datetime.min.time()),
published=datetime.combine(post.date, datetime.min.time()))
return feed.get_response()
@app.route('/tag/<string:tag>/')
def tag(tag):
tagged = [p for p in posts if tag in p.meta.get('tags', [])]
return render_template('tag.html', posts=tagged, tag=tag)
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'build':
freezer.freeze()
elif len(sys.argv) > 1 and sys.argv[1] == 'deploy':
freezer.freeze()
deploy('build')
else:
post_files = [post.filepath for post in blog.posts]
app.run(port=8000, debug=True, extra_files=post_files)