diff --git a/lib/hexo/index.js b/lib/hexo/index.js index d9e8d6e587..591289d051 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -230,6 +230,7 @@ class Hexo extends EventEmitter { return Promise.each([ 'update_package', // Update package.json 'load_config', // Load config + 'load_theme_config', // Load alternate theme config 'load_plugins' // Load external plugins & scripts ], name => require(`./${name}`)(this)).then(() => this.execFilter('after_init', null, {context: this})).then(() => { // Ready to go! @@ -356,6 +357,7 @@ class Hexo extends EventEmitter { const ctx = { config: { url: this.config.url } }; const localsObj = this.locals.toObject(); + // config.theme_config has "_config.[theme].yml" merged in load_theme_config.js if (config.theme_config) { theme.config = deepMerge(theme.config, config.theme_config); } diff --git a/lib/hexo/load_theme_config.js b/lib/hexo/load_theme_config.js new file mode 100644 index 0000000000..dd4ecbfa67 --- /dev/null +++ b/lib/hexo/load_theme_config.js @@ -0,0 +1,42 @@ +'use strict'; + +const { join, parse } = require('path'); +const tildify = require('tildify'); +const { exists, readdir } = require('hexo-fs'); +const { magenta } = require('chalk'); +const { deepMerge } = require('hexo-util'); + +module.exports = ctx => { + if (!ctx.env.init) return; + if (!ctx.config.theme) return; + + let configPath = join(ctx.base_dir, `_config.${String(ctx.config.theme)}.yml`); + + return exists(configPath).then(exist => { + return exist ? configPath : findConfigPath(configPath); + }).then(path => { + if (!path) return; + + configPath = path; + return ctx.render.render({ path }); + }).then(config => { + if (!config || typeof config !== 'object') return; + + ctx.log.debug('Second Theme Config loaded: %s', magenta(tildify(configPath))); + + // ctx.config.theme_config should have highest piority + // If ctx.config.theme_config exists, then merge it with _config.[theme].yml + // If ctx.config.theme_config doesn't exist, set it to _config.[theme].yml + ctx.config.theme_config = ctx.config.theme_config + ? deepMerge(config, ctx.config.theme_config) : config; + }); +}; + +function findConfigPath(path) { + const { dir, name } = parse(path); + + return readdir(dir).then(files => { + const item = files.find(item => item.startsWith(name)); + if (item != null) return join(dir, item); + }); +} diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js index db45b9b7c4..e654455d9b 100644 --- a/test/scripts/hexo/index.js +++ b/test/scripts/hexo/index.js @@ -5,6 +5,7 @@ describe('Core', () => { require('./load_config'); require('./load_database'); require('./load_plugins'); + require('./load_theme_config'); require('./locals'); require('./multi_config_path'); require('./post'); diff --git a/test/scripts/hexo/load_theme_config.js b/test/scripts/hexo/load_theme_config.js new file mode 100644 index 0000000000..51ef9b18a4 --- /dev/null +++ b/test/scripts/hexo/load_theme_config.js @@ -0,0 +1,65 @@ +'use strict'; + +const { join } = require('path'); +const { mkdirs, unlink, writeFile, rmdir } = require('hexo-fs'); + +describe('Load alternate theme config', () => { + const Hexo = require('../../../lib/hexo'); + const hexo = new Hexo(join(__dirname, 'config_test'), {silent: true}); + const loadThemeConfig = require('../../../lib/hexo/load_theme_config'); + + hexo.env.init = true; + + before(() => mkdirs(hexo.base_dir).then(() => hexo.init())); + + after(() => rmdir(hexo.base_dir)); + + beforeEach(() => { + hexo.config.theme_config = { foo: { bar: 'ahhhhhh' } }; + hexo.config.theme = 'test_theme'; + }); + + it('_config.[theme].yml does not exist', () => loadThemeConfig(hexo).then(() => { + hexo.config.theme_config = {}; + })); + + it('_config.[theme].yml exists', () => { + const configPath = join(hexo.base_dir, '_config.test_theme.yml'); + + return writeFile(configPath, 'bar: 1').then(() => loadThemeConfig(hexo)).then(() => { + hexo.config.theme_config.bar.should.eql(1); + }).finally(() => unlink(configPath)); + }); + + it('_config.[theme].json exists', () => { + const configPath = join(hexo.base_dir, '_config.test_theme.json'); + + return writeFile(configPath, '{"baz": 3}').then(() => loadThemeConfig(hexo)).then(() => { + hexo.config.theme_config.baz.should.eql(3); + }).finally(() => unlink(configPath)); + }); + + it('_config.[theme].txt exists', () => { + const configPath = join(hexo.base_dir, '_config.test_theme.txt'); + + return writeFile(configPath, 'qux: 1').then(() => loadThemeConfig(hexo)).then(() => { + should.not.exist(hexo.config.theme_config.qux); + }).finally(() => unlink(configPath)); + }); + + it('merge config', () => { + const configPath = join(hexo.base_dir, '_config.test_theme.yml'); + + const content = [ + 'foo:', + ' bar: yoooo', + ' baz: true' + ].join('\n'); + + return writeFile(configPath, content).then(() => loadThemeConfig(hexo)).then(() => { + hexo.config.theme_config.foo.baz.should.eql(true); + hexo.config.theme_config.foo.bar.should.eql('ahhhhhh'); + hexo.config.theme_config.foo.bar.should.not.eql('yoooo'); + }).finally(() => unlink(configPath)); + }); +});