152 lines
6.0 KiB
Markdown
152 lines
6.0 KiB
Markdown
title: Howto
|
|
---
|
|
summary: Covers the most common things plugins would do and how to implement it.
|
|
---
|
|
sort_key: 30
|
|
---
|
|
body:
|
|
|
|
So you want to build a plugin but it's not quite clear how you would structure
|
|
a plugin to accomplish this? This part of the documentation should help you
|
|
find answers to those questions.
|
|
|
|
## Expose Functionality
|
|
|
|
Most plugins will provide functionality of sorts. There are two places where
|
|
functionality is typically needed: in templates or for other plugins to use.
|
|
Plugins can import from each other just like this, but functionality exposed
|
|
into templates should follow some basic rules:
|
|
|
|
* modify `jinja_env.globals` or `jinja_env.filters` and do not use
|
|
`process-template-context` unless you absolutely know what you are doing.
|
|
* expose as few global variables as possible. If you have a lot you want to
|
|
provide then consider registering an object with the templates and to
|
|
attach multiple attributes on it.
|
|
* use clear names for template globals. This is a shared space that all
|
|
plugins and Lektor itself modify so do not provide a function called
|
|
`get_script` but for instance call it `get_my_plugin_script`.
|
|
|
|
A simple example of a plugin that implements [Gravatar
|
|
:ext](https://en.gravatar.com/) support:
|
|
|
|
```python
|
|
from hashlib import md5
|
|
from werkzeug.urls import url_encode
|
|
from lektor.pluginsystem import Plugin
|
|
|
|
BASE_URL = 'https://secure.gravatar.com/avatar/'
|
|
|
|
def get_gravatar(email, **options):
|
|
fn = md5(email.lower().strip().encode('utf-8')).hexdigest()
|
|
return '%s/%s?%s' % (BASE_URL, fn, url_encode(options))
|
|
|
|
class GravatarPlugin(Plugin):
|
|
name = 'Gravatar'
|
|
def on_setup_env(self, **extra):
|
|
env.jinja_env.filters['gravatar'] = get_gravatar
|
|
```
|
|
|
|
## Trigger off of Lektor events
|
|
|
|
Plugins have handlers which are attached to *events* emitted by Lektor's core,
|
|
or emitted by other plugins. To attach to an event, the plugin needs to have a
|
|
method that's named `on_<event>`, where `<event>` is the event's name with
|
|
underscores instead of hyphons.
|
|
|
|
Events can pass arguments through to handlers in a payload, and handlers should
|
|
always accept an `**extra` argument to accomodate future expansion of the
|
|
payload.
|
|
|
|
The following is the list of all events emitted by Lektor:
|
|
|
|
- `setup-env`: Sent while setting up the environment, after all plugins have
|
|
been instantiated. This is a good place to change values stored on the
|
|
environment.
|
|
- `before-prune`, `after-prune`: Sent before and after cleaning up all data in
|
|
the build folder that does not correspond to known artifacts.
|
|
- `before-build`, `after-build`: Sent before and after building a source
|
|
object. These each get sent once for each source object in the build.
|
|
- `before-build-all`, `after-build-all` Sent before and after the build
|
|
step. These get sent just once for the entire build process.
|
|
- `server-spawn`: Sent right after `lektor server` starts its background
|
|
server process.
|
|
- `server-stop`: Sent right after `lektor server`'s background server process
|
|
is stopped.
|
|
- `process-template-context`: Sent before processing a template, but after
|
|
loading defaults for the template. This is a good place to add things to the
|
|
Jinja environment like filters.
|
|
- `markdown-config`: Sent right after Lektor's Markdown processor is
|
|
configured. The `MarkdownConfig` that is getting set up is passed in, so
|
|
this is a good place to add mixins to change the behavior of the Markdown
|
|
processor.
|
|
- `markdown-meta-init`: Sent right before Lektor's Markdown processor parses
|
|
text to convert it into HTML.
|
|
- `markdown-meta-postprocess`: Sent right after Lektor's Markdown processor
|
|
parses text to convert it into HTML.
|
|
|
|
## Configure Plugins
|
|
|
|
Plugins can come with their own config files and it's encouraged that plugins
|
|
take advantage of this. Each plugin has exactly one INI file called
|
|
`<plugin-id>.ini` inside the `configs` folder.
|
|
|
|
To get access to the plugin the `get_config` function can be used which
|
|
returns a dict like object to access the ini file.
|
|
|
|
```python
|
|
config = self.get_config()
|
|
value = config.get('section.key', 'default_value')
|
|
```
|
|
|
|
This would correspond to this config in `configs/my-plugin.ini`:
|
|
|
|
```ini
|
|
[section]
|
|
key = the value
|
|
```
|
|
|
|
## Dependency Tracking
|
|
|
|
While a lot of dependencies are tracked automatically, when you develop a
|
|
plugin you probably will discover that sometimes you need to track your own
|
|
ones. There are different ways in which dependency tracking can work and
|
|
depending on the situation you might have to use two different mechanisms.
|
|
|
|
1. **Dependency tracking by association**: while a build of a source object
|
|
into an artifact is active more dependencies for that artifact can be
|
|
registered with the [record_dependency :ref](../../api/build/context/record-dependency/)
|
|
method of the context. It takes a filename that should be recorded as
|
|
additional dependency for the current artifact
|
|
2. **Dependency tracking as artifact source**: if you build your own artifact
|
|
you need to define the source files that make up the artifact (if you have
|
|
such a thing). For instance if you build a thumbnail you will need to
|
|
track those source files that are the source images. This can be done
|
|
through the [sub_artifact :ref](../../api/build/context/sub-artifact/)
|
|
method which declares a new artifact.
|
|
|
|
Here examples for both of those in one:
|
|
|
|
```python
|
|
import os
|
|
from flask import json
|
|
from lektor.pluginsystem import Plugin
|
|
|
|
def dump_exif(image):
|
|
ctx = get_ctx()
|
|
path = posixpath.join(image.path, '-exif.json')
|
|
@ctx.sub_artifact(path, sources=[image.source_filename])
|
|
def include_file(artifact):
|
|
ctx.record_dependency(__file__)
|
|
with artifact.open('wb') as f:
|
|
json.dump(image.exif.to_dict(), f)
|
|
return path
|
|
|
|
class ExifDumpPlugin(Plugin):
|
|
def setup_env(self, **extra):
|
|
self.env.jinja_env.globals['dump_exif'] = dump_exif
|
|
```
|
|
|
|
This dumps out the EXIF data into a JSON file and returns the artifact name.
|
|
The source image is tracked as direct source for the artifact and within the
|
|
function we also track the plugin's filename to rebuild if the plugin changes.
|