summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/backbone/article.js28
-rw-r--r--lib/backbone/summary.js99
-rw-r--r--lib/utils/location.js21
-rw-r--r--test/3-glossary.js2
-rw-r--r--test/5-summary.js40
-rw-r--r--test/6-parse.js3
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);
});
});
});