diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-01-27 10:24:06 +0100 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-01-27 10:24:06 +0100 |
commit | f305d57ab7702c3ca10fd6e32366d19e524ee1f0 (patch) | |
tree | 381ccb09c7cedc72cdf8ad6c98b681b72f4d1bc0 | |
parent | 877f2e477b010f9f37a9044606f110a90f077680 (diff) | |
download | gitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.zip gitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.tar.gz gitbook-f305d57ab7702c3ca10fd6e32366d19e524ee1f0.tar.bz2 |
Add more classes structures
-rw-r--r-- | lib/backbone/article.js | 28 | ||||
-rw-r--r-- | lib/backbone/file.js | 2 | ||||
-rw-r--r-- | lib/backbone/page.js | 15 | ||||
-rw-r--r-- | lib/backbone/summary.js | 1 | ||||
-rw-r--r-- | lib/book.js | 55 | ||||
-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.js | 29 | ||||
-rw-r--r-- | lib/generators/index.js | 10 | ||||
-rw-r--r-- | lib/generators/json.js | 14 | ||||
-rw-r--r-- | lib/output.js | 47 | ||||
-rw-r--r-- | lib/page.js | 10 | ||||
-rw-r--r-- | lib/parsers/asciidoc.js | 27 | ||||
-rw-r--r-- | lib/parsers/html.js | 90 | ||||
-rw-r--r-- | lib/parsers/index.js | 11 | ||||
-rw-r--r-- | lib/parsers/markdown.js | 6 | ||||
-rw-r--r-- | lib/plugins/index.js | 0 | ||||
-rw-r--r-- | lib/plugins/manager.js | 10 | ||||
-rw-r--r-- | lib/plugins/plugin.js | 8 | ||||
-rw-r--r-- | lib/templating/blocks.js | 11 | ||||
-rw-r--r-- | lib/templating/index.js | 28 | ||||
-rw-r--r-- | lib/templating/loader.js | 0 | ||||
-rw-r--r-- | lib/utils/path.js | 12 | ||||
-rw-r--r-- | lib/utils/promise.js | 26 | ||||
-rw-r--r-- | test/readme.js | 13 |
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'); + }); + }); + }); }); }); |