summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2016-01-27 10:24:06 +0100
committerSamy Pessé <samypesse@gmail.com>2016-01-27 10:24:06 +0100
commitf305d57ab7702c3ca10fd6e32366d19e524ee1f0 (patch)
tree381ccb09c7cedc72cdf8ad6c98b681b72f4d1bc0
parent877f2e477b010f9f37a9044606f110a90f077680 (diff)
downloadgitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.zip
gitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.tar.gz
gitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.tar.bz2
Add more classes structures
-rw-r--r--lib/backbone/article.js28
-rw-r--r--lib/backbone/file.js2
-rw-r--r--lib/backbone/page.js15
-rw-r--r--lib/backbone/summary.js1
-rw-r--r--lib/book.js55
-rw-r--r--lib/config/default.js (renamed from lib/config_default.js)0
-rw-r--r--lib/config/index.js (renamed from lib/config.js)7
-rw-r--r--lib/generators/base.js29
-rw-r--r--lib/generators/index.js10
-rw-r--r--lib/generators/json.js14
-rw-r--r--lib/output.js47
-rw-r--r--lib/page.js10
-rw-r--r--lib/parsers/asciidoc.js27
-rw-r--r--lib/parsers/html.js90
-rw-r--r--lib/parsers/index.js11
-rw-r--r--lib/parsers/markdown.js6
-rw-r--r--lib/plugins/index.js0
-rw-r--r--lib/plugins/manager.js10
-rw-r--r--lib/plugins/plugin.js8
-rw-r--r--lib/templating/blocks.js11
-rw-r--r--lib/templating/index.js28
-rw-r--r--lib/templating/loader.js0
-rw-r--r--lib/utils/path.js12
-rw-r--r--lib/utils/promise.js26
-rw-r--r--test/readme.js13
25 files changed, 438 insertions, 22 deletions
diff --git a/lib/backbone/article.js b/lib/backbone/article.js
new file mode 100644
index 0000000..68d8236
--- /dev/null
+++ b/lib/backbone/article.js
@@ -0,0 +1,28 @@
+var url = require('url');
+var _ = require('lodash');
+
+/*
+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 (optional)
+
+*/
+
+function Article(title, ref, articles) {
+ var parts = url.parse(ref);
+
+ this.title = title;
+ this.filename = parts.pathname;
+ this.anchor = parts.hash;
+ this.articles = _.map(articles || [], function(article) {
+ if (article instanceof Article) return article;
+ return new Article(article.title, article.ref, article.articles);
+ })
+}
+
+// Return true if has children
+Article.prototype.hasChildren = function() {
+ return this.articles.length > 0;
+};
+
+
+module.exports = Article;
diff --git a/lib/backbone/file.js b/lib/backbone/file.js
index 71fc78c..34cf066 100644
--- a/lib/backbone/file.js
+++ b/lib/backbone/file.js
@@ -15,7 +15,7 @@ BackboneFile.prototype.type = '';
// Parse a backbone file
BackboneFile.prototype.parse = function() {
-
+ // To be implemented by each child
};
// Return true if backbone file exists
diff --git a/lib/backbone/page.js b/lib/backbone/page.js
new file mode 100644
index 0000000..fdfe3f7
--- /dev/null
+++ b/lib/backbone/page.js
@@ -0,0 +1,15 @@
+
+/*
+A page represent a parsable file in the book (Markdown, Asciidoc, etc)
+*/
+
+function Page(book, filename) {
+ if (!(this instanceof Page)) return new Page();
+
+ this.book = book;
+ this.filename = filename;
+}
+
+
+
+module.exports = Page;
diff --git a/lib/backbone/summary.js b/lib/backbone/summary.js
index e2cd485..7eb6e0c 100644
--- a/lib/backbone/summary.js
+++ b/lib/backbone/summary.js
@@ -1,3 +1,4 @@
+var Article = require('./article');
function Summary() {
if (!(this instanceof Summary)) return new Summary();
diff --git a/lib/book.js b/lib/book.js
index 3bb5bf1..91dfa02 100644
--- a/lib/book.js
+++ b/lib/book.js
@@ -10,7 +10,7 @@ var Readme = require('./backbone/readme');
var Glossary = require('./backbone/glossary');
var Summary = require('./backbone/summary');
var Langs = require('./backbone/langs');
-var Page = require('./page');
+var Page = require('./backbone/page');
var pathUtil = require('./utils/path');
function Book(opts) {
@@ -47,7 +47,28 @@ function Book(opts) {
// Rules to ignore some files
this.ignore = Ignore();
- this.ignore.addPattern(['.git', '.svn', '.DS_Store']);
+ this.ignore.addPattern([
+ // Skip Git stuff
+ '.git/',
+ '.gitignore',
+
+ // Skip OS X meta data
+ '.DS_Store',
+
+ // Skip stuff installed by plugins
+ 'node_modules',
+
+ // Skip book outputs
+ '_book',
+ '*.pdf',
+ '*.epub',
+ '*.mobi',
+
+ // Skip config files
+ '.ignore',
+ '.bookignore',
+ 'book.json'
+ ]);
// Create a logger for the book
this.log = new Logger(opts.log, opts.logLevel);
@@ -59,9 +80,12 @@ function Book(opts) {
this.readme = new Readme(this);
this.summary = new Summary(this);
this.glossary = new Glossary(this);
+
+ // Multilinguals book
this.langs = new Langs(this);
+ this.books = [];
- // Map of pages in the bok
+ // List of page in the book
this.pages = {};
_.bindAll(this);
@@ -110,7 +134,10 @@ Book.prototype.parse = function() {
})
.then(function() {
- if (that.isMultilingual()) return;
+ if (that.isMultilingual()) {
+ that.log.info.ln('Parsing multilingual book, with', that.langs.count(), 'languages');
+ return;
+ }
return Q()
.then(that.readme.load)
@@ -131,6 +158,26 @@ Book.prototype.parse = function() {
});
};
+// Mark a filename as being parsable
+Book.prototype.addPage = function(filename) {
+ filename = pathUtil.normalize(filename);
+
+ if (this.pages[filename]) return;
+ this.pages[filename] = new Page(this, filename);
+};
+
+// Return a page by its filename (or undefined)
+Book.prototype.getPage = function(filename) {
+ filename = pathUtil.normalize(filename);
+ return this.pages[filename];
+};
+
+
+// Return true, if has a specific page
+Book.prototype.hasPage = function(filename) {
+ return Boolean(this.getPage(filename));
+};
+
// Test if a file is ignored, return true if it is
Book.prototype.isFileIgnored = function(filename) {
return this.ignore.filter([filename]).length == 0;
diff --git a/lib/config_default.js b/lib/config/default.js
index 92defaa..92defaa 100644
--- a/lib/config_default.js
+++ b/lib/config/default.js
diff --git a/lib/config.js b/lib/config/index.js
index c6d05da..267f650 100644
--- a/lib/config.js
+++ b/lib/config/index.js
@@ -1,10 +1,9 @@
var Q = require('q');
var _ = require('lodash');
var semver = require('semver');
-var path = require('path');
-var gitbook = require('./gitbook');
-var configDefault = require('./config_default');
+var gitbook = require('../gitbook');
+var configDefault = require('./default');
/*
Config is an interface for the book's configuration stored in "book.json" (or "book.js")
@@ -42,7 +41,7 @@ Config.prototype.load = function() {
}
}
- //that.options.output = path.resolve(that.options.output || that.book.resolve('_book'));
+ that.options.output = that.options.output || that.book.resolve('_book');
//that.options.plugins = normalizePluginsList(that.options.plugins);
//that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || '', false);
//that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
diff --git a/lib/generators/base.js b/lib/generators/base.js
new file mode 100644
index 0000000..554a389
--- /dev/null
+++ b/lib/generators/base.js
@@ -0,0 +1,29 @@
+
+function Generator(output, type) {
+ this.output = output;
+ this.book = output.book;
+ this.type = type;
+}
+
+// Prepare the generation
+Generator.prototype.prepare = function() {
+
+};
+
+// Copy an asset file (non-parsable), ex: images, etc
+Generator.prototype.writeFile = function(filename) {
+
+};
+
+// Write a page (parsable file), ex: markdown, etc
+Generator.prototype.writePage = function(page) {
+
+};
+
+// Finish the generation
+Generator.prototype.finish = function() {
+
+};
+
+
+module.exports = Generator;
diff --git a/lib/generators/index.js b/lib/generators/index.js
new file mode 100644
index 0000000..de8a1e6
--- /dev/null
+++ b/lib/generators/index.js
@@ -0,0 +1,10 @@
+var _ = require('lodash');
+var EbookGenerator = require('./ebook');
+
+module.exports = {
+ json: require('./json'),
+ /*website: require('./website'),
+ pdf: _.partialRight(EbookGenerator, 'pdf'),
+ mobi: _.partialRight(EbookGenerator, 'mobi'),
+ epub: _.partialRight(EbookGenerator, 'epub')*/
+};
diff --git a/lib/generators/json.js b/lib/generators/json.js
new file mode 100644
index 0000000..c5a87fe
--- /dev/null
+++ b/lib/generators/json.js
@@ -0,0 +1,14 @@
+var util = require('util');
+var Generator = require('./base');
+
+function JSONGenerator() {
+ Generator.apply(this, arguments);
+}
+util.inherits(JSONGenerator, Generator);
+
+
+
+
+
+
+module.exports = JSONGenerator;
diff --git a/lib/output.js b/lib/output.js
new file mode 100644
index 0000000..2f40fb6
--- /dev/null
+++ b/lib/output.js
@@ -0,0 +1,47 @@
+var Promise = require('utils/promise');
+var generators = require('./generators');
+var PluginsManager = require('./plugins');
+
+function Output(book, type) {
+ if (!generators[type]) throw new Error('Generator not found"' + type + '"');
+
+ this.book = book;
+ this.type = type;
+ this.plugins = new PluginsManager(book);
+ this.generator = new generators[type](this, type);
+}
+
+// Start the generation, for a parsed book
+Output.prototype.generate = function() {
+ var that = this;
+
+ return Promise()
+
+ // Initialize the generation
+ .then(function() {
+ return that.generator.prepare();
+ })
+
+ // Process all files
+ .then(function() {
+ return that.book.fs.listAllFiles(that.book.root);
+ })
+ .then(function(files) {
+ return Promise.serie(files, function(filename) {
+ var isPage = that.book.hasPage(filename);
+
+ if (isPage) {
+ return that.generator.writePage(that.book.getPage(filename));
+ } else {
+ return that.generator.writeFile(filename);
+ }
+ });
+ })
+
+ // Finish the generation
+ .then(function() {
+ return that.generator.finish();
+ });
+};
+
+module.exports = Output;
diff --git a/lib/page.js b/lib/page.js
deleted file mode 100644
index a6c2caf..0000000
--- a/lib/page.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-function Page() {
- if (!(this instanceof Page)) return new Page();
-
-}
-
-
-
-module.exports = Page;
diff --git a/lib/parsers/asciidoc.js b/lib/parsers/asciidoc.js
new file mode 100644
index 0000000..84e619d
--- /dev/null
+++ b/lib/parsers/asciidoc.js
@@ -0,0 +1,27 @@
+var Asciidoctor = require('asciidoctor.js');
+var htmlParser = require('./html');
+
+var asciidoctor = Asciidoctor();
+var opal = asciidoctor.Opal;
+
+var processor = null;
+var useExtensions = true;
+
+if (useExtensions) {
+ processor = asciidoctor.Asciidoctor(true);
+} else {
+ processor = asciidoctor.Asciidoctor();
+}
+
+
+// Convert asciidoc to HTML
+function asciidocToHTML(content) {
+ var options = opal.hash2(['attributes'], {'attributes': 'showtitle'});
+ return processor.$convert(content, options);
+}
+
+
+module.exports = htmlParser.inherits({
+ extensions: ['.adoc', '.asciidoc'],
+ toHTML: asciidocToHTML
+});
diff --git a/lib/parsers/html.js b/lib/parsers/html.js
new file mode 100644
index 0000000..8f4ed34
--- /dev/null
+++ b/lib/parsers/html.js
@@ -0,0 +1,90 @@
+var _ = require('lodash');
+var cheerio = require('cheerio');
+
+// Parse summary and returns a list of sections
+function parseSummary(html) {
+ var sections = [];
+ var $ = cheerio.load(html);
+
+ // Find main container
+ var $body = getContainer($);
+
+ // Extract sections, and parse
+ var $lists = $body.find('> ul, > ol');
+
+ $lists.each(function() {
+ sections.push({
+ articles: parseList($(this), $)
+ });
+ });
+
+ return sections;
+}
+
+// Parse readme and extract title, description
+function parseReadme(html) {
+ var $ = cheerio.load(html);
+
+ // Find main container
+ var $body = getContainer($);
+
+ return {
+ title: $body.find('h1:first-child').text().trim(),
+ description: $body.find('div.paragraph').first().text().trim()
+ };
+}
+
+// Return a page container (html, body tag or directly the root element)
+function getContainer($) {
+ var $body = $('body, html').first();
+ if (!$body) $body = $;
+
+ return $body;
+}
+
+// Parse a ul list and return list of chapters recursvely
+function parseList($ul, $) {
+ var articles = [];
+
+ $ul.children('li').each(function() {
+ var article = {};
+
+ var $li = $(this);
+
+ var $text = $li.find('> p, > span');
+ var $a = $li.find('> a, > p a, > span a');
+
+ article.title = $text.text();
+ if ($a.length > 0) {
+ article.title = $a.first().text();
+ article.ref = $a.attr('href');
+ }
+
+ // Inner list, with children article
+ var $sub = $li.find('> ol, > ul, > .olist > ol');
+ article.articles = parseList($sub, $);
+
+ articles.push(article);
+ });
+
+ return articles;
+}
+
+
+// Inherit from the html parser
+function inherits(opts) {
+ var parser = _.defaults(opts, {
+ toHTML: _.identity
+ });
+
+ parser.readme = _.compose(opts.toHTML, parseReadme);
+ parser.summary = _.compose(opts.toHTML, parseSummary);
+
+ return parser;
+}
+
+
+module.exports = inherits({
+ extensions: ['.html']
+});
+module.exports.inherits = inherits;
diff --git a/lib/parsers/index.js b/lib/parsers/index.js
new file mode 100644
index 0000000..d650386
--- /dev/null
+++ b/lib/parsers/index.js
@@ -0,0 +1,11 @@
+
+var PARSERS = {
+ html: require('./html'),
+ markdown: require('./markdown'),
+ asciidoc: require('./asciidoc')
+};
+
+
+module.exports = {
+
+};
diff --git a/lib/parsers/markdown.js b/lib/parsers/markdown.js
new file mode 100644
index 0000000..aac2858
--- /dev/null
+++ b/lib/parsers/markdown.js
@@ -0,0 +1,6 @@
+
+
+
+module.exports = {
+ extensions: ['.md', '.markdown', '.mdown']
+};
diff --git a/lib/plugins/index.js b/lib/plugins/index.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/plugins/index.js
diff --git a/lib/plugins/manager.js b/lib/plugins/manager.js
new file mode 100644
index 0000000..8617044
--- /dev/null
+++ b/lib/plugins/manager.js
@@ -0,0 +1,10 @@
+
+var BookPlugin = require('./plugin');
+
+function PluginsManager(book) {
+ this.book = book;
+ this.plugins = [];
+}
+
+
+module.exports = PluginsManager;
diff --git a/lib/plugins/plugin.js b/lib/plugins/plugin.js
new file mode 100644
index 0000000..8397205
--- /dev/null
+++ b/lib/plugins/plugin.js
@@ -0,0 +1,8 @@
+
+function BookPlugin() {
+
+
+}
+
+
+module.exports = BookPlugin;
diff --git a/lib/templating/blocks.js b/lib/templating/blocks.js
new file mode 100644
index 0000000..92097a7
--- /dev/null
+++ b/lib/templating/blocks.js
@@ -0,0 +1,11 @@
+var _ = require('lodash');
+
+module.exports = {
+ // Return non-parsed html
+ // since blocks are by default non-parsable, a simple identity method works fine
+ html: _.identity,
+
+ // Highlight a code block
+ // This block can be extent by plugins
+ code: _.identity
+};
diff --git a/lib/templating/index.js b/lib/templating/index.js
new file mode 100644
index 0000000..1031bdb
--- /dev/null
+++ b/lib/templating/index.js
@@ -0,0 +1,28 @@
+var nunjucks = require('nunjucks');
+
+function TemplatingEngine(book) {
+ this.book = book;
+ this.log = book.log;
+
+
+ this.nunjucks = new nunjucks.Environment(
+ this.loader,
+ {
+ // Escaping is done after by the asciidoc/markdown parser
+ autoescape: false,
+
+ // Syntax
+ tags: {
+ blockStart: '{%',
+ blockEnd: '%}',
+ variableStart: '{{',
+ variableEnd: '}}',
+ commentStart: '{###',
+ commentEnd: '###}'
+ }
+ }
+ );
+}
+
+
+module.exports = TemplatingEngine;
diff --git a/lib/templating/loader.js b/lib/templating/loader.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/templating/loader.js
diff --git a/lib/utils/path.js b/lib/utils/path.js
index dc97d5d..2d722a5 100644
--- a/lib/utils/path.js
+++ b/lib/utils/path.js
@@ -1,6 +1,11 @@
var _ = require('lodash');
var path = require('path');
+// Normalize a filename
+function normalizePath(filename) {
+ return path.normalize(filename);
+}
+
// Return true if file path is inside a folder
function isInRoot(root, filename) {
filename = path.normalize(filename);
@@ -17,10 +22,10 @@ function resolveInRoot(root) {
.slice(1)
.reduce(function(current, p) {
// Handle path relative to book root ("/README.md")
- if (p[0] == "/" || p[0] == "\\") return p.slice(1);
+ if (p[0] == '/' || p[0] == '\\') return p.slice(1);
return current? path.join(current, p) : path.normalize(p);
- }, "")
+ }, '')
.value();
result = path.resolve(root, input);
@@ -36,5 +41,6 @@ function resolveInRoot(root) {
module.exports = {
isInRoot: isInRoot,
- resolveInRoot: resolveInRoot
+ resolveInRoot: resolveInRoot,
+ normalize: normalizePath
};
diff --git a/lib/utils/promise.js b/lib/utils/promise.js
new file mode 100644
index 0000000..c25b349
--- /dev/null
+++ b/lib/utils/promise.js
@@ -0,0 +1,26 @@
+var Q = require('q');
+var _ = require('lodash');
+
+// Reduce an array to a promise
+function reduce(arr, iter, base) {
+ return _.reduce(arr, function(prev, elem, i) {
+ return prev.then(function(val) {
+ return iter(val, elem, i);
+ });
+ }, Q(base));
+}
+
+// Transform an array
+function serie(arr, iter, base) {
+ return reduce(arr, function(before, item, i) {
+ return Q(iter(item, i))
+ .then(function(r) {
+ before.push(r);
+ return before;
+ });
+ }, []);
+}
+
+module.exports = Q;
+module.exports.reduce = reduce;
+module.exports.serie = serie;
diff --git a/test/readme.js b/test/readme.js
index 0ebb490..fd084cb 100644
--- a/test/readme.js
+++ b/test/readme.js
@@ -28,6 +28,19 @@ describe('Readme', function() {
});
});
});
+
+ it('should parse AsciiDoc readme', function() {
+ return mock.setupBook({
+ 'README.adoc': '# Hello World\n\nThis is my book\n'
+ })
+ .then(function(book) {
+ return book.readme.load()
+ .then(function() {
+ book.readme.title.should.equal('Hello World');
+ book.readme.description.should.equal('This is my book');
+ });
+ });
+ });
});
});