comparison piecrust/sources/default.py @ 852:4850f8c21b6e

core: Start of the big refactor for PieCrust 3.0. * Everything is a `ContentSource`, including assets directories. * Most content sources are subclasses of the base file-system source. * A source is processed by a "pipeline", and there are 2 built-in pipelines, one for assets and one for pages. The asset pipeline is vaguely functional, but the page pipeline is completely broken right now. * Rewrite the baking process as just running appropriate pipelines on each content item. This should allow for better parallelization.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 17 May 2017 00:11:48 -0700
parents 58ebf50235a5
children f070a4fc033c
comparison
equal deleted inserted replaced
851:2c7e57d80bba 852:4850f8c21b6e
1 import os.path 1 import os.path
2 import logging 2 import logging
3 from piecrust import osutil
4 from piecrust.routing import RouteParameter 3 from piecrust.routing import RouteParameter
5 from piecrust.sources.base import ( 4 from piecrust.sources.base import REL_ASSETS, ContentItem
6 PageFactory, PageSource, InvalidFileSystemEndpointError, 5 from piecrust.sources.fs import FSContentSource
7 MODE_CREATING)
8 from piecrust.sources.interfaces import ( 6 from piecrust.sources.interfaces import (
9 IListableSource, IPreparingSource, IInteractiveSource, 7 IPreparingSource, IInteractiveSource, InteractiveField)
10 InteractiveField) 8 from piecrust.sources.mixins import SimpleAssetsSubDirMixin
11 from piecrust.sources.mixins import SimplePaginationSourceMixin 9 from piecrust.uriutil import uri_to_title
12 10
13 11
14 logger = logging.getLogger(__name__) 12 logger = logging.getLogger(__name__)
15 13
16 14
17 def filter_page_dirname(d): 15 class DefaultContentSource(FSContentSource,
18 return not (d.startswith('.') or d.endswith('-assets')) 16 SimpleAssetsSubDirMixin,
19 17 IPreparingSource, IInteractiveSource):
20
21 def filter_page_filename(f):
22 return (f[0] != '.' and # .DS_store and other crap
23 f[-1] != '~' and # Vim temp files and what-not
24 f not in ['Thumbs.db']) # Windows bullshit
25
26
27 class DefaultPageSource(PageSource,
28 IListableSource, IPreparingSource, IInteractiveSource,
29 SimplePaginationSourceMixin):
30 SOURCE_NAME = 'default' 18 SOURCE_NAME = 'default'
31 19
32 def __init__(self, app, name, config): 20 def __init__(self, app, name, config):
33 super(DefaultPageSource, self).__init__(app, name, config) 21 super().__init__(app, name, config)
34 self.fs_endpoint = config.get('fs_endpoint', name) 22 self.auto_formats = app.config.get('site/auto_formats')
35 self.fs_endpoint_path = os.path.join(self.root_dir, self.fs_endpoint)
36 self.supported_extensions = list(
37 app.config.get('site/auto_formats').keys())
38 self.default_auto_format = app.config.get('site/default_auto_format') 23 self.default_auto_format = app.config.get('site/default_auto_format')
24 self.supported_extensions = list(self.auto_formats)
39 25
40 def getSupportedRouteParameters(self): 26 def _createItemMetadata(self, path):
41 return [ 27 return self._doCreateItemMetadata(path)
42 RouteParameter('slug', RouteParameter.TYPE_PATH)]
43 28
44 def buildPageFactories(self): 29 def _finalizeContent(self, parent_group, items, groups):
45 logger.debug("Scanning for pages in: %s" % self.fs_endpoint_path) 30 SimpleAssetsSubDirMixin._onFinalizeContent(
46 if not os.path.isdir(self.fs_endpoint_path): 31 self, parent_group, items, groups)
47 if self.ignore_missing_dir:
48 return
49 raise InvalidFileSystemEndpointError(self.name,
50 self.fs_endpoint_path)
51 32
52 for dirpath, dirnames, filenames in osutil.walk(self.fs_endpoint_path): 33 def _doCreateItemMetadata(self, path):
53 rel_dirpath = os.path.relpath(dirpath, self.fs_endpoint_path) 34 slug = self._makeSlug(path)
54 dirnames[:] = list(filter(filter_page_dirname, dirnames)) 35 metadata = {
55 for f in sorted(filter(filter_page_filename, filenames)): 36 'slug': slug
56 fac_path = f 37 }
57 if rel_dirpath != '.': 38 _, ext = os.path.splitext(path)
58 fac_path = os.path.join(rel_dirpath, f) 39 if ext:
40 fmt = self.auto_formats.get(ext.lstrip('.'))
41 if fmt:
42 metadata['config'] = {'format': fmt}
43 return metadata
59 44
60 slug = self._makeSlug(fac_path) 45 def _makeSlug(self, path):
61 metadata = {'slug': slug} 46 rel_path = os.path.relpath(path, self.fs_endpoint_path)
62 fac_path = fac_path.replace('\\', '/')
63 self._populateMetadata(fac_path, metadata)
64 yield PageFactory(self, fac_path, metadata)
65
66 def buildPageFactory(self, path):
67 if not path.startswith(self.fs_endpoint_path):
68 raise Exception("Page path '%s' isn't inside '%s'." % (
69 path, self.fs_enpoint_path))
70 rel_path = path[len(self.fs_endpoint_path):].lstrip('\\/')
71 slug = self._makeSlug(rel_path)
72 metadata = {'slug': slug}
73 fac_path = rel_path.replace('\\', '/')
74 self._populateMetadata(fac_path, metadata)
75 return PageFactory(self, fac_path, metadata)
76
77 def resolveRef(self, ref_path):
78 path = os.path.normpath(
79 os.path.join(self.fs_endpoint_path, ref_path.lstrip("\\/")))
80 slug = self._makeSlug(ref_path)
81 metadata = {'slug': slug}
82 self._populateMetadata(ref_path, metadata)
83 return path, metadata
84
85 def findPageFactory(self, metadata, mode):
86 uri_path = metadata.get('slug', '')
87 if not uri_path:
88 uri_path = '_index'
89 path = os.path.join(self.fs_endpoint_path, uri_path)
90 _, ext = os.path.splitext(path)
91
92 if mode == MODE_CREATING:
93 if ext == '':
94 path = '%s.%s' % (path, self.default_auto_format)
95 rel_path = os.path.relpath(path, self.fs_endpoint_path)
96 rel_path = rel_path.replace('\\', '/')
97 self._populateMetadata(rel_path, metadata, mode)
98 return PageFactory(self, rel_path, metadata)
99
100 if ext == '':
101 paths_to_check = [
102 '%s.%s' % (path, e)
103 for e in self.supported_extensions]
104 else:
105 paths_to_check = [path]
106 for path in paths_to_check:
107 if os.path.isfile(path):
108 rel_path = os.path.relpath(path, self.fs_endpoint_path)
109 rel_path = rel_path.replace('\\', '/')
110 self._populateMetadata(rel_path, metadata, mode)
111 return PageFactory(self, rel_path, metadata)
112
113 return None
114
115 def listPath(self, rel_path):
116 rel_path = rel_path.lstrip('\\/')
117 path = os.path.join(self.fs_endpoint_path, rel_path)
118 names = sorted(osutil.listdir(path))
119 items = []
120 for name in names:
121 if os.path.isdir(os.path.join(path, name)):
122 if filter_page_dirname(name):
123 rel_subdir = os.path.join(rel_path, name)
124 items.append((True, name, rel_subdir))
125 else:
126 if filter_page_filename(name):
127 slug = self._makeSlug(os.path.join(rel_path, name))
128 metadata = {'slug': slug}
129
130 fac_path = name
131 if rel_path != '.':
132 fac_path = os.path.join(rel_path, name)
133 fac_path = fac_path.replace('\\', '/')
134
135 self._populateMetadata(fac_path, metadata)
136 fac = PageFactory(self, fac_path, metadata)
137
138 name, _ = os.path.splitext(name)
139 items.append((False, name, fac))
140 return items
141
142 def getDirpath(self, rel_path):
143 return os.path.dirname(rel_path)
144
145 def getBasename(self, rel_path):
146 filename = os.path.basename(rel_path)
147 name, _ = os.path.splitext(filename)
148 return name
149
150 def setupPrepareParser(self, parser, app):
151 parser.add_argument('uri', help='The URI for the new page.')
152
153 def buildMetadata(self, args):
154 return {'slug': args.uri}
155
156 def getInteractiveFields(self):
157 return [
158 InteractiveField('slug', InteractiveField.TYPE_STRING,
159 'new-page')]
160
161 def _makeSlug(self, rel_path):
162 slug, ext = os.path.splitext(rel_path) 47 slug, ext = os.path.splitext(rel_path)
163 slug = slug.replace('\\', '/') 48 slug = slug.replace('\\', '/')
164 if ext.lstrip('.') not in self.supported_extensions: 49 if ext.lstrip('.') not in self.supported_extensions:
165 slug += ext 50 slug += ext
166 if slug.startswith('./'): 51 if slug.startswith('./'):
167 slug = slug[2:] 52 slug = slug[2:]
168 if slug == '_index': 53 if slug == '_index':
169 slug = '' 54 slug = ''
170 return slug 55 return slug
171 56
172 def _populateMetadata(self, rel_path, metadata, mode=None): 57 def getRelatedContents(self, item, relationship):
173 pass 58 if relationship == REL_ASSETS:
59 SimpleAssetsSubDirMixin._getRelatedAssetsContents(self, item)
60 raise NotImplementedError()
174 61
62 def getSupportedRouteParameters(self):
63 return [
64 RouteParameter('slug', RouteParameter.TYPE_PATH)]
65
66 def findContent(self, route_params):
67 uri_path = route_params.get('slug', '')
68 if not uri_path:
69 uri_path = '_index'
70 path = os.path.join(self.fs_endpoint_path, uri_path)
71 _, ext = os.path.splitext(path)
72
73 if ext == '':
74 paths_to_check = [
75 '%s.%s' % (path, e)
76 for e in self.supported_extensions]
77 else:
78 paths_to_check = [path]
79 for path in paths_to_check:
80 if os.path.isfile(path):
81 metadata = self._doCreateItemMetadata(path)
82 return ContentItem(path, metadata)
83 return None
84
85 def setupPrepareParser(self, parser, app):
86 parser.add_argument('uri', help='The URI for the new page.')
87
88 def createContent(self, args):
89 if not hasattr(args, 'uri'):
90 uri = None
91 else:
92 uri = args.uri
93 if not uri:
94 uri = '_index'
95 path = os.path.join(self.fs_endpoint_path, uri)
96 _, ext = os.path.splitext(path)
97 if ext == '':
98 path = '%s.%s' % (path, self.default_auto_format)
99
100 metadata = self._doCreateItemMetadata(path)
101 config = metadata.setdefault('config', {})
102 config.update({'title': uri_to_title(
103 os.path.basename(metadata['slug']))})
104 return ContentItem(path, metadata)
105
106 def getInteractiveFields(self):
107 return [
108 InteractiveField('slug', InteractiveField.TYPE_STRING,
109 'new-page')]