diff options
-rw-r--r-- | lib/backbone/article.js | 28 | ||||
-rw-r--r-- | lib/backbone/summary.js | 99 | ||||
-rw-r--r-- | lib/utils/location.js | 21 | ||||
-rw-r--r-- | test/3-glossary.js | 2 | ||||
-rw-r--r-- | test/5-summary.js | 40 | ||||
-rw-r--r-- | test/6-parse.js | 3 |
6 files changed, 161 insertions, 32 deletions
diff --git a/lib/backbone/article.js b/lib/backbone/article.js deleted file mode 100644 index 299df25..0000000 --- a/lib/backbone/article.js +++ /dev/null @@ -1,28 +0,0 @@ -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/summary.js b/lib/backbone/summary.js index 96815a6..43a373a 100644 --- a/lib/backbone/summary.js +++ b/lib/backbone/summary.js @@ -1,16 +1,111 @@ +var _ = require('lodash'); var util = require('util'); +var url = require('url'); + +var location = require('../utils/location'); var BackboneFile = require('./file'); -var Article = require('./article'); +/* +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(summary, title, ref, articles, parent) { + this.summary = summary; + this.title = title; + + if (ref && location.isExternal(ref)) { + throw new Error('SUMMARY can only contains relative locations'); + } + if (!title) { + throw new Error('SUMMARY entries should have an non-empty title'); + } + + var parts = url.parse(ref); + this.filename = parts.pathname; + this.anchor = parts.hash; + + this.articles = _.map(articles || [], function(article) { + if (article instanceof TOCArticle) return article; + return new TOCArticle(article.title, article.ref, article.articles, this); + }, this); +} + +// Iterate over all articles in this articles +TOCArticle.prototype.walk = function(iter) { + _.each(this.articles, function(article) { + iter(article); + article.walk(iter); + }); +}; + +// Return true if has children +TOCArticle.prototype.hasChildren = function() { + return this.articles.length > 0; +}; + +/* +A part of a ToC is a composed of a tree of articles. +*/ +function TOCPart(summary, part) { + var that = this; + + this.summary = summary; + this.articles = _.map(part.articles || part.chapters, function(article) { + return new TOCArticle(that.summary, article.title, article.path, article.articles); + }); +} + +// Iterate over all entries of the part +TOCPart.prototype.walk = function(iter) { + _.each(this.articles, function(article) { + iter(article); + article.walk(iter); + }); +}; + +/* +A summary is composed of a list of parts, each composed wit a tree of articles. +*/ function Summary() { BackboneFile.apply(this, arguments); - this.articles = []; + this.parts = []; + this._length = 0; } util.inherits(Summary, BackboneFile); Summary.prototype.type = 'summary'; +// Parse the summary content +Summary.prototype.parse = function(content) { + var that = this; + + return this.parser.summary(content) + + // TODO: update GitBook's parsers to return a list of parts + .then(function(part) { + that.parts = [new TOCPart(that, part)]; + + // Update count of articles + that._length = 0; + that.walk(function() { + that._length += 1; + }); + }); +}; + +// Iterate over all entries of the summary +// iter is called with an TOCArticle +Summary.prototype.walk = function(iter) { + _.each(this.parts, function(part) { + part.walk(iter); + }); +}; + +// Return the count of articles in the summary +Summary.prototype.count = function() { + return this._length; +}; module.exports = Summary; diff --git a/lib/utils/location.js b/lib/utils/location.js new file mode 100644 index 0000000..d57e84f --- /dev/null +++ b/lib/utils/location.js @@ -0,0 +1,21 @@ +var url = require('url'); + +// Is the url an external url +function isExternal(href) { + try { + return Boolean(url.parse(href).protocol); + } catch(err) { + return false; + } +} + + +// Inverse of isExternal +function isRelative(href) { + return !isExternal(href); +} + +module.exports = { + isExternal: isExternal, + isRelative: isRelative +}; diff --git a/test/3-glossary.js b/test/3-glossary.js index 176298f..0efef34 100644 --- a/test/3-glossary.js +++ b/test/3-glossary.js @@ -61,7 +61,7 @@ describe('Glossary', function() { entry.id.should.equal('hello_world'); }); - it('should undefined return non existing entry', function() { + it('should return undefined for non existing entry', function() { var entry = book.glossary.find('Hello'); should.not.exist(entry); }); diff --git a/test/5-summary.js b/test/5-summary.js new file mode 100644 index 0000000..2744c43 --- /dev/null +++ b/test/5-summary.js @@ -0,0 +1,40 @@ +var mock = require('./mock'); + +describe('Summary / Table of contents', function() { + describe('Empty summary list', function() { + var book; + + before(function() { + return mock.setupDefaultBook({}) + .then(function(_book) { + book = _book; + return book.summary.load(); + }); + }); + + it('should correctly count articles', function() { + book.summary.count().should.equal(1); + }); + }); + + describe('Non-empty summary list', function() { + var book; + + before(function() { + return mock.setupDefaultBook({ + 'SUMMARY.md': '# Summary\n\n' + + '* [Hello](./hello.md)\n' + + '* [World](./world.md)\n\n' + }) + .then(function(_book) { + book = _book; + return book.summary.load(); + }); + }); + + it('should correctly count articles', function() { + book.summary.count().should.equal(3); + }); + }); +}); + diff --git a/test/6-parse.js b/test/6-parse.js index a575720..72e0260 100644 --- a/test/6-parse.js +++ b/test/6-parse.js @@ -40,7 +40,8 @@ describe('Parsing', function() { }); it('should list language books', function() { - + book.isMultilingual().should.equal(true); + book.books.should.have.lengthOf(2); }); }); }); |