comparison piecrust/sources/posts.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children 474c9882decf
comparison
equal deleted inserted replaced
2:40fa08b261b9 3:f485ba500df3
1 import os
2 import os.path
3 import re
4 import glob
5 import logging
6 import datetime
7 from piecrust import CONTENT_DIR
8 from piecrust.sources.base import (PageSource, IPreparingSource,
9 PageNotFoundError, InvalidFileSystemEndpointError,
10 PageFactory, MODE_CREATING)
11
12
13 logger = logging.getLogger(__name__)
14
15
16 class PostsSource(PageSource, IPreparingSource):
17 PATH_FORMAT = None
18
19 def __init__(self, app, name, config):
20 super(PostsSource, self).__init__(app, name, config)
21 self.fs_endpoint = config.get('fs_endpoint', name)
22 self.fs_endpoint_path = os.path.join(self.root_dir, CONTENT_DIR, self.fs_endpoint)
23 self.supported_extensions = app.config.get('site/auto_formats').keys()
24
25 @property
26 def path_format(self):
27 return self.__class__.PATH_FORMAT
28
29 def resolveRef(self, ref_path):
30 return os.path.join(self.fs_endpoint_path, ref_path)
31
32 def findPagePath(self, metadata, mode):
33 year = metadata.get('year')
34 month = metadata.get('month')
35 day = metadata.get('day')
36 slug = metadata.get('slug')
37
38 ext = metadata.get('ext')
39 if ext is None:
40 if len(self.supported_extensions) == 1:
41 ext = self.supported_extensions[0]
42
43 replacements = {
44 'year': year,
45 'month': month,
46 'day': day,
47 'slug': slug,
48 'ext': ext
49 }
50 needs_recapture = False
51 if year is None:
52 needs_recapture = True
53 replacements['year'] = '????'
54 if month is None:
55 needs_recapture = True
56 replacements['month'] = '??'
57 if day is None:
58 needs_recapture = True
59 replacements['day'] = '??'
60 if slug is None:
61 needs_recapture = True
62 replacements['slug'] = '*'
63 if ext is None:
64 needs_recapture = True
65 replacements['ext'] = '*'
66 path = os.path.join(self.fs_endpoint_path, self.path_format % replacements)
67
68 if needs_recapture:
69 if mode == MODE_CREATING:
70 raise ValueError("Not enough information to find a post path.")
71 possible_paths = glob.glob(path)
72 if len(possible_paths) != 1:
73 raise PageNotFoundError()
74 path = possible_paths[0]
75 elif not os.path.isfile(path):
76 raise PageNotFoundError()
77
78 regex_repl = {
79 'year': '(?P<year>\d{4})',
80 'month': '(?P<month>\d{2})',
81 'day': '(?P<day>\d{2})',
82 'slug': '(?P<slug>.*)',
83 'ext': '(?P<ext>.*)'
84 }
85 pattern = os.path.join(self.fs_endpoint_path, self.path_format) % regex_repl
86 m = re.match(pattern, path)
87 if not m:
88 raise Exception("Expected to be able to match path with path "
89 "format: %s" % path)
90 fac_metadata = {
91 'year': m.group('year'),
92 'month': m.group('month'),
93 'day': m.group('day'),
94 'slug': m.group('slug')
95 }
96
97 return path, fac_metadata
98
99 def setupPrepareParser(self, parser, app):
100 parser.add_argument('-d', '--date', help="The date of the post, "
101 "in `year/month/day` format (defaults to today).")
102 parser.add_argument('slug', help="The URL slug for the new post.")
103
104 def buildMetadata(self, args):
105 today = datetime.date.today()
106 year, month, day = today.year, today.month, today.day
107 if args.date:
108 year, month, day = filter(
109 lambda s: int(s),
110 args.date.split('/'))
111 return {'year': year, 'month': month, 'day': day, 'slug': args.slug}
112
113 def _checkFsEndpointPath(self):
114 if not os.path.isdir(self.fs_endpoint_path):
115 raise InvalidFileSystemEndpointError(self.name, self.fs_endpoint_path)
116
117 def _makeFactory(self, path, slug, year, month, day):
118 timestamp = datetime.date(year, month, day)
119 metadata = {
120 'slug': slug,
121 'year': year,
122 'month': month,
123 'day': day,
124 'date': timestamp}
125 return PageFactory(self, path, metadata)
126
127
128 class FlatPostsSource(PostsSource):
129 SOURCE_NAME = 'posts/flat'
130 PATH_FORMAT = '%(year)s-%(month)s-%(day)s_%(slug)s.%(ext)s'
131
132 def __init__(self, app, name, config):
133 super(FlatPostsSource, self).__init__(app, name, config)
134
135 def buildPageFactories(self):
136 logger.debug("Scanning for posts (flat) in: %s" % self.fs_endpoint_path)
137 pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2})_(.*)\.(\w+)$')
138 _, __, filenames = next(os.walk(self.fs_endpoint_path))
139 for f in filenames:
140 match = pattern.match(f)
141 if match is None:
142 name, ext = os.path.splitext(f)
143 logger.warning("'%s' is not formatted as 'YYYY-MM-DD_slug-title.%s' "
144 "and will be ignored. Is that a typo?" % (f, ext))
145 continue
146 yield self._makeFactory(
147 f,
148 match.group(4),
149 int(match.group(1)),
150 int(match.group(2)),
151 int(match.group(3)))
152
153
154 class ShallowPostsSource(PostsSource):
155 SOURCE_NAME = 'posts/shallow'
156 PATH_FORMAT = '%(year)s/%(month)s-%(day)s_%(slug)s.%(ext)s'
157
158 def __init__(self, app, name, config):
159 super(ShallowPostsSource, self).__init__(app, name, config)
160
161 def buildPageFactories(self):
162 logger.debug("Scanning for posts (shallow) in: %s" % self.fs_endpoint_path)
163 year_pattern = re.compile(r'(\d{4})$')
164 file_pattern = re.compile(r'(\d{2})-(\d{2})_(.*)\.(\w+)$')
165 _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
166 year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
167 for yd in year_dirs:
168 if year_pattern.match(yd) is None:
169 logger.warning("'%s' is not formatted as 'YYYY' and will be ignored. "
170 "Is that a typo?")
171 continue
172 year = int(yd)
173 year_dir = os.path.join(self.fs_endpoint_path, yd)
174
175 _, __, filenames = os.walk(year_dir)
176 for f in filenames:
177 match = file_pattern.match(f)
178 if match is None:
179 name, ext = os.path.splitext(f)
180 logger.warning("'%s' is not formatted as 'MM-DD_slug-title.%s' "
181 "and will be ignored. Is that a typo?" % (f, ext))
182 continue
183 yield self._makeFactory(
184 os.path.join(yd, f),
185 match.group(3),
186 year,
187 int(match.group(1)),
188 int(match.group(2)))
189
190
191 class HierarchyPostsSource(PostsSource):
192 SOURCE_NAME = 'posts/hierarchy'
193 PATH_FORMAT = '%(year)s/%(month)s/%(day)s_%(slug)s.%(ext)s'
194
195 def __init__(self, app, name, config):
196 super(HierarchyPostsSource, self).__init__(app, name, config)
197
198 def buildPageFactories(self):
199 logger.debug("Scanning for posts (hierarchy) in: %s" % self.fs_endpoint_path)
200 year_pattern = re.compile(r'(\d{4})$')
201 month_pattern = re.compile(r'(\d{2})$')
202 file_pattern = re.compile(r'(\d{2})_(.*)\.(\w+)$')
203 _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
204 year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
205 for yd in year_dirs:
206 year = int(yd)
207 year_dir = os.path.join(self.fs_endpoint_path, yd)
208
209 _, month_dirs, __ = next(os.walk(year_dir))
210 month_dirs = filter(lambda d: month_pattern.match(d), month_dirs)
211 for md in month_dirs:
212 month = int(md)
213 month_dir = os.path.join(year_dir, md)
214
215 _, __, filenames = next(os.walk(month_dir))
216 for f in filenames:
217 match = file_pattern.match(f)
218 if match is None:
219 name, ext = os.path.splitext(f)
220 logger.warning("'%s' is not formatted as 'DD_slug-title.%s' "
221 "and will be ignored. Is that a typo?" % (f, ext))
222 continue
223 rel_name = os.path.join(yd, md, f)
224 yield self._makeFactory(
225 rel_name,
226 match.group(2),
227 year,
228 month,
229 int(match.group(1)))
230