diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-04-22 11:00:21 +0200 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-04-22 11:00:21 +0200 |
commit | 4336fdb2414d460ffee68a0cc87c0cb0c85cf56e (patch) | |
tree | 279f711ab98666c892c19a7b9e4073a094f03f98 /lib/parse | |
parent | 87db7cf1d412fa6fbd18e9a7e4f4755f2c0c5547 (diff) | |
download | gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.zip gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.gz gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.bz2 |
Base
Diffstat (limited to 'lib/parse')
-rw-r--r-- | lib/parse/findParsableFile.js | 36 | ||||
-rw-r--r-- | lib/parse/index.js | 10 | ||||
-rw-r--r-- | lib/parse/parseBook.js | 26 | ||||
-rw-r--r-- | lib/parse/parseConfig.js | 51 | ||||
-rw-r--r-- | lib/parse/parseGlossary.js | 22 | ||||
-rw-r--r-- | lib/parse/parseIgnore.js | 43 | ||||
-rw-r--r-- | lib/parse/parsePage.js | 26 | ||||
-rw-r--r-- | lib/parse/parsePagesList.js | 39 | ||||
-rw-r--r-- | lib/parse/parseReadme.js | 24 | ||||
-rw-r--r-- | lib/parse/parseStructureFile.js | 57 | ||||
-rw-r--r-- | lib/parse/parseSummary.js | 22 | ||||
-rw-r--r-- | lib/parse/validateConfig.js | 31 | ||||
-rw-r--r-- | lib/parse/walkSummary.js | 34 |
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; |