mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-22 02:23:30 +00:00
Base
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
describe('satisfies', function() {
|
||||
|
||||
it('should return true for *', function() {
|
||||
expect(gitbook.satisfies('*')).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
module.exports = {};
|
||||
@@ -1,69 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
function BackboneFile(book) {
|
||||
if (!(this instanceof BackboneFile)) return new BackboneFile(book);
|
||||
|
||||
this.book = book;
|
||||
this.log = this.book.log;
|
||||
|
||||
// Filename in the book
|
||||
this.path = '';
|
||||
this.parser;
|
||||
|
||||
_.bindAll(this);
|
||||
}
|
||||
|
||||
// Type of the backbone file
|
||||
BackboneFile.prototype.type = '';
|
||||
|
||||
// Parse a backbone file
|
||||
BackboneFile.prototype.parse = function() {
|
||||
// To be implemented by each child
|
||||
};
|
||||
|
||||
// Handle case where file doesn't exists
|
||||
BackboneFile.prototype.parseNotFound = function() {
|
||||
|
||||
};
|
||||
|
||||
// Return true if backbone file exists
|
||||
BackboneFile.prototype.exists = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Locate a backbone file, could be .md, .asciidoc, etc
|
||||
BackboneFile.prototype.locate = function() {
|
||||
var that = this;
|
||||
var filename = this.book.config.getStructure(this.type, true);
|
||||
this.log.debug.ln('locating', this.type, ':', filename);
|
||||
|
||||
return this.book.findParsableFile(filename)
|
||||
.then(function(result) {
|
||||
if (!result) return;
|
||||
|
||||
that.path = result.path;
|
||||
that.parser = result.parser;
|
||||
});
|
||||
};
|
||||
|
||||
// Read and parse the file
|
||||
BackboneFile.prototype.load = function() {
|
||||
var that = this;
|
||||
this.log.debug.ln('loading', this.type, ':', that.path);
|
||||
|
||||
return this.locate()
|
||||
.then(function() {
|
||||
if (!that.path) return that.parseNotFound();
|
||||
|
||||
that.log.debug.ln(that.type, 'located at', that.path);
|
||||
|
||||
return that.book.readFile(that.path)
|
||||
|
||||
// Parse it
|
||||
.then(function(content) {
|
||||
return that.parse(content);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = BackboneFile;
|
||||
@@ -1,99 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
// Normalize a glossary entry name into a unique id
|
||||
function nameToId(name) {
|
||||
return name.toLowerCase()
|
||||
.replace(/[\/\\\?\%\*\:\;\|\"\'\\<\\>\#\$\(\)\!\.\@]/g, '')
|
||||
.replace(/ /g, '_')
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
A glossary entry is represented by a name and a short description
|
||||
An unique id for the entry is generated using its name
|
||||
*/
|
||||
function GlossaryEntry(name, description) {
|
||||
if (!(this instanceof GlossaryEntry)) return new GlossaryEntry(name, description);
|
||||
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
|
||||
Object.defineProperty(this, 'id', {
|
||||
get: _.bind(this.getId, this)
|
||||
});
|
||||
}
|
||||
|
||||
// Normalizes a glossary entry's name to create an ID
|
||||
GlossaryEntry.prototype.getId = function() {
|
||||
return nameToId(this.name);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A glossary is a list of entries stored in a GLOSSARY.md file
|
||||
*/
|
||||
function Glossary() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.entries = [];
|
||||
}
|
||||
util.inherits(Glossary, BackboneFile);
|
||||
|
||||
Glossary.prototype.type = 'glossary';
|
||||
|
||||
// Get templating context
|
||||
Glossary.prototype.getContext = function() {
|
||||
if (!this.path) return {};
|
||||
|
||||
return {
|
||||
glossary: {
|
||||
path: this.path
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Parse the readme content
|
||||
Glossary.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.glossary(content)
|
||||
.then(function(entries) {
|
||||
that.entries = _.map(entries, function(entry) {
|
||||
return new GlossaryEntry(entry.name, entry.description);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Return an entry by its id
|
||||
Glossary.prototype.get = function(id) {
|
||||
return _.find(this.entries, {
|
||||
id: id
|
||||
});
|
||||
};
|
||||
|
||||
// Find an entry by its name
|
||||
Glossary.prototype.find = function(name) {
|
||||
return this.get(nameToId(name));
|
||||
};
|
||||
|
||||
// Return false if glossary has entries (and exists)
|
||||
Glossary.prototype.isEmpty = function(id) {
|
||||
return _.size(this.entries) === 0;
|
||||
};
|
||||
|
||||
// Convert the glossary to a list of annotations
|
||||
Glossary.prototype.annotations = function() {
|
||||
return _.map(this.entries, function(entry) {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
description: entry.description,
|
||||
href: '/' + this.path + '#' + entry.id
|
||||
};
|
||||
}, this);
|
||||
};
|
||||
|
||||
module.exports = Glossary;
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
Readme: require('./readme'),
|
||||
Summary: require('./summary'),
|
||||
Glossary: require('./glossary'),
|
||||
Langs: require('./langs')
|
||||
};
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
function Language(title, folder) {
|
||||
var that = this;
|
||||
|
||||
this.title = title;
|
||||
this.folder = folder;
|
||||
|
||||
Object.defineProperty(this, 'id', {
|
||||
get: function() {
|
||||
return path.basename(that.folder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
A Langs is a list of languages stored in a LANGS.md file
|
||||
*/
|
||||
function Langs() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.languages = [];
|
||||
}
|
||||
util.inherits(Langs, BackboneFile);
|
||||
|
||||
Langs.prototype.type = 'langs';
|
||||
|
||||
// Parse the readme content
|
||||
Langs.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.langs(content)
|
||||
.then(function(langs) {
|
||||
that.languages = _.map(langs, function(entry) {
|
||||
return new Language(entry.title, entry.path);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Return the list of languages
|
||||
Langs.prototype.list = function() {
|
||||
return this.languages;
|
||||
};
|
||||
|
||||
// Return default/main language for the book
|
||||
Langs.prototype.getDefault = function() {
|
||||
return _.first(this.languages);
|
||||
};
|
||||
|
||||
// Return true if a language is the default one
|
||||
// "lang" cam be a string (id) or a Language entry
|
||||
Langs.prototype.isDefault = function(lang) {
|
||||
lang = lang.id || lang;
|
||||
return (this.cound() > 0 && this.getDefault().id == lang);
|
||||
};
|
||||
|
||||
// Return the count of languages
|
||||
Langs.prototype.count = function() {
|
||||
return _.size(this.languages);
|
||||
};
|
||||
|
||||
// Return templating context for the languages list
|
||||
Langs.prototype.getContext = function() {
|
||||
if (this.count() == 0) return {};
|
||||
|
||||
return {
|
||||
languages: {
|
||||
list: _.map(this.languages, function(lang) {
|
||||
return {
|
||||
id: lang.id,
|
||||
title: lang.title
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Langs;
|
||||
@@ -1,44 +0,0 @@
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
function Readme() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.title;
|
||||
this.description;
|
||||
}
|
||||
util.inherits(Readme, BackboneFile);
|
||||
|
||||
Readme.prototype.type = 'readme';
|
||||
|
||||
/*
|
||||
Return and extension of context to define the readme
|
||||
|
||||
@retrun {Object}
|
||||
*/
|
||||
Readme.prototype.getContext = function() {
|
||||
return {
|
||||
readme: {
|
||||
path: this.path
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Parse the readme content
|
||||
|
||||
@param {String} content
|
||||
@retrun {Promise}
|
||||
*/
|
||||
Readme.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.readme(content)
|
||||
.then(function(out) {
|
||||
that.title = out.title;
|
||||
that.description = out.description;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = Readme;
|
||||
@@ -1,349 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
|
||||
var location = require('../utils/location');
|
||||
var error = require('../utils/error');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
/*
|
||||
An article represent an entry in the Summary.
|
||||
It's defined by a title, a reference, and children articles,
|
||||
the reference (ref) can be a filename + anchor or an external file (optional)
|
||||
*/
|
||||
function TOCArticle(def, parent) {
|
||||
// Title
|
||||
this.title = def.title;
|
||||
|
||||
// Parent TOCPart or TOCArticle
|
||||
this.parent = parent;
|
||||
|
||||
// As string indicating the overall position
|
||||
// ex: '1.0.0'
|
||||
this.level;
|
||||
this._next;
|
||||
this._prev;
|
||||
|
||||
// When README has been automatically added
|
||||
this.isAutoIntro = def.isAutoIntro;
|
||||
this.isIntroduction = def.isIntroduction;
|
||||
|
||||
this.validate();
|
||||
|
||||
// Path can be a relative path or an url, or nothing
|
||||
this.ref = def.path;
|
||||
if (this.ref && !this.isExternal()) {
|
||||
var parts = this.ref.split('#');
|
||||
this.path = (parts.length > 1? parts.slice(0, -1).join('#') : this.ref);
|
||||
this.anchor = (parts.length > 1? '#' + _.last(parts) : null);
|
||||
|
||||
// Normalize path to remove ('./', etc)
|
||||
this.path = location.normalize(this.path);
|
||||
}
|
||||
|
||||
this.articles = _.map(def.articles || [], function(article) {
|
||||
if (article instanceof TOCArticle) return article;
|
||||
return new TOCArticle(article, this);
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Validate the article
|
||||
TOCArticle.prototype.validate = function() {
|
||||
if (!this.title) {
|
||||
throw error.ParsingError(new Error('SUMMARY entries should have an non-empty title'));
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over all articles in this articles
|
||||
TOCArticle.prototype.walk = function(iter, base) {
|
||||
base = base || this.level;
|
||||
|
||||
_.each(this.articles, function(article, i) {
|
||||
var level = levelId(base, i);
|
||||
|
||||
if (iter(article, level) === false) {
|
||||
return false;
|
||||
}
|
||||
article.walk(iter, level);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for an article
|
||||
TOCArticle.prototype.getContext = function() {
|
||||
return {
|
||||
level: this.level,
|
||||
title: this.title,
|
||||
depth: this.depth(),
|
||||
path: this.isExternal()? undefined : this.path,
|
||||
anchor: this.isExternal()? undefined : this.anchor,
|
||||
url: this.isExternal()? this.ref : undefined
|
||||
};
|
||||
};
|
||||
|
||||
// Return true if is pointing to a file
|
||||
TOCArticle.prototype.hasLocation = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Return true if is pointing to an external location
|
||||
TOCArticle.prototype.isExternal = function() {
|
||||
return location.isExternal(this.ref);
|
||||
};
|
||||
|
||||
// Return true if this article is the introduction
|
||||
TOCArticle.prototype.isIntro = function() {
|
||||
return Boolean(this.isIntroduction);
|
||||
};
|
||||
|
||||
// Return true if has children
|
||||
TOCArticle.prototype.hasChildren = function() {
|
||||
return this.articles.length > 0;
|
||||
};
|
||||
|
||||
// Return true if has an article as parent
|
||||
TOCArticle.prototype.hasParent = function() {
|
||||
return !(this.parent instanceof TOCPart);
|
||||
};
|
||||
|
||||
// Return depth of this article
|
||||
TOCArticle.prototype.depth = function() {
|
||||
return this.level.split('.').length;
|
||||
};
|
||||
|
||||
// Return next article in the TOC
|
||||
TOCArticle.prototype.next = function() {
|
||||
return this._next;
|
||||
};
|
||||
|
||||
// Return previous article in the TOC
|
||||
TOCArticle.prototype.prev = function() {
|
||||
return this._prev;
|
||||
};
|
||||
|
||||
// Map over all articles
|
||||
TOCArticle.prototype.map = function(iter) {
|
||||
return _.map(this.articles, iter);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A part of a ToC is a composed of a tree of articles and an optiona title
|
||||
*/
|
||||
function TOCPart(part, parent) {
|
||||
if (!(this instanceof TOCPart)) return new TOCPart(part, parent);
|
||||
|
||||
TOCArticle.apply(this, arguments);
|
||||
}
|
||||
util.inherits(TOCPart, TOCArticle);
|
||||
|
||||
// Validate the part
|
||||
TOCPart.prototype.validate = function() { };
|
||||
|
||||
// Return a sibling (next or prev) of this part
|
||||
TOCPart.prototype.sibling = function(direction) {
|
||||
var parts = this.parent.parts;
|
||||
var pos = _.findIndex(parts, this);
|
||||
|
||||
if (parts[pos + direction]) {
|
||||
return parts[pos + direction];
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Iterate over all entries of the part
|
||||
TOCPart.prototype.walk = function(iter, base) {
|
||||
var articles = this.articles;
|
||||
|
||||
if (articles.length == 0) return;
|
||||
|
||||
// Has introduction?
|
||||
if (articles[0].isIntro()) {
|
||||
if (iter(articles[0], '0') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
articles = articles.slice(1);
|
||||
}
|
||||
|
||||
|
||||
_.each(articles, function(article, i) {
|
||||
var level = levelId(base, i);
|
||||
|
||||
if (iter(article, level) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
article.walk(iter, level);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for a part
|
||||
TOCPart.prototype.getContext = function(onArticle) {
|
||||
onArticle = onArticle || function(article) {
|
||||
return article.getContext();
|
||||
};
|
||||
|
||||
return {
|
||||
title: this.title,
|
||||
articles: this.map(onArticle)
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
A summary is composed of a list of parts, each composed wit a tree of articles.
|
||||
*/
|
||||
function Summary() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.parts = [];
|
||||
this._length = 0;
|
||||
}
|
||||
util.inherits(Summary, BackboneFile);
|
||||
|
||||
Summary.prototype.type = 'summary';
|
||||
|
||||
// Prepare summary when non existant
|
||||
Summary.prototype.parseNotFound = function() {
|
||||
this.update([]);
|
||||
};
|
||||
|
||||
// Parse the summary content
|
||||
Summary.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.summary(content)
|
||||
|
||||
.then(function(summary) {
|
||||
that.update(summary.parts);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for the summary
|
||||
Summary.prototype.getContext = function() {
|
||||
function onArticle(article) {
|
||||
var result = article.getContext();
|
||||
if (article.hasChildren()) {
|
||||
result.articles = article.map(onArticle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
summary: {
|
||||
path: this.path,
|
||||
parts: _.map(this.parts, function(part) {
|
||||
return part.getContext(onArticle);
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Iterate over all entries of the summary
|
||||
// iter is called with an TOCArticle
|
||||
Summary.prototype.walk = function(iter) {
|
||||
var hasMultipleParts = this.parts.length > 1;
|
||||
|
||||
_.each(this.parts, function(part, i) {
|
||||
part.walk(iter, hasMultipleParts? levelId('', i) : null);
|
||||
});
|
||||
};
|
||||
|
||||
// Find a specific article using a filter
|
||||
Summary.prototype.find = function(filter) {
|
||||
var result;
|
||||
|
||||
this.walk(function(article) {
|
||||
if (filter(article)) {
|
||||
result = article;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Flatten the list of articles
|
||||
Summary.prototype.flatten = function() {
|
||||
var result = [];
|
||||
|
||||
this.walk(function(article) {
|
||||
result.push(article);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Return the first TOCArticle for a specific page (or path)
|
||||
Summary.prototype.getArticle = function(page) {
|
||||
if (!_.isString(page)) page = page.path;
|
||||
|
||||
return this.find(function(article) {
|
||||
return article.path == page;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the first TOCArticle for a specific level
|
||||
Summary.prototype.getArticleByLevel = function(lvl) {
|
||||
return this.find(function(article) {
|
||||
return article.level == lvl;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the count of articles in the summary
|
||||
Summary.prototype.count = function() {
|
||||
return this._length;
|
||||
};
|
||||
|
||||
// Prepare the summary
|
||||
Summary.prototype.update = function(parts) {
|
||||
var that = this;
|
||||
|
||||
|
||||
that.parts = _.map(parts, function(part) {
|
||||
return new TOCPart(part, that);
|
||||
});
|
||||
|
||||
// Create first part if none
|
||||
if (that.parts.length == 0) {
|
||||
that.parts.push(new TOCPart({}, that));
|
||||
}
|
||||
|
||||
// Add README as first entry
|
||||
var firstArticle = that.parts[0].articles[0];
|
||||
if (!firstArticle || firstArticle.path != that.book.readme.path) {
|
||||
that.parts[0].articles.unshift(new TOCArticle({
|
||||
title: 'Introduction',
|
||||
path: that.book.readme.path,
|
||||
isAutoIntro: true
|
||||
}, that.parts[0]));
|
||||
}
|
||||
that.parts[0].articles[0].isIntroduction = true;
|
||||
|
||||
|
||||
// Update the count and indexing of "level"
|
||||
var prev = undefined;
|
||||
|
||||
that._length = 0;
|
||||
that.walk(function(article, level) {
|
||||
// Index level
|
||||
article.level = level;
|
||||
|
||||
// Chain articles
|
||||
article._prev = prev;
|
||||
if (prev) prev._next = article;
|
||||
|
||||
prev = article;
|
||||
|
||||
that._length += 1;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Return a level string from a base level and an index
|
||||
function levelId(base, i) {
|
||||
i = i + 1;
|
||||
return (base? [base || '', i] : [i]).join('.');
|
||||
}
|
||||
|
||||
module.exports = Summary;
|
||||
@@ -1,137 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var semver = require('semver');
|
||||
|
||||
var gitbook = require('../gitbook');
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
var validator = require('./validator');
|
||||
var plugins = require('./plugins');
|
||||
|
||||
// Config files to tested (sorted)
|
||||
var CONFIG_FILES = [
|
||||
'book.js',
|
||||
'book.json'
|
||||
];
|
||||
|
||||
/*
|
||||
Config is an interface for the book's configuration stored in "book.json" (or "book.js")
|
||||
*/
|
||||
|
||||
function Config(book, baseConfig) {
|
||||
this.book = book;
|
||||
this.fs = book.fs;
|
||||
this.log = book.log;
|
||||
this.path = '';
|
||||
|
||||
this.baseConfig = baseConfig || {};
|
||||
this.replace({});
|
||||
}
|
||||
|
||||
// Load configuration of the book
|
||||
// and verify that the configuration is satisfying
|
||||
Config.prototype.load = function() {
|
||||
var that = this;
|
||||
var isLanguageBook = this.book.isLanguageBook();
|
||||
|
||||
// Try all potential configuration file
|
||||
return Promise.some(CONFIG_FILES, function(filename) {
|
||||
that.log.debug.ln('try loading configuration from', filename);
|
||||
|
||||
return that.fs.loadAsObject(that.book.resolve(filename))
|
||||
.then(function(_config) {
|
||||
that.log.debug.ln('configuration loaded from', filename);
|
||||
|
||||
that.path = filename;
|
||||
return that.replace(_config);
|
||||
})
|
||||
.fail(function(err) {
|
||||
if (err.code != 'MODULE_NOT_FOUND') throw(err);
|
||||
else return Promise(false);
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
if (!isLanguageBook) {
|
||||
if (!gitbook.satisfies(that.options.gitbook)) {
|
||||
throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
|
||||
}
|
||||
if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(gitbook.version, 'patch'), that.options.gitbook)) {
|
||||
that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \'>='+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
|
||||
}
|
||||
|
||||
that.options.plugins = plugins.toList(that.options.plugins);
|
||||
} else {
|
||||
// Multilingual book should inherits the plugins list from parent
|
||||
that.options.plugins = that.book.parent.config.get('plugins');
|
||||
}
|
||||
|
||||
that.options.gitbook = gitbook.version;
|
||||
});
|
||||
};
|
||||
|
||||
// Replace the whole configuration
|
||||
Config.prototype.replace = function(options) {
|
||||
var that = this;
|
||||
|
||||
// Extend base config
|
||||
options = _.defaults(_.cloneDeep(options), this.baseConfig);
|
||||
|
||||
// Validate the config
|
||||
this.options = validator.validate(options);
|
||||
|
||||
// options.input == book.root
|
||||
Object.defineProperty(this.options, 'input', {
|
||||
get: function () {
|
||||
return that.book.root;
|
||||
}
|
||||
});
|
||||
|
||||
// options.originalInput == book.parent.root
|
||||
Object.defineProperty(this.options, 'originalInput', {
|
||||
get: function () {
|
||||
return that.book.parent? that.book.parent.root : undefined;
|
||||
}
|
||||
});
|
||||
|
||||
error.deprecateField(this.options, 'generator', (this.book.output? this.book.output.name : null), '"options.generator" property is deprecated, use "output.name" instead');
|
||||
error.deprecateField(this.options, 'output', (this.book.output && this.book.output.root? this.book.output.root() : null), '"options.output" property is deprecated, use "output.root()" instead');
|
||||
};
|
||||
|
||||
// Return true if book has a configuration file
|
||||
Config.prototype.exists = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Return path to a structure file
|
||||
// Strip the extension by default
|
||||
Config.prototype.getStructure = function(name, dontStripExt) {
|
||||
var filename = this.options.structure[name];
|
||||
if (dontStripExt) return filename;
|
||||
|
||||
filename = filename.split('.').slice(0, -1).join('.');
|
||||
return filename;
|
||||
};
|
||||
|
||||
// Return a configuration using a key and a default value
|
||||
Config.prototype.get = function(key, def) {
|
||||
return _.get(this.options, key, def);
|
||||
};
|
||||
|
||||
// Update a configuration
|
||||
Config.prototype.set = function(key, value) {
|
||||
return _.set(this.options, key, value);
|
||||
};
|
||||
|
||||
// Return a dump of the configuration
|
||||
Config.prototype.dump = function() {
|
||||
var opts = _.omit(this.options, 'generator', 'output');
|
||||
return _.cloneDeep(opts);
|
||||
};
|
||||
|
||||
// Return templating context
|
||||
Config.prototype.getContext = function() {
|
||||
return {
|
||||
config: this.book.config.dump()
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Config;
|
||||
@@ -1,67 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
// Default plugins added to each books
|
||||
var DEFAULT_PLUGINS = ['highlight', 'search', 'lunr', 'sharing', 'fontsettings', 'theme-default'];
|
||||
|
||||
// Return true if a plugin is a default plugin
|
||||
function isDefaultPlugin(name, version) {
|
||||
return _.contains(DEFAULT_PLUGINS, name);
|
||||
}
|
||||
|
||||
// Normalize a list of plugins to use
|
||||
function normalizePluginsList(plugins) {
|
||||
// Normalize list to an array
|
||||
plugins = _.isString(plugins) ? plugins.split(',') : (plugins || []);
|
||||
|
||||
// Remove empty parts
|
||||
plugins = _.compact(plugins);
|
||||
|
||||
// Divide as {name, version} to handle format like 'myplugin@1.0.0'
|
||||
plugins = _.map(plugins, function(plugin) {
|
||||
if (plugin.name) return plugin;
|
||||
|
||||
var parts = plugin.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
return {
|
||||
'name': name,
|
||||
'version': version // optional
|
||||
};
|
||||
});
|
||||
|
||||
// List plugins to remove
|
||||
var toremove = _.chain(plugins)
|
||||
.filter(function(plugin) {
|
||||
return plugin.name.length > 0 && plugin.name[0] == '-';
|
||||
})
|
||||
.map(function(plugin) {
|
||||
return plugin.name.slice(1);
|
||||
})
|
||||
.value();
|
||||
|
||||
// Merge with defaults
|
||||
_.each(DEFAULT_PLUGINS, function(plugin) {
|
||||
if (_.find(plugins, { name: plugin })) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugins.push({
|
||||
'name': plugin
|
||||
});
|
||||
});
|
||||
// Remove plugin that start with '-'
|
||||
plugins = _.filter(plugins, function(plugin) {
|
||||
return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == '-');
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
plugins = _.uniq(plugins, 'name');
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isDefaultPlugin: isDefaultPlugin,
|
||||
toList: normalizePluginsList
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Configuration files to test (sorted)
|
||||
module.exports = [
|
||||
'book.js',
|
||||
'book.json'
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/*
|
||||
List of default plugins for all books,
|
||||
default plugins should be installed in node dependencies of GitBook
|
||||
*/
|
||||
module.exports = Immutable.List([
|
||||
'highlight',
|
||||
'search',
|
||||
'lunr',
|
||||
'sharing',
|
||||
'fontsettings',
|
||||
'theme-default'
|
||||
]);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Files containing ignore pattner (sorted by priority)
|
||||
module.exports = [
|
||||
'.ignore',
|
||||
'.gitignore',
|
||||
'.bookignore'
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
/*
|
||||
All GitBook plugins are NPM packages starting with this prefix.
|
||||
*/
|
||||
module.exports = 'gitbook-plugin-';
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
/*
|
||||
A filesystem is an interface to read files
|
||||
GitBook can works with a virtual filesystem, for example in the browser.
|
||||
*/
|
||||
|
||||
// .readdir return files/folder as a list of string, folder ending with '/'
|
||||
function pathIsFolder(filename) {
|
||||
return _.last(filename) == '/' || _.last(filename) == '\\';
|
||||
}
|
||||
|
||||
|
||||
function FS() {
|
||||
|
||||
}
|
||||
|
||||
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
FS.prototype.exists = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// Read a file and returns a promise with the content as a buffer
|
||||
FS.prototype.read = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// Read stat infos about a file
|
||||
FS.prototype.stat = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// List files/directories in a directory
|
||||
FS.prototype.readdir = function(folder) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// These methods don't require to be redefined, by default it uses .exists, .read, .write, .list
|
||||
// For optmization, it can be redefined:
|
||||
|
||||
// List files in a directory
|
||||
FS.prototype.listFiles = function(folder) {
|
||||
return this.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.reject(files, pathIsFolder);
|
||||
});
|
||||
};
|
||||
|
||||
// List all files in the fs
|
||||
FS.prototype.listAllFiles = function(folder) {
|
||||
var that = this;
|
||||
|
||||
return this.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.reduce(files, function(prev, file) {
|
||||
return prev.then(function(output) {
|
||||
var isDirectory = pathIsFolder(file);
|
||||
|
||||
if (!isDirectory) {
|
||||
output.push(file);
|
||||
return output;
|
||||
} else {
|
||||
return that.listAllFiles(path.join(folder, file))
|
||||
.then(function(files) {
|
||||
return output.concat(_.map(files, function(_file) {
|
||||
return path.join(file, _file);
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}, Promise([]));
|
||||
});
|
||||
};
|
||||
|
||||
// Read a file as a string (utf-8)
|
||||
FS.prototype.readAsString = function(filename) {
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
return buf.toString('utf-8');
|
||||
});
|
||||
};
|
||||
|
||||
// Find a file in a folder (case incensitive)
|
||||
// Return the real filename
|
||||
FS.prototype.findFile = function findFile(root, filename) {
|
||||
return this.listFiles(root)
|
||||
.then(function(files) {
|
||||
return _.find(files, function(file) {
|
||||
return (file.toLowerCase() == filename.toLowerCase());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Load a JSON file
|
||||
// By default, fs only supports JSON
|
||||
FS.prototype.loadAsObject = function(filename) {
|
||||
return this.readAsString(filename)
|
||||
.then(function(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = FS;
|
||||
+25
-51
@@ -1,36 +1,15 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var fs = require('../utils/fs');
|
||||
var Promise = require('../utils/promise');
|
||||
var BaseFS = require('./');
|
||||
var FS = require('../models/fs');
|
||||
|
||||
function NodeFS() {
|
||||
BaseFS.call(this);
|
||||
}
|
||||
util.inherits(NodeFS, BaseFS);
|
||||
|
||||
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
NodeFS.prototype.exists = function(filename) {
|
||||
return fs.exists(filename);
|
||||
};
|
||||
|
||||
// Read a file and returns a promise with the content as a buffer
|
||||
NodeFS.prototype.read = function(filename) {
|
||||
return fs.readFile(filename);
|
||||
};
|
||||
|
||||
// Read stat infos about a file
|
||||
NodeFS.prototype.stat = function(filename) {
|
||||
return fs.stat(filename);
|
||||
};
|
||||
|
||||
// List files in a directory
|
||||
NodeFS.prototype.readdir = function(folder) {
|
||||
function fsReadDir(folder) {
|
||||
return fs.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.chain(files)
|
||||
files = Immutable.List(files);
|
||||
|
||||
return files
|
||||
.map(function(file) {
|
||||
if (file == '.' || file == '..') return;
|
||||
|
||||
@@ -38,29 +17,24 @@ NodeFS.prototype.readdir = function(folder) {
|
||||
if (stat.isDirectory()) file = file + path.sep;
|
||||
return file;
|
||||
})
|
||||
.compact()
|
||||
.value();
|
||||
.filter(function(file) {
|
||||
return Boolean(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fsLoadObject(filename) {
|
||||
return require(filename);
|
||||
}
|
||||
|
||||
module.exports = function createNodeFS(root) {
|
||||
return FS.create({
|
||||
root: root,
|
||||
|
||||
fsExists: fs.exists,
|
||||
fsReadFile: fs.readFile,
|
||||
fsStatFile: fs.stat,
|
||||
fsReadDir: fsReadDir,
|
||||
fsLoadObject: fsLoadObject
|
||||
});
|
||||
};
|
||||
|
||||
// Load a JSON/JS file
|
||||
NodeFS.prototype.loadAsObject = function(filename) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var jsFile;
|
||||
|
||||
try {
|
||||
jsFile = require.resolve(filename);
|
||||
|
||||
// Invalidate node.js cache for livreloading
|
||||
delete require.cache[jsFile];
|
||||
|
||||
return require(jsFile);
|
||||
}
|
||||
catch(err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = NodeFS;
|
||||
|
||||
+7
-2
@@ -6,8 +6,13 @@ var VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
|
||||
|
||||
var START_TIME = new Date();
|
||||
|
||||
// Verify that this gitbook version satisfies a requirement
|
||||
// We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
|
||||
/**
|
||||
Verify that this gitbook version satisfies a requirement
|
||||
We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
|
||||
|
||||
@param {String} condition
|
||||
@return {Boolean}
|
||||
*/
|
||||
function satisfies(condition) {
|
||||
// Test with real version
|
||||
if (semver.satisfies(VERSION, condition)) return true;
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
var Book = require('./book');
|
||||
var cli = require('./cli');
|
||||
var Book = require('./models/book');
|
||||
var Parse = require('./parse');
|
||||
|
||||
module.exports = {
|
||||
Book: Book,
|
||||
commands: cli.commands
|
||||
Parse: Parse
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('Plugin', function() {
|
||||
var Plugin = require('../plugin');
|
||||
|
||||
describe('createFromString', function() {
|
||||
it('must parse name', function() {
|
||||
var plugin = Plugin.createFromString('hello');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('*');
|
||||
});
|
||||
|
||||
it('must parse version', function() {
|
||||
var plugin = Plugin.createFromString('hello@1.0.0');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLoaded', function() {
|
||||
it('must return false for empty plugin', function() {
|
||||
var plugin = Plugin.createFromString('hello');
|
||||
expect(plugin.isLoaded()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var Ignore = require('ignore');
|
||||
|
||||
var Logger = require('../utils/logger');
|
||||
|
||||
var FS = require('./fs');
|
||||
var Config = require('./config');
|
||||
var Readme = require('./readme');
|
||||
var Summary = require('./summary');
|
||||
var Glossary = require('./glossary');
|
||||
var Languages = require('./languages');
|
||||
|
||||
|
||||
var Book = Immutable.Record({
|
||||
// Logger for outptu message
|
||||
logger: Logger(),
|
||||
|
||||
// Filesystem binded to the book scope to read files/directories
|
||||
fs: FS(),
|
||||
|
||||
// Ignore files parser
|
||||
ignore: Ignore(),
|
||||
|
||||
// Structure files
|
||||
config: Config(),
|
||||
readme: Readme(),
|
||||
summary: Summary(),
|
||||
glossary: Glossary(),
|
||||
languages: Languages()
|
||||
});
|
||||
|
||||
Book.prototype.getLogger = function() {
|
||||
return this.get('logger');
|
||||
};
|
||||
|
||||
Book.prototype.getFS = function() {
|
||||
return this.get('fs');
|
||||
};
|
||||
|
||||
Book.prototype.getIgnore = function() {
|
||||
return this.get('ignore');
|
||||
};
|
||||
|
||||
Book.prototype.getConfig = function() {
|
||||
return this.get('config');
|
||||
};
|
||||
|
||||
Book.prototype.getReadme = function() {
|
||||
return this.get('readme');
|
||||
};
|
||||
|
||||
Book.prototype.getSummary = function() {
|
||||
return this.get('summary');
|
||||
};
|
||||
|
||||
Book.prototype.getGlossary = function() {
|
||||
return this.get('glossary');
|
||||
};
|
||||
|
||||
Book.prototype.getLanguages = function() {
|
||||
return this.get('languages');
|
||||
};
|
||||
|
||||
Book.prototype.getPages = function() {
|
||||
return this.get('pages');
|
||||
};
|
||||
|
||||
/**
|
||||
Return FS instance to access the content
|
||||
|
||||
@return {FS}
|
||||
*/
|
||||
Book.prototype.getContentFS = function() {
|
||||
var fs = this.getFS();
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
return FS.reduceScope(fs, rootFolder);
|
||||
}
|
||||
|
||||
return fs;
|
||||
};
|
||||
|
||||
/**
|
||||
Return root of the book
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getRoot = function() {
|
||||
var fs = this.getFS();
|
||||
return fs.getRoot();
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isFileIgnored = function(filename) {
|
||||
var ignore = this.getIgnore();
|
||||
return ignore.filter([filename]).length == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a content file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isContentFileIgnored = function(filename) {
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
filename = path.join(rootFolder, filename);
|
||||
}
|
||||
|
||||
return this.isFileIgnored(filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Return a page from a book by its path
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.getPage = function(ref) {
|
||||
return this.getPages().get(ref);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a book using a filesystem
|
||||
|
||||
@param {FS} fs
|
||||
@return {Book}
|
||||
*/
|
||||
Book.createForFS = function createForFS(fs) {
|
||||
return new Book({
|
||||
fs: fs
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Book;
|
||||
@@ -0,0 +1,47 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Config = Immutable.Record({
|
||||
file: File(),
|
||||
values: Immutable.Map()
|
||||
});
|
||||
|
||||
Config.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Config.prototype.getValues = function() {
|
||||
return this.get('values');
|
||||
};
|
||||
|
||||
/**
|
||||
Return a configuration value by its key path
|
||||
|
||||
@param {String} key
|
||||
@return {Mixed}
|
||||
*/
|
||||
Config.prototype.getValue = function(keyPath, def) {
|
||||
var values = this.getValues();
|
||||
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
||||
|
||||
return values.getIn(keyPath) || def;
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new config, throw error if invalid
|
||||
|
||||
@param {File} file
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
Config.create = function(file, values) {
|
||||
return new Config({
|
||||
file: file,
|
||||
values: Immutable.fromJS(values)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = Config;
|
||||
@@ -0,0 +1,55 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var parsers = require('../parsers');
|
||||
|
||||
var File = Immutable.Record({
|
||||
// Path of the file, relative to the FS
|
||||
path: String(),
|
||||
|
||||
// Time when file data last modified
|
||||
mtime: Date()
|
||||
});
|
||||
|
||||
File.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
File.prototype.getMTime = function() {
|
||||
return this.get('mtime');
|
||||
};
|
||||
|
||||
/**
|
||||
Return extension of this file (lowercased)
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
File.prototype.getExtension = function() {
|
||||
return path.extname(this.getPath()).toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
Return parser for this file
|
||||
|
||||
@return {Parser}
|
||||
*/
|
||||
File.prototype.getParser = function() {
|
||||
return parsers.getByExt(this.getExtension());
|
||||
};
|
||||
|
||||
/**
|
||||
Create a file from stats informations
|
||||
|
||||
@param {String} filepath
|
||||
@param {Object|fs.Stats} stat
|
||||
@return {File}
|
||||
*/
|
||||
File.createFromStat = function createFromStat(filepath, stat) {
|
||||
return new File({
|
||||
path: filepath,
|
||||
mtime: stat.mtime
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = File;
|
||||
@@ -0,0 +1,277 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
var PathUtil = require('../utils/path');
|
||||
|
||||
var FS = Immutable.Record({
|
||||
root: String(),
|
||||
|
||||
fsExists: Function(),
|
||||
fsReadFile: Function(),
|
||||
fsStatFile: Function(),
|
||||
fsReadDir: Function(),
|
||||
fsLoadObject: null
|
||||
});
|
||||
|
||||
/**
|
||||
Return path to the root
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
FS.prototype.getRoot = function() {
|
||||
return this.get('root');
|
||||
};
|
||||
|
||||
/**
|
||||
Verify that a file is in the fs scope
|
||||
|
||||
@param {String} filename
|
||||
@return {Boolean}
|
||||
*/
|
||||
FS.prototype.isInScope = function(filename) {
|
||||
var rootPath = this.getRoot();
|
||||
filename = path.resolve(rootPath, filename);
|
||||
return PathUtil.isInRoot(rootPath, filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Resolve a file in this FS
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
FS.prototype.resolve = function() {
|
||||
var rootPath = this.getRoot();
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var filename = path.resolve.apply(path, [rootPath].concat(args));
|
||||
|
||||
if (!this.isInScope(filename)) {
|
||||
throw error.FileOutOfScopeError({
|
||||
filename: filename,
|
||||
root: this.root
|
||||
});
|
||||
}
|
||||
|
||||
return filename;
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Boolean>}
|
||||
*/
|
||||
FS.prototype.exists = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
filename = that.resolve(filename);
|
||||
var exists = that.get('fsExists');
|
||||
|
||||
return exists(filename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read a file and returns a promise with the content as a buffer
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Buffer>}
|
||||
*/
|
||||
FS.prototype.read = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
filename = that.resolve(filename);
|
||||
var read = that.get('fsReadFile');
|
||||
|
||||
return read(filename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read a file as a string (utf-8)
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
FS.prototype.readAsString = function(filename, encoding) {
|
||||
encoding = encoding || 'utf8';
|
||||
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
return buf.toString(encoding);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read stat infos about a file
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<File>}
|
||||
*/
|
||||
FS.prototype.statFile = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var filepath = that.resolve(filename);
|
||||
var stat = that.get('fsStatFile');
|
||||
|
||||
return stat(filepath);
|
||||
})
|
||||
.then(function(stat) {
|
||||
return File.createFromStat(filename, stat);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List files/directories in a directory.
|
||||
Directories ends with '/'
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.readDir = function(dirname) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var dirpath = that.resolve(dirname);
|
||||
var readDir = that.get('fsReadDir');
|
||||
|
||||
return readDir(dirpath);
|
||||
})
|
||||
.then(function(files) {
|
||||
return Immutable.List(files);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List only files in a diretcory
|
||||
Directories ends with '/'
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.listFiles = function(dirname) {
|
||||
return this.readDir(dirname)
|
||||
.then(function(files) {
|
||||
return files.filterNot(pathIsFolder);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List all files in a directory
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.listAllFiles = function(folder) {
|
||||
var that = this;
|
||||
folder = folder || '.';
|
||||
|
||||
return this.readDir(folder)
|
||||
.then(function(files) {
|
||||
return Promise.reduce(files, function(out, file) {
|
||||
var isDirectory = pathIsFolder(file);
|
||||
|
||||
if (!isDirectory) {
|
||||
return out.push(file);
|
||||
}
|
||||
|
||||
return that.listAllFiles(path.join(folder, file))
|
||||
.then(function(inner) {
|
||||
return out.concat(inner);
|
||||
});
|
||||
}, Immutable.List());
|
||||
})
|
||||
.then(function(files) {
|
||||
return files.map(function(file) {
|
||||
return path.join(folder, file);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Find a file in a folder (case incensitive)
|
||||
Return the found filename
|
||||
|
||||
@param {String} dirname
|
||||
@param {String} filename
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
FS.prototype.findFile = function(dirname, filename) {
|
||||
return this.listFiles(dirname)
|
||||
.then(function(files) {
|
||||
return files.find(function(file) {
|
||||
return (file.toLowerCase() == filename.toLowerCase());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Load a JSON file
|
||||
By default, fs only supports JSON
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Object>}
|
||||
*/
|
||||
FS.prototype.loadAsObject = function(filename) {
|
||||
var that = this;
|
||||
var fsLoadObject = this.get('fsLoadObject');
|
||||
|
||||
return this.exists(filename)
|
||||
.then(function(exists) {
|
||||
if (!exists) {
|
||||
var err = new Error('Module doesn\'t exist');
|
||||
err.code = 'MODULE_NOT_FOUND';
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (fsLoadObject) {
|
||||
return fsLoadObject(that.resolve(filename));
|
||||
} else {
|
||||
return that.readAsString(filename)
|
||||
.then(function(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a FS instance
|
||||
|
||||
@param {Object} def
|
||||
@return {FS}
|
||||
*/
|
||||
FS.create = function create(def) {
|
||||
return new FS(def);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new FS instance with a reduced scope
|
||||
|
||||
@param {FS} fs
|
||||
@param {String} scope
|
||||
@return {FS}
|
||||
*/
|
||||
FS.reduceScope = function reduceScope(fs, scope) {
|
||||
return fs.set('root', path.join(fs.getRoot(), scope));
|
||||
};
|
||||
|
||||
|
||||
// .readdir return files/folder as a list of string, folder ending with '/'
|
||||
function pathIsFolder(filename) {
|
||||
var lastChar = filename[filename.length - 1];
|
||||
return lastChar == '/' || lastChar == '\\';
|
||||
}
|
||||
|
||||
module.exports = FS;
|
||||
@@ -0,0 +1,13 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Generator = Immutable.Record({
|
||||
name: String()
|
||||
});
|
||||
|
||||
|
||||
|
||||
Generator.create = function(def) {
|
||||
return new Generator(def);
|
||||
};
|
||||
|
||||
module.exports = Generator;
|
||||
@@ -0,0 +1,86 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
var GlossaryEntry = require('./glossaryEntry');
|
||||
|
||||
var Glossary = Immutable.Record({
|
||||
file: File(),
|
||||
entries: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Glossary.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Glossary.prototype.getEntries = function() {
|
||||
return this.get('entries');
|
||||
};
|
||||
|
||||
/**
|
||||
Return an entry by its name
|
||||
|
||||
@param {String} name
|
||||
@return {GlossaryEntry}
|
||||
*/
|
||||
Glossary.prototype.getEntry = function(name) {
|
||||
var entries = this.getEntries();
|
||||
var id = GlossaryEntry.nameToID(name);
|
||||
|
||||
return entries.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
Add/Replace an entry to a glossary
|
||||
|
||||
@param {Glossary} glossary
|
||||
@param {GlossaryEntry} entry
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.addEntry = function addEntry(glossary, entry) {
|
||||
var id = entry.getID();
|
||||
var entries = glossary.getEntries();
|
||||
|
||||
entries = entries.set(id, entry);
|
||||
return glossary.set('entries', entries);
|
||||
};
|
||||
|
||||
/**
|
||||
Add/Replace an entry to a glossary by name/description
|
||||
|
||||
@param {Glossary} glossary
|
||||
@param {GlossaryEntry} entry
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.addEntryByName = function addEntryByName(glossary, name, description) {
|
||||
var entry = new GlossaryEntry({
|
||||
name: name,
|
||||
description: description
|
||||
});
|
||||
|
||||
return Glossary.addEntry(glossary, entry);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a glossary from a list of entries
|
||||
|
||||
@param {String} filename
|
||||
@param {Array|List} entries
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.createFromEntries = function createFromEntries(file, entries) {
|
||||
entries = entries.map(function(entry) {
|
||||
if (!(entry instanceof GlossaryEntry)) {
|
||||
entry = new GlossaryEntry(entry);
|
||||
}
|
||||
|
||||
return [entry.getID(), entry];
|
||||
});
|
||||
|
||||
return new Glossary({
|
||||
file: file,
|
||||
entries: Immutable.OrderedMap(entries)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = Glossary;
|
||||
@@ -0,0 +1,45 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/*
|
||||
A definition represents an entry in the glossary
|
||||
*/
|
||||
|
||||
var GlossaryEntry = Immutable.Record({
|
||||
name: String(),
|
||||
description: String()
|
||||
});
|
||||
|
||||
GlossaryEntry.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
GlossaryEntry.prototype.getDescription = function() {
|
||||
return this.get('description');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Get identifier for this entry
|
||||
|
||||
@retrun {Boolean}
|
||||
*/
|
||||
GlossaryEntry.prototype.getID = function() {
|
||||
return GlossaryEntry.nameToID(this.getName());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Normalize a glossary entry name into a unique id
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
GlossaryEntry.nameToID = function nameToID(name) {
|
||||
return name.toLowerCase()
|
||||
.replace(/[\/\\\?\%\*\:\;\|\"\'\\<\\>\#\$\(\)\!\.\@]/g, '')
|
||||
.replace(/ /g, '_')
|
||||
.trim();
|
||||
};
|
||||
|
||||
|
||||
module.exports = GlossaryEntry;
|
||||
@@ -0,0 +1,21 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Language = Immutable.Record({
|
||||
title: String(),
|
||||
path: String()
|
||||
});
|
||||
|
||||
Language.prototype.getTitle = function() {
|
||||
return this.get('title');
|
||||
};
|
||||
|
||||
Language.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Language.prototype.getID = function() {
|
||||
return path.basename(this.getPath());
|
||||
};
|
||||
|
||||
module.exports = Language;
|
||||
@@ -0,0 +1,37 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Languages = Immutable.Record({
|
||||
file: File(),
|
||||
list: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Languages.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Languages.prototype.getList = function() {
|
||||
return this.get('list');
|
||||
};
|
||||
|
||||
/**
|
||||
Get default languages
|
||||
|
||||
@return {Language}
|
||||
*/
|
||||
Languages.prototype.getDefaultLanguage = function() {
|
||||
return this.getList().first();
|
||||
};
|
||||
|
||||
/**
|
||||
Get a language by its ID
|
||||
|
||||
@param {String} lang
|
||||
@return {Language}
|
||||
*/
|
||||
Languages.prototype.getLanguage = function(lang) {
|
||||
return this.getList().get(lang);
|
||||
};
|
||||
|
||||
module.exports = Languages;
|
||||
@@ -0,0 +1,39 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Page = Immutable.Record({
|
||||
file: File(),
|
||||
|
||||
// Attributes extracted from the YAML header
|
||||
attributes: Immutable.Map(),
|
||||
|
||||
// Content of the page
|
||||
content: String()
|
||||
});
|
||||
|
||||
Page.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Page.prototype.getAttributes = function() {
|
||||
return this.get('attributes');
|
||||
};
|
||||
|
||||
Page.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
/**
|
||||
Create a page for a file
|
||||
|
||||
@param {File} file
|
||||
@return {Page}
|
||||
*/
|
||||
Page.createForFile = function(file) {
|
||||
return new Page({
|
||||
file: file
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
@@ -0,0 +1,84 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
var Plugin = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
// Requirement version (ex: ">1.0.0")
|
||||
version: String(DEFAULT_VERSION),
|
||||
|
||||
// Path to load this plugin
|
||||
path: String(),
|
||||
|
||||
// Depth of this plugin in the dependency tree
|
||||
depth: Number(0),
|
||||
|
||||
// Content of the "package.json"
|
||||
package: Immutable.Map(),
|
||||
|
||||
// Content of the package itself
|
||||
content: Immutable.Map()
|
||||
}, 'Plugin');
|
||||
|
||||
Plugin.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Plugin.prototype.getVersion = function() {
|
||||
return this.get('version');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPackage = function() {
|
||||
return this.get('package');
|
||||
};
|
||||
|
||||
Plugin.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
Plugin.prototype.getDepth = function() {
|
||||
return this.get('depth');
|
||||
};
|
||||
|
||||
/**
|
||||
Return the ID on NPM for this plugin
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Plugin.prototype.getNpmID = function() {
|
||||
return PREFIX + this.getName();
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a plugin is loaded
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isLoaded = function() {
|
||||
return Boolean(this.getPackage().size > 0 && this.getContent().size > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a string
|
||||
|
||||
@param {String}
|
||||
@return {Plugin}
|
||||
*/
|
||||
Plugin.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
|
||||
return new Plugin({
|
||||
name: name,
|
||||
version: version || DEFAULT_VERSION
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Plugin;
|
||||
@@ -0,0 +1,30 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Readme = Immutable.Record({
|
||||
file: File(),
|
||||
title: String(),
|
||||
description: String()
|
||||
});
|
||||
|
||||
Readme.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new readme
|
||||
|
||||
@param {File} file
|
||||
@param {Object} def
|
||||
@return {Readme}
|
||||
*/
|
||||
Readme.create = function(file, def) {
|
||||
return new Readme({
|
||||
file: file,
|
||||
title: def.title,
|
||||
description: def.description
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Readme;
|
||||
@@ -0,0 +1,41 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
var SummaryPart = require('./summaryPart');
|
||||
|
||||
var Summary = Immutable.Record({
|
||||
file: File(),
|
||||
parts: Immutable.List()
|
||||
});
|
||||
|
||||
Summary.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Summary.prototype.getParts = function() {
|
||||
return this.get('parts');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Create a new summary for a list of parts
|
||||
|
||||
@param {Lust|Array} parts
|
||||
@return {Summary}
|
||||
*/
|
||||
Summary.createFromParts = function createFromParts(file, parts) {
|
||||
parts = parts.map(function(part) {
|
||||
if (part instanceof SummaryPart) {
|
||||
return part;
|
||||
}
|
||||
|
||||
return SummaryPart.create(part);
|
||||
});
|
||||
|
||||
return new Summary({
|
||||
file: file,
|
||||
parts: new Immutable.List(parts)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Summary;
|
||||
@@ -0,0 +1,100 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var location = require('../utils/location');
|
||||
|
||||
/*
|
||||
An article represents an entry in the Summary / table of Contents
|
||||
*/
|
||||
|
||||
var SummaryArticle = Immutable.Record({
|
||||
level: String(),
|
||||
title: String(),
|
||||
ref: String(),
|
||||
articles: Immutable.List()
|
||||
});
|
||||
|
||||
SummaryArticle.prototype.getLevel = function() {
|
||||
return this.get('level');
|
||||
};
|
||||
|
||||
SummaryArticle.prototype.getTitle = function() {
|
||||
return this.get('title');
|
||||
};
|
||||
|
||||
SummaryArticle.prototype.getRef = function() {
|
||||
return this.get('ref');
|
||||
};
|
||||
|
||||
SummaryArticle.prototype.getArticles = function() {
|
||||
return this.get('articles');
|
||||
};
|
||||
|
||||
/**
|
||||
Get path (without anchor) to the pointing file
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.getPath = function() {
|
||||
var ref = this.getRef();
|
||||
var parts = ref.split('#');
|
||||
|
||||
var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
|
||||
|
||||
// Normalize path to remove ('./', etc)
|
||||
return location.normalize(pathname);
|
||||
};
|
||||
|
||||
/**
|
||||
Get anchor for this article (or undefined)
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.getAnchor = function() {
|
||||
var ref = this.getRef();
|
||||
var parts = ref.split('#');
|
||||
|
||||
var anchor = (parts.length > 1? '#' + parts[parts.length - 1] : null);
|
||||
return anchor;
|
||||
};
|
||||
|
||||
/**
|
||||
Is article pointing to a page of an absolute url
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
SummaryArticle.prototype.isPage = function() {
|
||||
return !this.isExternal() && this.getRef();
|
||||
};
|
||||
|
||||
/**
|
||||
Is article pointing to aan absolute url
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
SummaryArticle.prototype.isExternal = function() {
|
||||
return location.isExternal(this.getRef());
|
||||
};
|
||||
|
||||
/**
|
||||
Create a SummaryArticle
|
||||
|
||||
@param {Object} def
|
||||
@return {SummaryArticle}
|
||||
*/
|
||||
SummaryArticle.create = function(def) {
|
||||
var articles = (def.articles || []).map(function(article) {
|
||||
if (article instanceof SummaryArticle) {
|
||||
return article;
|
||||
}
|
||||
return SummaryArticle.create(article);
|
||||
});
|
||||
|
||||
return new SummaryArticle({
|
||||
title: def.title,
|
||||
ref: def.ref || def.path,
|
||||
articles: Immutable.List(articles)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = SummaryArticle;
|
||||
@@ -0,0 +1,42 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var SummaryArticle = require('./summaryArticle');
|
||||
|
||||
/*
|
||||
A part represents a section in the Summary / table of Contents
|
||||
*/
|
||||
|
||||
var SummaryPart = Immutable.Record({
|
||||
title: String(),
|
||||
articles: Immutable.List()
|
||||
});
|
||||
|
||||
SummaryPart.prototype.getTitle = function() {
|
||||
return this.get('title');
|
||||
};
|
||||
|
||||
SummaryPart.prototype.getArticles = function() {
|
||||
return this.get('articles');
|
||||
};
|
||||
|
||||
/**
|
||||
Create a SummaryPart
|
||||
|
||||
@param {Object} def
|
||||
@return {SummaryPart}
|
||||
*/
|
||||
SummaryPart.create = function(def) {
|
||||
var articles = (def.articles || []).map(function(article) {
|
||||
if (article instanceof SummaryArticle) {
|
||||
return article;
|
||||
}
|
||||
return SummaryArticle.create(article);
|
||||
});
|
||||
|
||||
return new SummaryPart({
|
||||
title: def.title,
|
||||
articles: Immutable.List(articles)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = SummaryPart;
|
||||
@@ -0,0 +1,42 @@
|
||||
var Parse = require('../parse');
|
||||
|
||||
/**
|
||||
List all assets for a book
|
||||
|
||||
@param {Book} book
|
||||
@param {Map<String:Page>} pages
|
||||
@param
|
||||
*/
|
||||
function listAssets(book, pages) {
|
||||
var fs = book.getContentFS();
|
||||
|
||||
return fs.listAllFiles()
|
||||
.then(function(files) {
|
||||
return files.filterNot(function(file) {
|
||||
return (
|
||||
book.isContentFileIgnored(file) ||
|
||||
pages.has(file)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Generate a book using a generator
|
||||
|
||||
@param {Generator} generator
|
||||
@param {Book} book
|
||||
|
||||
@return {Promise}
|
||||
*/
|
||||
function generateBook(generator, book) {
|
||||
// List all parsable pages
|
||||
return Parse.parsePagesList(book)
|
||||
.then(function(pages) {
|
||||
return listAssets(book, pages);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = generateBook;
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
Generate a page using a generator
|
||||
*/
|
||||
function generatePage(generator, book, page) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = generatePage;
|
||||
@@ -0,0 +1,26 @@
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
var Modifier = require('../');
|
||||
|
||||
|
||||
function JSONGenerator(book) {
|
||||
this.book = book;
|
||||
}
|
||||
|
||||
|
||||
JSONGenerator.prototype.onPage = function(page) {
|
||||
return Modifier.HTMLTransformations(page, [
|
||||
Modifier.svgToImg(),
|
||||
Modifier.svgToPng()
|
||||
])
|
||||
.then(function() {
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
JSONGenerator.prototype.onAsset = function(file) {
|
||||
|
||||
};
|
||||
|
||||
module.exports = JSONGenerator;
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
function generate(book, output) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
generate: generate
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
var slug = require('github-slugid');
|
||||
var HTMLModifier = require('./html');
|
||||
|
||||
var addHeadingID = HTMLModifier('h1,h2,h3,h4,h5,h6', function(heading) {
|
||||
if (heading.attr('id')) return;
|
||||
heading.attr('id', slug(heading.text()));
|
||||
});
|
||||
|
||||
module.exports = addHeadingID;
|
||||
@@ -0,0 +1,16 @@
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
/**
|
||||
|
||||
|
||||
*/
|
||||
function transformTags() {
|
||||
var $elements = $(query);
|
||||
|
||||
return Promise.serie($elements, function(el) {
|
||||
var $el = that.$(el);
|
||||
return fn.call(that, $el);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = transformTags;
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
function modifyPage() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = modifyPage;
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
function inlineAssets() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = inlineAssets;
|
||||
@@ -0,0 +1,28 @@
|
||||
var cheerio = require('cheerio');
|
||||
var domSerializer = require('dom-serializer');
|
||||
|
||||
// Render a cheerio DOM as html
|
||||
function renderDOM($, dom, options) {
|
||||
if (!dom && $._root && $._root.children) {
|
||||
dom = $._root.children;
|
||||
}
|
||||
options = options|| dom.options || $._options;
|
||||
return domSerializer(dom, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
var svgToImg = HTMLModifier('svg', function($svg, $) {
|
||||
var content = '<?xml version="1.0" encoding="UTF-8"?>' + renderDOM($, $svg);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
function svgToImg(page) {
|
||||
var $ = cheerio.load(page.content);
|
||||
|
||||
}
|
||||
|
||||
module.exports = svgToImg;
|
||||
@@ -0,0 +1,36 @@
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var parsers = require('../parsers');
|
||||
|
||||
/**
|
||||
Find a file parsable (Markdown or AsciiDoc) in a book
|
||||
|
||||
@param {Book} book
|
||||
@param {String} filename
|
||||
@return {Promise<>}
|
||||
*/
|
||||
function findParsableFile(book, filename) {
|
||||
var fs = book.getContentFS();
|
||||
var ext = path.extname(filename);
|
||||
var basename = path.basename(filename, ext);
|
||||
var basedir = path.dirname(filename);
|
||||
|
||||
// Ordered list of extensions to test
|
||||
var exts = parsers.extensions;
|
||||
|
||||
return Promise.some(exts, function(ext) {
|
||||
var filepath = basename + ext;
|
||||
|
||||
return fs.findFile(basedir, filepath)
|
||||
.then(function(found) {
|
||||
if (!found || book.isContentFileIgnored(found)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fs.statFile(found);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = findParsableFile;
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
module.exports = {
|
||||
parseBook: require('./parseBook'),
|
||||
parseSummary: require('./parseSummary'),
|
||||
parseGlossary: require('./parseGlossary'),
|
||||
parseReadme: require('./parseReadme'),
|
||||
parseConfig: require('./parseConfig'),
|
||||
parsePagesList: require('./parsePagesList'),
|
||||
parseIgnore: require('./parseIgnore')
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
var parseIgnore = require('./parseIgnore');
|
||||
var parseConfig = require('./parseConfig');
|
||||
var parseGlossary = require('./parseGlossary');
|
||||
var parseSummary = require('./parseSummary');
|
||||
var parseReadme = require('./parseReadme');
|
||||
//var parseLanguages = require('./parseLanguages');
|
||||
|
||||
/**
|
||||
Parse a whole book from a filesystem
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function parseBook(book) {
|
||||
return Promise(book)
|
||||
.then(parseIgnore)
|
||||
.then(parseConfig)
|
||||
//.then(parseLanguages)
|
||||
.then(parseReadme)
|
||||
.then(parseSummary)
|
||||
.then(parseGlossary);
|
||||
}
|
||||
|
||||
module.exports = parseBook;
|
||||
@@ -0,0 +1,51 @@
|
||||
var Promise = require('../utils/promise');
|
||||
var Config = require('../models/config');
|
||||
|
||||
var File = require('../models/file');
|
||||
var validateConfig = require('./validateConfig');
|
||||
var CONFIG_FILES = require('../constants/configFiles');
|
||||
|
||||
/**
|
||||
Parse configuration from "book.json" or "book.js"
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function parseConfig(book) {
|
||||
var fs = book.getFS();
|
||||
|
||||
return Promise.some(CONFIG_FILES, function(filename) {
|
||||
// Is this file ignored?
|
||||
if (book.isFileIgnored(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try loading it
|
||||
return Promise.all([
|
||||
fs.loadAsObject(filename),
|
||||
fs.statFile(filename)
|
||||
])
|
||||
.spread(function(cfg, file) {
|
||||
return {
|
||||
file: file,
|
||||
values: cfg
|
||||
};
|
||||
})
|
||||
.fail(function(err) {
|
||||
if (err.code != 'MODULE_NOT_FOUND') throw(err);
|
||||
else return Promise(false);
|
||||
});
|
||||
})
|
||||
|
||||
.then(function(result) {
|
||||
var file = result? result.file : File();
|
||||
var values = result? result.values : {};
|
||||
|
||||
values = validateConfig(values);
|
||||
|
||||
var config = Config.create(file, values);
|
||||
return book.set('config', config);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseConfig;
|
||||
@@ -0,0 +1,22 @@
|
||||
var parseStructureFile = require('./parseStructureFile');
|
||||
var Glossary = require('../models/glossary');
|
||||
|
||||
/**
|
||||
Parse glossary
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function parseGlossary(book) {
|
||||
return parseStructureFile(book, 'glossary')
|
||||
.spread(function(file, entries) {
|
||||
if (!file) {
|
||||
return book;
|
||||
}
|
||||
|
||||
var glossary = Glossary.createFromEntries(file, entries);
|
||||
return book.set('glossary', glossary);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseGlossary;
|
||||
@@ -0,0 +1,43 @@
|
||||
var Promise = require('../utils/promise');
|
||||
var IGNORE_FILES = require('../constants/ignoreFiles');
|
||||
|
||||
/**
|
||||
Parse ignore files
|
||||
|
||||
@param {Book}
|
||||
@return {Book}
|
||||
*/
|
||||
function parseIgnore(book) {
|
||||
var fs = book.getFS();
|
||||
var ignore = book.getIgnore();
|
||||
|
||||
ignore.addPattern([
|
||||
// Skip Git stuff
|
||||
'.git/',
|
||||
|
||||
// Skip OS X meta data
|
||||
'.DS_Store',
|
||||
|
||||
// Skip stuff installed by plugins
|
||||
'node_modules',
|
||||
|
||||
// Skip book outputs
|
||||
'_book',
|
||||
'*.pdf',
|
||||
'*.epub',
|
||||
'*.mobi'
|
||||
]);
|
||||
|
||||
return Promise.serie(IGNORE_FILES, function(filename) {
|
||||
return fs.readAsString(filename)
|
||||
.then(function(content) {
|
||||
ignore.addPattern(content.toString().split(/\r?\n/));
|
||||
}, function() {
|
||||
return Promise();
|
||||
});
|
||||
})
|
||||
|
||||
.thenResolve(book);
|
||||
}
|
||||
|
||||
module.exports = parseIgnore;
|
||||
@@ -0,0 +1,26 @@
|
||||
var fm = require('front-matter');
|
||||
|
||||
/**
|
||||
Parse a page, read its content and parse the YAMl header
|
||||
|
||||
@param {Book} book
|
||||
@param {Page} page
|
||||
@return {Promise<Page>}
|
||||
*/
|
||||
function parsePage(book, page) {
|
||||
var fs = book.getContentFS();
|
||||
var file = page.getFile();
|
||||
|
||||
return fs.readAsString(file.getPath())
|
||||
.then(function(content) {
|
||||
var parsed = fm(content);
|
||||
|
||||
page = page.set('content', parsed.body);
|
||||
page = page.set('attributes', parsed.attributes);
|
||||
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = parsePage;
|
||||
@@ -0,0 +1,39 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Page = require('../models/page');
|
||||
var walkSummary = require('./walkSummary');
|
||||
|
||||
/**
|
||||
Parse all pages from a book as an OrderedMap
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<OrderedMap<Page>>}
|
||||
*/
|
||||
function parsePagesList(book) {
|
||||
var fs = book.getContentFS();
|
||||
var summary = book.getSummary();
|
||||
var map = Immutable.OrderedMap();
|
||||
|
||||
return walkSummary(summary, function(article) {
|
||||
if (!article.isPage()) return;
|
||||
|
||||
var filepath = article.getPath();
|
||||
|
||||
// Is the page ignored?
|
||||
if (book.isContentFileIgnored(filepath)) return;
|
||||
|
||||
return fs.statFile(filepath)
|
||||
.then(function(file) {
|
||||
map = map.set(
|
||||
filepath,
|
||||
Page.createForFile(file)
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
return map;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = parsePagesList;
|
||||
@@ -0,0 +1,24 @@
|
||||
var parseStructureFile = require('./parseStructureFile');
|
||||
var Readme = require('../models/readme');
|
||||
|
||||
var error = require('../utils/error');
|
||||
|
||||
/**
|
||||
Parse readme from book
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function parseReadme(book) {
|
||||
return parseStructureFile(book, 'readme')
|
||||
.spread(function(file, result) {
|
||||
if (!file) {
|
||||
throw new error.FileNotFoundError({ filename: 'README' });
|
||||
}
|
||||
|
||||
var readme = Readme.create(file, result);
|
||||
return book.set('readme', readme);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseReadme;
|
||||
@@ -0,0 +1,57 @@
|
||||
var findParsableFile = require('./findParsableFile');
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
|
||||
/**
|
||||
Parse a ParsableFile using a specific method
|
||||
|
||||
@param {FS} fs
|
||||
@param {ParsableFile} file
|
||||
@param {String} type
|
||||
@return {Promise<Array<String, List|Map>>}
|
||||
*/
|
||||
function parseFile(fs, file, type) {
|
||||
var filepath = file.getPath();
|
||||
var parser = file.getParser();
|
||||
|
||||
if (!parser) {
|
||||
return Promise.reject(
|
||||
error.FileNotParsableError({
|
||||
filename: filepath
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return fs.readAsString(filepath)
|
||||
.then(function(content) {
|
||||
return [
|
||||
file,
|
||||
parser[type](content)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Parse a structure file (ex: SUMMARY.md, GLOSSARY.md).
|
||||
It uses the configuration to find the specified file.
|
||||
|
||||
@param {Book} book
|
||||
@param {String} type: one of ["glossary", "readme", "summary"]
|
||||
@return {Promise<List|Map>}
|
||||
*/
|
||||
function parseStructureFile(book, type) {
|
||||
var fs = book.getContentFS();
|
||||
var config = book.getConfig();
|
||||
|
||||
var fileToSearch = config.getValue(['structure', type]);
|
||||
|
||||
return findParsableFile(book, fileToSearch)
|
||||
.then(function(file) {
|
||||
if (!file) return [undefined, undefined];
|
||||
|
||||
return parseFile(fs, file, type);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseStructureFile;
|
||||
@@ -0,0 +1,22 @@
|
||||
var parseStructureFile = require('./parseStructureFile');
|
||||
var Summary = require('../models/summary');
|
||||
|
||||
/**
|
||||
Parse summary in a book
|
||||
|
||||
@param {Book} book
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function parseSummary(book) {
|
||||
return parseStructureFile(book, 'summary')
|
||||
.spread(function(file, result) {
|
||||
if (!file) {
|
||||
return book;
|
||||
}
|
||||
|
||||
var summary = Summary.createFromParts(file, result.parts);
|
||||
return book.set('summary', summary);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseSummary;
|
||||
@@ -2,12 +2,17 @@ var jsonschema = require('jsonschema');
|
||||
var jsonSchemaDefaults = require('json-schema-defaults');
|
||||
var mergeDefaults = require('merge-defaults');
|
||||
|
||||
var schema = require('./schema');
|
||||
var schema = require('../constants/configSchema');
|
||||
var error = require('../utils/error');
|
||||
|
||||
// Validate a book.json content
|
||||
// And return a mix with the default value
|
||||
function validate(bookJson) {
|
||||
/**
|
||||
Validate a book.json content
|
||||
And return a mix with the default value
|
||||
|
||||
@param {Object} bookJson
|
||||
@return {Object}
|
||||
*/
|
||||
function validateConfig(bookJson) {
|
||||
var v = new jsonschema.Validator();
|
||||
var result = v.validate(bookJson, schema, {
|
||||
propertyName: 'config'
|
||||
@@ -23,6 +28,4 @@ function validate(bookJson) {
|
||||
return mergeDefaults(bookJson, defaults);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validate: validate
|
||||
};
|
||||
module.exports = validateConfig;
|
||||
@@ -0,0 +1,34 @@
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
/**
|
||||
Walk over a list of articles
|
||||
|
||||
@param {List<Article>} articles
|
||||
@param {Function(article)}
|
||||
@return {Promise}
|
||||
*/
|
||||
function walkArticles(articles, fn) {
|
||||
return Promise.forEach(articles, function(article) {
|
||||
return Promise(fn(article))
|
||||
.then(function() {
|
||||
return walkArticles(article.getArticles(), fn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Walk over summary and execute "fn" on each article
|
||||
|
||||
@param {Summary} summary
|
||||
@param {Function(article)}
|
||||
@return {Promise}
|
||||
*/
|
||||
function walkSummary(summary, fn) {
|
||||
var parts = summary.getParts();
|
||||
|
||||
return Promise.forEach(parts, function(part) {
|
||||
return walkArticles(part.getArticles(), fn);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = walkSummary;
|
||||
@@ -0,0 +1,18 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
var path = require('path');
|
||||
|
||||
describe('findInstalled', function() {
|
||||
var findInstalled = require('../findInstalled');
|
||||
|
||||
pit('must list default plugins for gitbook directory', function() {
|
||||
return findInstalled(path.resolve(__dirname, '../../../'))
|
||||
.then(function(plugins) {
|
||||
expect(plugins.size).toBe(7);
|
||||
|
||||
expect(plugins.has('fontsettings')).toBe(true);
|
||||
expect(plugins.has('search')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('listAll', function() {
|
||||
var listAll = require('../listAll');
|
||||
|
||||
it('must list from string', function() {
|
||||
var plugins = listAll('ga,great');
|
||||
|
||||
expect(plugins.size).toBe(8);
|
||||
|
||||
expect(plugins.has('ga')).toBe(true);
|
||||
expect(plugins.has('great')).toBe(true);
|
||||
|
||||
expect(plugins.has('search')).toBe(true);
|
||||
});
|
||||
|
||||
it('must list from array', function() {
|
||||
var plugins = listAll(['ga', 'great']);
|
||||
|
||||
expect(plugins.size).toBe(8);
|
||||
|
||||
expect(plugins.has('ga')).toBe(true);
|
||||
expect(plugins.has('great')).toBe(true);
|
||||
|
||||
expect(plugins.has('search')).toBe(true);
|
||||
});
|
||||
|
||||
it('must parse version (semver)', function() {
|
||||
var plugins = listAll(['ga@1.0.0', 'great@>=4.0.0']);
|
||||
|
||||
expect(plugins.has('ga')).toBe(true);
|
||||
expect(plugins.has('great')).toBe(true);
|
||||
|
||||
var ga = plugins.get('ga');
|
||||
expect(ga.getVersion()).toBe('1.0.0');
|
||||
|
||||
var great = plugins.get('great');
|
||||
expect(great.getVersion()).toBe('>=4.0.0');
|
||||
});
|
||||
|
||||
it('must parse version (git)', function() {
|
||||
var plugins = listAll(['ga@git+https://github.com/GitbookIO/plugin-ga.git', 'great@git+ssh://samy@github.com/GitbookIO/plugin-ga.git']);
|
||||
|
||||
expect(plugins.has('ga')).toBe(true);
|
||||
expect(plugins.has('great')).toBe(true);
|
||||
|
||||
var ga = plugins.get('ga');
|
||||
expect(ga.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
|
||||
|
||||
var great = plugins.get('great');
|
||||
expect(great.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
var Promise = require('../../utils/promise');
|
||||
var Plugin = require('../../models/plugin');
|
||||
|
||||
|
||||
describe('validatePlugin', function() {
|
||||
var validatePlugin = require('../validatePlugin');
|
||||
|
||||
pit('must not validate a not loaded plugin', function() {
|
||||
var plugin = Plugin.createFromString('test');
|
||||
|
||||
return validatePlugin(plugin)
|
||||
.then(function() {
|
||||
throw new Error('Should not be validate');
|
||||
}, function(err) {
|
||||
return Promise();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var findInstalled = require('./findInstalled');
|
||||
|
||||
/**
|
||||
List all plugins installed in a book
|
||||
|
||||
@param {Book}
|
||||
@return {Promise<OrderedMap<String:Plugin>>}
|
||||
*/
|
||||
function findForBook(book) {
|
||||
return Promise.all([
|
||||
findInstalled(path.resolve(__dirname, '../..')),
|
||||
findInstalled(book.getRoot())
|
||||
])
|
||||
.then(function(results) {
|
||||
return Immutable.List(results)
|
||||
.reduce(function(out, result) {
|
||||
return out.merge(result);
|
||||
}, Immutable.OrderedMap());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = findForBook;
|
||||
@@ -0,0 +1,66 @@
|
||||
var readInstalled = require('read-installed');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var Plugin = require('../models/plugin');
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
|
||||
/**
|
||||
Validate if a package name is a GitBook plugin
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
function validateId(name) {
|
||||
return name && name.indexOf(PREFIX) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
List all packages installed inside a folder
|
||||
|
||||
@param {String} folder
|
||||
@return {OrderedMap<String:Plugin>}
|
||||
*/
|
||||
function findInstalled(folder) {
|
||||
var options = {
|
||||
dev: false,
|
||||
log: function() {},
|
||||
depth: 4
|
||||
};
|
||||
var results = Immutable.OrderedMap();
|
||||
|
||||
function onPackage(pkg, isRoot) {
|
||||
if (!pkg.name) return;
|
||||
|
||||
var name = pkg.name;
|
||||
var version = pkg.version;
|
||||
var pkgPath = pkg.realPath;
|
||||
var depth = pkg.depth;
|
||||
var dependencies = pkg.dependencies;
|
||||
|
||||
var pluginName = name.slice(PREFIX.length);
|
||||
|
||||
if (!validateId(name)){
|
||||
if (!isRoot) return;
|
||||
} else {
|
||||
results = results.set(pluginName, Plugin({
|
||||
name: pluginName,
|
||||
version: version,
|
||||
path: pkgPath,
|
||||
depth: depth
|
||||
}));
|
||||
}
|
||||
|
||||
Immutable.Map(dependencies).forEach(function(dep) {
|
||||
onPackage(dep);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.nfcall(readInstalled, folder, options)
|
||||
.then(function(data) {
|
||||
onPackage(data, true);
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = findInstalled;
|
||||
+1
-183
@@ -1,188 +1,6 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var fs = require('../utils/fs');
|
||||
var BookPlugin = require('./plugin');
|
||||
var registry = require('./registry');
|
||||
var pluginsConfig = require('../config/plugins');
|
||||
module.exports = {
|
||||
|
||||
/*
|
||||
PluginsManager is an interface to work with multiple plugins at once:
|
||||
- Extract assets from plugins
|
||||
- Call hooks for all plugins, etc
|
||||
*/
|
||||
|
||||
function PluginsManager(book) {
|
||||
this.book = book;
|
||||
this.log = this.book.log;
|
||||
this.plugins = [];
|
||||
|
||||
_.bindAll(this);
|
||||
}
|
||||
|
||||
// Returns the list of plugins
|
||||
PluginsManager.prototype.list = function() {
|
||||
return this.plugins;
|
||||
};
|
||||
|
||||
// Return count of plugins loaded
|
||||
PluginsManager.prototype.count = function() {
|
||||
return _.size(this.plugins);
|
||||
};
|
||||
|
||||
// Returns a plugin by its name
|
||||
PluginsManager.prototype.get = function(name) {
|
||||
return _.find(this.plugins, {
|
||||
id: name
|
||||
});
|
||||
};
|
||||
|
||||
// Load a plugin (could be a BookPlugin or {name,path})
|
||||
PluginsManager.prototype.load = function(plugin) {
|
||||
var that = this;
|
||||
|
||||
if (_.isArray(plugin)) {
|
||||
return Promise.serie(plugin, that.load);
|
||||
}
|
||||
|
||||
return Promise()
|
||||
|
||||
// Initiate and load the plugin
|
||||
.then(function() {
|
||||
if (!(plugin instanceof BookPlugin)) {
|
||||
plugin = new BookPlugin(that.book, plugin.name, plugin.path);
|
||||
}
|
||||
|
||||
if (that.get(plugin.id)) {
|
||||
throw new Error('Plugin "'+plugin.id+'" is already loaded');
|
||||
}
|
||||
|
||||
|
||||
if (plugin.isLoaded()) return plugin;
|
||||
else return plugin.load()
|
||||
.thenResolve(plugin);
|
||||
})
|
||||
|
||||
// Setup the plugin
|
||||
.then(this._setup);
|
||||
};
|
||||
|
||||
// Load all plugins from the book's configuration
|
||||
PluginsManager.prototype.loadAll = function() {
|
||||
var that = this;
|
||||
var pluginNames = _.pluck(this.book.config.get('plugins'), 'name');
|
||||
|
||||
return registry.list(this.book)
|
||||
.then(function(plugins) {
|
||||
// Filter out plugins not listed of first level
|
||||
// (aka pre-installed plugins)
|
||||
plugins = _.filter(plugins, function(plugin) {
|
||||
return (
|
||||
plugin.depth > 1 ||
|
||||
_.contains(pluginNames, plugin.name)
|
||||
);
|
||||
});
|
||||
|
||||
// Sort plugins to match list in book.json
|
||||
plugins.sort(function(a, b){
|
||||
return pluginNames.indexOf(a.name) < pluginNames.indexOf(b.name) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Log state
|
||||
that.log.info.ln(_.size(plugins) + ' are installed');
|
||||
if (_.size(pluginNames) != _.size(plugins)) that.log.info.ln(_.size(pluginNames) + ' explicitly listed');
|
||||
|
||||
// Verify that all plugins are present
|
||||
var notInstalled = _.filter(pluginNames, function(name) {
|
||||
return !_.find(plugins, { name: name });
|
||||
});
|
||||
|
||||
if (_.size(notInstalled) > 0) {
|
||||
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
return that.load(plugins);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup a plugin
|
||||
// Register its filter, blocks, etc
|
||||
PluginsManager.prototype._setup = function(plugin) {
|
||||
this.plugins.push(plugin);
|
||||
};
|
||||
|
||||
// Install all plugins for the book
|
||||
PluginsManager.prototype.install = function() {
|
||||
var that = this;
|
||||
var plugins = _.filter(this.book.config.get('plugins'), function(plugin) {
|
||||
return !pluginsConfig.isDefaultPlugin(plugin.name);
|
||||
});
|
||||
|
||||
if (plugins.length == 0) {
|
||||
this.log.info.ln('nothing to install!');
|
||||
return Promise(0);
|
||||
}
|
||||
|
||||
this.log.info.ln('installing', plugins.length, 'plugins');
|
||||
|
||||
return Promise.serie(plugins, function(plugin) {
|
||||
return registry.install(that.book, plugin.name, plugin.version);
|
||||
})
|
||||
.thenResolve(plugins.length);
|
||||
};
|
||||
|
||||
// Call a hook on all plugins to transform an input
|
||||
PluginsManager.prototype.hook = function(name, input) {
|
||||
return Promise.reduce(this.plugins, function(current, plugin) {
|
||||
return plugin.hook(name, current);
|
||||
}, input);
|
||||
};
|
||||
|
||||
// Extract all resources for a namespace
|
||||
PluginsManager.prototype.getResources = function(namespace) {
|
||||
return Promise.reduce(this.plugins, function(out, plugin) {
|
||||
return plugin.getResources(namespace)
|
||||
.then(function(pluginResources) {
|
||||
_.each(BookPlugin.RESOURCES, function(resourceType) {
|
||||
out[resourceType] = (out[resourceType] || []).concat(pluginResources[resourceType] || []);
|
||||
});
|
||||
|
||||
return out;
|
||||
});
|
||||
}, {});
|
||||
};
|
||||
|
||||
// Copy all resources for a plugin
|
||||
PluginsManager.prototype.copyResources = function(namespace, outputRoot) {
|
||||
return Promise.serie(this.plugins, function(plugin) {
|
||||
return plugin.getResources(namespace)
|
||||
.then(function(resources) {
|
||||
if (!resources.assets) return;
|
||||
|
||||
var input = path.resolve(plugin.root, resources.assets);
|
||||
var output = path.resolve(outputRoot, plugin.npmId);
|
||||
|
||||
return fs.copyDir(input, output);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get all filters and blocks
|
||||
PluginsManager.prototype.getFilters = function() {
|
||||
return _.reduce(this.plugins, function(out, plugin) {
|
||||
var filters = plugin.getFilters();
|
||||
|
||||
return _.extend(out, filters);
|
||||
}, {});
|
||||
};
|
||||
PluginsManager.prototype.getBlocks = function() {
|
||||
return _.reduce(this.plugins, function(out, plugin) {
|
||||
var blocks = plugin.getBlocks();
|
||||
|
||||
return _.extend(out, blocks);
|
||||
}, {});
|
||||
};
|
||||
|
||||
module.exports = PluginsManager;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
var Plugin = require('../models/plugin');
|
||||
|
||||
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||
|
||||
/**
|
||||
List all plugins for a book
|
||||
|
||||
@param {List<Plugin|String>}
|
||||
@return {OrderedMap<Plugin>}
|
||||
*/
|
||||
function listAll(plugins) {
|
||||
if (is.string(plugins)) {
|
||||
plugins = new Immutable.List(plugins.split(','));
|
||||
}
|
||||
|
||||
// Convert to an ordered map
|
||||
plugins = plugins.map(function(plugin) {
|
||||
if (is.string(plugin)) {
|
||||
plugin = Plugin.createFromString(plugin);
|
||||
} else {
|
||||
plugin = new Plugin(plugin);
|
||||
}
|
||||
|
||||
return [plugin.getName(), plugin];
|
||||
});
|
||||
plugins = Immutable.OrderedMap(plugins);
|
||||
|
||||
// Extract list of plugins to disable (starting with -)
|
||||
var toRemove = plugins.toList()
|
||||
.filter(function(plugin) {
|
||||
return plugin.getName()[0] == '-';
|
||||
})
|
||||
.map(function(plugin) {
|
||||
return plugin.slice(1);
|
||||
});
|
||||
|
||||
// Append default plugins
|
||||
DEFAULT_PLUGINS.forEach(function(pluginName) {
|
||||
if (plugins.has(pluginName)) return;
|
||||
|
||||
plugins = plugins.set(pluginName, new Plugin({
|
||||
name: pluginName
|
||||
}));
|
||||
});
|
||||
|
||||
// Remove plugins
|
||||
plugins = plugins.filterNot(function(plugin) {
|
||||
return toRemove.includes(plugin.getName());
|
||||
});
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
module.exports = listAll;
|
||||
@@ -0,0 +1,18 @@
|
||||
var listAll = require('./listAll');
|
||||
|
||||
/**
|
||||
List all plugin requirements for a book.
|
||||
It can be different from the final list of plugins,
|
||||
since plugins can have their own dependencies
|
||||
|
||||
@param {Book}
|
||||
@return {OrderedMap<Plugin>}
|
||||
*/
|
||||
function listForBook(book) {
|
||||
var config = book.getConfig();
|
||||
var plugins = config.getValue('plugins');
|
||||
|
||||
return listAll(plugins);
|
||||
}
|
||||
|
||||
module.exports = listForBook;
|
||||
@@ -0,0 +1,57 @@
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
var listForBook = require('./listForBook');
|
||||
var listInstalledForBook = require('./listInstalledForBook');
|
||||
var loadPlugin = require('./loadPlugin');
|
||||
|
||||
|
||||
/**
|
||||
Load a list of plugins in a book
|
||||
|
||||
@param {Book}
|
||||
@return {Promise<Map<String:Plugin>}
|
||||
*/
|
||||
function loadForBook(book) {
|
||||
var logger = book.getLogger();
|
||||
var requirements = listForBook(book);
|
||||
var requirementsKeys = requirements.keys().toList();
|
||||
|
||||
return listInstalledForBook(book)
|
||||
.then(function(installed) {
|
||||
// Filter out plugins not listed of first level
|
||||
// (aka pre-installed plugins)
|
||||
installed = installed.filter(function(plugin) {
|
||||
return (
|
||||
plugin.getDepth() > 1 ||
|
||||
requirements.has(plugin.getName())
|
||||
);
|
||||
});
|
||||
|
||||
// Sort plugins to match list in book.json
|
||||
installed = installed.sort(function(a, b){
|
||||
return requirementsKeys.indexOf(a.getName()) < requirementsKeys.indexOf(b.getName()) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Log state
|
||||
logger.info.ln(installed.size + ' are installed');
|
||||
if (requirements.size != installed.size) {
|
||||
logger.info.ln(requirements.size + ' explicitly listed');
|
||||
}
|
||||
|
||||
// Verify that all plugins are present
|
||||
var notInstalled = requirementsKeys.filter(function(name) {
|
||||
return !installed.has(name);
|
||||
});
|
||||
|
||||
if (notInstalled.size > 0) {
|
||||
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
||||
}
|
||||
|
||||
return Promise.map(installed, function(plugin) {
|
||||
return loadPlugin(plugin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = loadForBook;
|
||||
@@ -0,0 +1,89 @@
|
||||
var path = require('path');
|
||||
var resolve = require('resolve');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
|
||||
var validatePlugin = require('./validatePlugin');
|
||||
|
||||
// Return true if an error is a "module not found"
|
||||
// Wait on https://github.com/substack/node-resolve/pull/81 to be merged
|
||||
function isModuleNotFound(err) {
|
||||
return err.message.indexOf('Cannot find module') >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Load a plugin in a book
|
||||
|
||||
@param {Book} book
|
||||
@param {Plugin} plugin
|
||||
@param {String} pkgPath (optional)
|
||||
@return {Promise<Plugin>}
|
||||
*/
|
||||
function loadPlugin(book, plugin) {
|
||||
var logger = book.getLogger();
|
||||
|
||||
var name = plugin.getName();
|
||||
var pkgPath = plugin.getPath();
|
||||
|
||||
|
||||
// Try loading plugins from different location
|
||||
var p = Promise()
|
||||
.then(function() {
|
||||
var packageContent;
|
||||
var content;
|
||||
|
||||
// Locate plugin and load pacjage.json
|
||||
try {
|
||||
var res = resolve.sync('./package.json', { basedir: pkgPath });
|
||||
|
||||
pkgPath = path.dirname(res);
|
||||
packageContent = require(res);
|
||||
} catch (err) {
|
||||
if (!isModuleNotFound(err)) throw err;
|
||||
|
||||
packageContent = undefined;
|
||||
content = undefined;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load plugin JS content
|
||||
try {
|
||||
content = require(pkgPath);
|
||||
} catch(err) {
|
||||
// It's no big deal if the plugin doesn't have an "index.js"
|
||||
// (For example: themes)
|
||||
if (isModuleNotFound(err)) {
|
||||
content = {};
|
||||
} else {
|
||||
throw new error.PluginError(err, {
|
||||
plugin: name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update plugin
|
||||
return plugin.merge({
|
||||
'package': packageContent,
|
||||
'content': content
|
||||
});
|
||||
})
|
||||
|
||||
.then(validatePlugin)
|
||||
|
||||
// Validate the configuration and update it
|
||||
.then(function() {
|
||||
var config = that.book.config.get(that.getConfigKey(), {});
|
||||
return that.validateConfig(config);
|
||||
})
|
||||
.then(function(config) {
|
||||
that.book.config.set(that.getConfigKey(), config);
|
||||
});
|
||||
|
||||
logger.info('loading plugin "' + name + '"... ');
|
||||
return logger.info.promise(p);
|
||||
}
|
||||
|
||||
|
||||
module.exports = loadPlugin;
|
||||
@@ -0,0 +1,33 @@
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
/**
|
||||
Validate a plugin
|
||||
|
||||
@param {Plugin}
|
||||
@return {Promise<Plugin>}
|
||||
*/
|
||||
function validatePlugin(plugin) {
|
||||
var packageInfos = plugin.getPackage();
|
||||
|
||||
var isValid = (
|
||||
plugin.isLoaded() &&
|
||||
packageInfos &&
|
||||
packageInfos.name &&
|
||||
packageInfos.engines &&
|
||||
packageInfos.engines.gitbook
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
return Promise.reject(new Error('Error loading plugin "' + plugin.getName() + '" at "' + plugin.getPath() + '"'));
|
||||
}
|
||||
|
||||
if (!gitbook.satisfies(this.packageInfos.engines.gitbook)) {
|
||||
return Promise.reject(new Error('GitBook doesn\'t satisfy the requirements of this plugin: ' + packageInfos.engines.gitbook));
|
||||
}
|
||||
|
||||
return Promise();
|
||||
}
|
||||
|
||||
module.exports = validatePlugin;
|
||||
@@ -0,0 +1,188 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var fs = require('../utils/fs');
|
||||
var BookPlugin = require('./plugin');
|
||||
var registry = require('./registry');
|
||||
var pluginsConfig = require('../config/plugins');
|
||||
|
||||
/*
|
||||
PluginsManager is an interface to work with multiple plugins at once:
|
||||
- Extract assets from plugins
|
||||
- Call hooks for all plugins, etc
|
||||
*/
|
||||
|
||||
function PluginsManager(book) {
|
||||
this.book = book;
|
||||
this.log = this.book.log;
|
||||
this.plugins = [];
|
||||
|
||||
_.bindAll(this);
|
||||
}
|
||||
|
||||
// Returns the list of plugins
|
||||
PluginsManager.prototype.list = function() {
|
||||
return this.plugins;
|
||||
};
|
||||
|
||||
// Return count of plugins loaded
|
||||
PluginsManager.prototype.count = function() {
|
||||
return _.size(this.plugins);
|
||||
};
|
||||
|
||||
// Returns a plugin by its name
|
||||
PluginsManager.prototype.get = function(name) {
|
||||
return _.find(this.plugins, {
|
||||
id: name
|
||||
});
|
||||
};
|
||||
|
||||
// Load a plugin (could be a BookPlugin or {name,path})
|
||||
PluginsManager.prototype.load = function(plugin) {
|
||||
var that = this;
|
||||
|
||||
if (_.isArray(plugin)) {
|
||||
return Promise.serie(plugin, that.load);
|
||||
}
|
||||
|
||||
return Promise()
|
||||
|
||||
// Initiate and load the plugin
|
||||
.then(function() {
|
||||
if (!(plugin instanceof BookPlugin)) {
|
||||
plugin = new BookPlugin(that.book, plugin.name, plugin.path);
|
||||
}
|
||||
|
||||
if (that.get(plugin.id)) {
|
||||
throw new Error('Plugin "'+plugin.id+'" is already loaded');
|
||||
}
|
||||
|
||||
|
||||
if (plugin.isLoaded()) return plugin;
|
||||
else return plugin.load()
|
||||
.thenResolve(plugin);
|
||||
})
|
||||
|
||||
// Setup the plugin
|
||||
.then(this._setup);
|
||||
};
|
||||
|
||||
// Load all plugins from the book's configuration
|
||||
PluginsManager.prototype.loadAll = function() {
|
||||
var that = this;
|
||||
var pluginNames = _.pluck(this.book.config.get('plugins'), 'name');
|
||||
|
||||
return registry.list(this.book)
|
||||
.then(function(plugins) {
|
||||
// Filter out plugins not listed of first level
|
||||
// (aka pre-installed plugins)
|
||||
plugins = _.filter(plugins, function(plugin) {
|
||||
return (
|
||||
plugin.depth > 1 ||
|
||||
_.contains(pluginNames, plugin.name)
|
||||
);
|
||||
});
|
||||
|
||||
// Sort plugins to match list in book.json
|
||||
plugins.sort(function(a, b){
|
||||
return pluginNames.indexOf(a.name) < pluginNames.indexOf(b.name) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Log state
|
||||
that.log.info.ln(_.size(plugins) + ' are installed');
|
||||
if (_.size(pluginNames) != _.size(plugins)) that.log.info.ln(_.size(pluginNames) + ' explicitly listed');
|
||||
|
||||
// Verify that all plugins are present
|
||||
var notInstalled = _.filter(pluginNames, function(name) {
|
||||
return !_.find(plugins, { name: name });
|
||||
});
|
||||
|
||||
if (_.size(notInstalled) > 0) {
|
||||
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
return that.load(plugins);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup a plugin
|
||||
// Register its filter, blocks, etc
|
||||
PluginsManager.prototype._setup = function(plugin) {
|
||||
this.plugins.push(plugin);
|
||||
};
|
||||
|
||||
// Install all plugins for the book
|
||||
PluginsManager.prototype.install = function() {
|
||||
var that = this;
|
||||
var plugins = _.filter(this.book.config.get('plugins'), function(plugin) {
|
||||
return !pluginsConfig.isDefaultPlugin(plugin.name);
|
||||
});
|
||||
|
||||
if (plugins.length == 0) {
|
||||
this.log.info.ln('nothing to install!');
|
||||
return Promise(0);
|
||||
}
|
||||
|
||||
this.log.info.ln('installing', plugins.length, 'plugins');
|
||||
|
||||
return Promise.serie(plugins, function(plugin) {
|
||||
return registry.install(that.book, plugin.name, plugin.version);
|
||||
})
|
||||
.thenResolve(plugins.length);
|
||||
};
|
||||
|
||||
// Call a hook on all plugins to transform an input
|
||||
PluginsManager.prototype.hook = function(name, input) {
|
||||
return Promise.reduce(this.plugins, function(current, plugin) {
|
||||
return plugin.hook(name, current);
|
||||
}, input);
|
||||
};
|
||||
|
||||
// Extract all resources for a namespace
|
||||
PluginsManager.prototype.getResources = function(namespace) {
|
||||
return Promise.reduce(this.plugins, function(out, plugin) {
|
||||
return plugin.getResources(namespace)
|
||||
.then(function(pluginResources) {
|
||||
_.each(BookPlugin.RESOURCES, function(resourceType) {
|
||||
out[resourceType] = (out[resourceType] || []).concat(pluginResources[resourceType] || []);
|
||||
});
|
||||
|
||||
return out;
|
||||
});
|
||||
}, {});
|
||||
};
|
||||
|
||||
// Copy all resources for a plugin
|
||||
PluginsManager.prototype.copyResources = function(namespace, outputRoot) {
|
||||
return Promise.serie(this.plugins, function(plugin) {
|
||||
return plugin.getResources(namespace)
|
||||
.then(function(resources) {
|
||||
if (!resources.assets) return;
|
||||
|
||||
var input = path.resolve(plugin.root, resources.assets);
|
||||
var output = path.resolve(outputRoot, plugin.npmId);
|
||||
|
||||
return fs.copyDir(input, output);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get all filters and blocks
|
||||
PluginsManager.prototype.getFilters = function() {
|
||||
return _.reduce(this.plugins, function(out, plugin) {
|
||||
var filters = plugin.getFilters();
|
||||
|
||||
return _.extend(out, filters);
|
||||
}, {});
|
||||
};
|
||||
PluginsManager.prototype.getBlocks = function() {
|
||||
return _.reduce(this.plugins, function(out, plugin) {
|
||||
var blocks = plugin.getBlocks();
|
||||
|
||||
return _.extend(out, blocks);
|
||||
}, {});
|
||||
};
|
||||
|
||||
module.exports = PluginsManager;
|
||||
@@ -32,6 +32,13 @@ var FileNotFoundError = TypedError({
|
||||
filename: null
|
||||
});
|
||||
|
||||
// A file cannot be parsed
|
||||
var FileNotParsableError = TypedError({
|
||||
type: 'file.not-parsable',
|
||||
message: '"{filename}" file cannot be parsed',
|
||||
filename: null
|
||||
});
|
||||
|
||||
// A file is outside the scope
|
||||
var FileOutOfScopeError = TypedError({
|
||||
type: 'file.out-of-scope',
|
||||
@@ -92,6 +99,7 @@ module.exports = {
|
||||
OutputError: OutputError,
|
||||
RequireInstallError: RequireInstallError,
|
||||
|
||||
FileNotParsableError: FileNotParsableError,
|
||||
FileNotFoundError: FileNotFoundError,
|
||||
FileOutOfScopeError: FileOutOfScopeError,
|
||||
|
||||
|
||||
+60
-12
@@ -1,16 +1,43 @@
|
||||
var Q = require('q');
|
||||
var _ = require('lodash');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
// Reduce an array to a promise
|
||||
/**
|
||||
Reduce an array to a promise
|
||||
|
||||
@param {Array|List} arr
|
||||
@param {Function(value, element, index)}
|
||||
@return {Promise<Mixed>}
|
||||
*/
|
||||
function reduce(arr, iter, base) {
|
||||
return _.reduce(arr, function(prev, elem, i) {
|
||||
arr = Immutable.List(arr);
|
||||
|
||||
return arr.reduce(function(prev, elem, i) {
|
||||
return prev.then(function(val) {
|
||||
return iter(val, elem, i);
|
||||
});
|
||||
}, Q(base));
|
||||
}
|
||||
|
||||
// Transform an array
|
||||
/**
|
||||
Iterate over an array using an async iter
|
||||
|
||||
@param {Array|List} arr
|
||||
@param {Function(value, element, index)}
|
||||
@return {Promise}
|
||||
*/
|
||||
function forEach(arr, iter) {
|
||||
return reduce(arr, function(val, el) {
|
||||
return iter(el);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Transform an array
|
||||
|
||||
@param {Array|List} arr
|
||||
@param {Function(value, element, index)}
|
||||
@return {Promise}
|
||||
*/
|
||||
function serie(arr, iter, base) {
|
||||
return reduce(arr, function(before, item, i) {
|
||||
return Q(iter(item, i))
|
||||
@@ -21,9 +48,17 @@ function serie(arr, iter, base) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Iter over an array and return first result (not null)
|
||||
/**
|
||||
Iter over an array and return first result (not null)
|
||||
|
||||
@param {Array|List} arr
|
||||
@param {Function(element, index)}
|
||||
@return {Promise<Mixed>}
|
||||
*/
|
||||
function some(arr, iter) {
|
||||
return _.reduce(arr, function(prev, elem, i) {
|
||||
arr = Immutable.List(arr);
|
||||
|
||||
return arr.reduce(function(prev, elem, i) {
|
||||
return prev.then(function(val) {
|
||||
if (val) return val;
|
||||
|
||||
@@ -32,7 +67,13 @@ function some(arr, iter) {
|
||||
}, Q());
|
||||
}
|
||||
|
||||
// Map an array using an async (promised) iterator
|
||||
/**
|
||||
Map an array using an async (promised) iterator
|
||||
|
||||
@param {Array|List} arr
|
||||
@param {Function(element, index)}
|
||||
@return {Promise<List>}
|
||||
*/
|
||||
function map(arr, iter) {
|
||||
return reduce(arr, function(prev, entry, i) {
|
||||
return Q(iter(entry, i))
|
||||
@@ -43,18 +84,25 @@ function map(arr, iter) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Wrap a fucntion in a promise
|
||||
/**
|
||||
Wrap a fucntion in a promise
|
||||
|
||||
@param {Function} func
|
||||
@return {Funciton}
|
||||
*/
|
||||
function wrap(func) {
|
||||
return _.wrap(func, function(_func) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
return Q()
|
||||
.then(function() {
|
||||
return _func.apply(null, args);
|
||||
return func.apply(null, args);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Q;
|
||||
module.exports.forEach = forEach;
|
||||
module.exports.reduce = reduce;
|
||||
module.exports.map = map;
|
||||
module.exports.serie = serie;
|
||||
|
||||
+5
-2
@@ -24,14 +24,16 @@
|
||||
"gitbook-plugin-fontsettings": "1.0.3",
|
||||
"gitbook-plugin-highlight": "2.0.2",
|
||||
"gitbook-plugin-livereload": "0.0.1",
|
||||
"gitbook-plugin-search": "2.2.0",
|
||||
"gitbook-plugin-lunr": "1.0.0",
|
||||
"gitbook-plugin-theme-default": "1.0.0-pre.8",
|
||||
"gitbook-plugin-search": "2.2.0",
|
||||
"gitbook-plugin-sharing": "1.0.2",
|
||||
"gitbook-plugin-theme-default": "1.0.0-pre.8",
|
||||
"github-slugid": "1.0.1",
|
||||
"graceful-fs": "4.1.3",
|
||||
"i18n-t": "1.0.0",
|
||||
"ignore": "3.1.1",
|
||||
"immutable": "^3.8.1",
|
||||
"is": "^3.1.0",
|
||||
"json-schema-defaults": "0.1.1",
|
||||
"jsonschema": "1.1.0",
|
||||
"juice": "1.10.0",
|
||||
@@ -57,6 +59,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "2.7.0",
|
||||
"jest-cli": "^11.0.2",
|
||||
"mocha": "2.4.5",
|
||||
"should": "8.3.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user