Skip to content

Commit

Permalink
plugins: support for configuring the execution order
Browse files Browse the repository at this point in the history
This is comes from a section of PR ether#3408
  • Loading branch information
ilmartyrk committed Jun 22, 2018
1 parent d1cb72e commit 0a42084
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 8 deletions.
32 changes: 32 additions & 0 deletions doc/api/hooks_server-side.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
# Server-side hooks
These hooks are called on server-side.

## Ordering execution of plugins
In some cases you might want to change the order of which plugins are loaded. Starting from Etherpad **1.8.3**, the `ep.json` syntax allows this.

The `pre` and `post` configuration items allow to schedule the execution order of a plugin using a basic regexp syntax. For example, to schedule the execution of a plugin after every other one, use: `pre: ["ep_*"]`.

If there is a conflict, for example `pre: ["ep_*"], post: ["ep_etherpad-lite/static"]` then the stricter values takes precedence. In the described example, all the other plugins will be loaded before except for `ep_etherpad-lite/static`.

An example of an `ep.json` file where we want `ep_spellcheck` to be run *after* all plugins beginning with `ep_font` (i.e.: `ep_font*`, which would catch plugins such as `ep_font_family` and `ep_font_size`):

```
{
"pre":["ep_font_*"],
"parts": [
{
...
}
]
}
```

And to do before (note the `post` value):
```
{
"post":["ep_font_*"],
"parts": [
{
...
}
]
}
```

## loadSettings
Called from: src/node/server.js

Expand Down
4 changes: 2 additions & 2 deletions doc/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ As your plugins become more and more complex, you will find yourself in the need
Usually a plugin will add only one functionality at a time, so it will probably only use one `part` definition to register its hooks. However, sometimes you have to put different (unrelated) functionalities into one plugin. For this you will want use parts, so other plugins can depend on them.

#### pre/post
The `"pre"` and `"post"` definitions, affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order.
The `"pre"` and `"post"` definitions affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order.

`"pre"` lists parts that must be executed *before* the defining part. `"post"` lists parts that must be executed *after* the defining part.

You can, on a basic level, think of this as double-ended dependency listing. If you have a dependency on another plugin, you can make sure it loads before yours by putting it in `"pre"`. If you are setting up things that might need to be used by a plugin later, you can ensure proper order by putting it in `"post"`.

Note that it would be far more sane to use `"pre"` in almost any case, but if you want to change config variables for another plugin, or maybe modify its environment, `"post"` could definitely be useful.
Note that **it would be far more sane to use `"pre"` in almost any case**, but if you want to change config variables for another plugin, or maybe modify its environment, `"post"` could definitely be useful.

Also, note that dependencies should *also* be listed in your package.json, so they can be `npm install`'d automagically when your plugin gets installed.

Expand Down
141 changes: 135 additions & 6 deletions src/static/js/pluginfw/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,144 @@ async function loadPlugin(packages, plugin_name, plugins, parts) {
}
}

var matcher = function (name, list) {
let matches = [];

try {
const nameRegExp = RegExp(name);

list.forEach(function (item) {
if (nameRegExp.test(item)) {
matches.push(item);
}
});
} catch(e) {
console.log('ERROR', e);
}

return matches;
}

function conflictMatcher (parts, name, type) {
var type;
var opposite = 'pre';
var results = [];

if (type === 'pre') {
opposite = 'post';
}

_.each(parts[name][type] || [], function (item_name) {
var conflictMatches = matcher(item_name, parts[name][opposite] || []);

if (conflictMatches.length) {
var matchObj = {};

matchObj[item_name] = conflictMatches;
results.push(matchObj);
}
});

return results;
}

function checkPluginConflicts(parts) {
var conflicts = [];

_.chain(parts).keys().forEach(function (name) {
var conflictObj = {};
var conflictsPre = conflictMatcher(parts, name, 'pre');
var conflictsPost = conflictMatcher(parts, name, 'post');

conflictObj[name] = {pre: conflictsPre, post: conflictsPost};
conflicts.push(conflictObj);
});

return conflicts;
}

function closedChainChecker (parts, name, match, matchCount, res) {
var noChain = true;
for (var i = 0; i < res.length; i++ ) {
var item = res[i];

if (item[0] === name || item[1] === name) {
if (item[0] === match || item[1] === match) {
//value allready exists
noChain = false;
// check which plugins has stricter pre/post definition
_.each(parts[match].pre || [], function (item_name) {
var matches = matcher(item_name, _.chain(parts).keys().value());
if (matches.length > matchCount) {
res.splice(i, 1);
noChain = true;
}
});
_.each(parts[match].post || [], function (item_name) {
var matches = matcher(item_name, _.chain(parts).keys().value());
if (matches.length > matchCount) {
res.splice(i, 1);
noChain = true;
}
});

i = res.length;
break;
}
}
}

return noChain;
}

function matchHooks (parts, name, res, conflicts, type) {
var names = _.chain(parts).keys().value();
_.each(parts[name][type], function (item_name) {
var matchedNames = matcher(item_name, names);
_.each(matchedNames, function (matchName) {
if (name !== matchName) {
var noConflict = true;

if (conflicts) {
_.each(conflicts, function (conflictObject) {
if (conflictObject[item_name] && (conflictObject[item_name].matches && conflictObject[item_name].matches.indexOf(matchName) > -1)) {
noConflict = false;
}
})
}
if (noConflict) {
noConflict = closedChainChecker(parts, name, matchName, matchedNames.length, res);
}

if (noConflict && type === 'post') {
res.push([name, matchName]);
} else if (noConflict){
res.push([matchName, name]);
}
}
});
});
}

function partsToParentChildList(parts) {
var res = [];
var conflicts = checkPluginConflicts(parts);

_.chain(parts).keys().forEach(function (name) {
_.each(parts[name].post || [], function (child_name) {
res.push([name, child_name]);
});
_.each(parts[name].pre || [], function (parent_name) {
res.push([parent_name, name]);
});
var conflictsPre = [];
var conflictsPost = [];

for (var i=0; i < conflicts.length; i++) {
if (conflicts[i][name]) {
conflictsPre = conflicts[i][name].pre;
conflictsPost = conflicts[i][name].post;
i = conflicts.length;
break;
}
}
matchHooks(parts, name, res, conflictsPost, 'post');
matchHooks(parts, name, res, conflictsPre, 'pre');

if (!parts[name].pre && !parts[name].post) {
res.push([name, ":" + name]); // Include apps with no dependency info
}
Expand Down

0 comments on commit 0a42084

Please sign in to comment.