From da15fe1304dbad1de393791ca5372d0656afeda7 Mon Sep 17 00:00:00 2001 From: relikd Date: Fri, 25 Mar 2022 22:32:33 +0100 Subject: [PATCH] add support for config setup --- README.md | 41 +++++++++++++++++++++++++--- lektor_groupby.py | 68 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 96 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a57ddf7..f306141 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ -# Lektor Plugin: @groupby +# Lektor Plugin: groupby -A generic grouping / clustering plugin. Can be used for tagging and similar tasks. +A generic grouping / clustering plugin. Can be used for tagging or similar tasks. + +Overview: +- the [basic example](#usage-basic-example) goes into detail how this plugin works. +- the [quick config](#usage-quick-config) example show how you can use the plugin config to setup a quick and easy tagging system. +- the [complex example](#usage-a-slightly-more-complex-example) touches on the potential of what is possible. -## Usage: Simple Example +## Usage: Basic example Lets start with a simple example: adding a tags field to your model. Assuming you have a `blog-entry.ini` that is used for all children of `/blog` path. @@ -107,8 +112,37 @@ This is: Iterator[str]: + if self.dependency: + yield self.dependency for record, _ in self.children: yield from record.iter_source_filenames() @@ -194,6 +201,7 @@ class GroupByCreator: self._models: Dict[AttributeKey, Dict[str, Dict[str, str]]] = {} self._func: Dict[str, Set[GroupProducer]] = {} self._resolve_map: Dict[str, UrlResolverConf] = {} # only for server + self._watched_once: Set[GroupingCallback] = set() # -------------- # Initialize @@ -243,8 +251,9 @@ class GroupByCreator: root: str, attrib: AttributeKey, *, flatten: bool = True, # if False, dont explode FlowType + slug: Optional[str] = None, # default: "{attrib}/{group}/index.html" template: Optional[str] = None, # default: "groupby-attrib.html" - slug: Optional[str] = None # default: "{attrib}/{group}/index.html" + dependency: Optional[str] = None ) -> Callable[[GroupingCallback], None]: ''' Decorator to subscribe to attrib-elements. Converter for groupby(). @@ -256,14 +265,30 @@ class GroupByCreator: template: "groupby-attrib.html" slug: "{attrib}/{group}/index.html" ''' + root = root.rstrip('/') + '/' + def _decorator(fn: GroupingCallback): if root not in self._func: self._func[root] = set() self._func[root].add( - GroupProducer(attrib, fn, flatten, template, slug)) + GroupProducer(attrib, fn, flatten, template, slug, dependency)) return _decorator + def watch_once(self, *args, **kwarg) -> Callable[[GroupingCallback], None]: + ''' Same as watch() but listener is auto removed after build. ''' + def _decorator(fn: GroupingCallback): + self._watched_once.add(fn) + self.watch(*args, **kwarg)(fn) + return _decorator + + def remove_watch_once(self) -> None: + ''' Remove all watch-once listeners. ''' + for k, v in self._func.items(): + not_once = {x for x in v if x.func not in self._watched_once} + self._func[k] = not_once + self._watched_once.clear() + # ---------- # Helper # ---------- @@ -346,11 +371,11 @@ class GroupByCreator: def make_cluster(self, root: lektor.db.Record) -> Iterator[GroupBySource]: ''' Group by attrib and build Artifacts. ''' assert isinstance(root, lektor.db.Record) - for attrib, fn, flat, temp, slug in self._func.get(root.url_path, []): - groups = self.groupby(attrib, root, func=fn, flatten=flat) + for attr, fn, fl, temp, slug, dep in self._func.get(root.url_path, []): + groups = self.groupby(attr, root, func=fn, flatten=fl) for group_key, children in groups.items(): - obj = GroupBySource(root, attrib, group_key, children, - template=temp, slug=slug) + obj = GroupBySource(root, attr, group_key, children, + template=temp, slug=slug, dependency=dep) self.track_dev_server_path(obj) yield obj @@ -365,7 +390,7 @@ class GroupByCreator: if len(pieces) >= 2: attrib: AttributeKey = pieces[0] # type: ignore[assignment] group: GroupKey = pieces[1] # type: ignore[assignment] - for attr, _, _, _, slug in self._func.get(node.url_path, []): + for attr, _, _, _, slug, _ in self._func.get(node.url_path, []): if attr == attrib: # TODO: do we need to provide the template too? return GroupBySource(node, attr, group, slug=slug) @@ -420,10 +445,35 @@ class GroupByPlugin(Plugin): if self.creator.should_process(node): yield from self.creator.make_cluster(node) + def _quick_config(self): + config = self.get_config() + for attrib in config.sections(): + sect = config.section_as_dict(attrib) + root = sect.get('root', '/') + slug = sect.get('slug') + temp = sect.get('template') + split = sect.get('split') + + @self.creator.watch_once(root, attrib, template=temp, slug=slug, + dependency=self.config_filename) + def _fn(args): + val = args.field + if isinstance(val, str): + val = val.split(split) if split else [val] # make list + if isinstance(val, list): + for tag in val: + yield slugify(tag), tag + def on_before_build_all(self, builder, **extra): + # load config file quick listeners (before initialize!) + self._quick_config() # parse all models to detect attribs of listeners self.creator.initialize(builder.pad.db) + def on_after_build_all(self, builder, **extra): + # remove all quick listeners (will be added again in the next build) + self.creator.remove_watch_once() + def on_after_prune(self, builder, **extra): # TODO: find better way to prune unreferenced elements GroupByPruner.prune(builder)