update example readme v0.9.3

This commit is contained in:
relikd
2022-04-06 15:42:02 +02:00
parent df4be7c60a
commit 2a6bdf05fd
5 changed files with 148 additions and 62 deletions

7
examples/Makefile Normal file
View File

@@ -0,0 +1,7 @@
.PHONY: server clean plugins
server:
lektor server
clean:
lektor clean --yes -v
plugins:
lektor plugins flush-cache && lektor plugins list

View File

@@ -1,9 +1,10 @@
# Usage # Usage
Overview: Overview:
- the [quick config](#quick-config) example shows how you can use the plugin config to setup a quick and easy tagging system. - [quick config example](#quick-config) shows how you can use the plugin config to setup a quick and easy tagging system.
- the [simple example](#simple-example) goes into detail how this plugin works. - [simple example](#simple-example) goes into detail how to use it in your own plugin.
- the [advanced example](#advanced-example) touches on the potentials of the plugin. - [advanced example](#advanced-example) touches on the potentials of the plugin.
- [Misc](#misc) shows other use-cases.
@@ -45,9 +46,16 @@ The easiest way to add tags to your site is by defining the `groupby.ini` config
```ini ```ini
[testA] [testA]
root = / root = /
slug = config/{group}.html slug = config/{key}.html
template = example-config.html template = example-config.html
split = ' ' split = ' '
enabled = True
[testA.fields]
title = "Tagged: " ~ this.group
[testA.key_map]
Blog = News
``` ```
The configuration parameter are: The configuration parameter are:
@@ -57,9 +65,11 @@ The configuration parameter are:
All results will be placed under this directory, e.g., `/tags/tagname/`. All results will be placed under this directory, e.g., `/tags/tagname/`.
If you use `root = /blog`, the results path will be `/blog/tags/tagname/`. If you use `root = /blog`, the results path will be `/blog/tags/tagname/`.
The groupby plugin will traverse all sub-pages wich contain the attribute `testA`. The groupby plugin will traverse all sub-pages wich contain the attribute `testA`.
3. The `slug` parameter (`config/{group}.html`) is where the results are placed. 3. The `slug` parameter (`config/{key}.html`) is where the results are placed.
In our case, the path resolves to `config/tagname.html`. In our case, the path resolves to `config/tagname.html`.
The default value is `{attrib}/{group}/index.html` which would resolve to `testA/tagname/index.html`. The default value is `{attrib}/{key}/index.html` which would resolve to `testA/tagname/index.html`.
If this field contains `{key}`, it just replaces the value with the group-key.
In all other cases the field value is evaluated in a jinja context.
4. The `template`parameter (`example-config.html`) is used to render the results page. 4. The `template`parameter (`example-config.html`) is used to render the results page.
If no explicit template is set, the default template `groupby-testA.html` will be used. If no explicit template is set, the default template `groupby-testA.html` will be used.
Where `testA` is replaced with whatever attribute you chose. Where `testA` is replaced with whatever attribute you chose.
@@ -68,18 +78,54 @@ The configuration parameter are:
The split is only relevant for fields of type `string` or `text`. The split is only relevant for fields of type `string` or `text`.
These single-line fields are then expanded to lists as well. These single-line fields are then expanded to lists as well.
If you do not provide the `split` option, the whole field value will be used as tagname. If you do not provide the `split` option, the whole field value will be used as tagname.
6. The `enabled` parameter allows you to quickly disable the grouping.
You can have multiple listeners, e.g., one for `/blog/` and another for `/projects/`. You can have multiple listeners, e.g., one for `/blog/` and another for `/projects/`.
Just create as many custom attributes as you like, each having its own section. Just create as many custom attributes as you like, each having its own section.
In your template file you have access to the children (pages) and their tags. There are two additional config mappings, `.fields` and `.key_map`.
The emitted `extras` for the child is a list of original tagnames. Key-value pairs in `.fields` will be added as attributes to your grouping.
You can access them in your template (e.g., `{{this.title}}`).
All of the `.fields` values are evaluted in a jinja context, so be cautious when using plain strings.
The built-in field attributes are:
- `group`: returned group name, e.g., "A Title?"
- `key`: slugified group value, e.g., "a-title"
- `slug`: url path after root node, e.g. "config/a-title.html" (can be `None`)
- `record`: parent node, e.g., `Page(path="/")`
- `children`: dictionary of `{record: extras}` pairs
- `first_child`: first page
- `first_extra`: first extra
- `config`: configuration object (see below)
Without any changes, the `key` value will just be `slugify(group)`.
However, the other mapping `.key_map` will replace `group` with whatever replacement value is provided in the `.key_map` and then slugified.
You could, for example, add a `C# = c-sharp` mapping, which would otherwise just be slugified to `c`.
This is equivalent to `slugify(key_map.get(group))`.
The `config` attribute contains the values that created the group:
- `key`: attribute key, e.g., `TestA`
- `root`: as provided by init, e.g., `/`
- `slug`: the raw value, e.g., `config/{key}.html`
- `template`: as provided by init, e.g., `example-config.html`
- `enabled`: boolean
- `dependencies`: path to config file (if initialized from config)
- `fields`: raw values from `TestA.fields`
- `key_map`: raw values from `TestA.key_map`
In your template file you have access to the attributes, config, and children (pages):
```jinja2 ```jinja2
{%- for child, extras in this.children.items() %} <h2>{{ this.title }}</h2>
<li>Page: {{ child.path }}, Tags: {{ extras }}</li> <p>Key: {{ this.key }}, Attribute: {{ this.config.key }}</p>
<ul>
{%- for child in this.children %}
<li>Page: {{ child.path }}</li>
{%- endfor %} {%- endfor %}
</ul>
``` ```
@@ -93,60 +139,64 @@ templates/example-simple.html
``` ```
```python ```python
def on_groupby_after_build_all(self, groupby, builder, **extra): def on_groupby_before_build_all(self, groupby, builder, **extra):
@groupby.watch('/blog', 'testB', slug='simple/{group}/index.html', watcher = groupby.add_watcher('testB', {
template='example-simple.html', flatten=True) 'root': '/blog',
def convert_simple_example(args): 'slug': 'simple/{key}/index.html',
value = args.field # list, since model is 'strings' type 'template': 'example-simple.html',
for tag in value: })
yield slugify(tag), {'val': tag, 'tags_in_page': len(value)} watcher.config.set_key_map({'Foo': 'bar'})
watcher.config.set_fields({'date': datetime.now()})
# page = args.record # extract additional info from source @watcher.grouping(flatten=True)
# fieldKey, flowIndex, flowKey = args.key # or get field index def convert_simple_example(args):
# if flowIndex is None: # Yield groups
# obj = page[fieldKey] value = args.field # type: list # since model is 'strings' type
# else: for tag in value:
# obj = page[fieldKey].blocks[flowIndex].get(flowKey) yield tag, {'tags_in_page': value}
``` ```
This example is roughly equivalent to the config file example. This example is roughly equivalent to the config example above the parameters of the `groupby.add_watcher` function correspond to the same config parameters.
The parameters of the `@groupby.watch` function (`root`, `attribute`, `slug`, `template`) correspond to the same config parameters described above. Additionally, you can set other types in `set_fields` (all strings are evaluated in jinja context!).
There is a new `flatten` parameter:
- Flatten determines how Flow elements are processed. `@watcher.grouping` sets the callback to generate group keys.
It has one optional flatten parameter:
- `flatten` determines how Flow elements are processed.
If `False`, the callback function is called once per Flow element. If `False`, the callback function is called once per Flow element.
If `True` (default), the callback is called for all Flow-Blocks of the Flow individually. If `True` (default), the callback is called for all Flow-Blocks of the Flow individually.
The attribute `testB` can be attached to either the Flow or a Flow-Block regardless. The attribute `testB` can be attached to either the Flow or a Flow-Block regardless.
The `args` parameter of the `convert_simple_example()` function is a named tuple, it has three attributes: The `args` parameter of the `convert_simple_example()` function is a named tuple, it has three attributes:
1. The `record` points to the `Page` source which contains the tag. 1. The `record` points to the `Page` record that contains the tag.
2. The `key` tuple `(field-key, flow-index, flow-key)` tells which field is processed. 2. The `key` tuple `(field-key, flow-index, flow-key)` tells which field is processed.
For Flow types, `flow-index` and `flow-key` are set, otherwise they are `None`. For Flow types, `flow-index` and `flow-key` are set, otherwise they are `None`.
3. The `field` value is the content of the processed field. 3. The `field` value is the content of the processed field.
The field value is reoughly equivalent to the following: The field value is equivalent to the following:
```python ```python
args.page[fieldKey].blocks[flowIndex].get(flowKey) k = args.key
field = args.record[k.fieldKey].blocks[k.flowIndex].get(k.flowKey)
``` ```
The callback body **can** produce groupings but does not have to. The callback body **can** produce groupings but does not have to.
If you choose to produce an entry, you have to `yield` a tuple pair of `(groupkey, extra-info)`. If you choose to produce an entry, you have to `yield` a string or tuple pair `(group, extra-info)`.
`groupkey` is used to combine & cluster pages and must be URL-safe. `group` is slugified (see above) and then used to combine & cluster pages.
The `extra-info` is passed through to your template file. The `extra-info` (optional) is passed through to your template file.
You can yield more than one entry per source or filter / ignore pages if you don't yield anything. You can yield more than one entry per source.
Or ignore pages if you don't yield anything.
The template file can access and display the `extra-info`: The template file can access and display the `extra-info`:
```jinja2 ```jinja2
{%- for child, extras in this.children.items() %} <p>Custom field date: {{this.date}}</p>
<b>Page: {{ child.title }}<b>
<ul> <ul>
{%- for extra in extras %} {%- for child, extras in this.children.items() -%}
<li>Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}</li> {%- set etxra = (extras|first).tags_in_page %}
<li>{{etxra|length}} tags on page "{{child.path}}": {{etxra}}</li>
{%- endfor %} {%- endfor %}
</ul> </ul>
{%- endfor %}
``` ```
@@ -166,44 +216,72 @@ Except that it loads a config file and replaces in-text occurrences of `{{Tagnam
```python ```python
def on_groupby_before_build_all(self, groupby, builder, **extra): def on_groupby_before_build_all(self, groupby, builder, **extra):
# load config # load config
regex = re.compile(self.get_config().get('match')) config = self.get_config()
# since we load and use a config file, we need to track the dependency regex = config.get('testC.pattern.match')
@groupby.depends_on(self.config_filename) try:
@groupby.watch('/', 'testC', slug='advanced/{group}/', regex = re.compile(regex)
template='example-advanced.html') except Exception as e:
print('inlinetags.regex not valid: ' + str(e))
return
# load config directly (which also tracks dependency)
watcher = groupby.add_watcher('testC', config)
@watcher.grouping()
def convert_replace_example(args): def convert_replace_example(args):
# args.field assumed to be Markdown # args.field assumed to be Markdown
obj = args.field.source obj = args.field.source
slugify_map = {} # type Dict[str, str]
for match in regex.finditer(obj): for match in regex.finditer(obj):
tag = match.group(1) tag = match.group(1)
yield slugify(tag), tag key = yield tag
print('[advanced] slugify:', tag, '->', key)
slugify_map[tag] = key
def _fn(match: re.Match) -> str: def _fn(match: re.Match) -> str:
tag = match.group(1) tag = match.group(1)
return f'<a href="/advanced/{slugify(tag)}/">{tag}</a>' return f'<a href="/advanced/{slugify_map[tag]}/">{tag}</a>'
args.field.source = regex.sub(_fn, obj) args.field.source = regex.sub(_fn, obj)
``` ```
One **important** thing to notice is, we use `on_groupby_before_build_all` to register our callback function. Notice, `add_watcher` accepts a config file as parameter which keeps also track of dependencies and rebuilds pages when you edit the config file.
This is required because we would like to modify the source **before** it is written to disk. Further, the `yield` call returns the slugified group-key.
If you look back to the [simple example](#simple-example), we used `on_groupby_after_build_all` because we did not care when it is executed. First, you do not need to slugify it yourself and second, potential replacements from `key_map` are already handled.
Generally, it makes little difference which one you use (`on-after` is likely less busy).
Just know that you can process the source before or after it is build.
For Markdown fields, we can modify the `source` attribute directly. For Markdown fields, we can modify the `source` attribute directly.
All other field typed need to be accessed via `args.record` key indirection. All other field types need to be accessed via `args.record` key indirection (see [simple example](#simple-example)).
```ini ```ini
[testC]
root = /
slug = "advanced/{}/".format(this.key)
template = example-advanced.html
[testC.pattern]
match = {{([^}]{1,32})}} match = {{([^}]{1,32})}}
``` ```
Lastly, the config file contains a regular expression which matches `{{` + any string less than 32 characters + `}}`. The config file takes the same parameters as the [config example](#quick-config).
Notice, the parenthesis (`()`) will match the inner part but the replace function (`re.sub`) will remove the `{{}}` too. As you can see, `slug` is evaluated in jinja context.
If the user changes the regex pattern in the config file, we need to rebuild all tags. We introduced a new config option `testC.pattern.match`.
For this purpose we need to track changes to the config file. This regular expression matches `{{` + any string less than 32 characters + `}}`.
This is done by calling: Notice, the parenthesis (`()`) will match only the inner part but the replace function (`re.sub`) will remove the `{{}}`.
```python
@groupby.depends_on(file1, file2, ...)
## Misc
It was shortly mentioned above that slugs can be `None` (only if manually set to `slug = None`).
This is useful if you do not want to create subpages but rather an index page containing all groups.
This can be done in combination with the next use-case:
```jinja2
{%- for x in this|vgroups('TestA', 'TestB', recursive=True)|unique|sort %}
<a href="{{ x|url }}">({{ x.group }})</a>
{%- endfor %}
``` ```
You can query the groups of any parent node (including those without slug).
The keys (`'TestA', 'TestB'`) can be omitted which will return all groups of all attributes (you can still filter them with `x.config.key == 'TestC'`).
Refer to `templates/page.html` for usage.

View File

@@ -1,6 +1,6 @@
[testC] [testC]
root = / root = /
slug = advanced/{key}/ slug = "advanced/{}/".format(this.key)
template = example-advanced.html template = example-advanced.html
[testC.pattern] [testC.pattern]

View File

@@ -1,7 +1,7 @@
[testA] [testA]
enabled = True enabled = True
root = / root = /
slug = "config/{}.html".format(this.key) slug = config/{key}.html
template = example-config.html template = example-config.html
split = ' ' split = ' '

View File

@@ -16,7 +16,8 @@ class AdvancedGroupByPlugin(Plugin):
print('inlinetags.regex not valid: ' + str(e)) print('inlinetags.regex not valid: ' + str(e))
return return
watcher = groupby.add_watcher('testC', config) # tracks dependency # load config directly (which also tracks dependency)
watcher = groupby.add_watcher('testC', config)
@watcher.grouping() @watcher.grouping()
def _replace(args: GroupByCallbackArgs) -> Generator[str, str, None]: def _replace(args: GroupByCallbackArgs) -> Generator[str, str, None]: