lektor-website/content/docs/plugins/howto/contents.lr

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.