feat(script): add config checking specs

pull/351/head
ppoffice 2018-10-19 00:53:09 -04:00
parent b60e51694d
commit c56a3c46c4
14 changed files with 806 additions and 135 deletions

View File

@ -1,95 +0,0 @@
# Website's icon url.
favicon: /favicon.png
# Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)
open_graph:
fb_app_id:
fb_admins:
twitter_id:
google_plus:
rss:
# Website's logo shown on the left of the navigation bar.
logo:
# Navigation bar menu links.
menu:
Home: /
Archives: /archives
Categories: /categories
Tags: /tags
About: /about
article:
highlight: atom-one-light
thumbnail: true
search:
type: insight
comment:
type: disqus
shortname: hexo-theme-icarus
share:
type: sharethis
install_url:
widgets:
- type: profile
position: left
author: PPOffice
author_title: Web Developer
location: Earth, Solar System
avatar:
gravatar:
follow_link: http://github.com/ppoffice
social_links:
Github:
icon: fab fa-github
url: http://github.com/ppoffice
Facebook:
icon: fab fa-facebook
url: http://facebook.com
Twitter:
icon: fab fa-twitter
url: http://twitter.com
Dribbble:
icon: fab fa-dribbble
url: http://dribbble.com
RSS:
icon: fas fa-rss
url: /
- type: toc
position: left
- type: links
position: left
links:
Hexo: https://hexo.io
Github: https://github.com/ppoffice
- type: category
position: left
- type: tagcloud
position: left
- type: recent_posts
position: right
- type: archive
position: right
- type: tag
position: right
plugins:
gallery: true
outdated-browser: true
animejs: true
mathjax: true
google-analytics:
tracking_id:
baidu-analytics:
tracking_id:
providers:
cdn:
fontcdn:
iconcdn:

View File

