Files
lektor-groupby-plugin/lektor_groupby/groupby.py

110 lines
4.1 KiB
Python

from lektor.builder import PathCache
from lektor.db import Record # isinstance
from lektor.reporter import reporter # build
from typing import TYPE_CHECKING, List, Optional, Iterable
from .config import Config
from .watcher import Watcher
if TYPE_CHECKING:
from lektor.builder import Builder
from lektor.sourceobj import SourceObject
from .config import AnyConfig
from .resolver import Resolver
from .vobj import GroupBySource
class GroupBy:
'''
Process all children with matching conditions under specified page.
Creates a grouping of pages with similar (self-defined) attributes.
The grouping is performed only once per build.
'''
def __init__(self, resolver: 'Resolver') -> None:
self._building = False
self._watcher = [] # type: List[Watcher]
self._results = [] # type: List[GroupBySource]
self.resolver = resolver
@property
def isBuilding(self) -> bool:
return self._building
def add_watcher(self, key: str, config: 'AnyConfig') -> Watcher:
''' Init Config and add to watch list. '''
w = Watcher(Config.from_any(key, config))
self._watcher.append(w)
return w
def queue_all(self, builder: 'Builder') -> None:
''' Iterate full site-tree and queue all children. '''
# remove disabled watchers
self._watcher = [w for w in self._watcher if w.config.enabled]
if not self._watcher:
return
# initialize remaining (enabled) watchers
for w in self._watcher:
w.initialize(builder.pad)
# iterate over whole build tree
queue = builder.pad.get_all_roots() # type: List[SourceObject]
while queue:
record = queue.pop()
if hasattr(record, 'attachments'):
queue.extend(record.attachments)
if hasattr(record, 'children'):
queue.extend(record.children)
if isinstance(record, Record):
for w in self._watcher:
if w.should_process(record):
w.remember(record)
def make_once(self, filter_keys: Optional[Iterable[str]] = None) -> None:
'''
Perform groupby, iter over sources with watcher callback.
If `filter_keys` is set, ignore all other watchers.
'''
if not self._watcher:
return
remaining = []
for w in self._watcher:
# only process vobjs that are used somewhere
if filter_keys and w.config.key not in filter_keys:
remaining.append(w)
continue
self.resolver.reset(w.config.key)
# these are used in the current context (or on `build_all`)
for vobj in w.iter_sources():
# add original source
self._results.append(vobj)
self.resolver.add(vobj)
# and also add pagination sources
for sub_vobj in vobj.__iter_pagination_sources__():
self._results.append(sub_vobj)
self.resolver.add(sub_vobj)
# TODO: if this should ever run concurrently, pop() from watchers
self._watcher = remaining
def build_all(
self,
builder: 'Builder',
specific: Optional['GroupBySource'] = None
) -> None:
'''
Build actual artifacts (if needed).
If `specific` is set, only build the artifacts for that single vobj
'''
if not self._watcher and not self._results:
return
with reporter.build('groupby', builder): # type:ignore
# in case no page used the |vgroups filter
self.make_once([specific.config.key] if specific else None)
self._building = True
path_cache = PathCache(builder.env)
for vobj in self._results:
if specific and vobj.path != specific.path:
continue
if vobj.slug:
builder.build(vobj, path_cache)
del path_cache
self._building = False
self._results.clear() # garbage collect weak refs