diff options
author | Mike Crute <mcrute@gmail.com> | 2010-06-06 21:46:53 -0400 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2010-06-06 21:46:53 -0400 |
commit | 5ca018a916a23bdfadbc2f063e7e82de885f04ed (patch) | |
tree | 82340b4f9f09d27628c027aa9547cea40f48dcd3 | |
download | ventriloquy-5ca018a916a23bdfadbc2f063e7e82de885f04ed.tar.bz2 ventriloquy-5ca018a916a23bdfadbc2f063e7e82de885f04ed.tar.xz ventriloquy-5ca018a916a23bdfadbc2f063e7e82de885f04ed.zip |
Initial import. Really rough code.
-rw-r--r-- | .hgignore | 2 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rwxr-xr-x | metabuild.py | 23 | ||||
-rw-r--r-- | site_builder/__init__.py | 63 | ||||
-rw-r--r-- | site_builder/blog.py | 182 | ||||
-rw-r--r-- | site_builder/blogbuilder.py | 88 | ||||
-rw-r--r-- | site_builder/feeds.py | 111 | ||||
-rw-r--r-- | site_builder/pagebuilder.py | 2 | ||||
-rw-r--r-- | site_builder/robots.py | 15 | ||||
-rw-r--r-- | site_builder/sitemap.py | 21 | ||||
-rw-r--r-- | templates/blog_archive.html | 14 | ||||
-rw-r--r-- | templates/blog_index.html | 18 | ||||
-rw-r--r-- | templates/blog_post.html | 26 | ||||
-rw-r--r-- | templates/blog_tags.html | 22 | ||||
-rw-r--r-- | templates/page.html | 57 |
15 files changed, 655 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..a26d142 --- /dev/null +++ b/.hgignore | |||
@@ -0,0 +1,2 @@ | |||
1 | syntax:glob | ||
2 | *.pyc | ||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cb60495 --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,11 @@ | |||
1 | WEB_ROOT=/srv/www/crute.org/mike/htdocs | ||
2 | |||
3 | site: | ||
4 | ./metabuild.py | ||
5 | rsync -auvz rendered/ $(WEB_ROOT) | ||
6 | chgrp -R www-data $(WEB_ROOT) | ||
7 | chmod -R g+r $(WEB_ROOT) | ||
8 | find $(WEB_ROOT) -type d -exec chmod g+x {} \; | ||
9 | |||
10 | css: | ||
11 | python page_source/downloads/cmpcss page_source/resources/site.css > page_source/resources/site-min.css | ||
diff --git a/metabuild.py b/metabuild.py new file mode 100755 index 0000000..6508cbb --- /dev/null +++ b/metabuild.py | |||
@@ -0,0 +1,23 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # vim: set filencoding=utf8 | ||
3 | """ | ||
4 | Website Build Script | ||
5 | |||
6 | @author: Mike Crute (mcrute@ag.com) | ||
7 | @organization: American Greetings Interactive | ||
8 | @date: June 03, 2010 | ||
9 | |||
10 | TODO: | ||
11 | * Site Map Builder | ||
12 | * Full Blog Builder | ||
13 | * Index page | ||
14 | * Archive page | ||
15 | * Feeds | ||
16 | """ | ||
17 | |||
18 | if __name__ == '__main__': | ||
19 | from site_builder import build_all | ||
20 | from site_builder.blogbuilder import build_blog | ||
21 | build_all('page_source', 'rendered') | ||
22 | build_blog('page_source/blog', 'rendered/blog') | ||
23 | build_blog('page_source/personal_blog', 'rendered/personal_blog') | ||
diff --git a/site_builder/__init__.py b/site_builder/__init__.py new file mode 100644 index 0000000..f60820a --- /dev/null +++ b/site_builder/__init__.py | |||
@@ -0,0 +1,63 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Site Builder | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 04, 2010 | ||
8 | """ | ||
9 | |||
10 | import os | ||
11 | import jinja2 | ||
12 | import pagebuilder | ||
13 | from datetime import datetime | ||
14 | from docutils.core import publish_parts | ||
15 | from docutils.io import FileInput | ||
16 | |||
17 | |||
18 | def get_template(name): | ||
19 | loader = jinja2.FileSystemLoader('templates') | ||
20 | renderer = jinja2.Environment(loader=loader) | ||
21 | return renderer.get_template(name) | ||
22 | |||
23 | |||
24 | def build_standard_page(filename, output_name): | ||
25 | parts = publish_parts(open(filename, 'r').read(), writer_name='html') | ||
26 | template = get_template('page.html') | ||
27 | |||
28 | try: | ||
29 | os.makedirs(os.path.dirname(output_name)) | ||
30 | except OSError: | ||
31 | pass # directory exists | ||
32 | |||
33 | open(output_name, 'w').write(template.render( | ||
34 | contents=parts['html_body'], | ||
35 | build_date=datetime.now().strftime('%B %d, %Y'), | ||
36 | source_link=filename)) | ||
37 | |||
38 | |||
39 | def get_output_name(base_dir, output_dir, filename): | ||
40 | base_depth = len(base_dir.split(os.path.sep)) | ||
41 | out_name = filename.split(os.path.sep)[base_depth:] | ||
42 | new_path = os.path.join(output_dir, *out_name) | ||
43 | |||
44 | if new_path.endswith('.rst'): | ||
45 | new_path = new_path[:-len('.rst')] + '.html' | ||
46 | |||
47 | return new_path | ||
48 | |||
49 | |||
50 | def build_all(base_dir, output_dir): | ||
51 | for root, dirs, files in os.walk(base_dir): | ||
52 | for filename in files: | ||
53 | if ('personal_blog' in root or | ||
54 | 'blog' in root or | ||
55 | not filename.endswith('.rst')): | ||
56 | continue | ||
57 | |||
58 | old_path = os.path.join(root, filename) | ||
59 | new_path = get_output_name(base_dir, output_dir, old_path) | ||
60 | |||
61 | print "BUILDING: ", old_path | ||
62 | |||
63 | build_standard_page(old_path, new_path) | ||
diff --git a/site_builder/blog.py b/site_builder/blog.py new file mode 100644 index 0000000..afa1fc8 --- /dev/null +++ b/site_builder/blog.py | |||
@@ -0,0 +1,182 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Blog Post Builder | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 03, 2010 | ||
8 | """ | ||
9 | |||
10 | import os | ||
11 | from functools import wraps | ||
12 | from datetime import datetime | ||
13 | |||
14 | # Docutils imports, crazy yo | ||
15 | from docutils import nodes | ||
16 | from docutils.core import Publisher, publish_string | ||
17 | from docutils.transforms import Transform | ||
18 | from docutils.io import NullOutput, FileInput | ||
19 | from docutils.parsers.rst import Parser as RSTParser | ||
20 | from docutils.writers.html4css1 import Writer as HTMLWriter | ||
21 | from docutils.readers.standalone import Reader as StandaloneReader | ||
22 | |||
23 | |||
24 | class BlogMetaTransform(Transform): | ||
25 | """ | ||
26 | Removes metadata tags from the document tree. | ||
27 | |||
28 | This transformer removes the metadata nodes from the document tree | ||
29 | and places them in a blog_meta dictionary on the document object. | ||
30 | This happens before rendering so the meta won't show up in the output. | ||
31 | """ | ||
32 | |||
33 | default_priority = 360 # Fuck if I know, same as the PEP header transform | ||
34 | |||
35 | def __init__(self, *args, **kwargs): | ||
36 | Transform.__init__(self, *args, **kwargs) | ||
37 | |||
38 | self.meta = self.document.blog_meta = { | ||
39 | 'tags': [], | ||
40 | } | ||
41 | |||
42 | def apply(self): | ||
43 | docinfo = None | ||
44 | |||
45 | # One to get the docinfo and title | ||
46 | # We need a copy of the document as a list so we can modify it | ||
47 | # without messing up iteration. | ||
48 | for node in list(self.document): | ||
49 | if isinstance(node, nodes.docinfo): | ||
50 | docinfo = node | ||
51 | self.document.remove(node) | ||
52 | |||
53 | if isinstance(node, nodes.title): | ||
54 | self.meta['title'] = unicode(node[0]) | ||
55 | self.document.remove(node) | ||
56 | |||
57 | # And one to process the docinfo | ||
58 | for node in docinfo: | ||
59 | if isinstance(node, nodes.author): | ||
60 | self._handle_author(node) | ||
61 | |||
62 | if isinstance(node, nodes.date): | ||
63 | self._handle_date(node) | ||
64 | |||
65 | if isinstance(node, nodes.field): | ||
66 | self._handle_field(node) | ||
67 | |||
68 | def _handle_author(self, node): | ||
69 | self.meta['author'] = Author(node[0]['name'], node[0]['refuri']) | ||
70 | |||
71 | def _handle_date(self, node): | ||
72 | raw_date = unicode(node[0]) | ||
73 | self.meta['post_date'] = datetime.strptime(raw_date, | ||
74 | '%a %b %d %H:%M:%S %Y') | ||
75 | |||
76 | def _handle_field(self, node): | ||
77 | name = node[0][0] | ||
78 | value = unicode(node[1][0][0]) | ||
79 | |||
80 | if name == 'Tag': | ||
81 | self.meta['tags'].append(value) | ||
82 | |||
83 | |||
84 | |||
85 | class BlogPostReader(StandaloneReader): | ||
86 | """ | ||
87 | Post reader for blog posts. | ||
88 | |||
89 | This exists only so that we can append our custom blog | ||
90 | transformers on to the regular ones. | ||
91 | """ | ||
92 | |||
93 | def get_transforms(self): | ||
94 | return StandaloneReader.get_transforms(self) + [ | ||
95 | BlogMetaTransform, | ||
96 | ] | ||
97 | |||
98 | |||
99 | class Author(object): | ||
100 | """ | ||
101 | Representation of the author information for a blog post. | ||
102 | """ | ||
103 | |||
104 | def __init__(self, name, email): | ||
105 | self.name = name | ||
106 | self.email = email | ||
107 | |||
108 | if email.startswith('mailto:'): | ||
109 | self.email = email[len('mailto:'):] | ||
110 | |||
111 | def __str__(self): | ||
112 | return '{0} <{1}>'.format(self.name, self.email) | ||
113 | |||
114 | |||
115 | class BlogPost(object): | ||
116 | """ | ||
117 | Representation of a blog post. | ||
118 | |||
119 | Constructed from a docutils dom version of the blog post. | ||
120 | """ | ||
121 | |||
122 | def __init__(self, title, post_date, author, tags, contents=None): | ||
123 | self.title = title | ||
124 | self.post_date = post_date | ||
125 | self.author = author | ||
126 | self.tags = tags | ||
127 | self.contents = contents | ||
128 | self._filename = None | ||
129 | |||
130 | @property | ||
131 | def filename(self): | ||
132 | return os.path.basename(self._filename) | ||
133 | |||
134 | @filename.setter | ||
135 | def filename(self, value): | ||
136 | self._filename = value | ||
137 | |||
138 | @property | ||
139 | def pretty_date(self): | ||
140 | return self.post_date.strftime("%B %d, %Y") | ||
141 | |||
142 | @classmethod | ||
143 | def from_file(cls, filename): | ||
144 | """ | ||
145 | Loads a file from disk, parses it and constructs a new BlogPost. | ||
146 | |||
147 | This method reflects a bit of the insanity of docutils. Basically | ||
148 | this is just the docutils.core.publish_doctree function with some | ||
149 | modifications to use an html writer and to load a file instead of | ||
150 | a string. | ||
151 | """ | ||
152 | pub = Publisher(destination_class=NullOutput, | ||
153 | source=FileInput(source_path=filename), | ||
154 | reader=BlogPostReader(), writer=HTMLWriter(), | ||
155 | parser=RSTParser()) | ||
156 | |||
157 | pub.get_settings() # This is not sane. | ||
158 | pub.settings.traceback = True # Damnit | ||
159 | pub.publish() | ||
160 | |||
161 | meta = pub.document.blog_meta | ||
162 | post = cls(meta['title'], meta['post_date'], meta['author'], | ||
163 | meta['tags'], pub.writer.parts['html_body']) | ||
164 | |||
165 | post.filename = filename | ||
166 | |||
167 | return post | ||
168 | |||
169 | |||
170 | def load_post_index(directory='.'): | ||
171 | """ | ||
172 | Scan the current directory for rst files and build an index. | ||
173 | """ | ||
174 | posts = [] | ||
175 | for filename in os.listdir(directory): | ||
176 | if not filename.endswith('.rst'): | ||
177 | continue | ||
178 | |||
179 | filename = os.path.join(directory, filename) | ||
180 | posts.append(BlogPost.from_file(filename)) | ||
181 | |||
182 | return posts | ||
diff --git a/site_builder/blogbuilder.py b/site_builder/blogbuilder.py new file mode 100644 index 0000000..ebe77c0 --- /dev/null +++ b/site_builder/blogbuilder.py | |||
@@ -0,0 +1,88 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Blog Builder | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 04, 2010 | ||
8 | """ | ||
9 | |||
10 | import os | ||
11 | import operator | ||
12 | from datetime import datetime | ||
13 | from collections import defaultdict | ||
14 | from site_builder import get_template, get_output_name | ||
15 | from blog import load_post_index | ||
16 | from feeds import Atom1Feed | ||
17 | |||
18 | |||
19 | def build_feed(output_dir, post_index): | ||
20 | page_name = os.path.join(output_dir, 'feed.atom') | ||
21 | feed = Atom1Feed(post_index, "The Random Thoughts of a Programmer", | ||
22 | "http://mike.crute.org/blog/feed", | ||
23 | post_index[0].post_date, | ||
24 | "http://mike.crute.org/blog") | ||
25 | |||
26 | open(page_name, 'w').write(feed.get_feed()) | ||
27 | |||
28 | |||
29 | def build_tags(output_dir, post_index): | ||
30 | tag_index = defaultdict(list) | ||
31 | |||
32 | template = get_template('blog_tags.html') | ||
33 | page_name = os.path.join(output_dir, 'tags.html') | ||
34 | |||
35 | for post in post_index: | ||
36 | for tag in post.tags: | ||
37 | tag_index[tag].append(post) | ||
38 | |||
39 | tag_index = sorted(tag_index.items()) | ||
40 | open(page_name, 'w').write(template.render(posts=tag_index, | ||
41 | build_date=datetime.now().strftime("%B %d, %Y"))) | ||
42 | |||
43 | |||
44 | def build_archive(output_dir, post_index): | ||
45 | date_index = defaultdict(list) | ||
46 | |||
47 | template = get_template('blog_archive.html') | ||
48 | page_name = os.path.join(output_dir, 'archive.html') | ||
49 | |||
50 | for post in post_index: | ||
51 | date_index[post.post_date.year].append(post) | ||
52 | |||
53 | date_index = sorted(date_index.items(), reverse=True) | ||
54 | open(page_name, 'w').write(template.render(posts=date_index, | ||
55 | build_date=datetime.now().strftime("%B %d, %Y"))) | ||
56 | |||
57 | |||
58 | def build_index(output_dir, post_index): | ||
59 | template = get_template('blog_index.html') | ||
60 | page_name = os.path.join(output_dir, 'index.html') | ||
61 | |||
62 | open(page_name, 'w').write(template.render(posts=post_index[:3], | ||
63 | build_date=datetime.now().strftime("%B %d, %Y"))) | ||
64 | |||
65 | |||
66 | def build_blog(base_dir, output_dir): | ||
67 | post_index = load_post_index(base_dir) | ||
68 | post_index.sort(key=operator.attrgetter('post_date'), reverse=True) | ||
69 | |||
70 | try: | ||
71 | os.makedirs(output_dir) | ||
72 | except OSError: | ||
73 | pass # directory already exists | ||
74 | |||
75 | for post in post_index: | ||
76 | template = get_template('blog_post.html') | ||
77 | |||
78 | out_filename = os.path.join(output_dir, post.filename) | ||
79 | out_filename = out_filename[:-len('rst')] + 'html' | ||
80 | |||
81 | print "BUILDING BLOG: ", out_filename | ||
82 | |||
83 | open(out_filename, 'w').write(template.render(post=post)) | ||
84 | |||
85 | build_index(output_dir, post_index) | ||
86 | build_archive(output_dir, post_index) | ||
87 | build_feed(output_dir, post_index) | ||
88 | build_tags(output_dir, post_index) | ||
diff --git a/site_builder/feeds.py b/site_builder/feeds.py new file mode 100644 index 0000000..6c38443 --- /dev/null +++ b/site_builder/feeds.py | |||
@@ -0,0 +1,111 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Atom Feed Writer | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 04, 2010 | ||
8 | """ | ||
9 | |||
10 | #from io import StringIO | ||
11 | from StringIO import StringIO | ||
12 | from xml.sax.saxutils import XMLGenerator | ||
13 | |||
14 | |||
15 | class SimpleXMLGenerator(XMLGenerator): | ||
16 | |||
17 | def __init__(self, encoding='utf-8'): | ||
18 | self.output = StringIO() | ||
19 | XMLGenerator.__init__(self, out=self.output, encoding=encoding) | ||
20 | |||
21 | def get_contents(self): | ||
22 | return self.output.getvalue() | ||
23 | |||
24 | def startElement(self, tag, attrs=None): | ||
25 | attrs = attrs if attrs else {} | ||
26 | return XMLGenerator.startElement(self, tag, attrs) | ||
27 | |||
28 | def addElement(self, tag, contents=None, attrs=None): | ||
29 | attrs = attrs if attrs else {} | ||
30 | self.startElement(tag, attrs) | ||
31 | if contents: | ||
32 | self.characters(contents) | ||
33 | self.endElement(tag) | ||
34 | |||
35 | |||
36 | class Atom1Feed(object): | ||
37 | |||
38 | def __init__(self, posts, title, feed_url, updated, blog_url, | ||
39 | post_filter=None): | ||
40 | self.posts = posts | ||
41 | self.title = title | ||
42 | self.feed_url = feed_url | ||
43 | self.updated = updated | ||
44 | self.blog_url = blog_url | ||
45 | self.handler = SimpleXMLGenerator() | ||
46 | |||
47 | if not post_filter: | ||
48 | post_filter = lambda post: True | ||
49 | |||
50 | self.post_filter = post_filter | ||
51 | |||
52 | def _format_time(self, timeobj): | ||
53 | return timeobj.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
54 | |||
55 | def get_feed(self): | ||
56 | self.handler.startDocument() | ||
57 | self.handler.startElement('feed', { | ||
58 | 'xmlns': 'http://www.w3.org/2005/Atom' }) | ||
59 | |||
60 | self.add_root_elements() | ||
61 | |||
62 | for post in self.posts: | ||
63 | if not self.post_filter(post): | ||
64 | continue | ||
65 | |||
66 | self.add_post(post) | ||
67 | |||
68 | self.handler.endElement('feed') | ||
69 | |||
70 | return self.handler.get_contents() | ||
71 | |||
72 | def add_root_elements(self): | ||
73 | self.handler.addElement('title', self.title) | ||
74 | self.handler.addElement('updated', self._format_time(self.updated)) | ||
75 | self.handler.addElement('id', self.feed_url) | ||
76 | self.handler.addElement('link', attrs={ | ||
77 | 'rel': 'alternate', | ||
78 | 'type': 'text/html', | ||
79 | 'href': self.blog_url }) | ||
80 | self.handler.addElement('link', attrs={ | ||
81 | 'rel': 'self', | ||
82 | 'type': 'application/atom+xml', | ||
83 | 'href': self.feed_url }) | ||
84 | |||
85 | def add_post(self, post): | ||
86 | handler = self.handler | ||
87 | |||
88 | handler.startElement('entry') | ||
89 | |||
90 | handler.startElement('author') | ||
91 | handler.addElement('name', post.author.name) | ||
92 | handler.addElement('email', post.author.email) | ||
93 | handler.endElement('author') | ||
94 | |||
95 | post_href = '{0}/{1}'.format(self.blog_url, post.filename) | ||
96 | |||
97 | handler.addElement('title', post.title) | ||
98 | handler.addElement('link', attrs={ | ||
99 | 'rel': 'alternate', | ||
100 | 'type': 'text/html', | ||
101 | 'href': post_href }) | ||
102 | handler.addElement('id', post_href) | ||
103 | handler.addElement('updated', self._format_time(post.post_date)) | ||
104 | handler.addElement('published', self._format_time(post.post_date)) | ||
105 | |||
106 | for tag in post.tags: | ||
107 | handler.addElement('category', attrs={ 'term': tag }) | ||
108 | |||
109 | handler.addElement('content', post.contents, attrs={ 'type': 'html' }) | ||
110 | |||
111 | handler.endElement('entry') | ||
diff --git a/site_builder/pagebuilder.py b/site_builder/pagebuilder.py new file mode 100644 index 0000000..2af7556 --- /dev/null +++ b/site_builder/pagebuilder.py | |||
@@ -0,0 +1,2 @@ | |||
1 | def build(): | ||
2 | pass | ||
diff --git a/site_builder/robots.py b/site_builder/robots.py new file mode 100644 index 0000000..9ad5698 --- /dev/null +++ b/site_builder/robots.py | |||
@@ -0,0 +1,15 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Robots.txt Generator | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 05, 2010 | ||
8 | """ | ||
9 | |||
10 | """ | ||
11 | Sitemap: http://mike.crute.org/sitemap.xml | ||
12 | |||
13 | User-agent: * | ||
14 | Disallow: /blog/wp-admin/ | ||
15 | """ | ||
diff --git a/site_builder/sitemap.py b/site_builder/sitemap.py new file mode 100644 index 0000000..b70bd24 --- /dev/null +++ b/site_builder/sitemap.py | |||
@@ -0,0 +1,21 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Sitemap Generator | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: June 05, 2010 | ||
8 | """ | ||
9 | |||
10 | """ | ||
11 | <?xml version="1.0" encoding="UTF-8"?> | ||
12 | <?xml-stylesheet type="text/xsl" href="/blog/wp-content/plugins/google-sitemap-generator/sitemap.xsl"?> | ||
13 | <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> | ||
14 | <url> | ||
15 | <loc>http://mike.crute.org/blog/</loc> | ||
16 | <lastmod>2009-09-09T13:28:46+00:00</lastmod> | ||
17 | <changefreq>daily</changefreq> | ||
18 | <priority>1.0</priority> | ||
19 | </url> | ||
20 | </urlset> | ||
21 | """ | ||
diff --git a/templates/blog_archive.html b/templates/blog_archive.html new file mode 100644 index 0000000..df6b810 --- /dev/null +++ b/templates/blog_archive.html | |||
@@ -0,0 +1,14 @@ | |||
1 | {% extends "page.html" %} | ||
2 | |||
3 | {% set is_blog = True %} | ||
4 | |||
5 | {% block contents %} | ||
6 | {% for year, post_set in posts %} | ||
7 | <h2>{{ year }}</h2> | ||
8 | <ul> | ||
9 | {% for post in post_set %} | ||
10 | <li><a href="{{ post.filename|replace('.rst', '.html') }}">{{ post.title }}</a></li> | ||
11 | {% endfor %} | ||
12 | </ul> | ||
13 | {% endfor %} | ||
14 | {% endblock %} | ||
diff --git a/templates/blog_index.html b/templates/blog_index.html new file mode 100644 index 0000000..bb918fa --- /dev/null +++ b/templates/blog_index.html | |||
@@ -0,0 +1,18 @@ | |||
1 | {% extends "page.html" %} | ||
2 | |||
3 | {% set is_blog = True %} | ||
4 | |||
5 | {% block contents %} | ||
6 | {% for post in posts %} | ||
7 | <h1 class="title">{{ post.title }}</h1> | ||
8 | <p><span class="label">Posted:</span> {{ post.pretty_date }}</span></p> | ||
9 | {{ post.contents }} | ||
10 | {% if not loop.last %} | ||
11 | <hr /> | ||
12 | {% endif %} | ||
13 | {% endfor %} | ||
14 | {% endblock %} | ||
15 | |||
16 | {% block update_date %} | ||
17 | <p class="updated"><span class="label">Page last updated:</span> {{ posts[0].pretty_date }}</p> | ||
18 | {% endblock %} | ||
diff --git a/templates/blog_post.html b/templates/blog_post.html new file mode 100644 index 0000000..96c7bd9 --- /dev/null +++ b/templates/blog_post.html | |||
@@ -0,0 +1,26 @@ | |||
1 | {% extends "page.html" %} | ||
2 | |||
3 | {% set is_blog = True %} | ||
4 | |||
5 | {% block contents %} | ||
6 | <h1 class="title">{{ post.title }}</h1> | ||
7 | {{ post.contents }} | ||
8 | |||
9 | {# Maybe enable comments... some day... | ||
10 | <hr /> | ||
11 | <h2>Comments</h2> | ||
12 | <hr /> | ||
13 | <div id="disqus_thread"></div> | ||
14 | <script type="text/javascript"> | ||
15 | (function() { | ||
16 | var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; | ||
17 | dsq.src = 'http://randomthoughtsofaprogrammer.disqus.com/embed.js'; | ||
18 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); | ||
19 | })(); | ||
20 | </script> | ||
21 | #} | ||
22 | {% endblock %} | ||
23 | |||
24 | {% block update_date %} | ||
25 | <p class="updated"><span class="label">Page last updated:</span> {{ post.pretty_date }}</p> | ||
26 | {% endblock %} | ||
diff --git a/templates/blog_tags.html b/templates/blog_tags.html new file mode 100644 index 0000000..bcf7d42 --- /dev/null +++ b/templates/blog_tags.html | |||
@@ -0,0 +1,22 @@ | |||
1 | {% extends "page.html" %} | ||
2 | |||
3 | {% set is_blog = True %} | ||
4 | |||
5 | {% block contents %} | ||
6 | <h2>All Tags</h2> | ||
7 | <p> | ||
8 | {%- for tag, post_set in posts %} | ||
9 | <a href="#{{ tag }}">{{ tag }}</a> | ||
10 | {% endfor %} | ||
11 | </p> | ||
12 | |||
13 | |||
14 | {% for tag, post_set in posts %} | ||
15 | <h2 id="{{ tag }}">{{ tag }}</h2> | ||
16 | <ul> | ||
17 | {% for post in post_set %} | ||
18 | <li><a href="{{ post.filename|replace('.rst', '.html') }}">{{ post.title }}</a></li> | ||
19 | {% endfor %} | ||
20 | </ul> | ||
21 | {% endfor %} | ||
22 | {% endblock %} | ||
diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..83fa0dc --- /dev/null +++ b/templates/page.html | |||
@@ -0,0 +1,57 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> | ||
3 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | ||
4 | <head> | ||
5 | <meta http-equiv="content-type" content="text/html;charset=utf-8" /> | ||
6 | <meta name="google-site-verification" content="Ycqn8jLbKJrWCDs97nD20cjYEhFf875j1teoofSd6Sc" /> | ||
7 | <meta name="generator" content="Ventriloquy 0.1" /> | ||
8 | <meta name="author" content="Michael Crute" /> | ||
9 | <title>The Random Thoughts of a Programmer</title> | ||
10 | <style type="text/css">@import url('/resources/site-min.css');</style> | ||
11 | </head> | ||
12 | <body> | ||
13 | |||
14 | <div id="header"> | ||
15 | <h1 class="headline"><a href="/">mike<span>.</span>crute<span>.org</span></a></h1> | ||
16 | </div> | ||
17 | |||
18 | <div id="contents"> | ||
19 | |||
20 | {% block contents %} | ||
21 | {{ contents }} | ||
22 | {% endblock %} | ||
23 | |||
24 | </div> | ||
25 | |||
26 | <div id="footer"> | ||
27 | {% if is_blog %} | ||
28 | <div id="footer-blog"> | ||
29 | <ul class="links"> | ||
30 | <li><span class="label">Blog Links:</span></li> | ||
31 | <li><a href="archive">Archive</a></li> | ||
32 | <li><a href="tags">Tags</a></li> | ||
33 | <li><a href="feed">Atom Feed</a></li> | ||
34 | </ul> | ||
35 | </div> | ||
36 | {% endif %} | ||
37 | |||
38 | <div id="footer-page"> | ||
39 | <ul class="links"> | ||
40 | <li><span class="label">Site Links:</span></li> | ||
41 | <li><a href="/">Home</a></li> | ||
42 | <li><a href="/blog">Blog</a></li> | ||
43 | <li><a href="/personal_blog">Personal Blog</a></li> | ||
44 | <li><a href="/code">Code</a></li> | ||
45 | <li><a href="http://twitter.com/mcrute">Twitter</a></li> | ||
46 | </ul> | ||
47 | |||
48 | {% block update_date %} | ||
49 | <p class="updated"><span class="label">Page last updated:</span> {{ build_date }}</p> | ||
50 | {% endblock %} | ||
51 | <p class="copyright">Copyright © 2010 Michael E. Crute <<a href="mailto:mcrute@gmail.com">mcrute@gmail.com</a>>. Contact me for other uses.</p> | ||
52 | <p class="poweredby">This site powered by <a href="http://www.vim.org">VIM</a>, <a href="http://docutils.sourceforge.net">ReStructuredText</a> and <a href="http://python.org">Python</a>.</p> | ||
53 | </div> | ||
54 | </div> | ||
55 | |||
56 | </body> | ||
57 | </html> | ||