diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7dd95dc
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+examples/** linguist-documentation
diff --git a/examples/Examples.lektorproject b/examples/Examples.lektorproject
new file mode 100644
index 0000000..9e6763f
--- /dev/null
+++ b/examples/Examples.lektorproject
@@ -0,0 +1,5 @@
+[project]
+name = GroupBy Examples
+
+[packages]
+lektor-groupby = 0.9.1
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..89e4914
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,209 @@
+# Usage
+
+Overview:
+- the [quick config](#quick-config) example 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.
+- the [advanced example](#advanced-example) touches on the potentials of the plugin.
+
+
+
+## About
+
+To use the groupby plugin you have to add an attribute to your model file.
+In our case you can refer to the `models/page.ini` model:
+
+```ini
+[fields.tags]
+label = Tags
+type = strings
+testA = true
+testB = true
+
+[fields.body]
+label = Body
+type = markdown
+testC = true
+```
+
+We did define three custom attributes `testA`, `testB`, and `testC`.
+You may add custom attributes to all of the fields.
+It is crucial that the value of the custom attribute is set to true.
+The attribute name is later used for grouping.
+
+
+
+## Quick config
+
+Relevant files:
+```
+configs/groupby.ini
+templates/example-config.html
+```
+
+The easiest way to add tags to your site is by defining the `groupby.ini` config file.
+
+```ini
+[testA]
+root = /
+slug = config/{group}.html
+template = example-config.html
+split = ' '
+```
+
+The configuration parameter are:
+
+1. The section title (`testA`) must be one of the attribute variables we defined in our model.
+2. The `root` parameter (`/`) is the root page of the groupby.
+ 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/`.
+ 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.
+ 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`.
+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.
+ Where `testA` is replaced with whatever attribute you chose.
+5. The `split` parameter (`' '`) will be used as string delimiter.
+ Fields of type `strings` and `checkboxes` are already lists and don't need splitting.
+ The split is only relevant for fields of type `string` or `text`.
+ 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.
+
+
+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.
+
+In your template file you have access to the children (pages) and their tags.
+The emitted `extras` for the child is a list of original tagnames.
+
+```jinja2
+{%- for child, extras in this.children.items() %}
+
Page: {{ child.path }}, Tags: {{ extras }}
+{%- endfor %}
+```
+
+
+
+## Simple example
+
+Relevant files:
+```
+packages/simple-example/lektor_simple.py
+templates/example-simple.html
+```
+
+```python
+def on_groupby_after_build_all(self, groupby, builder, **extra):
+ @groupby.watch('/blog', 'testB', slug='simple/{group}/index.html',
+ template='example-simple.html', flatten=True)
+ def convert_simple_example(args):
+ value = args.field # list, since model is 'strings' type
+ for tag in value:
+ yield slugify(tag), {'val': tag, 'tags_in_page': len(value)}
+
+ # page = args.record # extract additional info from source
+ # fieldKey, flowIndex, flowKey = args.key # or get field index
+ # if flowIndex is None:
+ # obj = page[fieldKey]
+ # else:
+ # obj = page[fieldKey].blocks[flowIndex].get(flowKey)
+```
+
+This example is roughly equivalent to the config file example.
+The parameters of the `@groupby.watch` function (`root`, `attribute`, `slug`, `template`) correspond to the same config parameters described above.
+There is a new `flatten` parameter:
+
+- Flatten determines how Flow elements are processed.
+ 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.
+ 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:
+
+1. The `record` points to the `Page` source which contains the tag.
+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`.
+3. The `field` value is the content of the processed field.
+ The field value is reoughly equivalent to the following:
+
+```python
+args.page[fieldKey].blocks[flowIndex].get(flowKey)
+```
+
+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)`.
+`groupkey` is used to combine & cluster pages and must be URL-safe.
+The `extra-info` 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.
+
+The template file can access and display the `extra-info`:
+
+```jinja2
+{%- for child, extras in this.children.items() %}
+Page: {{ child.title }}
+
+{%- for extra in extras %}
+
Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}
+{%- endfor %}
+
+{%- endfor %}
+```
+
+
+
+## Advanced example
+
+Relevant files:
+```
+configs/advanced.ini
+packages/advanced-example/lektor_advanced.py
+templates/example-advanced.html
+```
+
+The following example is similar to the previous one.
+Except that it loads a config file and replaces in-text occurrences of `{{Tagname}}` with `Tagname`.
+
+```python
+def on_groupby_before_build_all(self, groupby, builder, **extra):
+ # load config
+ regex = re.compile(self.get_config().get('match'))
+ # since we load and use a config file, we need to track the dependency
+ @groupby.depends_on(self.config_filename)
+ @groupby.watch('/', 'testC', slug='advanced/{group}/',
+ template='example-advanced.html')
+ def convert_replace_example(args):
+ # args.field assumed to be Markdown
+ obj = args.field.source
+ for match in regex.finditer(obj):
+ tag = match.group(1)
+ yield slugify(tag), tag
+
+ def _fn(match: re.Match) -> str:
+ tag = match.group(1)
+ return f'{tag}'
+ 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.
+This is required because we would like to modify the source **before** it is written to disk.
+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.
+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.
+All other field typed need to be accessed via `args.record` key indirection.
+
+```ini
+match = {{([^}]{1,32})}}
+```
+
+Lastly, the config file contains a regular expression which matches `{{` + any string less than 32 characters + `}}`.
+Notice, the parenthesis (`()`) will match the inner part but the replace function (`re.sub`) will remove the `{{}}` too.
+
+If the user changes the regex pattern in the config file, we need to rebuild all tags.
+For this purpose we need to track changes to the config file.
+This is done by calling:
+
+```python
+@groupby.depends_on(file1, file2, ...)
+```
diff --git a/examples/configs/advanced.ini b/examples/configs/advanced.ini
new file mode 100644
index 0000000..041a182
--- /dev/null
+++ b/examples/configs/advanced.ini
@@ -0,0 +1 @@
+match = {{([^}]{1,32})}}
\ No newline at end of file
diff --git a/examples/configs/groupby.ini b/examples/configs/groupby.ini
new file mode 100644
index 0000000..bb447ac
--- /dev/null
+++ b/examples/configs/groupby.ini
@@ -0,0 +1,5 @@
+[testA]
+root = /
+slug = config/{group}.html
+template = example-config.html
+split = ' '
diff --git a/examples/content/blog/contents.lr b/examples/content/blog/contents.lr
new file mode 100644
index 0000000..294ee6a
--- /dev/null
+++ b/examples/content/blog/contents.lr
@@ -0,0 +1,9 @@
+_model: blog
+---
+title: Blog
+---
+tags:
+
+Directory
+Blog
+samegroup
diff --git a/examples/content/blog/first-post/contents.lr b/examples/content/blog/first-post/contents.lr
new file mode 100644
index 0000000..184db78
--- /dev/null
+++ b/examples/content/blog/first-post/contents.lr
@@ -0,0 +1,9 @@
+title: Hello Website
+---
+body: This is an example blog post. {{Tag}} in {{blog}}
+---
+tags:
+
+Initial
+Blog Post
+samegroup
diff --git a/examples/content/contents.lr b/examples/content/contents.lr
new file mode 100644
index 0000000..ac2ad0d
--- /dev/null
+++ b/examples/content/contents.lr
@@ -0,0 +1,8 @@
+title: GroupBy Examples
+---
+body: Main body {{tag}} {{Two}}
+---
+tags:
+
+Root
+samegroup
diff --git a/examples/content/projects/contents.lr b/examples/content/projects/contents.lr
new file mode 100644
index 0000000..f3658c6
--- /dev/null
+++ b/examples/content/projects/contents.lr
@@ -0,0 +1,9 @@
+title: Projects
+---
+body:
+
+This is a list of the projects:
+
+* Project 1
+* Project 2
+* Project 3
diff --git a/examples/models/blog-post.ini b/examples/models/blog-post.ini
new file mode 100644
index 0000000..42b4554
--- /dev/null
+++ b/examples/models/blog-post.ini
@@ -0,0 +1,4 @@
+[model]
+name = Blog Post
+inherits = page
+hidden = yes
diff --git a/examples/models/blog.ini b/examples/models/blog.ini
new file mode 100644
index 0000000..7026a3a
--- /dev/null
+++ b/examples/models/blog.ini
@@ -0,0 +1,7 @@
+[model]
+name = Blog
+inherits = page
+hidden = yes
+
+[children]
+model = blog-post
diff --git a/examples/models/page.ini b/examples/models/page.ini
new file mode 100644
index 0000000..20e7173
--- /dev/null
+++ b/examples/models/page.ini
@@ -0,0 +1,18 @@
+[model]
+name = Page
+label = {{ this.title }}
+
+[fields.title]
+label = Title
+type = string
+
+[fields.tags]
+label = Tags
+type = strings
+testA = true
+testB = true
+
+[fields.body]
+label = Body
+type = markdown
+testC = true
diff --git a/examples/packages/advanced-example/lektor_advanced.py b/examples/packages/advanced-example/lektor_advanced.py
new file mode 100644
index 0000000..5bb2942
--- /dev/null
+++ b/examples/packages/advanced-example/lektor_advanced.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+from lektor.pluginsystem import Plugin
+from lektor.utils import slugify
+import re
+
+
+class AdvancedGroupByPlugin(Plugin):
+ def on_groupby_before_build_all(self, groupby, builder, **extra):
+ # load config
+ regex = self.get_config().get('match')
+ try:
+ regex = re.compile(regex)
+ except Exception as e:
+ print('inlinetags.regex not valid: ' + str(e))
+ return
+
+ # since we load and use a config file, we need to track the dependency
+ @groupby.depends_on(self.config_filename)
+ @groupby.watch('/', 'testC', slug='advanced/{group}/',
+ template='example-advanced.html')
+ def convert_replace_example(args):
+ # args.field assumed to be Markdown
+ obj = args.field.source
+ for match in regex.finditer(obj):
+ tag = match.group(1)
+ yield slugify(tag), tag
+
+ def _fn(match: re.Match) -> str:
+ tag = match.group(1)
+ return f'{tag}'
+ args.field.source = regex.sub(_fn, obj)
diff --git a/examples/packages/advanced-example/setup.py b/examples/packages/advanced-example/setup.py
new file mode 100644
index 0000000..d1b1e46
--- /dev/null
+++ b/examples/packages/advanced-example/setup.py
@@ -0,0 +1,12 @@
+from setuptools import setup
+
+setup(
+ name='lektor-advanced',
+ py_modules=['lektor_advanced'],
+ version='1.0',
+ entry_points={
+ 'lektor.plugins': [
+ 'advanced = lektor_advanced:AdvancedGroupByPlugin',
+ ]
+ }
+)
diff --git a/examples/packages/simple-example/lektor_simple.py b/examples/packages/simple-example/lektor_simple.py
new file mode 100644
index 0000000..61490ae
--- /dev/null
+++ b/examples/packages/simple-example/lektor_simple.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from lektor.pluginsystem import Plugin
+from lektor.utils import slugify
+
+
+class SimpleGroupByPlugin(Plugin):
+ def on_groupby_after_build_all(self, groupby, builder, **extra):
+ @groupby.watch('/blog', 'testB', slug='simple/{group}/index.html',
+ template='example-simple.html', flatten=True)
+ def convert_simple_example(args):
+ # Yield groups
+ value = args.field # list type since model is 'strings' type
+ for tag in value:
+ yield slugify(tag), {'val': tag, 'tags_in_page': len(value)}
+ # Everything below is just for documentation purposes
+ page = args.record # extract additional info from source
+ fieldKey, flowIndex, flowKey = args.key # or get field index
+ if flowIndex is None:
+ obj = page[fieldKey]
+ else:
+ obj = page[fieldKey].blocks[flowIndex].get(flowKey)
+ print('page:', page)
+ print(' obj:', obj)
+ print()
diff --git a/examples/packages/simple-example/setup.py b/examples/packages/simple-example/setup.py
new file mode 100644
index 0000000..850c710
--- /dev/null
+++ b/examples/packages/simple-example/setup.py
@@ -0,0 +1,12 @@
+from setuptools import setup
+
+setup(
+ name='lektor-simple',
+ py_modules=['lektor_simple'],
+ version='1.0',
+ entry_points={
+ 'lektor.plugins': [
+ 'simple = lektor_simple:SimpleGroupByPlugin',
+ ]
+ }
+)
diff --git a/examples/templates/blog-post.html b/examples/templates/blog-post.html
new file mode 100644
index 0000000..3109b22
--- /dev/null
+++ b/examples/templates/blog-post.html
@@ -0,0 +1,4 @@
+{% extends "page.html" %}
+{% block body %}
+ {{ this.body }}
+{% endblock %}
diff --git a/examples/templates/blog.html b/examples/templates/blog.html
new file mode 100644
index 0000000..15eaa7f
--- /dev/null
+++ b/examples/templates/blog.html
@@ -0,0 +1,6 @@
+{% extends "page.html" %}
+{% block body %}
+ {% for child in this.children %}
+ {{ child.title }}
+ {% endfor %}
+{% endblock %}
diff --git a/examples/templates/example-advanced.html b/examples/templates/example-advanced.html
new file mode 100644
index 0000000..9804b09
--- /dev/null
+++ b/examples/templates/example-advanced.html
@@ -0,0 +1,3 @@
+
Path: {{ this | url(absolute=True) }}
+
This is: {{this}}
+
Children: {{this.children}}
diff --git a/examples/templates/example-config.html b/examples/templates/example-config.html
new file mode 100644
index 0000000..1deafc2
--- /dev/null
+++ b/examples/templates/example-config.html
@@ -0,0 +1,7 @@
+
Path: {{ this | url(absolute=True) }}
+
This is: {{this}}
+
+ {%- for child, extras in this.children.items() %}
+
Page: {{ child.path }}, Tags: {{ extras }}
+ {%- endfor %}
+
\ No newline at end of file
diff --git a/examples/templates/example-simple.html b/examples/templates/example-simple.html
new file mode 100644
index 0000000..8483d07
--- /dev/null
+++ b/examples/templates/example-simple.html
@@ -0,0 +1,12 @@
+
Path: {{ this | url(absolute=True) }}
+
This is: {{this}}
+
+ {%- for child, extras in this.children.items() %}
+
Page: {{ child.path }}
+
+ {%- for extra in extras %}
+ Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}
+ {%- endfor %}
+
+ {%- endfor %}
+
\ No newline at end of file
diff --git a/examples/templates/page.html b/examples/templates/page.html
new file mode 100644
index 0000000..6a36472
--- /dev/null
+++ b/examples/templates/page.html
@@ -0,0 +1,37 @@
+
+
+{% block title %}Welcome{% endblock %}
+
+
+
+