Mercurial > piecrust2
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 |
