forked from mythmon/wok
-
Notifications
You must be signed in to change notification settings - Fork 0
/
page.py
179 lines (145 loc) · 5.63 KB
/
page.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import os
from collections import namedtuple
from datetime import datetime
import jinja2
import yaml
import re
import isodate
import util
import renderers
class Page(object):
"""
A single page on the website in all it's form, as well as it's
associated metadata.
"""
meta = None
class Author(object):
"""Smartly manages a author with name and email"""
parse_author_regex = re.compile(r'([^<>]*)( +<(.*@.*)>)$')
def __init__(self, raw='', name=None, email=None):
self.raw = raw
self.name = name
self.email = email
@classmethod
def parse(cls, raw):
a = cls(raw)
a.name, _, a.email = cls.parse_author_regex.match(raw).groups()
def __str__(self):
if not self.name:
return self.raw
if not self.email:
return self.name
return "{0} <{1}>".format(self.name, self.email)
def __init__(self, path, options, renderer=None):
"""
Load a file from disk, and parse the metadata from it.
Note that you still need to call `render` and `write` to do anything
interesting.
"""
self.header = None
self.original = None
self.parsed = None
self.options = options
self.renderer = renderer if renderer else renderers.Plain
self.subpages = []
# TODO: It's not good to make a new environment every time, but we if
# we pass the options in each time, its possible it will change per
# instance. Fix this.
self.tmpl_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(
self.options.get('template_dir', 'templates')))
self.path = path
_, self.filename = os.path.split(path)
with open(path) as f:
self.original = f.read()
# Maximum of one split, so --- in the content doesn't get split.
splits = self.original.split('---', 1)
# Handle the case where no meta data was provided
if len(splits) == 1:
self.original = splits[0]
else:
header = splits[0]
self.original = splits[1]
self.meta = yaml.load(header)
self.build_meta()
def build_meta(self):
"""
Ensures the guarantees about metadata for documents are valid.
`page.title` - will exist.
`page.slug` - will exist.
`page.author` - will exist, and contain fields `name` and `email`.
`page.category` - will exist, and be a list.
`page.published` - will exist
`page.datetime` - will exist
"""
if self.meta is None:
self.meta = {}
if not 'title' in self.meta:
self.meta['title'] = '.'.join(self.filename.split('.')[:-1])
if (self.meta['title'] == ''):
self.meta['title'] = self.filename
util.out.warn('metadata',
"You didn't specify a title in {0}. Using the file name as a title."
.format(self.filename))
# Guarantee: title exists.
if not 'slug' in self.meta:
self.meta['slug'] = util.slugify(self.meta['title'])
util.out.debug('metadata',
'You didn\'t specify a slug, generating it from the title.')
elif self.meta['slug'] != util.slugify(self.meta['slug']):
util.out.warn('metadata',
'Your slug should probably be all lower case,' +
'and match the regex "[a-z0-9-]*"')
# Guarantee: slug exists.
if 'author' in self.meta:
self.meta['author'] = Page.Author.parse(self.meta['author'])
else:
self.meta['author'] = Page.Author()
# Guarantee: author exists.
if 'category' in self.meta:
self.meta['category'] = self.meta['category'].split('/')
else:
self.meta['category'] = []
# Guarantee: category exists
if not 'published' in self.meta:
self.meta['published'] = True
# Guarantee: published exists
datetime_name=None
for name in ['time', 'date', 'datetime']:
if name in self.meta:
datetime_name = name
if datetime_name:
self.meta['datetime'] = isodate.parse_datetime(self.meta[datetime_name])
else:
self.meta['datetime'] = datetime.now()
# Gurantee: datetime exists
def render(self):
"""
Renders the page to full html.
First parse the content, then build a set of variables for the
template, finally render it with jinja2.
"""
self.content = self.renderer.render(self.original)
type = self.meta.get('type', 'default')
template = self.tmpl_env.get_template(type + '.html')
templ_vars = {
'page': self,
'site': {
'title': self.options.get('site_title', 'Untitled'),
'datetime': datetime.now(),
},
}
self.html = template.render(templ_vars)
def write(self, dir=None):
"""Write the page to an html file on disk."""
# Use what we are passed, or the default given, or the current dir
if not dir:
dir = self.options.get('output_dir', '.')
path = os.path.join(dir, self.slug + '.html')
with open(path, 'w') as f:
f.write(self.html)
# Make the public interface ignore the seperation between the meta
# dictionary and the properies of the Page object.
def __getattr__(self, name):
if name in self.meta:
return self.meta[name]