summaryrefslogtreecommitdiffstats
path: root/lib/plugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugin.js')
-rw-r--r--lib/plugin.js183
1 files changed, 183 insertions, 0 deletions
diff --git a/lib/plugin.js b/lib/plugin.js
new file mode 100644
index 0000000..11298fc
--- /dev/null
+++ b/lib/plugin.js
@@ -0,0 +1,183 @@
+var _ = require("lodash");
+var Q = require("q");
+var semver = require("semver");
+var path = require("path");
+var url = require("url");
+var fs = require("./utils/fs");
+var resolve = require('resolve');
+
+var pkg = require("../package.json");
+
+var Plugin = function(book, name) {
+ this.book = book;
+ this.name = name;
+ this.packageInfos = {};
+ this.infos = {};
+
+ // Bind methods
+ _.bindAll(this);
+
+ _.each([
+ "gitbook-plugin-"+name,
+ "gitbook-"+name,
+ name,
+ ], function(_name) {
+ // Load from the book
+ if (this.load(_name, book.root)) return false;
+
+ // Load from default plugins
+ if (this.load(_name, __dirname)) return false;
+ }, this);
+};
+
+// Type of plugins resources
+Plugin.RESOURCES = ["js", "css"];
+Plugin.HOOKS = [
+ "init", "finish", "finish:before"
+]
+
+// Load from a name
+Plugin.prototype.load = function(name, baseDir) {
+ try {
+ var res = resolve.sync(name+"/package.json", { basedir: baseDir });
+
+ this.baseDir = path.dirname(res);
+ this.packageInfos = require(res);
+ this.infos = require(resolve.sync(name, { basedir: baseDir }));
+ this.name = name;
+
+ return true;
+ } catch (e) {
+ this.packageInfos = {};
+ this.infos = {};
+ return false;
+ }
+};
+
+Plugin.prototype.normalizeResource = function(resource) {
+ // Parse the resource path
+ var parsed = url.parse(resource);
+
+ // This is a remote resource
+ // so we will simply link to using it's URL
+ if (parsed.protocol) {
+ return {
+ "url": resource
+ };
+ }
+
+ // This will be copied over from disk
+ // and shipped with the book's build
+ return { "path": this.name+"/"+resource };
+};
+
+// Return resources
+Plugin.prototype._getResources = function(base) {
+ base = base;
+ var book = this.infos[base];
+
+ // Compatibility with version 1.x.x
+ if (base == "website") book = book || this.infos["book"];
+
+ // Nothing specified, fallback to default
+ if (!book) {
+ return Q({});
+ }
+
+ // Dynamic function
+ if(typeof book === "function") {
+ // Call giving it the context of our book
+ return Q().then(book.bind(this.book));
+ }
+
+ // Plain data object
+ return Q(_.cloneDeep(book));
+};
+
+// Normalize resources and return them
+Plugin.prototype.getResources = function(base) {
+ var that = this;
+
+ return this._getResources(base)
+ .then(function(resources) {
+
+ _.each(Plugin.RESOURCES, function(resourceType) {
+ resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource);
+ });
+
+ return resources;
+ });
+};
+
+// Normalize filters and return them
+Plugin.prototype.getFilters = function() {
+ return this.infos.filters || {};
+};
+
+// Normalize blocks and return them
+Plugin.prototype.getBlocks = function() {
+ return this.infos.blocks || {};
+};
+
+// Test if it's a valid plugin
+Plugin.prototype.isValid = function() {
+ var that = this;
+ var isValid = (
+ this.packageInfos &&
+ this.packageInfos.name &&
+ this.packageInfos.engines &&
+ this.packageInfos.engines.gitbook &&
+ semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
+ );
+
+ // Valid hooks
+ _.each(this.infos.hooks, function(hook, hookName) {
+ if (_.contains(Plugin.HOOKS, hookName)) return;
+ that.book.log.warn.ln("Hook '"+hookName+" 'used by plugin '"+that.packageInfos.name+"' has been removed or is deprecated");
+ });
+
+ return isValid;
+};
+
+// Resolve file path
+Plugin.prototype.resolveFile = function(filename) {
+ return path.resolve(this.baseDir, filename);
+};
+
+// Resolve file path
+Plugin.prototype.callHook = function(name, data) {
+ // Our book will be the context to apply
+ var context = this.book;
+
+ var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
+ data = data || {};
+
+ if (!hookFunc) return Q(data);
+
+ this.book.log.debug.ln("call hook", name);
+ if (!_.contains(Plugin.HOOKS, name)) this.book.log.warn.ln("hook '"+name+"' used by plugin '"+this.name+"' is deprecated, and will be remove in the coming versions");
+
+ return Q()
+ .then(function() {
+ return hookFunc.apply(context, [data]);
+ });
+};
+
+// Copy plugin assets fodler
+Plugin.prototype.copyAssets = function(out, base) {
+ var that = this;
+
+ return this.getResources(base)
+ .get('assets')
+ .then(function(assets) {
+ // Assets are undefined
+ if(!assets) return false;
+
+ return fs.copy(
+ that.resolveFile(assets),
+ out
+ ).then(_.constant(true));
+ }, _.constant(false));
+};
+
+module.exports = Plugin;