summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2016-02-25 10:12:46 +0100
committerSamy Pessé <samypesse@gmail.com>2016-02-25 10:12:46 +0100
commit82a4f80cd23ecb0a734c21668bc8561734d96e36 (patch)
treef50f2cbbc79b0b05ed9d58f66f63e26f35a1eaba
parentf47a3968b7a78af8091906fe8936cd00581ae05f (diff)
downloadgitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.zip
gitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.tar.gz
gitbook-82a4f80cd23ecb0a734c21668bc8561734d96e36.tar.bz2
Use jsonschema to validate configuration
-rw-r--r--lib/config/default.js100
-rw-r--r--lib/config/index.js12
-rw-r--r--lib/config/schema.js192
-rw-r--r--lib/config/validator.js31
-rw-r--r--package.json2
-rw-r--r--test/config.js39
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;