Support generating a plugin's book info dynamically

This commit is contained in:
Aaron O'Mullan
2014-07-29 21:50:15 -07:00
parent 5dff9b795e
commit 60a7c28e66
2 changed files with 114 additions and 70 deletions
+2 -2
View File
@@ -14,13 +14,13 @@ var BaseGenerator = function(options) {
};
BaseGenerator.prototype.callHook = function(name, data) {
return this.plugins.hook(name, this, data);
return this.plugins.hook(name, data);
};
BaseGenerator.prototype.loadPlugins = function() {
var that = this;
return Plugin.fromList(this.options.plugins, this.options.input)
return Plugin.fromList(this.options.plugins, this.options.input, this)
.then(function(_plugins) {
that.plugins = _plugins;
+112 -68
View File
@@ -10,11 +10,15 @@ var pkg = require("../../package.json");
var RESOURCES = ["js", "css"];
var Plugin = function(name, root) {
var Plugin = function(name, root, generator) {
this.name = name;
this.root = root;
this.packageInfos = {};
this.infos = {};
this.generator = generator;
// Bind methods
_.bindAll(this);
_.each([
"gitbook-plugin-"+name,
@@ -43,28 +47,50 @@ Plugin.prototype.load = function(name, baseDir) {
}
};
// Return resources
Plugin.prototype.getResources = function(resource) {
if (!this.infos.book || !this.infos.book[resource]) {
return [];
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
};
}
return _.chain(this.infos.book[resource])
.map(function(resource) {
var parsed = url.parse(resource);
if (parsed.protocol) return {"url": resource}
else return { "path": this.name+"/"+resource };
}.bind(this))
.value();
// 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() {
var 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 generator
return Q().then(book.bind(this.generator));
}
// Plain data object
return Q(book);
};
// Test if it's a valid plugin
Plugin.prototype.isValid = function() {
return (
this.packageInfos
&& this.packageInfos.name
&& this.packageInfos.engines
&& this.packageInfos.engines.gitbook
&& semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
this.packageInfos &&
this.packageInfos.name &&
this.packageInfos.engines &&
this.packageInfos.engines.gitbook &&
semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
);
};
@@ -74,7 +100,10 @@ Plugin.prototype.resolveFile = function(filename) {
};
// Resolve file path
Plugin.prototype.callHook = function(name, context, data) {
Plugin.prototype.callHook = function(name, data) {
// Our generator will be the context to apply
var context = this.generator;
var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
data = data || {};
@@ -114,7 +143,7 @@ Plugin.normalizeNames = function(names) {
return name.length > 0 && name[0] == "-";
})
.map(function(name) {
return name.slice(1)
return name.slice(1);
})
.value();
@@ -133,69 +162,84 @@ Plugin.normalizeNames = function(names) {
};
// Extract data from a list of plugin
Plugin.fromList = function(names, root) {
Plugin.fromList = function(names, root, generator) {
var failed = [];
// Load plugins
var plugins = _.map(names, function(name) {
var plugin = new Plugin(name, root);
var plugin = new Plugin(name, root, generator);
if (!plugin.isValid()) failed.push(name);
return plugin;
});
if (_.size(failed) > 0) return Q.reject(new Error("Error loading plugins: "+failed.join(",")));
// Get all resources
var resources = _.chain(RESOURCES)
.map(function(resource) {
return [
resource,
_.chain(plugins)
.map(function(plugin) {
return plugin.getResources(resource);
})
// Get resources of plugins
return Q.all(_.map(plugins, function(plugin) {
return plugin.getResources();
}))
// Extract resources out
// css, js, etc ...
.then(function(resources) {
// Group by resource types
return _.chain(RESOURCES)
.map(function(resourceType) {
// Get resources from all the plugins for this current type
return _.chain(resources)
.pluck(resourceType)
.compact()
.flatten()
.value()
];
})
.object()
.value();
resources.html = {}
_.each(plugins, function(plugin) {
if (!plugin.infos.book || !plugin.infos.book.html) return;
var html = plugin.infos.book.html || {};
_.each(html, function(code, key) {
if (!_.isFunction(code)) code = _.constant(code);
if (!resources.html[key]) resources.html[key] = [];
resources.html[key].push(code);
.value();
})
});
return Q({
'list': plugins,
'resources': resources,
'hook': function(name, context, data) {
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, context, ret);
})
}, Q(data));
},
'template': function(name) {
var withTpl = _.find(plugins, function(plugin) {
return (plugin.infos.templates
&& plugin.infos.templates[name]);
.object()
.value();
})
// Extract html snippets
.then(function(resources) {
// Map of html resources by name added by each plugin
resources.html = plugins.reduce(function(accu, plugin) {
var html = plugin.infos.book.html || {};
_.each(html, function(code, key) {
// Turn into function if not one already
if (!_.isFunction(code)) code = _.constant(code);
// Append
accu[key] = (accu[key] || []).concat([code]);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
},
'html': function(tag, context, options) {
return _.map(resources.html[tag] || [], function(code) {
return code.call(context, options);
}).join("\n");
}
return accu;
}, {});
return resources;
})
// Return big multi-plugin object
.then(function(resources) {
return {
'list': plugins,
'resources': resources,
'hook': function(name, data) {
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, ret);
});
}, Q(data));
},
'template': function(name) {
var withTpl = _.find(plugins, function(plugin) {
return (
plugin.infos.templates &&
plugin.infos.templates[name]
);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
},
'html': function(tag, context, options) {
return _.map(resources.html[tag] || [], function(code) {
return code.call(context, options);
}).join("\n");
}
};
});
};