diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-02-25 10:12:46 +0100 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-02-25 10:12:46 +0100 |
commit | 82a4f80cd23ecb0a734c21668bc8561734d96e36 (patch) | |
tree | f50f2cbbc79b0b05ed9d58f66f63e26f35a1eaba | |
parent | f47a3968b7a78af8091906fe8936cd00581ae05f (diff) | |
download | gitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.zip gitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.tar.gz gitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.tar.bz2 |
Use jsonschema to validate configuration
-rw-r--r-- | lib/config/default.js | 100 | ||||
-rw-r--r-- | lib/config/index.js | 12 | ||||
-rw-r--r-- | lib/config/schema.js | 192 | ||||
-rw-r--r-- | lib/config/validator.js | 31 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | test/config.js | 39 |
6 files changed, 264 insertions, 112 deletions
diff --git a/lib/config/default.js b/lib/config/default.js deleted file mode 100644 index 23a92f9..0000000 --- a/lib/config/default.js +++ /dev/null @@ -1,100 +0,0 @@ -module.exports = { - // Book metadatas (somes are extracted from the README by default) - 'title': null, - 'description': null, - 'isbn': null, - 'language': 'en', - 'direction': null, - 'author': null, - - // Version of gitbook to use - 'gitbook': '*', - - // Structure - 'structure': { - 'langs': 'LANGS.md', - 'readme': 'README.md', - 'glossary': 'GLOSSARY.md', - 'summary': 'SUMMARY.md' - }, - - // Theme for output - 'theme': 'default', - - // CSS Styles - 'styles': { - 'website': 'styles/website.css', - 'print': 'styles/print.css', - 'ebook': 'styles/ebook.css', - 'pdf': 'styles/pdf.css', - 'mobi': 'styles/mobi.css', - 'epub': 'styles/epub.css' - }, - - // Plugins list, can contain '-name' for removing default plugins - 'plugins': [], - - // Global configuration for plugins - 'pluginsConfig': {}, - - // Variables for templating - 'variables': {}, - - // Links in template (null: default, false: remove, string: new value) - 'links': { - // Custom links at top of sidebar - 'sidebar': { - // 'Custom link name': 'https://customlink.com' - }, - - // Sharing links - 'sharing': { - 'google': null, - 'facebook': null, - 'twitter': null, - 'weibo': null, - 'all': null - } - }, - - - // Options for PDF generation - 'pdf': { - // Add toc at the end of the file - 'toc': true, - - // Add page numbers to the bottom of every page - 'pageNumbers': false, - - // Font for the file content - 'fontSize': 12, - 'fontFamily': 'Arial', - - // Paper size for the pdf - // Choices are [u’a0’, u’a1’, u’a2’, u’a3’, u’a4’, u’a5’, u’a6’, u’b0’, u’b1’, u’b2’, u’b3’, u’b4’, u’b5’, u’b6’, u’legal’, u’letter’] - 'paperSize': 'a4', - - // How to mark detected chapters. - // Choices are “pagebreak”, “rule”, 'both' or “none”. - 'chapterMark' : 'pagebreak', - - // An XPath expression. Page breaks are inserted before the specified elements. - // To disable use the expression: '/' - 'pageBreaksBefore': '/', - - // Margin (in pts) - // Note: 72 pts equals 1 inch - 'margin': { - 'right': 62, - 'left': 62, - 'top': 56, - 'bottom': 56 - }, - - // Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_. - 'headerTemplate': null, - - // Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_. - 'footerTemplate': null - } -}; diff --git a/lib/config/index.js b/lib/config/index.js index 8bb93ad..272e92a 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -3,7 +3,7 @@ var semver = require('semver'); var gitbook = require('../gitbook'); var Promise = require('../utils/promise'); -var configDefault = require('./default'); +var validator = require('./validator'); var plugins = require('./plugins'); // Config files to tested (sorted) @@ -69,8 +69,7 @@ Config.prototype.load = function() { Config.prototype.replace = function(options) { var that = this; - this.options = _.cloneDeep(configDefault); - this.options = _.merge(this.options, options || {}); + this.options = validator.validate(options); // options.input == book.root Object.defineProperty(this.options, 'input', { @@ -85,13 +84,6 @@ Config.prototype.replace = function(options) { return that.book.parent? that.book.parent.root : undefined; } }); - - // options.originalOutput == book.parent.options.output - Object.defineProperty(this.options, 'originalOutput', { - get: function () { - return that.book.parent? that.book.parent.options.output : undefined; - } - }); }; // Return true if book has a configuration file diff --git a/lib/config/schema.js b/lib/config/schema.js new file mode 100644 index 0000000..10d99fc --- /dev/null +++ b/lib/config/schema.js @@ -0,0 +1,192 @@ +module.exports = { + '$schema': 'http://json-schema.org/schema#', + 'id': 'https://gitbook.com/schemas/book.json', + 'title': 'GitBook Configuration', + 'type': 'object', + 'properties': { + 'title': { + 'type': 'string', + 'description': 'Title of the book, default is extracted from README' + }, + 'description': { + 'type': 'string', + 'description': 'Description of the book, default is extracted from README' + }, + 'isbn': { + 'type': 'string', + 'description': 'ISBN for published book' + }, + 'author': { + 'type': 'string', + 'description': 'Name of the author' + }, + 'gitbook': { + 'type': 'string', + 'default': '*', + 'description': 'GitBook version to match' + }, + 'direction': { + 'type': 'string', + 'enum': ['ltr', 'rtl'], + 'description': 'Direction of texts, default is detected in the pages' + }, + 'theme': { + 'type': 'string', + 'default': 'default', + 'description': 'Name of the theme plugin to use' + }, + 'variables': { + 'type': 'object', + 'description': 'Templating context variables' + }, + 'plugins': { + 'oneOf': [ + { '$ref': '#/definitions/pluginsArray' }, + { '$ref': '#/definitions/pluginsString' } + ], + 'default': [] + }, + 'pluginsConfig': { + 'type': 'object', + 'description': 'Configuration for plugins' + }, + 'structure': { + 'type': 'object', + 'properties': { + 'langs': { + 'default': 'LANGS.md', + 'type': 'string', + 'description': 'File to use as languages index', + 'pattern': '^[0-9a-zA-Z ... ]+$' + }, + 'readme': { + 'default': 'README.md', + 'type': 'string', + 'description': 'File to use as preface', + 'pattern': '^[0-9a-zA-Z ... ]+$' + }, + 'glossary': { + 'default': 'GLOSSARY.md', + 'type': 'string', + 'description': 'File to use as glossary index', + 'pattern': '^[0-9a-zA-Z ... ]+$' + }, + 'summary': { + 'default': 'SUMMARY.md', + 'type': 'string', + 'description': 'File to use as table of contents', + 'pattern': '^[0-9a-zA-Z ... ]+$' + } + }, + 'additionalProperties': false + }, + 'pdf': { + 'type': 'object', + 'description': 'PDF specific configurations', + 'properties': { + 'pageNumbers': { + 'type': 'boolean', + 'default': true, + 'description': 'Add page numbers to the bottom of every page' + }, + 'fontSize': { + 'type': 'integer', + 'minimum': 8, + 'maximum': 30, + 'default': 12, + 'description': 'Font size for the PDF output' + }, + 'fontFamily': { + 'type': 'string', + 'default': 'Arial', + 'description': 'Font family for the PDF output' + }, + 'paperSize': { + 'type': 'string', + 'enum': ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'legal', 'letter'], + 'default': 'a4', + 'description': 'Paper size for the PDF' + }, + 'chapterMark': { + 'type': 'string', + 'enum': ['pagebreak', 'rule', 'both', 'none'], + 'default': 'pagebreak', + 'description': 'How to mark detected chapters' + }, + 'pageBreaksBefore': { + 'type': 'string', + 'default': '/', + 'description': 'An XPath expression. Page breaks are inserted before the specified elements. To disable use the expression: "/"' + }, + 'margin': { + 'type': 'object', + 'properties': { + 'right': { + 'type': 'integer', + 'description': 'Right Margin', + 'minimum': 0, + 'maximum': 100, + 'default': 62 + }, + 'left': { + 'type': 'integer', + 'description': 'Left Margin', + 'minimum': 0, + 'maximum': 100, + 'default': 62 + }, + 'top': { + 'type': 'integer', + 'description': 'Top Margin', + 'minimum': 0, + 'maximum': 100, + 'default': 56 + }, + 'bottom': { + 'type': 'integer', + 'description': 'Bottom Margin', + 'minimum': 0, + 'maximum': 100, + 'default': 56 + } + } + } + } + } + }, + 'required': [], + 'definitions': { + 'pluginsArray': { + 'type': 'array', + 'items': { + 'oneOf': [ + { '$ref': '#/definitions/pluginObject' }, + { '$ref': '#/definitions/pluginString' } + ] + } + }, + 'pluginsString': { + 'type': 'string' + }, + 'pluginString': { + 'type': 'string' + }, + 'pluginObject': { + 'type': 'object', + 'properties': { + 'name': { + 'type': 'string' + }, + 'version': { + 'type': 'string' + }, + 'isDefault': { + 'type': 'boolean', + 'default': false + } + }, + 'additionalProperties': false, + 'required': ['name'] + } + } +}; diff --git a/lib/config/validator.js b/lib/config/validator.js new file mode 100644 index 0000000..0ea278f --- /dev/null +++ b/lib/config/validator.js @@ -0,0 +1,31 @@ +var _ = require('lodash'); +var jsonschema = require('jsonschema'); +var jsonSchemaDefaults = require('json-schema-defaults'); +var mergeDefaults = require('merge-defaults'); + +var schema = require('./schema'); +var error = require('../utils/error'); + +// Validate a book.json content +// And return a mix with the default value +function validate(bookJson) { + bookJson = _.cloneDeep(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 = { + validate: validate +}; diff --git a/package.json b/package.json index 66ac058..80a440f 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "spawn-cmd": "0.0.2", "escape-string-regexp": "1.0.3", "juice": "1.9.0", - "jsonschema": "1.0.2", + "jsonschema": "1.1.0", "json-schema-defaults": "0.1.1", "merge-defaults": "0.2.1", "github-slugid": "1.0.0", diff --git a/test/config.js b/test/config.js index 3ec76ca..7a96a5c 100644 --- a/test/config.js +++ b/test/config.js @@ -1,6 +1,43 @@ +var should = require('should'); var mock = require('./mock'); +var validator = require('../lib/config/validator'); -describe('Config', function() { +describe('Configuration', function() { + + describe('Validation', function() { + it('should merge default', function() { + validator.validate({}).should.have.property('gitbook').which.equal('*'); + }); + + it('should throw error for invalid configuration', function() { + should.throws(function() { + validator.validate({ + direction: 'invalid' + }); + }); + }); + + it('should not throw error for non existing configuration', function() { + validator.validate({ + style: { + 'pdf': 'test.css' + } + }); + }); + + it('should validate plugins as an array', function() { + validator.validate({ + plugins: ['hello'] + }); + }); + + it('should validate plugins as a string', function() { + validator.validate({ + plugins: 'hello,world' + }); + }); + + }); describe('No configuration', function() { var book; |