@ -1,6 +1,6 @@
/**
* CDN static file resolvers.
*
*
* @example
* <%- cdn(package, version, filename) %>
* <%- fontcdn(fontName) %>
@ -23,9 +23,9 @@ const icon_providers = {
};
module.exports = function (hexo) {
hexo.extend.helper.register('cdn', function (package, version, filename, provider = 'cdnjs') {
provider = hexo.extend.helper.get('get_config').bind(this)('providers.cdn', provider);
if (provider != null && cdn_providers.hasOwnProperty(provider)) {
hexo.extend.helper.register('cdn', function (package, version, filename) {
let provider = hexo.extend.helper.get('get_config').bind(this)('providers.cdn');
if (provider !== null && cdn_providers.hasOwnProperty(provider)) {
provider = cdn_providers[provider];
}
return provider.replace(/\${\s*package\s*}/gi, package)
@ -33,18 +33,22 @@ module.exports = function (hexo) {
.replace(/\${\s*filename\s*}/gi, filename);
});
hexo.extend.helper.register('fontcdn', function (fontName, provider = 'google') {
provider = hexo.extend.helper.get('get_config').bind(this)('providers.font', provider);
if (provider != null && font_providers.hasOwnProperty(provider)) {
hexo.extend.helper.register('fontcdn', function (fontName) {
let provider = hexo.extend.helper.get('get_config').bind(this)('providers.fontcdn');
if (provider !== null && font_providers.hasOwnProperty(provider)) {
provider = font_providers[provider];
}
return provider.replace(/\${\s*fontname\s*}/gi, fontName);
});
hexo.extend.helper.register('iconcdn', function (provider = 'fontawesome') {
provider = hexo.extend.helper.get('get_config').bind(this)('providers.icon', provider);
if (provider != null && icon_providers.hasOwnProperty(provider)) {
hexo.extend.helper.register('iconcdn', function (provider = null) {
if (provider !== null && icon_providers.hasOwnProperty(provider)) {
provider = icon_providers[provider];
} else {
provider = hexo.extend.helper.get('get_config').bind(this)('providers.iconcdn');
if (provider !== null && icon_providers.hasOwnProperty(provider)) {
provider = icon_providers[provider];
}
}
return provider;
});

View File

@ -1,12 +1,15 @@
/**
* Theme configuration helpers.
*
*
* @description Test if a configuration is set or fetch its value. If `exclude_page` is set, the helpers will
* not look up configurations in the current page's front matter.
* @example
* <%- has_config(config_name, exclude_page) %>
* <%- get_config(config_name, default_value, exclude_page) %>
*/
const specs = require('../specs/_config.yml');
const descriptors = require('../specs/common').descriptor;
module.exports = function (hexo) {
function readProperty(object, path) {
const paths = path.split('.');
@ -19,10 +22,18 @@ module.exports = function (hexo) {
return object;
}
hexo.extend.helper.register('get_config', function (configName, defaultValue = null, excludePage = false) {
hexo.extend.helper.register('get_config', function (configName, defaultValue = undefined, excludePage = false) {
const value = readProperty(Object.assign({}, this.config, hexo.theme.config,
!excludePage ? this.page : {}), configName);
return value == null ? defaultValue : value;
if (value === null) {
if (typeof(defaultValue) !== 'undefined') {
return defaultValue;
} else {
const property = readProperty(specs, configName);
return property === null ? null : property[descriptors.defaultValue];
}
}
return value;
});
hexo.extend.helper.register('has_config', function (configName, excludePage = false) {
@ -32,6 +43,6 @@ module.exports = function (hexo) {
hexo.extend.helper.register('get_config_from_obj', function (object, configName, defaultValue = null) {
const value = readProperty(object, configName);
return value == null ? defaultValue : value;
return value === null ? defaultValue : value;
});
}

View File

@ -1,6 +1,6 @@
/**
* Helper functions for controlling layout.
*
*
* @example
* <%- get_widgets(position) %>
* <%- has_column() %>
@ -8,7 +8,11 @@
*/
module.exports = function (hexo) {
hexo.extend.helper.register('get_widgets', function (position) {
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets', []);
const hasWidgets = hexo.extend.helper.get('has_config').bind(this)('widgets');
if (!hasWidgets) {
return [];
}
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets');
return widgets.filter(widget => widget.hasOwnProperty('position') && widget.position === position);
});

View File

@ -0,0 +1,473 @@
const { version } = require('../../package.json');
const { type, required, defaultValue, description, condition } = require('./common').descriptor;
const desc = description;
const IconLink = {
[type]: 'object',
[desc]: 'Link icon settings',
'*': {
[type]: ['string', 'object'],
[desc]: 'Path or URL to the menu item, and/or link icon class names',
icon: {
[required]: true,
[type]: 'string',
[desc]: 'Link icon class names'
},
url: {
[required]: true,
[type]: 'string',
[desc]: 'Path or URL to the menu item'
}
}
};
const DEFAULT_WIDGETS = [
{
type: 'profile',
position: 'left',
author: 'Your name',
author_title: 'Your title',
location: 'Your location',
avatar: null,
gravatar: null,
follow_link: 'http://github.com/ppoffice',
social_links: {
Github: {
icon: 'fab fa-github',
url: 'http://github.com/ppoffice'
},
Facebook: {
icon: 'fab fa-facebook',
url: 'http://facebook.com'
},
Twitter: {
icon: 'fab fa-twitter',
url: 'http://twitter.com'
},
Dribbble: {
icon: 'fab fa-dribbble',
url: 'http://dribbble.com'
},
RSS: {
icon: 'fas fa-rss',
url: '/'
}
}
},
{
type: 'toc',
position: 'left'
},
{
type: 'links',
position: 'left',
links: {
Hexo: 'https://hexo.io',
Github: 'https://github.com/ppoffice'
}
},
{
type: 'category',
position: 'left'
},
{
type: 'tagcloud',
position: 'left'
},
{
type: 'recent_posts',
position: 'right'
},
{
type: 'archive',
position: 'right'
},
{
type: 'tag',
position: 'right'
}
];
module.exports = {
[type]: 'object',
[desc]: 'Root of the configuration file',
[required]: true,
version: {
[type]: 'string',
[desc]: 'Version of the Icarus theme that is currently used',
[required]: true,
[defaultValue]: version
},
favicon: {
[type]: 'string',
[desc]: 'Path or URL to the website\'s icon',
[defaultValue]: null
},
open_graph: {
[type]: 'object',
[desc]: 'Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)',
fb_app_id: {
[type]: 'string',
[desc]: 'Facebook App ID',
[defaultValue]: null
},
fb_admins: {
[type]: 'string',
[desc]: 'Facebook Admin ID',
[defaultValue]: null
},
twitter_id: {
[type]: 'string',
[desc]: 'Twitter ID',
[defaultValue]: null
},
twitter_site: {
[type]: 'string',
[desc]: 'Twitter site',
[defaultValue]: null
},
google_plus: {
[type]: 'string',
[desc]: 'Google+ profile link',
[defaultValue]: null
}
},
rss: {
[type]: 'string',
[desc]: 'Path or URL to RSS atom.xml',
[defaultValue]: null
},
logo: {
[type]: ['string', 'object'],
[defaultValue]: '/images/logo.svg',
[desc]: 'Path or URL to the website\'s logo to be shown on the left of the navigation bar or footer',
text: {
[type]: 'string',
[desc]: 'Text to be shown in place of the logo image'
}
},
navbar: {
[type]: 'object',
[desc]: 'Navigation bar link settings',
menu: {
[type]: 'object',
[desc]: 'Navigation bar menu links',
[defaultValue]: {
Home: '/',
Archives: '/archives',
Categories: '/categories',
Tags: '/tags',
About: '/about'
},
'*': {
[type]: 'string',
[desc]: 'Path or URL to the menu item'
}
},
links: {
...IconLink,
[desc]: 'Navigation bar links to be shown on the right',
[defaultValue]: {
'Download on GitHub': {
icon: 'fab fa-github',
url: 'http://github.com/ppoffice/hexo-theme-icarus'
}
}
}
},
footer: {
[type]: 'object',
[description]: 'Footer section link settings',
links: {
...IconLink,
[desc]: 'Links to be shown on the right of the footer section',
[defaultValue]: {
'Creative Commons': {
icon: 'fab fa-creative-commons',
url: 'https://creativecommons.org/'
},
'Attribution 4.0 International': {
icon: 'fab fa-creative-commons-by',
url: 'https://creativecommons.org/licenses/by/4.0/'
},
'Download on GitHub': {
icon: 'fab fa-github',
url: 'http://github.com/ppoffice/hexo-theme-icarus'
}
}
}
},
article: {
[type]: 'object',
[desc]: 'Article display settings',
highlight: {
[type]: 'string',
[desc]: 'Code highlight theme (https://github.com/highlightjs/highlight.js/tree/master/src/styles)',
[defaultValue]: 'atom-one-light'
},
thumbnail: {
[type]: 'boolean',
[desc]: 'Whether to show article thumbnail images',
[defaultValue]: true
},
readtime: {
[type]: 'boolean',
[desc]: 'Whether to show estimate article reading time',
[defaultValue]: true
}
},
search: {
[type]: 'object',
[desc]: 'Search plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Search-Plugins)',
type: {
[type]: 'string',
[desc]: 'Name of the search plugin',
[defaultValue]: 'insight'
},
cx: {
[type]: 'string',
[desc]: 'Google CSE cx value',
[required]: true,
[condition]: parent => parent.type === 'google-cse'
}
},
comment: {
[type]: 'object',
[desc]: 'Comment plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Comment-Plugins)',
type: {
[type]: 'string',
[desc]: 'Name of the comment plugin',
[defaultValue]: null
},
appid: {
[type]: 'string',
[desc]: 'Changyan comment app ID',
[required]: true,
[condition]: parent => parent.type === 'changyan'
},
conf: {
[type]: 'string',
[desc]: 'Changyan comment configuration ID',
[required]: true,
[condition]: parent => parent.type === 'changyan'
},
shortname: {
[type]: 'string',
[desc]: 'Disqus shortname',
[required]: true,
[condition]: parent => parent.type === 'disqus'
},
owner: {
[type]: 'string',
[desc]: 'Your GitHub ID',
[required]: true,
[condition]: parent => parent.type === 'gitment'
},
repo: {
[type]: 'string',
[desc]: 'The repo to store comments',
[required]: true,
[condition]: parent => parent.type === 'gitment'
},
client_id: {
[type]: 'string',
[desc]: 'Your client ID',
[required]: true,
[condition]: parent => parent.type === 'gitment'
},
client_secret: {
[type]: 'string',
[desc]: 'Your client secret',
[required]: true,
[condition]: parent => parent.type === 'gitment'
},
url: {
[type]: 'string',
[desc]: 'URL to your Isso comment service',
[required]: true,
[condition]: parent => parent.type === 'isso'
},
uid: {
[type]: 'string',
[desc]: 'LiveRe comment service UID',
[required]: true,
[condition]: parent => parent.type === 'livere'
},
app_id: {
[type]: 'boolean',
[desc]: 'LeanCloud APP ID',
[required]: true,
[condition]: parent => parent.type === 'valine'
},
app_key: {
[type]: 'boolean',
[desc]: 'LeanCloud APP key',
[required]: true,
[condition]: parent => parent.type === 'valine'
},
notify: {
[type]: 'boolean',
[desc]: 'Enable email notification when someone comments',
[defaultValue]: false,
[condition]: parent => parent.type === 'valine'
},
verify: {
[type]: 'boolean',
[desc]: 'Enable verification code service',
[defaultValue]: false,
[condition]: parent => parent.type === 'valine'
},
placeholder: {
[type]: 'boolean',
[desc]: 'Placeholder text in the comment box',
[defaultValue]: 'Say something...',
[condition]: parent => parent.type === 'valine'
}
},
share: {
[type]: 'object',
[desc]: 'Share plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Share-Plugins)',
type: {
[type]: 'string',
[desc]: 'Share plugin name',
[defaultValue]: null
},
install_url: {
[type]: 'string',
[desc]: 'URL to the share plugin script provided by share plugin service provider',
[required]: true,
[condition]: parent => parent.type === 'sharethis' || parent.type === 'addthis'
}
},
widgets: {
[type]: 'array',
[desc]: 'Sidebar widget settings',
[defaultValue]: DEFAULT_WIDGETS,
'*': {
[type]: 'object',
[desc]: 'Single widget settings',
type: {
[type]: 'string',
[desc]: 'Widget name',
[required]: true,
[defaultValue]: 'profile'
},
position: {
[type]: 'string',
[desc]: 'Where should the widget be placed, left or right',
[required]: true,
[defaultValue]: 'left'
},
author: {
[type]: 'string',
[desc]: 'Author name to be shown in the profile widget',
[condition]: parent => parent.type === 'profile',
[defaultValue]: 'Your name'
},
author_title: {
[type]: 'string',
[desc]: 'Title of the author to be shown in the profile widget',
[condition]: parent => parent.type === 'profile',
[defaultValue]: 'Your title'
},
location: {
[type]: 'string',
[desc]: 'Author\'s current location to be shown in the profile widget',
[condition]: parent => parent.type === 'profile',
[defaultValue]: 'Your location'
},
avatar: {
[type]: 'string',
[desc]: 'Path or URL to the avatar to be shown in the profile widget',
[condition]: parent => parent.type === 'profile',
[defaultValue]: '/images/avatar.png'
},
gravatar: {
[type]: 'string',
[desc]: 'Email address for the Gravatar to be shown in the profile widget',
[condition]: parent => parent.type === 'profile'
},
follow_link: {
[type]: 'string',
[desc]: 'Path or URL for the follow button',
[condition]: parent => parent.type === 'profile'
},
social_links: {
...IconLink,
[desc]: 'Links to be shown on the bottom of the profile widget',
[condition]: parent => parent.type === 'profile'
},
links: {
[type]: 'object',
[desc]: 'Links to be shown in the links widget',
[condition]: parent => parent.type === 'links',
'*': {
[type]: 'string',
[desc]: 'Path or URL to the link',
[required]: true
}
}
}
},
plugins: {
[type]: 'object',
[desc]: 'Other plugin settings',
gallery: {
[type]: 'boolean',
[desc]: 'Enable the lightGallery and Justified Gallery plugins',
[defaultValue]: true
},
'outdated-browser': {
[type]: 'boolean',
[desc]: 'Enable the Outdated Browser plugin',
[defaultValue]: true
},
animejs: {
[type]: 'boolean',
[desc]: 'Enable page animations',
[defaultValue]: true
},
mathjax: {
[type]: 'boolean',
[desc]: 'Enable the MathJax plugin',
[defaultValue]: true
},
'google-analytics': {
[type]: ['boolean', 'object'],
[desc]: 'Google Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Google-Analytics)',
tracking_id: {
[type]: 'string',
[desc]: 'Google Analytics tracking id',
[defaultValue]: null
}
},
'baidu-analytics': {
[type]: ['boolean', 'object'],
[desc]: 'Baidu Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Baidu-Analytics)',
tracking_id: {
[type]: 'string',
[desc]: 'Baidu Analytics tracking id',
[defaultValue]: null
}
}
},
providers: {
[type]: 'object',
[desc]: 'CDN provider settings',
cdn: {
[type]: 'string',
[desc]: 'Name or URL of the JavaScript and/or stylesheet CDN provider',
[defaultValue]: 'cdnjs'
},
fontcdn: {
[type]: 'string',
[desc]: 'Name or URL of the webfont CDN provider',
[defaultValue]: 'google'
},
iconcdn: {
[type]: 'string',
[desc]: 'Name or URL of the webfont Icon CDN provider',
[defaultValue]: 'fontawesome'
}
}
};

24
includes/specs/common.js Normal file
View File

@ -0,0 +1,24 @@
module.exports = {
projectName: 'Icarus',
is: {
string(value) {
return typeof(value) === 'string';
},
array(value) {
return Array.isArray(value);
},
boolean(value) {
return typeof(value) === 'boolean';
},
object(value) {
return typeof(value) === 'object' && value.constructor == Object;
}
},
descriptor: {
type: Symbol('@type'),
required: Symbol('@required'),
description: Symbol('@description'),
defaultValue: Symbol('@defaultValue'),
condition: Symbol('@condition'),
}
};

View File

@ -1,10 +1,260 @@
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const logger = require('hexo-log')();
const Schema = require('js-yaml/lib/js-yaml/schema');
const Type = require('js-yaml/lib/js-yaml/type');
const conf = path.join(__dirname, '../..', '_config.yml');
const rootSpec = require('../specs/_config.yml');
const { projectName, is } = require('../specs/common');
const { type, required, condition, defaultValue, description } = require('../specs/common').descriptor;
logger.info('Checking if the configuration file exists');
if (!fs.existsSync(conf)) {
logger.warn(`${conf} is not found. Please create one from the template _config.yml.example.`)
}
const UNDEFINED = Symbol('undefined');
const CONFIG_PATH = path.join(__dirname, '../..', '_config.yml');
const YAML_SCHEMA = new Schema({
include: [
require('js-yaml/lib/js-yaml/schema/default_full')
],
implicit: [
new Type('tag:yaml.org,2002:null', {
kind: 'scalar',
resolve(data) {
if (data === null) {
return true;
}
const max = data.length;
return (max === 1 && data === '~') ||
(max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
},
construct: () => null,
predicate: object => object === null,
represent: {
empty: function () { return ''; }
},
defaultStyle: 'empty'
})
]
});
function toPlainObject(object, depth) {
if (object === null || (!is.object(object) && !is.array(object))) {
return object;
}
if (depth <= 0) {
return is.array(object) ? '[Array]' : '[Object]';
}
if (is.array(object)) {
const result = [];
for (let child of object) {
result.push(toPlainObject(child, depth - 1));
}
return result;
}
const result = {};
for (let key in object) {
result[key] = toPlainObject(object[key], depth - 1);
}
for (let key of Object.getOwnPropertySymbols(object)) {
result[key.toString()] = toPlainObject(object[key], depth - 1);
}
return result;
}
function isValidSpec(spec) {
if (!spec.hasOwnProperty(type)) {
return false;
}
return true;
}
function checkPrecondition(spec, parameter) {
if (!spec.hasOwnProperty(condition)) {
return true;
}
try {
if (spec[condition](parameter) === true) {
return true;
}
} catch (e) { }
return false;
}
function createDefaultConfig(spec, parentConfig = null) {
if (!isValidSpec(spec)) {
return UNDEFINED;
}
if (!checkPrecondition(spec, parentConfig)) {
return UNDEFINED;
}
if (spec.hasOwnProperty(defaultValue)) {
return spec[defaultValue];
}
const types = is.array(spec[type]) ? spec[type] : [spec[type]];
if (types.includes('object')) {
let defaults = UNDEFINED;
for (let key in spec) {
if (typeof (key) === 'symbol' || key === '*') {
continue;
}
const value = createDefaultConfig(spec[key], defaults);
if (value !== UNDEFINED) {
if (defaults === UNDEFINED) {
defaults = {};
}
if (spec[key].hasOwnProperty(description)) {
defaults['#' + key] = spec[key][description];
}
defaults[key] = value;
}
}
return defaults;
} else if (types.includes('array') && spec.hasOwnProperty('*')) {
return [createDefaultConfig(spec['*'], {})];
}
return UNDEFINED;
}
function dumpConfig(config, path) {
const configYaml = yaml.safeDump(config, {
indent: 4,
lineWidth: 1024,
schema: YAML_SCHEMA
}).replace(/^(\s*)\'#.*?\':\s*\'*(.*?)\'*$/mg, '$1# $2');
fs.writeFileSync(path, configYaml);
}
function validateConfigVersion(config, spec) {
function getMajorVersion(version) {
try {
return parseInt(version.split('.')[0]);
} catch (e) {
logger.error(`Configuration version number ${version} is malformed.`);
}
return null;
}
if (!config.hasOwnProperty('version')) {
logger.error('Failed to get the version number of the configuration file.');
logger.warn('You are probably using a previous version of confiugration.');
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
return false;
}
const specMajorVersion = getMajorVersion(spec.version[defaultValue]);
const configMajorVersion = getMajorVersion(config.version);
if (configMajorVersion === null || specMajorVersion === null) {
return false;
}
if (configMajorVersion < specMajorVersion) {
logger.warn('You are using a previous version of confiugration.');
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
return false;
}
if (configMajorVersion > specMajorVersion) {
logger.warn('You are probably using a more recent version of confiugration.');
logger.warn(`Please be noted that it may not work in the previous versions of ${projectName}.`);
return false;
}
return true;
}
function validateConfigType(config, specTypes) {
specTypes = is.array(specTypes) ? specTypes : [specTypes];
for (let specType of specTypes) {
if (is[specType](config)) {
return specType;
}
}
logger.error(`Config ${toPlainObject(config, 2)} do not match types ${specTypes}`);
return null;
}
const INVALID_SPEC = Symbol();
const MISSING_REQUIRED = Symbol();
const INVALID_TYPE = Symbol();
function validateConfigAndWarn(config, spec, parentConfig, configPath = []) {
const result = validateConfig(config, spec, parentConfig, configPath);
if (result !== true) {
const pathString = configPath.join('.');
const specTypes = is.array(spec[type]) ? spec[type] : [spec[type]];
switch(result) {
case INVALID_SPEC:
logger.error(`Invalid specification! The specification '${pathString}' does not have a [type] field:`);
logger.error('The specification of this configuration is:');
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
break;
case MISSING_REQUIRED:
logger.error(`Configuration '${pathString}' in required by the specification but is missing from the configuration!`);
logger.error('The specification of this configuration is:');
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
break;
case INVALID_TYPE:
logger.error(`Type mismatch! Configuration '${pathString}' is not the '${specTypes.join(' or ')}' type.`);
logger.error('The configuration value is:');
logger.error(JSON.stringify(toPlainObject(config, 2), null, 4));
logger.error('The specification of this configuration is:');
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
break;
}
}
return result;
}
function validateConfig(config, spec, parentConfig = null, configPath = []) {
if (!isValidSpec(spec)) {
return INVALID_SPEC;
}
if (!checkPrecondition(spec, parentConfig)) {
return true;
}
if (typeof(config) === 'undefined' || config === null) {
if (spec[required] === true) {
return MISSING_REQUIRED;
}
return true;
}
const configType = validateConfigType(config, spec[type]);
if (configType === null) {
return INVALID_TYPE;
}
if (configType === 'array' && spec.hasOwnProperty('*')) {
for (let i = 0; i < config.length; i++) {
if (!validateConfigAndWarn(config[i], spec['*'], config, configPath.concat(`[${i}]`))) {
return false;
}
}
} else if (configType === 'object') {
for (let key in spec) {
if (key === '*') {
for (let configKey in config) {
if (!validateConfigAndWarn(config[configKey], spec['*'], config, configPath.concat(configKey))) {
return false;
}
}
} else {
if (!validateConfigAndWarn(config[key], spec[key], config, configPath.concat(key))) {
return false;
}
}
}
}
return true;
}
logger.info('Checking if the configuration file exists...');
if (!fs.existsSync(CONFIG_PATH)) {
const relativePath = path.relative(process.cwd(), CONFIG_PATH);
logger.warn(`${relativePath} is not found. We are creating one for you...`);
dumpConfig(createDefaultConfig(rootSpec), CONFIG_PATH);
logger.info(`${relativePath} is created. Please restart Hexo to apply changes.`);
process.exit(0);
}
logger.info('Validating the configuration file...');
const config = yaml.safeLoad(fs.readFileSync(CONFIG_PATH));
if (!validateConfigVersion(config, rootSpec)) {
logger.info(`To let ${projectName} create a fresh configuration file for you, please delete or rename the following file:`);
logger.info(CONFIG_PATH);
} else {
validateConfigAndWarn(config, rootSpec);
}

View File

@ -12,13 +12,13 @@ function checkDependency(name) {
logger.info('Checking dependencies');
const missingDeps = [
// 'moment',
// 'lodash',
// 'cheerio',
// 'js-yaml',
// 'highlight.js',
// 'hexo-util',
'js-yaml',
'moment',
'lodash',
'cheerio',
'hexo-util',
'hexo-log',
'hexo-pagination',
'hexo-generator-archive',
'hexo-generator-category',
'hexo-generator-index',

View File

@ -9,11 +9,11 @@
<script>
new Valine({
el: '#valine-thread' ,
notify: <%= get_config('comment.notify', false) %>,
verify: <%= get_config('comment.verify', false) %>,
notify: <%= get_config('comment.notify') %>,
verify: <%= get_config('comment.verify') %>,
app_id: '<%= get_config('comment.app_id') %>',
app_key: '<%= get_config('comment.app_key') %>',
placeholder: '<%= get_config('comment.placeholder', 'Say something...') %>'
placeholder: '<%= get_config('comment.placeholder') %>'
});
</script>
<% } %>

View File

@ -6,7 +6,7 @@
<% if (has_config('logo.text') && get_config('logo.text')) { %>
<%= get_config('logo.text') %>
<% } else { %>
<img src="<%- url_for(get_config('logo', 'images/logo.svg')) %>" alt="" height="28">
<img src="<%- url_for(get_config('logo')) %>" alt="" height="28">
<% } %>
</a>
<p class="is-size-7">

View File

@ -5,11 +5,11 @@
<% if (has_config('open_graph')) { %>
<%- open_graph({
twitter_id: get_config('open_graph.twitter_id', ''),
twitter_site: get_config('open_graph.twitter_site', ''),
google_plus: get_config('open_graph.google_plus', ''),
fb_admins: get_config('open_graph.fb_admins', ''),
fb_app_id: get_config('open_graph.fb_app_id', '')
twitter_id: get_config('open_graph.twitter_id'),
twitter_site: get_config('open_graph.twitter_site'),
google_plus: get_config('open_graph.google_plus'),
fb_admins: get_config('open_graph.fb_admins'),
fb_app_id: get_config('open_graph.fb_app_id')
}) %>
<% } %>
@ -25,7 +25,7 @@
<%- css(iconcdn()) %>
<%- css(iconcdn('material')) %>
<%- css(fontcdn('Ubuntu:400,600|Source+Code+Pro')) %>
<%- css(cdn('highlight.js', '9.12.0', 'styles/' + get_config('article.highlight', 'atom-one-light') + '.min.css')) %>
<%- css(cdn('highlight.js', '9.12.0', 'styles/' + get_config('article.highlight') + '.min.css')) %>
<%- css('css/style') %>
<% if (has_config('plugins')) { %>

View File

@ -5,7 +5,7 @@
<% if (has_config('logo.text') && get_config('logo.text')) { %>
<%= get_config('logo.text') %>
<% } else { %>
<img src="<%- url_for(get_config('logo', 'images/logo.svg')) %>" alt="" height="28">
<img src="<%- url_for(get_config('logo')) %>" alt="" height="28">
<% } %>
</a>
</div>

View File

@ -6,7 +6,7 @@
</h3>
<% site.posts.sort('date', -1).limit(5).each(post => { %>
<article class="media">
<% if (!has_config('article.thumbnail') || get_config('article.thumbnail', true) !== false) { %>
<% if (!has_config('article.thumbnail') || get_config('article.thumbnail') !== false) { %>
<a href="<%- url_for((post.link?post.link:post.path)) %>" class="media-left">
<p class="image is-64x64">
<img class="thumbnail" src="<%= get_thumbnail(post) %>" alt="<%= post.title %>">

View File

@ -1,5 +1,5 @@
{
"name": "hexo-theme-icarus",
"version": "0.2.1",
"version": "2.0.0",
"private": true
}