Use jsonschema to validate configuration

This commit is contained in:
Samy Pessé
2016-02-25 10:12:46 +01:00
parent f47a3968b7
commit 82a4f80cd2
6 changed files with 264 additions and 112 deletions
-100
View File
@@ -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 [ua0, ua1, ua2, ua3, ua4, ua5, ua6, ub0, ub1, ub2, ub3, ub4, ub5, ub6, ulegal, uletter]
'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
}
};
+2 -10
View File
@@ -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
+192
View File
@@ -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']
}
}
};
+31
View File
@@ -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
};
+1 -1
View File
@@ -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",
+38 -1
View File
@@ -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;