comparison piecrust/sources/mixins.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 ab5c6a8ae90a
children f070a4fc033c
comparison
equal deleted inserted replaced
851:2c7e57d80bba 852:4850f8c21b6e
1 import os
2 import os.path 1 import os.path
3 import logging 2 import logging
4 from piecrust.data.filters import PaginationFilter, page_value_accessor 3 from piecrust import osutil
5 from piecrust.data.paginationdata import PaginationData 4 from piecrust.data.paginationdata import PaginationData
6 from piecrust.sources.base import PageFactory 5 from piecrust.sources.base import ContentItem
7 from piecrust.sources.interfaces import IPaginationSource, IListableSource 6 from piecrust.sources.interfaces import IPaginationSource
8 from piecrust.sources.pageref import PageRef
9 7
10 8
11 logger = logging.getLogger(__name__) 9 logger = logging.getLogger(__name__)
12 10
11 assets_suffix = '-assets'
13 12
14 class SourceFactoryIterator(object): 13
14 class ContentSourceIterator(object):
15 def __init__(self, source): 15 def __init__(self, source):
16 self.source = source 16 self.source = source
17 17
18 # This is to permit recursive traversal of the 18 # This is to permit recursive traversal of the
19 # iterator chain. It acts as the end. 19 # iterator chain. It acts as the end.
20 self.it = None 20 self.it = None
21 21
22 def __iter__(self): 22 def __iter__(self):
23 return self.source.getPages() 23 return self.source.getAllContentItems()
24
25
26 class SourceFactoryWithoutGeneratorsIterator(object):
27 def __init__(self, source):
28 self.source = source
29 self._generator_pages = None
30 # See comment above.
31 self.it = None
32
33 def __iter__(self):
34 self._cacheGeneratorPages()
35 for p in self.source.getPages():
36 if p.rel_path in self._generator_pages:
37 continue
38 yield p
39
40 def _cacheGeneratorPages(self):
41 if self._generator_pages is not None:
42 return
43
44 app = self.source.app
45 self._generator_pages = set()
46 for src in app.sources:
47 for gen in app.generators:
48 for sn, rp in gen.page_ref.possible_split_ref_specs:
49 if sn == self.source.name:
50 self._generator_pages.add(rp)
51 24
52 25
53 class DateSortIterator(object): 26 class DateSortIterator(object):
54 def __init__(self, it, reverse=True): 27 def __init__(self, it, reverse=True):
55 self.it = it 28 self.it = it
64 def __init__(self, it): 37 def __init__(self, it):
65 self.it = it 38 self.it = it
66 39
67 def __iter__(self): 40 def __iter__(self):
68 for page in self.it: 41 for page in self.it:
69 if page is None: 42 if page is not None:
43 yield PaginationData(page)
44 else:
70 yield None 45 yield None
71 else:
72 yield PaginationData(page)
73 46
74 47
75 class SimplePaginationSourceMixin(IPaginationSource): 48 class SimplePaginationSourceMixin(IPaginationSource):
76 """ Implements the `IPaginationSource` interface in a standard way that 49 """ Implements the `IPaginationSource` interface in a standard way that
77 should fit most page sources. 50 should fit most page sources.
78 """ 51 """
79 def getItemsPerPage(self): 52 def getItemsPerPage(self):
80 return self.config['items_per_page'] 53 return self.config['items_per_page']
81 54
82 def getSourceIterator(self): 55 def getSourceIterator(self):
83 if self.config.get('iteration_includes_generator_pages', False): 56 return ContentSourceIterator(self)
84 return SourceFactoryIterator(self)
85 return SourceFactoryWithoutGeneratorsIterator(self)
86 57
87 def getSorterIterator(self, it): 58 def getSorterIterator(self, it):
88 return DateSortIterator(it) 59 return DateSortIterator(it)
89 60
90 def getTailIterator(self, it): 61 def getTailIterator(self, it):
91 return PaginationDataBuilderIterator(it) 62 return PaginationDataBuilderIterator(it)
92 63
93 def getPaginationFilter(self, page):
94 conf = (page.config.get('items_filters') or
95 self.config.get('items_filters'))
96 if conf == 'none' or conf == 'nil' or conf == '':
97 conf = None
98 if conf is not None:
99 f = PaginationFilter(value_accessor=page_value_accessor)
100 f.addClausesFromConfig(conf)
101 return f
102 return None
103 64
104 def getSettingAccessor(self): 65 class SimpleAssetsSubDirMixin:
105 return page_value_accessor 66 def _getRelatedAssetsContents(self, item, relationship):
67 if not item.metadata.get('__has_assets', False):
68 return None
106 69
70 assets = {}
71 assets_dir = item.spec + assets_suffix
72 for f in osutil.listdir(assets_dir):
73 fpath = os.path.join(assets_dir, f)
74 name, _ = os.path.splitext(f)
75 if name in assets:
76 raise Exception("Multiple assets are named '%s'." %
77 name)
78 assets[name] = ContentItem(fpath, {'__is_asset': True})
79 return assets
107 80
108 class SimpleListableSourceMixin(IListableSource): 81 def _onFinalizeContent(self, parent_group, items, groups):
109 """ Implements the `IListableSource` interface for sources that map to 82 assetsGroups = []
110 simple file-system structures. 83 for g in groups:
111 """ 84 if not g.spec.endswith(assets_suffix):
112 def listPath(self, rel_path): 85 continue
113 rel_path = rel_path.lstrip('\\/') 86 match = g.spec[:-len(assets_suffix)]
114 path = self._getFullPath(rel_path) 87 item = next(filter(lambda i: i.spec == match), None)
115 names = self._sortFilenames(os.listdir(path)) 88 if item:
89 item.metadata['__has_assets'] = True
90 assetsGroups.append(g)
91 for g in assetsGroups:
92 groups.remove(g)
116 93
117 items = []
118 for name in names:
119 if os.path.isdir(os.path.join(path, name)):
120 if self._filterPageDirname(name):
121 rel_subdir = os.path.join(rel_path, name)
122 items.append((True, name, rel_subdir))
123 else:
124 if self._filterPageFilename(name):
125 slug = self._makeSlug(os.path.join(rel_path, name))
126 metadata = {'slug': slug}
127
128 fac_path = name
129 if rel_path != '.':
130 fac_path = os.path.join(rel_path, name)
131 fac_path = fac_path.replace('\\', '/')
132
133 self._populateMetadata(fac_path, metadata)
134 fac = PageFactory(self, fac_path, metadata)
135
136 name, _ = os.path.splitext(name)
137 items.append((False, name, fac))
138 return items
139
140 def getDirpath(self, rel_path):
141 return os.path.dirname(rel_path)
142
143 def getBasename(self, rel_path):
144 filename = os.path.basename(rel_path)
145 name, _ = os.path.splitext(filename)
146 return name
147
148 def _getFullPath(self, rel_path):
149 return os.path.join(self.fs_endpoint_path, rel_path)
150
151 def _sortFilenames(self, names):
152 return sorted(names)
153
154 def _filterPageDirname(self, name):
155 return True
156
157 def _filterPageFilename(self, name):
158 return True
159
160 def _makeSlug(self, rel_path):
161 return rel_path.replace('\\', '/')
162
163 def _populateMetadata(self, rel_path, metadata, mode=None):
164 pass
165