summaryrefslogtreecommitdiffstats
path: root/lib/parse
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2016-04-22 11:00:21 +0200
committerSamy Pessé <samypesse@gmail.com>2016-04-22 11:00:21 +0200
commit4336fdb2414d460ffee68a0cc87c0cb0c85cf56e (patch)
tree279f711ab98666c892c19a7b9e4073a094f03f98 /lib/parse
parent87db7cf1d412fa6fbd18e9a7e4f4755f2c0c5547 (diff)
downloadgitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.zip
gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.gz
gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.bz2
Base
Diffstat (limited to 'lib/parse')
-rw-r--r--lib/parse/findParsableFile.js36
-rw-r--r--lib/parse/index.js10
-rw-r--r--lib/parse/parseBook.js26
-rw-r--r--lib/parse/parseConfig.js51
-rw-r--r--lib/parse/parseGlossary.js22
-rw-r--r--lib/parse/parseIgnore.js43
-rw-r--r--lib/parse/parsePage.js26
-rw-r--r--lib/parse/parsePagesList.js39
-rw-r--r--lib/parse/parseReadme.js24
-rw-r--r--lib/parse/parseStructureFile.js57
-rw-r--r--lib/parse/parseSummary.js22
-rw-r--r--lib/parse/validateConfig.js31
-rw-r--r--lib/parse/walkSummary.js34
13 files changed, 421 insertions, 0 deletions
diff --git a/lib/parse/findParsableFile.js b/lib/parse/findParsableFile.js
new file mode 100644
index 0000000..4434d64
--- /dev/null
+++ b/lib/parse/findParsableFile.js
@@ -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;
diff --git a/lib/parse/index.js b/lib/parse/index.js
new file mode 100644
index 0000000..042024b
--- /dev/null
+++ b/lib/parse/index.js
@@ -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')
+};
diff --git a/lib/parse/parseBook.js b/lib/parse/parseBook.js
new file mode 100644
index 0000000..4af1768
--- /dev/null
+++ b/lib/parse/parseBook.js
@@ -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;
diff --git a/lib/parse/parseConfig.js b/lib/parse/parseConfig.js
new file mode 100644
index 0000000..5200de2
--- /dev/null
+++ b/lib/parse/parseConfig.js
@@ -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;
diff --git a/lib/parse/parseGlossary.js b/lib/parse/parseGlossary.js
new file mode 100644
index 0000000..f56c751
--- /dev/null
+++ b/lib/parse/parseGlossary.js
@@ -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;
diff --git a/lib/parse/parseIgnore.js b/lib/parse/parseIgnore.js
new file mode 100644
index 0000000..3ffe89e
--- /dev/null
+++ b/lib/parse/parseIgnore.js
@@ -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;
diff --git a/lib/parse/parsePage.js b/lib/parse/parsePage.js
new file mode 100644
index 0000000..75bcf61
--- /dev/null
+++ b/lib/parse/parsePage.js
@@ -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;
diff --git a/lib/parse/parsePagesList.js b/lib/parse/parsePagesList.js
new file mode 100644
index 0000000..36fcdec
--- /dev/null
+++ b/lib/parse/parsePagesList.js
@@ -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;
diff --git a/lib/parse/parseReadme.js b/lib/parse/parseReadme.js
new file mode 100644
index 0000000..ea6ef59
--- /dev/null
+++ b/lib/parse/parseReadme.js
@@ -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;
diff --git a/lib/parse/parseStructureFile.js b/lib/parse/parseStructureFile.js
new file mode 100644
index 0000000..bdb97db
--- /dev/null
+++ b/lib/parse/parseStructureFile.js
@@ -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;
diff --git a/lib/parse/parseSummary.js b/lib/parse/parseSummary.js
new file mode 100644
index 0000000..3fb471e
--- /dev/null
+++ b/lib/parse/parseSummary.js
@@ -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;
diff --git a/lib/parse/validateConfig.js b/lib/parse/validateConfig.js
new file mode 100644
index 0000000..855edc3
--- /dev/null
+++ b/lib/parse/validateConfig.js
@@ -0,0 +1,31 @@
+var jsonschema = require('jsonschema');
+var jsonSchemaDefaults = require('json-schema-defaults');
+var mergeDefaults = require('merge-defaults');
+
+var schema = require('../constants/configSchema');
+var error = require('../utils/error');
+
+/**
+ 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'
+ });
+
+ // Throw error
+ if (result.errors.length > 0) {
+ throw new error.ConfigurationError(new Error(result.errors[0].stack));
+ }
+
+ // Insert default values
+ var defaults = jsonSchemaDefaults(schema);
+ return mergeDefaults(bookJson, defaults);
+}
+
+module.exports = validateConfig;
diff --git a/lib/parse/walkSummary.js b/lib/parse/walkSummary.js
new file mode 100644
index 0000000..0117752
--- /dev/null
+++ b/lib/parse/walkSummary.js
@@ -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;