diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/generate/generator.js | 21 | ||||
-rw-r--r-- | lib/generate/index.js | 16 | ||||
-rw-r--r-- | lib/generate/plugin.js | 180 | ||||
-rw-r--r-- | lib/generate/site/index.js | 39 |
4 files changed, 247 insertions, 9 deletions
diff --git a/lib/generate/generator.js b/lib/generate/generator.js index 13022a5..e303345 100644 --- a/lib/generate/generator.js +++ b/lib/generate/generator.js @@ -1,9 +1,30 @@ +var _ = require("lodash"); var path = require("path"); var Q = require("q"); var fs = require("./fs"); +var Plugin = require("./plugin"); + var BaseGenerator = function(options) { this.options = options; + + this.options.plugins = Plugin.normalizeNames(this.options.plugins); + this.plugins = []; +}; + +BaseGenerator.prototype.callHook = function(name) { + return this.plugins.hook(name, this); +}; + +BaseGenerator.prototype.loadPlugins = function() { + var that = this; + + return Plugin.fromList(this.options.plugins) + .then(function(_plugins) { + that.plugins = _plugins; + + return that.callHook("init"); + }); }; BaseGenerator.prototype.convertFile = function(content, input) { diff --git a/lib/generate/index.js b/lib/generate/index.js index 33edc1f..bbe5ec2 100644 --- a/lib/generate/index.js +++ b/lib/generate/index.js @@ -6,6 +6,7 @@ var tmp = require('tmp'); var fs = require("./fs"); var parse = require("../parse"); +var Plugin = require("./plugin"); var generators = { "site": require("./site"), @@ -42,7 +43,11 @@ var generate = function(options) { githubHost: 'https://github.com/', // Theming - theme: path.resolve(__dirname, '../../theme') + theme: path.resolve(__dirname, '../../theme'), + + // Plugins + plugins: [], + pluginsConfig: {} }); if (!options.input) { @@ -70,9 +75,10 @@ var generate = function(options) { }) }) - // Create the generator + // Create the generator and load plugins .then(function() { generator = new generators[options.generator](options); + return generator.loadPlugins(); }) // Detect multi-languages book @@ -163,6 +169,9 @@ var generate = function(options) { // Finish gneration .then(function() { return generator.finish(); + }) + .then(function() { + return generator.callHook("finish"); }); } }) @@ -234,5 +243,6 @@ var generateFile = function(options) { module.exports = { generators: generators, folder: generate, - file: generateFile + file: generateFile, + Plugin: Plugin }; diff --git a/lib/generate/plugin.js b/lib/generate/plugin.js new file mode 100644 index 0000000..5bd332b --- /dev/null +++ b/lib/generate/plugin.js @@ -0,0 +1,180 @@ +var _ = require("lodash"); +var Q = require("q"); +var semver = require("semver"); +var path = require("path"); +var url = require("url"); +var fs = require("./fs"); + +var pkg = require("../../package.json"); + +var RESOURCES = ["js", "css"]; + +var Plugin = function(name) { + this.name = name; + this.packageInfos = {}; + this.infos = {}; + + _.each([ + name, + "gitbook-"+name, + "gitbook-plugin-"+name, + "gitbook-theme-"+name + ], function(_name) { + if (this.load(_name)) return false; + }.bind(this)); +}; + +// Load from a name +Plugin.prototype.load = function(name) { + try { + this.packageInfos = require(name+"/package.json"); + this.infos = require(name); + this.name = name; + return true; + } catch (e) { + return false; + } +}; + +// Return resources +Plugin.prototype.getResources = function(resource) { + if (!this.infos.book || !this.infos.book[resource]) { + return []; + } + 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(); +}; + +// 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) + ); +}; + +// Resolve file path +Plugin.prototype.resolveFile = function(filename) { + return path.resolve(path.dirname(require.resolve(this.name)), filename); +}; + +// Resolve file path +Plugin.prototype.callHook = function(name, args) { + var hookFunc = this.infos.hooks? this.infos.hooks[name] : null; + args = _.isArray(args) ? args : [args]; + + if (!hookFunc) return Q(); + + return Q() + .then(function() { + return hookFunc.apply(null, args); + }); +}; + +// Copy plugin assets fodler +Plugin.prototype.copyAssets = function(out) { + if (!this.infos.book || !this.infos.book.assets) return Q(); + return fs.copy( + this.resolveFile(this.infos.book.assets), + out + ); +}; + + + + +// Normalize a list of plugin name to use +Plugin.normalizeNames = function(names) { + // Normalize list to an array + names = _.isString(names) ? names.split(":") : (names || []); + + // List plugins to remove + var toremove = _.chain(names) + .filter(function(name) { + return name.length > 0 && name[0] == "-"; + }) + .map(function(name) { + return name.slice(1) + }) + .value(); + + // Merge with defaults + names = _.chain(names) + .concat(Plugin.defaults) + .uniq() + .value(); + + // Remove plugins starting with + names = _.filter(names, function(name) { + return !_.contains(toremove, name) && !(name.length > 0 && name[0] == "-"); + }); + + return names; +}; + +// Extract data from a list of plugin +Plugin.fromList = function(names) { + var failed = []; + + // Load plugins + var plugins = _.map(names, function(name) { + var plugin = new Plugin(name); + 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); + }) + .flatten() + .value() + ]; + }) + .object() + .value(); + + return Q({ + 'list': plugins, + 'resources': resources, + 'hook': function(name, args) { + return _.reduce(plugins, function(prev, plugin) { + return prev.then(function() { + return plugin.callHook(name, args); + }) + }, Q()); + }, + '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]); + } + }); +}; + +// Default plugins +Plugin.defaults = [ + "mixpanel", + "mathjax" +]; + +module.exports = Plugin;
\ No newline at end of file diff --git a/lib/generate/site/index.js b/lib/generate/site/index.js index 4bea547..68328e7 100644 --- a/lib/generate/site/index.js +++ b/lib/generate/site/index.js @@ -30,13 +30,28 @@ var Generator = function() { this.revision = Date.now(); this.indexer = indexer(); - - // Load base template - this.template = swig.compileFile(path.resolve(this.options.theme, 'templates/site.html')); - this.langsTemplate = swig.compileFile(path.resolve(this.options.theme, 'templates/langs.html')); }; util.inherits(Generator, BaseGenerator); +// Load all templates +Generator.prototype.loadTemplates = function() { + this.template = swig.compileFile( + this.plugins.template("site") || path.resolve(this.options.theme, 'templates/site.html') + ); + this.langsTemplate = swig.compileFile( + this.plugins.template("langs") || path.resolve(this.options.theme, 'templates/langs.html') + ); +}; + +// Load plugins +Generator.prototype.loadPlugins = function() { + var that = this; + + return BaseGenerator.prototype.loadPlugins.apply(this) + .then(function() { + return that.loadTemplates(); + }); +}; // Generate a template Generator.prototype._writeTemplate = function(tpl, options, output) { @@ -54,7 +69,10 @@ Generator.prototype._writeTemplate = function(tpl, options, output) { githubHost: that.options.githubHost, summary: that.options.summary, - allNavigation: that.options.navigation + allNavigation: that.options.navigation, + + plugins: that.plugins, + pluginsConfig: JSON.stringify(that.options.pluginsConfig) }, options)); }) .then(function(html) { @@ -134,10 +152,19 @@ Generator.prototype.langsIndex = function(langs) { Generator.prototype.copyAssets = function() { var that = this; + // Copy gitbook assets return fs.copy( path.join(that.options.theme, "assets"), path.join(that.options.output, "gitbook") - ); + ) + // Copy plugins assets + .then(function() { + return Q.all( + _.map(that.plugins.list, function(plugin) { + return plugin.copyAssets(path.join(that.options.output, "gitbook/plugins/", plugin.name)) + }) + ); + }) }; // Dump search index to disk |