mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-22 18:43:29 +00:00
Use jsonschema to validate configuration
This commit is contained in:
@@ -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
|
||||
}
|
||||
};
|
||||
+2
-10
@@ -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
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user