summaryrefslogtreecommitdiff
path: root/site_builder/blog.py
diff options
context:
space:
mode:
Diffstat (limited to 'site_builder/blog.py')
-rw-r--r--site_builder/blog.py182
1 files changed, 182 insertions, 0 deletions
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"""
3Blog Post Builder
4
5@author: Mike Crute (mcrute@ag.com)
6@organization: American Greetings Interactive
7@date: June 03, 2010
8"""
9
10import os
11from functools import wraps
12from datetime import datetime
13
14# Docutils imports, crazy yo
15from docutils import nodes
16from docutils.core import Publisher, publish_string
17from docutils.transforms import Transform
18from docutils.io import NullOutput, FileInput
19from docutils.parsers.rst import Parser as RSTParser
20from docutils.writers.html4css1 import Writer as HTMLWriter
21from docutils.readers.standalone import Reader as StandaloneReader
22
23
24class 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
85class 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
99class 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
115class 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
170def 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