From 2ad42b3501554ce48401c09ee3b4158d4b6e38f7 Mon Sep 17 00:00:00 2001 From: Tommy Chen Date: Sat, 15 Nov 2014 18:01:20 +0800 Subject: [PATCH 01/15] Totally rewrite - Replace async with bluebird - Warehouse is updated to 1.0.0 - Use bunyan as logger --- .gitignore | 9 +- .jshintrc | 9 +- .npmignore | 9 +- Makefile | 8 - assets/gitignore | 2 +- bin/hexo | 31 +- lib/box/file.js | 116 ++---- lib/box/index.js | 444 ++++---------------- lib/box/pattern.js | 105 ++--- lib/cli/init.js | 67 +++ lib/core/index.js | 237 ----------- lib/core/locals.js | 66 --- lib/core/render.js | 221 ---------- lib/core/scaffold.js | 165 -------- lib/core/source.js | 34 -- lib/error/extend.js | 20 - lib/error/index.js | 40 -- lib/extend/console.js | 83 +--- lib/extend/deployer.js | 44 +- lib/extend/filter.js | 174 ++------ lib/extend/generator.js | 46 +-- lib/extend/helper.js | 43 +- lib/extend/index.js | 43 +- lib/extend/migrator.js | 44 +- lib/extend/processor.js | 53 +-- lib/extend/renderer.js | 160 ++------ lib/extend/tag.js | 72 +--- lib/hexo.js | 16 - lib/hexo/create_logger.js | 90 +++++ lib/hexo/index.js | 217 ++++++++++ lib/hexo/load_config.js | 49 +++ lib/hexo/load_database.js | 24 ++ lib/hexo/load_plugins.js | 24 ++ lib/hexo/load_scripts.js | 26 ++ lib/hexo/post.js | 21 + lib/hexo/render.js | 70 ++++ lib/hexo/scaffold.js | 0 lib/hexo/source.js | 0 lib/hexo/update_package.js | 28 ++ lib/index.js | 5 + lib/init.js | 96 ----- lib/loaders/box.js | 71 ---- lib/loaders/config.js | 203 ---------- lib/loaders/database.js | 64 --- lib/loaders/extend.js | 34 -- lib/loaders/logger.js | 44 -- lib/loaders/plugins.js | 40 -- lib/loaders/scripts.js | 40 -- lib/loaders/update.js | 56 --- lib/logger/console.js | 106 ----- lib/logger/file.js | 138 ------- lib/logger/index.js | 228 ----------- lib/logger/stream.js | 63 --- lib/model/category.js | 12 - lib/model/hooks/category.js | 8 - lib/model/hooks/post.js | 158 -------- lib/model/hooks/tag.js | 8 - lib/model/index.js | 69 ---- lib/model/post.js | 12 - lib/model/schema.js | 147 ------- lib/model/tag.js | 12 - lib/model/types/moment.js | 57 --- lib/models/asset.js | 9 + lib/models/cache.js | 6 + lib/models/category.js | 40 ++ lib/models/index.js | 6 + lib/models/page.js | 25 ++ lib/models/post.js | 41 ++ lib/models/tag.js | 31 ++ lib/models/types/moment.js | 87 ++++ lib/plugins/console/clean.js | 61 ++- lib/plugins/console/config.js | 4 - lib/plugins/console/deploy.js | 84 ---- lib/plugins/console/generate.js | 199 --------- lib/plugins/console/help.js | 122 +++--- lib/plugins/console/index.js | 140 ++----- lib/plugins/console/init.js | 45 +-- lib/plugins/console/list/index.js | 19 - lib/plugins/console/list/route.js | 79 ---- lib/plugins/console/migrate.js | 24 -- lib/plugins/console/new.js | 32 -- lib/plugins/console/publish.js | 17 - lib/plugins/console/render.js | 97 ++--- lib/plugins/console/server.js | 64 --- lib/plugins/console/version.js | 30 +- lib/plugins/deployer/git.js | 121 ------ lib/plugins/deployer/github.js | 125 ------ lib/plugins/deployer/heroku/Procfile | 1 - lib/plugins/deployer/heroku/app.js | 10 - lib/plugins/deployer/heroku/index.js | 137 ------- lib/plugins/deployer/index.js | 10 +- lib/plugins/deployer/openshift.js | 77 ---- lib/plugins/deployer/rsync.js | 63 --- lib/plugins/deployer/util.js | 18 - lib/plugins/filter/backtick_code_block.js | 47 --- lib/plugins/filter/excerpt.js | 18 - lib/plugins/filter/external_link.js | 35 -- lib/plugins/filter/index.js | 21 +- lib/plugins/filter/middlewares/gzip.js | 5 - lib/plugins/filter/middlewares/header.js | 6 - lib/plugins/filter/middlewares/logger.js | 16 - lib/plugins/filter/middlewares/redirect.js | 15 - lib/plugins/filter/middlewares/route.js | 42 -- lib/plugins/filter/middlewares/static.js | 5 - lib/plugins/filter/new_post_path.js | 102 ----- lib/plugins/filter/post_permalink.js | 38 -- lib/plugins/filter/titlecase.js | 10 - lib/plugins/generator/archive.js | 54 --- lib/plugins/generator/asset.js | 56 --- lib/plugins/generator/category.js | 24 -- lib/plugins/generator/home.js | 7 - lib/plugins/generator/index.js | 12 +- lib/plugins/generator/page.js | 18 - lib/plugins/generator/paginator.js | 52 --- lib/plugins/generator/post.js | 23 -- lib/plugins/generator/tag.js | 24 -- lib/plugins/helper/index.js | 114 +++--- lib/plugins/processor/asset.js | 31 -- lib/plugins/processor/index.js | 54 +-- lib/plugins/processor/page.js | 90 ----- lib/plugins/processor/post.js | 213 ---------- lib/plugins/processor/post_asset.js | 71 ---- lib/plugins/renderer/index.js | 18 +- lib/plugins/tag/index.js | 55 +-- lib/post/create.js | 125 ------ lib/post/index.js | 12 - lib/post/load.js | 82 ---- lib/post/publish.js | 117 ------ lib/post/render.js | 127 ------ lib/theme/index.js | 265 ------------ lib/theme/processors/config.js | 20 - lib/theme/processors/i18n.js | 23 -- lib/theme/processors/source.js | 41 -- lib/theme/processors/view.js | 33 -- lib/theme/view.js | 209 ---------- lib/util/escape.js | 27 +- lib/util/exec.js | 40 -- lib/util/format.js | 2 +- lib/util/fs.js | 403 ++++++++++++++++++ lib/util/highlight.js | 17 +- lib/{core => util}/i18n.js | 0 lib/util/index.js | 121 +----- lib/util/inflector.js | 402 ------------------ lib/util/locals.js | 0 lib/util/pool.js | 208 ---------- lib/{core => util}/router.js | 0 lib/util/spawn.js | 39 -- package.json | 30 +- test/fixtures/test.yml | 9 + test/index.js | 29 +- test/scripts/box/box.js | 27 ++ test/scripts/box/file.js | 53 +++ test/scripts/box/index.js | 5 + test/scripts/box/pattern.js | 58 +++ test/scripts/core/index.js | 3 - test/scripts/core/router.js | 83 ---- test/scripts/filter/excerpt.js | 25 -- test/scripts/filter/external_link.js | 40 -- test/scripts/filter/index.js | 4 - test/scripts/helper/date.js | 110 ----- test/scripts/helper/gravatar.js | 29 -- test/scripts/helper/index.js | 9 - test/scripts/helper/is.js | 50 --- test/scripts/helper/number.js | 28 -- test/scripts/helper/tag.js | 372 ----------------- test/scripts/helper/toc.js | 110 ----- test/scripts/helper/url.js | 60 --- test/scripts/hexo/hexo.js | 3 + test/scripts/hexo/index.js | 4 + test/scripts/hexo/render.js | 198 +++++++++ test/scripts/i18n/default.json | 11 - test/scripts/i18n/index.js | 50 --- test/scripts/i18n/zh-TW.json | 10 - test/scripts/init.js | 61 --- test/scripts/post/assets/287.jpg | Bin 16361 -> 0 bytes test/scripts/post/assets/test.txt | 9 - test/scripts/post/create.js | 350 ---------------- test/scripts/post/index.js | 4 - test/scripts/post/publish.js | 192 --------- test/scripts/post/scaffold.md | 4 - test/scripts/scaffold/fixture.md | 4 - test/scripts/scaffold/index.js | 83 ---- test/scripts/tag/blockquote.js | 44 -- test/scripts/tag/code.js | 50 --- test/scripts/tag/gist.js | 18 - test/scripts/tag/iframe.js | 36 -- test/scripts/tag/img.js | 81 ---- test/scripts/tag/include_code.js | 50 --- test/scripts/tag/index.js | 14 - test/scripts/tag/jsfiddle.js | 52 --- test/scripts/tag/link.js | 51 --- test/scripts/tag/pullquote.js | 24 -- test/scripts/tag/raw.js | 9 - test/scripts/tag/vimeo.js | 15 - test/scripts/tag/youtube.js | 15 - test/scripts/util/fs.js | 449 +++++++++++++++++++++ test/scripts/util/index.js | 1 + 197 files changed, 2797 insertions(+), 10223 deletions(-) delete mode 100644 Makefile create mode 100644 lib/cli/init.js delete mode 100644 lib/core/index.js delete mode 100644 lib/core/locals.js delete mode 100644 lib/core/render.js delete mode 100644 lib/core/scaffold.js delete mode 100644 lib/core/source.js delete mode 100644 lib/error/extend.js delete mode 100644 lib/error/index.js delete mode 100644 lib/hexo.js create mode 100644 lib/hexo/create_logger.js create mode 100644 lib/hexo/index.js create mode 100644 lib/hexo/load_config.js create mode 100644 lib/hexo/load_database.js create mode 100644 lib/hexo/load_plugins.js create mode 100644 lib/hexo/load_scripts.js create mode 100644 lib/hexo/post.js create mode 100644 lib/hexo/render.js create mode 100644 lib/hexo/scaffold.js create mode 100644 lib/hexo/source.js create mode 100644 lib/hexo/update_package.js create mode 100644 lib/index.js delete mode 100644 lib/init.js delete mode 100644 lib/loaders/box.js delete mode 100644 lib/loaders/config.js delete mode 100644 lib/loaders/database.js delete mode 100644 lib/loaders/extend.js delete mode 100644 lib/loaders/logger.js delete mode 100644 lib/loaders/plugins.js delete mode 100644 lib/loaders/scripts.js delete mode 100644 lib/loaders/update.js delete mode 100644 lib/logger/console.js delete mode 100644 lib/logger/file.js delete mode 100644 lib/logger/index.js delete mode 100644 lib/logger/stream.js delete mode 100644 lib/model/category.js delete mode 100644 lib/model/hooks/category.js delete mode 100644 lib/model/hooks/post.js delete mode 100644 lib/model/hooks/tag.js delete mode 100644 lib/model/index.js delete mode 100644 lib/model/post.js delete mode 100644 lib/model/schema.js delete mode 100644 lib/model/tag.js delete mode 100644 lib/model/types/moment.js create mode 100644 lib/models/asset.js create mode 100644 lib/models/cache.js create mode 100644 lib/models/category.js create mode 100644 lib/models/index.js create mode 100644 lib/models/page.js create mode 100644 lib/models/post.js create mode 100644 lib/models/tag.js create mode 100644 lib/models/types/moment.js delete mode 100644 lib/plugins/console/config.js delete mode 100644 lib/plugins/console/deploy.js delete mode 100644 lib/plugins/console/generate.js delete mode 100644 lib/plugins/console/list/index.js delete mode 100644 lib/plugins/console/list/route.js delete mode 100644 lib/plugins/console/migrate.js delete mode 100644 lib/plugins/console/new.js delete mode 100644 lib/plugins/console/publish.js delete mode 100644 lib/plugins/console/server.js delete mode 100644 lib/plugins/deployer/git.js delete mode 100644 lib/plugins/deployer/github.js delete mode 100644 lib/plugins/deployer/heroku/Procfile delete mode 100644 lib/plugins/deployer/heroku/app.js delete mode 100644 lib/plugins/deployer/heroku/index.js delete mode 100644 lib/plugins/deployer/openshift.js delete mode 100644 lib/plugins/deployer/rsync.js delete mode 100644 lib/plugins/deployer/util.js delete mode 100644 lib/plugins/filter/backtick_code_block.js delete mode 100644 lib/plugins/filter/excerpt.js delete mode 100644 lib/plugins/filter/external_link.js delete mode 100644 lib/plugins/filter/middlewares/gzip.js delete mode 100644 lib/plugins/filter/middlewares/header.js delete mode 100644 lib/plugins/filter/middlewares/logger.js delete mode 100644 lib/plugins/filter/middlewares/redirect.js delete mode 100644 lib/plugins/filter/middlewares/route.js delete mode 100644 lib/plugins/filter/middlewares/static.js delete mode 100644 lib/plugins/filter/new_post_path.js delete mode 100644 lib/plugins/filter/post_permalink.js delete mode 100644 lib/plugins/filter/titlecase.js delete mode 100644 lib/plugins/generator/archive.js delete mode 100644 lib/plugins/generator/asset.js delete mode 100644 lib/plugins/generator/category.js delete mode 100644 lib/plugins/generator/home.js delete mode 100644 lib/plugins/generator/page.js delete mode 100644 lib/plugins/generator/paginator.js delete mode 100644 lib/plugins/generator/post.js delete mode 100644 lib/plugins/generator/tag.js delete mode 100644 lib/plugins/processor/asset.js delete mode 100644 lib/plugins/processor/page.js delete mode 100644 lib/plugins/processor/post.js delete mode 100644 lib/plugins/processor/post_asset.js delete mode 100644 lib/post/create.js delete mode 100644 lib/post/index.js delete mode 100644 lib/post/load.js delete mode 100644 lib/post/publish.js delete mode 100644 lib/post/render.js delete mode 100644 lib/theme/index.js delete mode 100644 lib/theme/processors/config.js delete mode 100644 lib/theme/processors/i18n.js delete mode 100644 lib/theme/processors/source.js delete mode 100644 lib/theme/processors/view.js delete mode 100644 lib/theme/view.js delete mode 100644 lib/util/exec.js create mode 100644 lib/util/fs.js rename lib/{core => util}/i18n.js (100%) delete mode 100644 lib/util/inflector.js create mode 100644 lib/util/locals.js delete mode 100644 lib/util/pool.js rename lib/{core => util}/router.js (100%) delete mode 100644 lib/util/spawn.js create mode 100644 test/fixtures/test.yml create mode 100644 test/scripts/box/box.js create mode 100644 test/scripts/box/file.js create mode 100644 test/scripts/box/index.js create mode 100644 test/scripts/box/pattern.js delete mode 100644 test/scripts/core/index.js delete mode 100644 test/scripts/core/router.js delete mode 100644 test/scripts/filter/excerpt.js delete mode 100644 test/scripts/filter/external_link.js delete mode 100644 test/scripts/filter/index.js delete mode 100644 test/scripts/helper/date.js delete mode 100644 test/scripts/helper/gravatar.js delete mode 100644 test/scripts/helper/index.js delete mode 100644 test/scripts/helper/is.js delete mode 100644 test/scripts/helper/number.js delete mode 100644 test/scripts/helper/tag.js delete mode 100644 test/scripts/helper/toc.js delete mode 100644 test/scripts/helper/url.js create mode 100644 test/scripts/hexo/hexo.js create mode 100644 test/scripts/hexo/index.js create mode 100644 test/scripts/hexo/render.js delete mode 100644 test/scripts/i18n/default.json delete mode 100644 test/scripts/i18n/index.js delete mode 100644 test/scripts/i18n/zh-TW.json delete mode 100644 test/scripts/init.js delete mode 100644 test/scripts/post/assets/287.jpg delete mode 100644 test/scripts/post/assets/test.txt delete mode 100644 test/scripts/post/create.js delete mode 100644 test/scripts/post/index.js delete mode 100644 test/scripts/post/publish.js delete mode 100644 test/scripts/post/scaffold.md delete mode 100644 test/scripts/scaffold/fixture.md delete mode 100644 test/scripts/scaffold/index.js delete mode 100644 test/scripts/tag/blockquote.js delete mode 100644 test/scripts/tag/code.js delete mode 100644 test/scripts/tag/gist.js delete mode 100644 test/scripts/tag/iframe.js delete mode 100644 test/scripts/tag/img.js delete mode 100644 test/scripts/tag/include_code.js delete mode 100644 test/scripts/tag/index.js delete mode 100644 test/scripts/tag/jsfiddle.js delete mode 100644 test/scripts/tag/link.js delete mode 100644 test/scripts/tag/pullquote.js delete mode 100644 test/scripts/tag/raw.js delete mode 100644 test/scripts/tag/vimeo.js delete mode 100644 test/scripts/tag/youtube.js create mode 100644 test/scripts/util/fs.js diff --git a/.gitignore b/.gitignore index 5c797b7396..241a5d3a28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store -node_modules -tmp -debug.log -.idea \ No newline at end of file +node_modules/ +tmp/ +*.log +.idea/ +coverage/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 15b532fb14..6cabfd079b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,9 +1,14 @@ { - "proto": true, "eqnull": true, "expr": true, "indent": 2, "node": true, "trailing": true, - "quotmark": "single" + "quotmark": "single", + "undef": true, + "unused": "vars", + "globals": { + "Promise": true, + "hexo": true + } } \ No newline at end of file diff --git a/.npmignore b/.npmignore index be7eac5d87..3a43090e28 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,8 @@ test/ -tmp -debug.log \ No newline at end of file +tmp/ +coverage/ +*.log +.jshintrc +.travis.yml +gulpfile.js +.idea/ \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 0a6a99c26c..0000000000 --- a/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -test: - gulp test - -install: - npm install - git submodule update --init - -.PHONY: test \ No newline at end of file diff --git a/assets/gitignore b/assets/gitignore index a7640be627..8870d1d79d 100644 --- a/assets/gitignore +++ b/assets/gitignore @@ -1,7 +1,7 @@ .DS_Store Thumbs.db db.json -debug.log +*.log node_modules/ public/ .deploy/ \ No newline at end of file diff --git a/bin/hexo b/bin/hexo index cd9b77fbfa..cfa6acbeae 100755 --- a/bin/hexo +++ b/bin/hexo @@ -1,32 +1,5 @@ #!/usr/bin/env node -var args = require('minimist')(process.argv.slice(2)), - fs = require('graceful-fs'), - path = require('path'), - async = require('async'), - init = require('../lib/init'), - cwd = process.cwd(), - lastCwd = cwd; +var args = require('minimist')(process.argv.slice(2)); -// Find Hexo folder recursively -async.doUntil( - function(next){ - var configFile = path.join(cwd, '_config.yml'); - - fs.exists(configFile, function(exist){ - if (exist){ - init(cwd, args); - } else { - lastCwd = cwd; - cwd = path.dirname(cwd); - next(); - } - }); - }, - function(){ - return cwd === lastCwd; - }, - function(){ - init(process.cwd(), args); - } -); \ No newline at end of file +require('../lib/cli/init')(args); \ No newline at end of file diff --git a/lib/box/file.js b/lib/box/file.js index c039b17d39..78a3816510 100644 --- a/lib/box/file.js +++ b/lib/box/file.js @@ -1,110 +1,50 @@ -var fs = require('graceful-fs'), - _ = require('lodash'), - util = require('../util'), - file = util.file2; - -/** -* The file object of the Box class. -* -* @class File -* @param {Box} box -* @param {String} source The full path of the file. -* @param {String} path The relative path of the file. -* @param {String} type -* @param {Object} params -* @constructor -* @namespace Box -* @module hexo -*/ -var File = module.exports = function File(box, source, path, type, params){ - this.box = box; - this.source = source; - this.path = path; - this.type = type; - this.params = params; -}; +var Promise = require('bluebird'); +var util = require('../util'); +var fs = util.fs; + +function File(data){ + this.source = data.source; + this.path = data.path; + this.type = data.type; + this.params = data.params; + + if (this._context){ + this._render = this._context.render; + } +} -/** -* Reads the file. -* -* @method read -* @param {Object} [options] -* @param {Function} [callback] -* @async -*/ File.prototype.read = function(options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; } - file.readFile(this.source, options, callback); + return fs.readFile(this.source, options).nodeify(callback); }; -/** -* Reads the file synchronizedly. -* -* @method read -* @param {Object} [options] -* @return {String} -*/ File.prototype.readSync = function(options){ - return file.readFileSync(this.source, options); + return fs.readFileSync(this.source, options); }; -/** -* Gets the file status. -* -* @method stat -* @param {Function} callback -* @async -*/ File.prototype.stat = function(callback){ - fs.stat(this.source, callback); + return fs.stat(this.source).nodeify(callback); }; -/** -* Gets the file status synchronizedly. -* -* @method statSync -* @return {fs.Stats} -*/ File.prototype.statSync = function(){ return fs.statSync(this.source); }; -/** -* Renders the file with renderers. -* -* @method render -* @param {Object} [options] -* @param {Function} [callback] -* @async -*/ File.prototype.render = function(options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; } - hexo.render.render({path: this.source}, options, callback); + return this._render.render({path: this.source}, options).nodeify(callback); }; -/** -* Renders the file with renderers synchronizedly. -* -* @method renderSync -* @param {Object} [options] -* @return {String} -*/ File.prototype.renderSync = function(options){ - return hexo.render.renderSync({path: this.source}, options); -}; \ No newline at end of file + return this._render.renderSync({path: this.source}, options); +}; + +module.exports = File; \ No newline at end of file diff --git a/lib/box/index.js b/lib/box/index.js index 23e2faeda0..e8f87bf998 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -1,96 +1,43 @@ -var fs = require('graceful-fs'), - pathFn = require('path'), - async = require('async'), - _ = require('lodash'), - chokidar = require('chokidar'), - colors = require('colors'), - domain = require('domain'), - EventEmitter = require('events').EventEmitter, - Pattern = require('./pattern'), - HexoError = require('../error'), - util = require('../util'), - file = util.file2, - escape = util.escape, - File = require('./file'); - -/** -* This module is used to manage files and processors. -* -* @class Box -* @module hexo -* @constructor -* @param {String} base -* @param {Object} [options] See [chokidar](https://github.com/paulmillr/chokidar) -* @param {Boolean} [options.presistent=true] -* @param {RegExp} [options.ignored=/[\/\\]\./] -* @param {Boolean} [options.ignoreInitial=true] -* @extends EventEmitter -*/ - -var Box = module.exports = function Box(base, options){ - /** - * Base path. - * - * @property base - * @type {String} - */ +var pathFn = require('path'); +var Promise = require('bluebird'); +var _ = require('lodash'); +var chokidar = require('chokidar'); +var File = require('./file'); +var Pattern = require('./pattern'); +var util = require('../util'); +var fs = util.fs; + +require('colors'); + +function Box(base, options){ this.base = base; - - /** - * Processor collection. - * - * @property processors - * @type {Array} - */ this.processors = []; - - /** - * Processing files. - * - * @property processingFiles - * @type {Object} - * @private - */ this.processingFiles = {}; - - /** - * A instance of watcher. - * - * @property watcher - * @type {FSWatcher} - */ this.watcher = null; - - /** - * Indicates if the box is processing. - * - * @property isProcessing - * @type {Boolean} - * @private - */ this.isProcessing = false; - - /** - * @property options - * @type Object - */ this.options = _.extend({ presistent: true, ignored: /[\/\\]\./, ignoreInitial: true }, options); -}; -Box.prototype.__proto__ = EventEmitter.prototype; + var _File = this.File = function(data){ + File.call(this, data); + }; + + util.inherits(_File, File); + + _File.prototype._box = this; + _File.prototype._context = this._context; +} -/** -* Adds a processor to the box. -* -* @method addProcessor -* @param {RegExp|String} pattern The path pattern of the processor. See {% crosslink Box.Pattern %} for more info. -* @param {Function} fn The processor function. -*/ Box.prototype.addProcessor = function(pattern, fn){ + if (!fn && typeof pattern === 'function'){ + fn = pattern; + pattern = new Pattern(/(.*)/); + } + + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); if (!(pattern instanceof Pattern)) pattern = new Pattern(pattern); this.processors.push({ @@ -99,305 +46,101 @@ Box.prototype.addProcessor = function(pattern, fn){ }); }; -/** -* Dispatches files to processors. -* -* @method _dispatch -* @param {String} type Available types: create, update, delete -* @param {String} path File path -* @param {Function} [callback] -* @private -* @async -*/ -Box.prototype._dispatch = function(type, path, callback){ - if (typeof callback !== 'function') callback = function(){}; - - // Skip processing files - if (this.processingFiles[path]) return callback(); - - // Replace backslashes on Windows - path = path.replace(/\\/g, '/'); - - var self = this, - d = domain.create(), - called = false, - processorNumber = 0, - start = Date.now(); - - this.processingFiles[path] = true; +Box.prototype.process = function(files){ + var self = this; + var base = this.base; - // NOTE: not all exceptions can be caught by domain - d.on('error', function(err){ - self.processingFiles[path] = false; + return new Promise(function(resolve, reject){ + if (self.isProcessing) return reject(new Error('Box is processing!')); - if (called) return; - called = true; + self.isProcessing = true; + self._context.emit('processBefore', base); - if (!(err instanceof HexoError)) err = HexoError.wrap(err, 'Process failed: ' + path); - callback(err); - }); - - async.each(this.processors, function(processor, next){ - var params = {}, - src = pathFn.join(self.base, path); - - if (processor.pattern){ - if (!processor.pattern.test(path)) return next(); - - params = processor.pattern.match(path); + if (files){ + return Array.isArray(files) ? files : [files]; + } else { + return self._loadFileList(); } - - d.add(processor); - - d.run(function(){ - processor.process(new File(self, src, path, type, params), function(err){ - processorNumber++; - d.remove(processor); - next(err); - }); - }); - }, function(err){ - self.processingFiles[path] = false; - - if (called) return; - called = true; - - if (err){ - if (!(err instanceof HexoError)) err = HexoError.wrap(err, 'Process failed: ' + path); - callback(err); + }).map(function(item){ + if (typeof item === 'object'){ + return self._dispatch(item); } else { - if (processorNumber) hexo.log.d('Processed: %s ' + '(%dms)'.grey, path, Date.now() - start); - callback(); + return self._dispatch({ + path: item, + type: 'update' + }); } - }); + }).finally(function(){ + self.isProcessing = false; + }) }; -/** -* Loads file list and checks their modified date. -* -* @method _loadFileList -* @param {Function} callback -* @private -* @async -*/ -Box.prototype._loadFileList = function(callback){ - var Cache = hexo.model('Cache'), - fullBase = this.base, - base = fullBase.substring(hexo.base_dir.length), - baseLength = base.length, - baseRegex = new RegExp('^' + escape.regex(base)), - result = []; - - var cache = Cache.find({_id: baseRegex}).map(function(item){ - return item._id.substring(baseLength); - }); +Box.prototype._dispatch = function(item){ + var path = item.path.replace(/\\/g, '/'); + var start = Date.now(); + var base = this.base; + var self = this; + var log = this._context.log; + var executed = false; - async.auto({ - list: function(next){ - file.list(fullBase, next); - }, - created: ['list', function(next, results){ - var created = _.difference(results.list, cache); + if (this.processingFiles[path]) return; - async.each(created, function(item, next){ - fs.stat(pathFn.join(fullBase, item), function(err, stats){ - if (err) return next(err); - - Cache.insert({ - _id: pathFn.join(base, item), - mtime: stats.mtime.getTime() - }, function(){ - result.push({path: item, type: 'create'}); - next(); - }); - }); - }, function(err){ - if (err) return next(err); - - next(null, created); - }); - }], - deleted: ['list', function(next, results){ - var deleted = _.difference(cache, results.list); + this.processingFiles[path] = true; - async.each(deleted, function(item, next){ - Cache.removeById(pathFn.join(base, item), function(){ - result.push({path: item, type: 'delete'}); - next(); - }); - }, function(err){ - if (err) return next(err); + return Promise.map(this.processors, function(processor){ + var params = processor.pattern.match(item); + if (!params) return; - next(null, deleted); - }); - }], - updated: ['deleted', function(next, results){ - var updated = _.difference(cache, results.deleted); - - async.each(updated, function(item, next){ - fs.stat(pathFn.join(fullBase, item), function(err, stats){ - if (err) return next(err); + var file = new self.File({ + src: pathFn.join(base, item), + path: path, + params: params, + type: item.type + }); - var data = Cache.get(pathFn.join(base, item)), - mtime = stats.mtime.getTime(); + executed = true; - if (data.mtime === mtime){ - result.push({path: item, type: 'skip'}); - next(); - } else { - data.mtime = mtime; - data.save(function(){ - result.push({path: item, type: 'update'}); - next(); - }); - } - }); - }, function(err){ - if (err) return next(err); + return processor.process(file); + }).then(function(){ + if (!executed) return; - next(null, updated); - }); - }] + log.debug('Processed: %s in ' + '%dms'.cyan, path.magenta, Date.now() - start); }, function(err){ - callback(null, result); + log.error('Process failed: %s', path.magenta); + return err; + }).finally(function(){ + self.processingFiles = false; }); }; -/** -* Loads all files and runs processors. -* -* @method process -* @param {Array|String} [files] Files to be processed -* @param {Function} [callback] -* @async -*/ -Box.prototype.process = function(files, callback){ - if (!callback){ - if (typeof files === 'function'){ - callback = files; - files = null; - } else { - callback = function(){}; - } - } - - if (this.isProcessing) return callback(new Error('Box is processing!')); - - var self = this, - base = this.base; - - this.isProcessing = true; - - /** - * Fired before the process started. - * - * @event processBefore - * @param {String} base The base path of the box - * @for Hexo - */ - hexo.emit('processBefore', base); - - async.waterfall([ - function(next){ - if (files){ - if (!Array.isArray(files)) files = [files]; - - next(null, files); - } else { - self._loadFileList(next); - } - }, - function(files, next){ - async.each(files, function(item, next){ - var type, path; - - if (_.isObject(item)){ - path = item.path; - type = item.type; - } else { - path = item; - type = 'update'; - } - - self._dispatch(type, path, next); - }, next); - } - ], function(err){ - self.isProcessing = false; - - /** - * Fired after the process has been done. - * - * @event processAfter - * @param {String} base The base path of the box - * @for Hexo - */ - - hexo.emit('processAfter', base); - callback(err); - }); +Box.prototype._loadFileList = function(){ + return fs.listDir(this.base); }; var chokidarEventMap = { add: 'create', change: 'update', unlink: 'delete' -}; - -/** -* Starts watching. -* -* @method watch -* @for Box -*/ +} Box.prototype.watch = function(){ if (this.watcher) throw new Error('Watcher has already started.'); - var self = this, - queue = [], - isRunning = false, - timer; - - var timerFn = function(){ - if (queue.length && !isRunning){ - isRunning = true; - - self.process(queue, function(err){ - isRunning = false; - queue.length = 0; - - if (err) return hexo.log.e(err); - }); - } - }; + var base = this.base; + var baseLength = base.length; + var log = this._context.log; this.watcher = chokidar.watch(this.base, this.options) .on('all', function(event, src){ - var type = chokidarEventMap[event], - path = src.substring(self.base.length); + var type = chokidarEventMap[event]; + var path = src.substring(baseLength); if (!type) return; - if (timer) clearTimeout(timer); - - queue.push({ - type: type, - path: path - }); - timer = setTimeout(timerFn, 100); - hexo.log.log(type, src); - }) - .on('error', function(err){ - self.emit('error', err); + self.process({type: type, path: path}); }); }; -/** -* Stops watching. -* -* @method unwatch -*/ Box.prototype.unwatch = function(){ if (!this.watcher) throw new Error('Watcher hasn\'t started yet.'); @@ -405,20 +148,7 @@ Box.prototype.unwatch = function(){ this.watcher = null; }; -/** -* See {% crosslink Box.File %} -* -* @property Box.File -* @type Box.File -* @static -*/ -Box.File = Box.prototype.File = File; +Box.File = File; +Box.Pattern = Box.prototype.Pattern = Pattern; -/** -* See {% crosslink Box.Pattern %} -* -* @property Box.Pattern -* @type Box.Pattern -* @static -*/ -Box.Pattern = Box.prototype.Pattern = Pattern; \ No newline at end of file +module.exports = Box; \ No newline at end of file diff --git a/lib/box/pattern.js b/lib/box/pattern.js index a3f5278410..a8db4af8e2 100644 --- a/lib/box/pattern.js +++ b/lib/box/pattern.js @@ -1,7 +1,7 @@ var util = require('../util'), escape = util.escape; -var rParam = /(\()?([:\*])(\w*)\)?/g; +var rParam = /([:\*])([\w\?]*)?/g; /** * The pattern object of the Box class. @@ -15,48 +15,15 @@ var rParam = /(\()?([:\*])(\w*)\)?/g; * @namespace Box * @module hexo */ -var Pattern = module.exports = function Pattern(rule){ +function Pattern(rule){ if (typeof rule === 'function'){ this.filter = rule; } else if (rule instanceof RegExp){ - this.rule = rule; - this.params = []; + this.filter = regexFilter(rule); } else { - var params = []; - - var regex = escape.regex(rule) - .replace(/\\\*/g, '*') - .replace(rParam, function(match, optional, operator, name){ - params.push(name); - - var str = ''; - - if (operator === '*'){ - str = '(.*?)'; - } else { - str = '([^\\/]+)'; - } - - if (optional) str += '?'; - - return str; - }); - - this.rule = new RegExp('^' + regex + '$'); - this.params = params; + this.filter = stringFilter(rule); } -}; - -/** -* Tests if the string matches the pattern. -* -* @method test -* @param {String} str -* @return {Boolean} -*/ -Pattern.prototype.test = function(str){ - return this.filter ? this.filter(str) : this.rule.test(str); -}; +} /** * Tests if the string matches the pattern and returns the parameters in the URL. Returns `null` if the string doesn't matches the pattern. @@ -78,20 +45,58 @@ Pattern.prototype.test = function(str){ * @return {Object} */ Pattern.prototype.match = function(str){ - if (this.filter) return this.filter(str); - if (!this.test(str)) return; + return this.filter(str); +}; - var match = str.match(this.rule), - params = this.params, - result = {}; +function regexFilter(rule){ + return function(str){ + return str.match(rule); + }; +} - for (var i = 0, len = match.length; i < len; i++){ - var name = params[i - 1]; +function stringFilter(rule){ + var params = []; - result[i] = match[i]; + var regex = escape.regex(rule) + .replace(/\\([\*\?])/g, '$1') + .replace(rParam, function(match, operator, name){ + var str = ''; + var optional = false; - if (name) result[name] = match[i]; - } + if (operator === '*'){ + str = '(.*)?'; + } else { + str = '([^\\/]+)'; + } + + if (name){ + if (name[name.length - 1] === '?'){ + name = name.slice(0, name.length - 1); + optional = true; + str += '?'; + } + + params.push(name); + } + + return str; + }); + + return function(str){ + var match = str.match(regex); + if (!match) return; + + var result = {}; + var name; + + for (var i = 0, len = match.length; i < len; i++){ + name = params[i - 1]; + result[i] = match[i]; + if (name) result[name] = match[i]; + } + + return result; + }; +} - return result; -}; \ No newline at end of file +module.exports = Pattern; \ No newline at end of file diff --git a/lib/cli/init.js b/lib/cli/init.js new file mode 100644 index 0000000000..83d5786a95 --- /dev/null +++ b/lib/cli/init.js @@ -0,0 +1,67 @@ +var Hexo = require('../hexo'); +var util = require('../util'); +var pathFn = require('path'); +var fs = util.fs; + +var cwd = process.cwd(); +var lastCwd = cwd; + +require('colors'); + +// Find Hexo folder recursively +function findConfigFile(){ + return fs.exists(pathFn.join(cwd, '_config.yml')).then(function(exist){ + if (exist) return; + + lastCwd = cwd; + cwd = pathFn.dirname(cwd); + + // Stop on root folder + if (lastCwd === cwd) return; + + return findConfigFile(); + }); +} + +module.exports = function(args){ + var hexo; + + findConfigFile().then(function(){ + // Use CWD if config file is not found + if (cwd === lastCwd){ + hexo = new Hexo(process.cwd(), args); + } else { + hexo = new Hexo(cwd, args); + } + + global.hexo = hexo; + + return hexo.init(); + }).then(function(){ + var command = args._.shift(); + + if (command){ + var c = hexo.extend.console.get(command); + + if (!c || (!hexo.env.init && !c.options.init)){ + command = 'help'; + } + } else if (args.v || args.version){ + command = 'version'; + } else { + command = 'help'; + } + + return hexo.call(command, args); + }).then(function(){ + hexo.emit('exit'); + }, function(err){ + if (!hexo) throw err; + + hexo.log.fatal( + {err: err}, + 'Something went wrong. Maybe you can find the solution here: %s', + 'http://hexo.io/docs/troubleshooting.html'.underline + ); + }); +}; \ No newline at end of file diff --git a/lib/core/index.js b/lib/core/index.js deleted file mode 100644 index 5270e86714..0000000000 --- a/lib/core/index.js +++ /dev/null @@ -1,237 +0,0 @@ -/** -* This is the main module of hexo. -* -* @module hexo -* @main hexo -*/ - -var EventEmitter = require('events').EventEmitter, - path = require('path'), - util = require('../util'), - Router = require('./router'), - Box = require('../box'), - version = require('../../package.json').version, - HexoError = require('../error'); - domain = require('domain'); - -/** -* All Hexo methods and functions are defined inside of this namespace. -* -* @class Hexo -* @constructor -* @extends EventEmitter -* @module hexo -*/ - -var Hexo = module.exports = function Hexo(){}; - -Hexo.prototype.__proto__ = EventEmitter.prototype; - -Hexo.Box = Box; -Hexo.Error = HexoError; - -/** -* Defines a constant. -* -* @method constant -* @param {String} name -* @param {Any} value -* @chainable -*/ - -Hexo.prototype.constant = function(name, value){ - var getter; - - if (typeof value !== 'function'){ - getter = function(){ - return value; - }; - } else { - getter = value; - } - - this.__defineGetter__(name, getter); - - return this; -}; - -/** -* Bootstraps Hexo environment. -* -* @method bootstrap -* @param {String} baseDir -* @param {Object} args -* @chainable -* @since 2.4.0 -*/ - -Hexo.prototype.bootstrap = function(baseDir, args){ - /** - * The path of core directory of Hexo. - * - * @property core_dir - * @type String - * @final - */ - - this.constant('core_dir', path.dirname(path.dirname(__dirname)) + path.sep); - - /** - * The path of library directory of Hexo. - * - * @property lib_dir - * @type String - * @final - */ - - this.constant('lib_dir', path.dirname(__dirname) + path.sep); - - /** - * Hexo version number. - * - * @property version - * @type String - * @final - */ - - this.constant('version', version); - - /** - * The path of base directory, equals to the current working directory (CWD). - * - * @property base_dir - * @type String - * @final - */ - - this.constant('base_dir', baseDir + path.sep); - - /** - * Environment variables. - * - * This object contains the following attributes: - * - * - debug: Determines whether debug mode is on. - * - safe: Determines whether safe mode is on. - * - silent: Determines whether silent mode is on. - * - env: Node.js environment variable. Default to `development`. - * - version: Hexo version number. - * - init: Determines whether Hexo has been initalized. - * - * @property env - * @type Object - * @final - */ - - this.env = { - args: args, - debug: !!args.debug, - safe: !!args.safe, - silent: !!args.silent, - env: process.env.NODE_ENV || 'development', - version: version, - init: false - }; - - /** - * See {% crosslink util %}. - * - * @property util - * @type util - */ - - this.util = util; - - /** - * See {% crosslink util.file2 %}. - * - * @property file - * @type util.file2 - */ - - this.file = util.file2; - - /** - * See {% crosslink Router %}. - * - * @property route - * @type Router - */ - - this.route = new Router(); - - /** - * See {% crosslink Locals %}. - * - * @property locals - * @type Function - */ - - this.locals = require('./locals'); - - /** - * See {% crosslink render %}. - * - * @property render - * @type render - */ - - this.render = require('./render'); - - /** - * See {% crosslink post %}. - * - * @property post - * @type post - */ - - this.post = require('../post'); - - return this; -}; - -/** -* Calls a console plugin. -* -* @method call -* @param {String} name -* @param {Object} [args] -* @param {Function} [callback] -* @chainable -* @async -*/ - -Hexo.prototype.call = function(name, args, callback){ - if (!callback){ - if (typeof args === 'function'){ - callback = args; - args = {}; - } else { - callback = function(){}; - } - } - - var console = this.extend.console.get(name); - - if (console){ - var d = domain.create(), - called = false; - - d.on('error', function(err){ - !called && callback(err); - }); - - d.add(console); - - d.run(function(){ - console(args, function(){ - !called && callback.apply(this, arguments); - called = true; - }); - }); - } else { - callback(new Error('Console `' + name + '` not found')); - } - - return this; -}; \ No newline at end of file diff --git a/lib/core/locals.js b/lib/core/locals.js deleted file mode 100644 index 57f7ae1091..0000000000 --- a/lib/core/locals.js +++ /dev/null @@ -1,66 +0,0 @@ -var _ = require('lodash'); - -var store = {}; - -/** -* This module is used to manage local variables used in templates. -* -* For example: -* -* ``` js -* hexo.locals({ -* foo: function(){ -* return 'bar'; -* } -* }); -* ``` -* -* yields: -* -* ``` js -* <%= foo %> -* // bar -* ``` -* -* @class Locals -* @param {Object} [locals] -* @static -* @module hexo -*/ - -var Locals = module.exports = function(locals){ - _.extend(store, locals); -}; - -/** -* Iterates over all elements in the object. -* -* `each` is also aliased as `forEach`. -* -* @method each -* @param {Function} iterator -* @static -*/ - -Locals.forEach = Locals.each = function(iterator){ - _.each(store, iterator); -}; - -/** -* Generates a static object. -* -* @method _generate -* @return {Object} -* @private -* @static -*/ - -Locals._generate = function(){ - var obj = {}; - - this.each(function(val, name){ - obj[name] = typeof val === 'function' ? val() : val; - }); - - return obj; -}; \ No newline at end of file diff --git a/lib/core/render.js b/lib/core/render.js deleted file mode 100644 index 03b6614907..0000000000 --- a/lib/core/render.js +++ /dev/null @@ -1,221 +0,0 @@ -/** -* Render functions. -* -* @class render -* @module hexo -* @static -*/ - -var async = require('async'), - pathFn = require('path'), - fs = require('graceful-fs'), - _ = require('lodash'), - util = require('../util'), - file = util.file2, - yfm = util.yfm; - -var cache = {}; - -var getExtname = function(str){ - return pathFn.extname(str).replace(/^\./, ''); -}; - -/** -* Checks if the given `path` is renderable. -* -* @method isRenderable -* @param {String} path -* @return {Boolean} -* @static -*/ - -var isRenderable = exports.isRenderable = function(path){ - return hexo.extend.renderer.isRenderable(path); -}; - -/** -* Checks if the given `path` is renderable by synchronized renderer. -* -* @method isRenderableSync -* @param {String} path -* @return {Boolean} -* @static -*/ - -var isRenderableSync = exports.isRenderableSync = function(path){ - return hexo.extend.renderer.isRenderableSync(path); -}; - -/** -* Gets the output extension name. -* -* @method getOutput -* @param {String} path -* @return {String} -* @static -*/ - -var getOutput = exports.getOutput = function(path){ - return hexo.extend.renderer.getOutput(path); -}; - -/** -* Renders data. -* -* @method render -* @param {Object} data -* @param {Object} [options] -* @param {Function} [callback] -* @async -* @static -*/ - -var render = exports.render = function(data, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - async.waterfall([ - function(next){ - if (data.text != null) return next(null, data.text); - if (!data.path) return next(new Error('No input file or string')); - - file.readFile(data.path, next); - }, - function(text, next){ - var ext = data.engine || getExtname(data.path); - - if (ext && isRenderable(ext)){ - var renderer = hexo.extend.renderer.get(ext); - - renderer({ - path: data.path, - text: text - }, options, next); - } else { - next(null, text); - } - } - ], callback); -}; - -/** -* Renders data synchronizedly. -* -* @method renderSync -* @param {Object} data -* @param {Object} [options] -* @static -*/ - -exports.renderSync = function(data, options){ - var text = ''; - - if (data.text != null){ - text = data.text; - } else if (data.path){ - text = file.readFileSync(data.path); - if (!text) return; - } else { - return; - } - - var ext = data.engine || getExtname(data.path); - - if (ext && isRenderableSync(ext)){ - var renderer = hexo.extend.renderer.get(ext, true); - - return renderer({path: data.path, text: text}, options); - } else { - return text; - } -}; - -/** -* Renders a file. This function supports helpers and layouts. -* -* @method renderFile -* @param {String} source -* @param {Object} [options] -* @param {Function} [callback] -* @async -* @static -*/ - -var renderFile = exports.renderFile = function(source, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - var helper = hexo.extend.helper.list(); - - async.waterfall([ - // Load cache - function(next){ - if (options.cache && cache.hasOwnProperty(source)){ - next(null, cache[source]); - } else { - file.readFile(source, function(err, content){ - if (err) return callback(err); - - var data = cache[source] = yfm(content); - next(null, data); - }); - } - }, - // Render template - function(data, next){ - var layout = data.hasOwnProperty('layout') ? data.layout : options.layout, - locals = _.extend({}, helper, options, _.omit(data, 'layout', '_content')), - extname = pathFn.extname(source), - renderer = hexo.extend.renderer.get(extname); - - renderer({path: source, text: data._content}, locals, function(err, result){ - if (err) return callback(err); - if (!layout) return callback(null, result); - - var layoutPath = ''; - - // Relative path - layoutPath = pathFn.resolve(source, layout); - if (!pathFn.extname(layoutPath)) layoutPath += extname; - - var layoutLocals = _.extend({}, locals, {body: result, layout: false}); - - fs.exists(layoutPath, function(exist){ - if (exist) return next(null, layoutPath, layoutLocals); - - var viewDir = options.view_dir || options.settings.views; - - if (!exist && !viewDir) return callback(null, result); - - // Absolute path - layoutPath = pathFn.join(viewDir, layout); - if (!pathFn.extname(layoutPath)) layoutPath += extname; - - fs.exists(layoutPath, function(exist){ - if (exist){ - next(null, layoutPath, layoutLocals); - } else { - callback(null, result); - } - }); - }); - }); - }, - // Wrap the template with layout - function(layoutPath, locals, next){ - renderFile(layoutPath, locals, callback); - } - ]); -}; \ No newline at end of file diff --git a/lib/core/scaffold.js b/lib/core/scaffold.js deleted file mode 100644 index 9745c99f59..0000000000 --- a/lib/core/scaffold.js +++ /dev/null @@ -1,165 +0,0 @@ -var pathFn = require('path'), - fs = require('graceful-fs'), - Box = require('../box'), - util = require('../util'), - file = util.file2, - HexoError = require('../error'); - -var rHiddenFile = /\/_/; - -var getScaffoldName = function(path){ - return path.substring(hexo.scaffold_dir.length, path.length - pathFn.extname(path).length); -}; - -var process = function(data, callback){ - if (data.path[0] === '_' || rHiddenFile.test(data.path)) return callback(); - - var name = getScaffoldName(data.source); - - if (data.type === 'delete'){ - data.box.scaffolds[name] = null; - return callback(); - } - - data.read(function(err, content){ - if (err) return callback(HexoError.wrap(err, 'Scaffold load failed: ' + data.path)); - - data.box.scaffolds[name] = { - path: data.source, - content: content - }; - - callback(); - }); -}; - -/** -* This module manages all files in the scaffold folder. -* -* @class Scaffold -* @constructor -* @module hexo -* @extend Box -*/ -var Scaffold = module.exports = function Scaffold(){ - Box.call(this, hexo.scaffold_dir); - - /** - * Asset folder. - * - * @property asset_dir - * @type String - */ - this.asset_dir = pathFn.join(hexo.core_dir, 'assets', 'scaffolds'); - - /** - * The scaffold collection. - * - * @property scaffolds - * @type Object - */ - this.scaffolds = {}; - - /** - * The default scaffold collection. - * - * @property defaults - * @type Object - */ - this.defaults = { - normal: [ - 'layout: {{ layout }}', - 'title: {{ title }}', - 'date: {{ date }}', - 'tags:', - '---' - ].join('\n') + '\n' - }; - - this.processors.push({process: process}); -}; - -Scaffold.prototype.__proto__ = Box.prototype; - -/** -* Gets a scaffold. -* -* @method get -* @param {String} layout -* @param {Function} callback -* @async -*/ -Scaffold.prototype.get = function(layout, callback){ - if (this.scaffolds[layout] != null){ - return callback(null, this.scaffolds[layout].content); - } else if (this.defaults[layout] != null){ - return callback(null, this.defaults[layout]); - } - - var scaffoldPath = pathFn.join(this.asset_dir, layout + '.md'), - self = this; - - fs.exists(scaffoldPath, function(exist){ - if (!exist) return callback(); - - file.readFile(scaffoldPath, function(err, content){ - if (err) return callback(err); - - self.defaults[getScaffoldName(layout)] = content; - - callback(null, content); - }); - }); -}; - -/** -* Creates/updates a scaffold. -* -* @method set -* @param {String} layout -* @param {String} content -* @param {Function} [callback] -* @async -*/ -Scaffold.prototype.set = function(layout, content, callback){ - if (typeof callback !== 'function') callback = function(){}; - - var scaffoldPath = '', - self = this; - - if (this.scaffolds[layout] != null){ - scaffoldPath = this.scaffolds[layout].path; - } else { - scaffoldPath = pathFn.join(hexo.scaffold_dir, layout); - if (!pathFn.extname(scaffoldPath)) scaffoldPath += '.md'; - } - - file.writeFile(scaffoldPath, content, function(err){ - if (err) return callback(err); - - self.scaffolds[layout] = { - path: scaffoldPath, - content: content - }; - - callback(); - }); -}; - -Scaffold.prototype.create = Scaffold.prototype.set; -Scaffold.prototype.update = Scaffold.prototype.set; - -/** -* Removes a scaffold. -* -* @method remove -* @param {String} layout -* @param {Function} [callback] -* @async -*/ -Scaffold.prototype.remove = function(layout, callback){ - if (typeof callback !== 'function') callback = function(){}; - if (this.scaffolds[layout] == null) return callback(); - - fs.unlink(this.scaffolds[layout].path, callback); -}; \ No newline at end of file diff --git a/lib/core/source.js b/lib/core/source.js deleted file mode 100644 index de881cbfd5..0000000000 --- a/lib/core/source.js +++ /dev/null @@ -1,34 +0,0 @@ -var async = require('async'), - pathFn = require('path'), - Box = require('../box'), - util = require('../util'), - file = util.file2; - -/** -* This module manages all files in the source folder. -* -* @class Source -* @constructor -* @module hexo -* @extend Box -*/ - -var Source = module.exports = function Source(){ - var base = hexo.source_dir; - - Box.call(this, base); -}; - -Source.prototype.__proto__ = Box.prototype; - -/** -* Loads all files and runs processors. -* -* @method process -* @param {Array|String} [files] Files to be processed -* @param {Function} [callback] -*/ -Source.prototype.load = Source.prototype.process = function(){ - this.processors = hexo.extend.processor.list(); - Box.prototype.process.apply(this, arguments); -}; \ No newline at end of file diff --git a/lib/error/extend.js b/lib/error/extend.js deleted file mode 100644 index ea7b8eedc9..0000000000 --- a/lib/error/extend.js +++ /dev/null @@ -1,20 +0,0 @@ -var HexoError = require('./index'); - -/** -* An error class used in Hexo extensions. -* -* @class ExtendError -* @param {String} msg -* @constructor -* @extends Hexo.Error -* @module hexo -* @namespace Hexo.Error -*/ - -var ExtendError = module.exports = function(msg){ - HexoError.call(this, msg); - Error.captureStackTrace(this, arguments.callee); - this.name = 'ExtendError'; -}; - -ExtendError.prototype.__proto__ = HexoError.prototype; \ No newline at end of file diff --git a/lib/error/index.js b/lib/error/index.js deleted file mode 100644 index 6d7c7c9bbb..0000000000 --- a/lib/error/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/** -* An error class used in Hexo. -* -* @class Error -* @param {String} msg -* @constructor -* @module hexo -* @namespace Hexo -*/ - -var HexoError = module.exports = function(msg){ - Error.call(this); - Error.captureStackTrace(this, arguments.callee); - this.message = msg; - this.name = 'HexoError'; -}; - -HexoError.prototype.__proto__ = Error.prototype; - -HexoError.ExtendError = require('./extend'); - -/** -* Replace the error message with the string. -* -* @method wrap -* @param {Error} err -* @param {String} msg -* @return {Error} -* @static -*/ - -HexoError.wrap = function(err, msg){ - var stack = err.stack; - - err.name = 'HexoError'; - err.message = msg; - err.stack = stack; - - return err; -}; \ No newline at end of file diff --git a/lib/extend/console.js b/lib/extend/console.js index 9737a88a28..3b6564bf74 100644 --- a/lib/extend/console.js +++ b/lib/extend/console.js @@ -1,87 +1,36 @@ -var _ = require('lodash'), - ExtendError = require('../error').ExtendError; - -/** -* This class is used to manage all console plugins in Hexo. -* -* @class Console -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Console = module.exports = function(){ - /** - * @property store - * @type Object - */ +var Promise = require('bluebird'), + abbrev = require('abbrev'); +function Console(){ this.store = {}; - - /** - * @property alias - * @type Object - */ - this.alias = {}; -}; - -/** -* Gets the console plugin. -* -* @method get -* @param {String} name You can use either full name or alias of the console plugin. -* @return {Object} -*/ +} Console.prototype.get = function(name){ name = name.toLowerCase(); - - return this.store[name] || this.alias[name]; + return this.store[this.alias[name]]; }; -/** -* Returns a list of console plugins. -* -* @method list -* @return {Object} -*/ - Console.prototype.list = function(){ return this.store; }; -/** -* Registers a console plugin. -* -* @method register -* @param {String} name Name -* @param {String} [desc] Description -* @param {Object} [options] -* @param {Boolean} [options.init=false] Determines whether the plugin is available even Hexo not initalized yet -* @param {String} [options.desc] The detailed description -* @param {Object} [options.options] Descriptions of each option used in the plugin -* @param {Object} [options.arguments] Descriptions of each argument used in the plugin -* @param {String} [options.alias] The alias for the plugin -* @param {Function} fn -*/ - Console.prototype.register = function(name, desc, options, fn){ - if (!name) throw new ExtendError('name is required'); + if (!name) throw new TypeError('name is required'); if (!fn){ if (options){ if (typeof options === 'function'){ fn = options; - if (_.isObject(desc)){ // name, options, fn + if (typeof desc === 'object'){ // name, options, fn options = desc; desc = ''; } else { // name, desc, fn options = {}; } } else { - throw new ExtendError('fn is required'); + throw new TypeError('fn must be a function'); } } else { // name, fn @@ -90,14 +39,18 @@ Console.prototype.register = function(name, desc, options, fn){ options = {}; desc = ''; } else { - throw new ExtendError('fn is required'); + throw new TypeError('fn must be a function'); } } } - var console = this.store[name.toLowerCase()] = fn; - console.desc = desc; - console.options = options; + if (fn.length > 1) fn = Promise.promisify(fn); + + var c = this.store[name.toLowerCase()] = fn; + c.options = options; + c.desc = desc; + + this.alias = abbrev(Object.keys(this.store)); +}; - if (options.alias) this.alias[options.alias] = console; -}; \ No newline at end of file +module.exports = Console; \ No newline at end of file diff --git a/lib/extend/deployer.js b/lib/extend/deployer.js index ccd3dcb407..fddbca954a 100644 --- a/lib/extend/deployer.js +++ b/lib/extend/deployer.js @@ -1,45 +1,19 @@ -var ExtendError = require('../error').ExtendError; - -/** -* This class is used to manage all deployer plugins in Hexo. -* -* @class Deployer -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Deployer = module.exports = function(){ - /** - * @property store - * @type Object - */ +var Promise = require('bluebird'); +function Deployer(){ this.store = {}; -}; - -/** -* Returns a list of deployer plugins. -* -* @method list -* @return {Object} -*/ +} Deployer.prototype.list = function(){ return this.store; }; -/** -* Registers a deployer plugin. -* -* @method register -* @param {String} name -* @param {Function} fn -*/ - Deployer.prototype.register = function(name, fn){ - if (!name) throw new ExtendError('name is required'); - if (typeof fn !== 'function') throw new ExtendError('fn is required'); + if (!name) throw new TypeError('name is required'); + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); + if (fn.length > 1) fn = Promise.promisify(fn); this.store[name] = fn; -}; \ No newline at end of file +}; + +module.exports = Deployer; \ No newline at end of file diff --git a/lib/extend/filter.js b/lib/extend/filter.js index 95a114b230..b8cc3693e3 100644 --- a/lib/extend/filter.js +++ b/lib/extend/filter.js @@ -1,69 +1,18 @@ -var async = require('async'), - domain = require('domain'), - HexoError = require('../error'), - ExtendError = HexoError.ExtendError; - -/** -* This class is used to manage all filter plugins in Hexo. -* -* @class Filter -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Filter = module.exports = function(){ - /** - * @property store - * @type Object - */ - - this.store = {}; -}; - -/** -* Returns a list of filter plugins. Use `type` argument to get filter plugins of a specified type. -* -* @method list -* @param {String} [type] -* @return {Object|Array} -*/ - -Filter.prototype.list = function(type){ - if (type){ - var store = this.store[type]; - if (!store) return []; - - var keys = Object.keys(store), - list = []; - - keys.sort(function(a, b){ - return a - b; - }); - - for (var i = 0, len = keys.length; i < len; i++){ - list = list.concat(store[keys[i]]); - } - - return list; - } else { - return this.store; - } -}; +var Promise = require('bluebird'); var typeAlias = { pre: 'before_post_render', post: 'after_post_render' }; -/** -* Registers a filter plugin. -* -* @method register -* @param {String} [type=after_post_render] The type of filter plugins. -* @param {Function} fn -* @param {Number} [priority=10] The execution priority of the plugin. It must be a positive number. -*/ +function Filter(){ + this.store = {}; +} + +Filter.prototype.list = function(type){ + if (!type) return this.store; + return this.store[type] || []; +}; Filter.prototype.register = function(type, fn, priority){ if (!fn){ @@ -71,107 +20,42 @@ Filter.prototype.register = function(type, fn, priority){ fn = type; type = 'after_post_render'; } else { - throw new ExtendError('fn is required'); + throw new TypeError('fn must be a function'); } } type = typeAlias[type] || type; - priority = priority ? +priority : 10; + priority = priority == null ? priority : 10; - if (!this.store.hasOwnProperty(type)) this.store[type] = {}; - if (!this.store[type].hasOwnProperty(priority)) this.store[type][priority] = []; + var store = this.store[type] = this.store[type] || []; - this.store[type][priority].push(fn); -}; + fn.priority = priority; + store.push(fn); -/** -* Runs all filter plugins of a specified type. -* -* @method apply -* @param {String} type -* @param {Array} [args] -* @param {Function} [callback] -* @param {Object} [context] -*/ + store.sort(function(a, b){ + return a.priority - b.priority; + }); +}; -Filter.prototype.apply = function(type, args, callback, context){ +Filter.prototype.apply = function(type, args, sync, context){ if (!args) args = []; if (!Array.isArray(args)) args = [args]; - var list = this.list(type), - d = domain.create(), - called = false; - - if (typeof callback === 'function'){ - var results = []; + var filters = this.list(type); - d.on('error', function(err){ - !called && callback(err); - called = true; - }); - - async.eachSeries(list, function(filter, next){ - d.add(filter); - - d.run(function(){ - filter.apply(context, [].concat(args, function(err, result){ - d.remove(filter); - if (err) return next(err); - - if (result) results.push(result); - next(); - })); - }); - }, function(err){ - if (err) return callback(err); - - callback(null, results); - }); - } else { + if (sync){ var result; - d.on('error', function(err){ - hexo.log.e(err); - }); - - list.forEach(function(filter){ - d.add(filter); - - d.run(function(){ - result = filter.apply(context, args); - d.remove(filter); - }); - }); + for (var i = 0, len = filters.length; i < len; i++){ + result = filters[i].apply(context, args); + } return result; + } else { + return Promise.map(filters, function(filter){ + return filter.apply(context, args); + }); } }; -var filterFunction = function(a, b){ - return a.toString() !== b.toString(); -}; - -/** -* Unregisters a filter plugin. -* -* @method unregister -* @param {String} type -* @param {Function} [fn] If `fn` is not defined, all filter plugins of the specified type will be unregistered. -*/ - -Filter.prototype.unregister = function(type, fn){ - var store = this.store[type]; - if (!store) return; - - if (typeof fn === 'function'){ - var keys = Object.keys(store); - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i]; - - store[key] = store[key].filter(filterFunction(filter, fn)); - } - } else { - this.store[type] = {}; - } -}; \ No newline at end of file +module.exports = Filter; \ No newline at end of file diff --git a/lib/extend/generator.js b/lib/extend/generator.js index e822934532..5416902a6a 100644 --- a/lib/extend/generator.js +++ b/lib/extend/generator.js @@ -1,52 +1,26 @@ -var ExtendError = require('../error').ExtendError; - -var num = 0; - -/** -* This class is used to manage all generator plugins in Hexo. -* -* @class Generator -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Generator = module.exports = function(){ - /** - * @property store - * @type Object - */ +var Promise = require('bluebird'); +function Generator(){ + this.id = 0; this.store = {}; -}; - -/** -* Returns a list of generator plugins. -* -* @method list -* @return {Object} -*/ +} Generator.prototype.list = function(){ return this.store; }; -/** -* Registers a generator plugin. -* -* @method register -* @param {Function} fn -*/ - Generator.prototype.register = function(name, fn){ if (!fn){ if (typeof name === 'function'){ fn = name; - name = 'generator-' + num++; + name = 'generator=' + this.id++; } else { - throw new ExtendError('fn is required'); + throw new TypeError('fn must be a function'); } } + if (fn.length > 2) fn = Promise.promisify(fn); this.store[name] = fn; -}; \ No newline at end of file +}; + +module.exports = Generator; \ No newline at end of file diff --git a/lib/extend/helper.js b/lib/extend/helper.js index a1643e9404..47a57fc388 100644 --- a/lib/extend/helper.js +++ b/lib/extend/helper.js @@ -1,45 +1,16 @@ -var ExtendError = require('../error').ExtendError; - -/** -* This class is used to manage all helper plugins in Hexo. -* -* @class Helper -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Helper = module.exports = function(){ - /** - * @property store - * @type Object - */ - +function Helper(){ this.store = {}; -}; - -/** -* Returns a list of helper plugins. -* -* @method list -* @return {Object} -*/ +} Helper.prototype.list = function(){ return this.store; }; -/** -* Registers a helper plugin. -* -* @method register -* @param {String} name -* @param {Function} fn -*/ - Helper.prototype.register = function(name, fn){ - if (!name) throw new ExtendError('name is required'); - if (typeof fn !== 'function') throw new ExtendError('fn is required'); + if (!name) throw new TypeError('name is required'); + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); this.store[name] = fn; -}; \ No newline at end of file +}; + +module.exports = Helper; \ No newline at end of file diff --git a/lib/extend/index.js b/lib/extend/index.js index e79944e21e..430a230b96 100644 --- a/lib/extend/index.js +++ b/lib/extend/index.js @@ -1,34 +1,9 @@ -/** -* This class is used to manage all plugins used in Hexo. -* -* There're 9 types of plugins: -* -* - {% crosslink Extend.Console Console %} -* - {% crosslink Extend.Deployer Deployer %} -* - {% crosslink Extend.Filter Filter %} -* - {% crosslink Extend.Generator Generator %} -* - {% crosslink Extend.Helper Helper %} -* - {% crosslink Extend.Migrator Migrator %} -* - {% crosslink Extend.Processor Processor %} -* - {% crosslink Extend.Renderer Renderer %} -* - {% crosslink Extend.Tag Tag %} -* -* @class Extend -* @constructor -* @module hexo -*/ - -var Extend = module.exports = function(){ -}; - -/** -* Registers a new module. -* -* @method module -* @param {String} name -* @param {Function} fn -*/ - -Extend.prototype.module = function(name, fn){ - this[name] = new fn(); -}; \ No newline at end of file +exports.Console = require('./console'); +exports.Deployer = require('./deployer'); +exports.Filter = require('./filter'); +exports.Generator = require('./generator'); +exports.Helper = require('./helper'); +exports.Migrator = require('./migrator'); +exports.Processor = require('./processor'); +exports.Renderer = require('./renderer'); +exports.Tag = require('./tag'); \ No newline at end of file diff --git a/lib/extend/migrator.js b/lib/extend/migrator.js index a727469a2e..d95069d70b 100644 --- a/lib/extend/migrator.js +++ b/lib/extend/migrator.js @@ -1,45 +1,19 @@ -var ExtendError = require('../error').ExtendError; - -/** -* This class is used to manage all migrator plugins in Hexo. -* -* @class Migrator -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Migrator = module.exports = function(){ - /** - * @property store - * @type Object - */ +var Promise = require('bluebird'); +function Migrator(){ this.store = {}; -}; - -/** -* Returns a list of migrator plugins. -* -* @method list -* @return {Object} -*/ +} Migrator.prototype.list = function(){ return this.store; }; -/** -* Registers a migrator plugin. -* -* @method register -* @param {String} type -* @param {Function} fn -*/ - Migrator.prototype.register = function(name, fn){ - if (!name) throw new ExtendError('name is required'); - if (typeof fn !== 'function') throw new ExtendError('fn is required'); + if (!name) throw new TypeError('name is required'); + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); + if (fn.length > 1) fn = Promise.promisify(fn); this.store[name] = fn; -}; \ No newline at end of file +}; + +module.exports = Migrator; \ No newline at end of file diff --git a/lib/extend/processor.js b/lib/extend/processor.js index e623a724a2..509c77b43f 100644 --- a/lib/extend/processor.js +++ b/lib/extend/processor.js @@ -1,55 +1,30 @@ -var ExtendError = require('../error').ExtendError, +var Promise = require('bluebird'), Pattern = require('../box/pattern'); -/** -* This class is used to manage all processor plugins in Hexo. -* -* @class Processor -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Processor = module.exports = function(){ - /** - * @property store - * @type Array - */ - +function Processor(){ this.store = []; -}; - -/** -* Returns a list of processor plugins. -* -* @method list -* @return {Array} -*/ +} Processor.prototype.list = function(){ return this.store; }; -/** -* Register a processor plugin. -* -* @method register -* @param {String|RegExp} [rule] -* @param {Function} fn -*/ - -Processor.prototype.register = function(rule, fn){ +Processor.prototype.register = function(pattern, fn){ if (!fn){ - if (typeof rule === 'function'){ - fn = rule; - rule = /(.*)/; + if (typeof pattern === 'function'){ + fn = pattern; + pattern = /(.*)/; } else { - throw new ExtendError('fn is required'); + throw new TypeError('fn must be a function'); } } + if (fn.length > 1) fn = Promise.promisify(fn); + this.store.push({ - pattern: new Pattern(rule), + pattern: new Pattern(pattern), process: fn }); -}; \ No newline at end of file +}; + +module.exports = Processor; \ No newline at end of file diff --git a/lib/extend/renderer.js b/lib/extend/renderer.js index e836581efd..ccf5992b5a 100644 --- a/lib/extend/renderer.js +++ b/lib/extend/renderer.js @@ -1,152 +1,58 @@ -var ExtendError = require('../error').ExtendError, - pathFn = require('path'), - domain = require('domain'); +var pathFn = require('path'), + Promise = require('bluebird'); -/** -* This class is used to manage all renderer plugins in Hexo. -* -* @class Renderer -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Renderer = module.exports = function(){ - /** - * @property store - * @type Object - */ +function getExtname(str){ + var extname = pathFn.extname(str) || str; + return extname[0] === '.' ? extname.slice(1) : extname; +} +function Renderer(){ this.store = {}; + this.storeSync = {}; +} - /** - * @property storeSync - * @type Object - */ +Renderer.prototype.list = function(sync){ + return sync ? this.storeSync : this.store; +}; - this.storeSync = {}; +Renderer.prototype.get = function(name, sync){ + var store = this[sync ? 'storeSync' : 'store']; + + return store[getExtname(name)] || store[name]; }; -/** -* Returns a list of renderer plugins. -* -* @method list -* @param {Boolean} sync -* @return {Object} -*/ +Renderer.prototype.isRenderable = function(path){ + return Boolean(this.get(path)); +}; -Renderer.prototype.list = function(sync){ - return sync ? this.storeSync : this.store; +Renderer.prototype.isRenderableSync = function(path){ + return Boolean(this.get(path, true)); }; -/** -* Registers a renderer plugin. -* -* @method register -* @param {String} name -* @param {String} output -* @param {Function} fn -* @param {Boolean} [sync=false] -*/ +Renderer.prototype.getOutput = function(path){ + var renderer = this.get(path); + return renderer ? renderer.output : ''; +}; Renderer.prototype.register = function(name, output, fn, sync){ - if (!name) throw new ExtendError('name is required'); - if (!output) throw new ExtendError('output is required'); - if (typeof fn !== 'function') throw new ExtendError('fn is required'); + if (!name) throw new TypeError('name is required'); + if (!output) throw new TypeError('output is required'); + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); - name = name.replace(/^\./, ''); - output = output.replace(/^\./, ''); + name = getExtname(name); + output = getExtname(output); if (sync){ this.storeSync[name] = fn; this.storeSync[name].output = output; - this.store[name] = function(){ - var args = Array.prototype.slice.call(arguments), - callback = args.pop(), - d = domain.create(), - called = false; - - d.on('error', function(err){ - !called && callback(err); - called = true; - }); - - d.add(fn); - - d.run(function(){ - !called && callback(null, fn.apply(null, args)); - called = true; - }); - }; + this.store[name] = Promise.method(fn); } else { + if (fn.length > 2) fn = Promise.promisify(fn); this.store[name] = fn; } this.store[name].output = output; }; -/** -* Gets extension name. -* -* @method getExtname -* @param {String} str -* @return {String} -* @private -*/ - -var getExtname = function(str){ - return (pathFn.extname(str) || str).replace(/^\./, ''); -}; - -/** -* Gets the renderer plugin. -* -* @method get -* @param {String} name -* @param {Boolean} [sync=false] -*/ - -Renderer.prototype.get = function(name, sync){ - var store = this[sync ? 'storeSync' : 'store']; - - return store[getExtname(name)] || store[name]; -}; - -/** -* Checks if the given `path` is renderable. -* -* @method isRenderable -* @param {String} path -* @return {Boolean} -*/ - -Renderer.prototype.isRenderable = function(path){ - return !!this.get(path); -}; - -/** -* Checks if the given `path` is renderable by synchronized renderer. -* -* @method isRenderableSync -* @param {String} path -* @return {Boolean} -*/ - -Renderer.prototype.isRenderableSync = function(path){ - return !!this.get(path, true); -}; - -/** -* Gets the output extension name. -* -* @method getOutput -* @param {String} path -* @return {String} -*/ - -Renderer.prototype.getOutput = function(path){ - var renderer = this.get(path); - - if (renderer) return renderer.output; -}; \ No newline at end of file +module.exports = Renderer; \ No newline at end of file diff --git a/lib/extend/tag.js b/lib/extend/tag.js index 9494c82308..40784fcbb7 100644 --- a/lib/extend/tag.js +++ b/lib/extend/tag.js @@ -1,76 +1,24 @@ -var _ = require('lodash'), - stripIndent = require('strip-indent'), - ExtendError = require('../error').ExtendError; +var stripIndent = require('strip-indent'); var placeholder = String.fromCharCode(65535), rPlaceholder = new RegExp(placeholder + '(\\d+)' + placeholder, 'g'); -/** -* This class is used to manage all tag plugins in Hexo. -* -* @class Tag -* @constructor -* @namespace Extend -* @module hexo -*/ - -var Tag = module.exports = function(){ - /** - * @property store - * @type Array - */ +function Tag(){ this.store = []; -}; - -/** -* Returns a list of tag plugins. -* -* @method list -* @return {Object} -*/ +} Tag.prototype.list = function(){ return this.store; }; -/** -* Registers a tag plugin. -* -* ``` js -* hexo.extend.tag.register('name', function(){ -* // ... -* }, true); -* ``` -* -* equals to: -* -* ``` js -* hexo.extend.tag.register('name', function(){ -* // ... -* }, {ends: true}); -* ``` -* -* @method register -* @param {String} name -* @param {Function} fn -* @param {Object|Boolean} [options] -* @param {Boolean} [options.ends=false] Whether the tag have an end tag -* @param {Boolean} [options.escape=true] Prevent contents within the tag from being rendered by markdown or other render engines. -*/ - Tag.prototype.register = function(name, fn, options){ - if (!name) throw new ExtendError('name is required'); - if (typeof fn !== 'function') throw new ExtendError('fn is required'); + if (!name) throw new TypeError('name is required'); + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); - if (typeof options === 'boolean'){ + if (options == null || typeof options === 'boolean'){ options = {ends: options}; } - options = _.extend({ - ends: false, - escape: true - }, options); - var tag = { name: name, ends: options.ends @@ -132,6 +80,7 @@ Tag.prototype.register = function(name, fn, options){ return true; }); + // TODO What's this for? parser.on('start', function(){ tag._line = line; }); @@ -140,8 +89,7 @@ Tag.prototype.register = function(name, fn, options){ }; tag.compile = function(compiler, args, content, parents, opts, blockName){ - var self = this, - tmp = {}; + var tmp = {}; content = content.map(function(line, i){ if (line.compile){ @@ -186,4 +134,6 @@ Tag.prototype.register = function(name, fn, options){ }; this.store.push(tag); -}; \ No newline at end of file +}; + +module.exports = Tag; \ No newline at end of file diff --git a/lib/hexo.js b/lib/hexo.js deleted file mode 100644 index 16084ed64d..0000000000 --- a/lib/hexo.js +++ /dev/null @@ -1,16 +0,0 @@ -var _ = require('lodash'); - -exports.init = function(options, callback){ - options = _.extend({ - cwd: process.cwd(), - _: [] - }, options); - - if (options.command){ - options._.unshift(options.command); - } - - return require('./init')(options.cwd, options, callback); -}; - -exports.util = require('./util'); \ No newline at end of file diff --git a/lib/hexo/create_logger.js b/lib/hexo/create_logger.js new file mode 100644 index 0000000000..5e4e444264 --- /dev/null +++ b/lib/hexo/create_logger.js @@ -0,0 +1,90 @@ +var bunyan = require('bunyan'); +var moment = require('moment'); + +var dateFormat = 'HH:mm:ss.SSS'; + +var levelNames = { + 10: 'TRACE', + 20: 'DEBUG', + 30: 'INFO ', + 40: 'WARN ', + 50: 'ERROR', + 60: 'FATAL' +}; + +var levelColors = { + 10: 'gray', + 20: 'gray', + 30: 'green', + 40: 'yellow', + 50: 'red', + 60: 'magenta' +}; + +require('colors'); + +function ConsoleStream(env){ + this.debug = env.debug; +} + +ConsoleStream.prototype.write = function(data){ + var level = data.level; + var msg = ''; + + // Time + if (this.debug){ + msg += moment(data.time).format(dateFormat).gray + ' '; + } + + // Level + msg += levelNames[level][levelColors[level]] + ' '; + + // Message + msg += data.msg + '\n'; + + // Error + if (data.err){ + msg += data.err.stack.grey + '\n'; + } + + process.stdout.write(msg); +}; + +function createLogger(env){ + var streams = []; + var timer = {}; + + if (!env.silent){ + streams.push({ + type: 'raw', + level: env.debug ? 'trace' : 'info', + stream: new ConsoleStream(env) + }); + } + + if (env.debug){ + streams.push({ + level: 'trace', + path: 'debug.log' + }); + } + + var logger = this.log = bunyan.createLogger({ + name: 'hexo', + streams: streams, + serializers: { + err: bunyan.stdSerializers.err + } + }); + + // Alias for logger levels + logger.d = logger.debug; + logger.i = logger.info; + logger.w = logger.warn; + logger.e = logger.error; + logger.log = logger.info; + + return logger; +} + +module.exports = createLogger; \ No newline at end of file diff --git a/lib/hexo/index.js b/lib/hexo/index.js new file mode 100644 index 0000000000..8c8c4e6c09 --- /dev/null +++ b/lib/hexo/index.js @@ -0,0 +1,217 @@ +var util = require('../util'); +var Promise = require('bluebird'); +var pathFn = require('path'); +var tildify = require('tildify'); +var Database = require('warehouse'); +var EventEmitter = require('events').EventEmitter; +var pkg = require('../../package.json'); +var createLogger = require('./create_logger'); +var Box = require('../box'); +var extend = require('../extend'); +var Render = require('./render'); +var models = require('../models'); +var Post = require('./post'); +var Router = util.Router; + +var libDir = pathFn.dirname(__dirname); +var dbVersion = 1; + +require('colors'); + +function Hexo(base, args){ + base = base || process.cwd(); + args = args || {}; + + EventEmitter.call(this); + + this.base_dir = base + pathFn.sep; + this.public_dir = pathFn.join(base, 'public') + pathFn.sep; + this.source_dir = pathFn.join(base, 'source') + pathFn.sep; + this.plugin_dir = pathFn.join(base, 'node_modules') + pathFn.sep; + this.script_dir = pathFn.join(base, 'scripts') + pathFn.sep; + this.scaffold_dir = pathFn.join(base, 'scaffolds') + pathFn.sep; + + this.env = { + args: args, + debug: Boolean(args.debug), + safe: Boolean(args.safe), + silent: Boolean(args.silent), + env: process.env.NODE_ENV || 'development', + version: pkg.version, + init: false + }; + + this.config_path = args.config ? pathFn.resolve(base, args.config) + : pathFn.join(base, '_config.yml'); + + this.extend = { + console: new extend.Console(), + deployer: new extend.Deployer(), + filter: new extend.Filter(), + generator: new extend.Generator(), + helper: new extend.Helper(), + migrator: new extend.Migrator(), + processor: new extend.Processor(), + renderer: new extend.Renderer(), + tag: new extend.Tag() + }; + + this.config = { + // Site + title: 'Hexo', + subtitle: '', + description: '', + author: 'John Doe', + email: '', + language: '', + // URL + url: 'http://yoursite.com', + root: '/', + permalink: ':year/:month/:day/:title/', + tag_dir: 'tags', + archive_dir: 'archives', + category_dir: 'categories', + code_dir: 'downloads/code', + permalink_defaults: {}, + // Directory + source_dir: 'source', + public_dir: 'public', + // Writing + new_post_name: ':title.md', + default_layout: 'post', + titlecase: false, + external_link: true, + filename_case: 0, + render_drafts: false, + post_asset_folder: false, + relative_link: false, + highlight: { + enable: true, + line_number: true, + tab_replace: '', + }, + // Category & Tag + default_category: 'uncategorized', + category_map: {}, + tag_map: {}, + // Archives + archive: 2, + category: 2, + tag: 2, + // Server + port: 4000, + server_ip: 'localhost', + logger: false, + logger_format: 'dev', + // Date / Time format + date_format: 'MMM D YYYY', + time_format: 'H:mm:ss', + // Pagination + per_page: 10, + pagination_dir: 'page', + // Disqus + disqus_shortname: '', + // Extensions + theme: 'landscape', + exclude_generator: [], + // Deployment + deploy: {} + }; + + this.log = createLogger(this.env); + + this.render = new Render(this); + + this.route = new Router(); + + this.post = new Post(this); + + var db = this.database = new Database({ + version: dbVersion, + path: pathFn.join(base, 'db.json') + }); + + db.model('Asset', models.Asset); + db.model('Cache', models.Cache); + db.model('Category', models.Category); + db.model('Page', models.Page); + db.model('Post', models.Post); + db.model('Tag', models.Tag); + + var _Box = this.Box = function(base, options){ + Box.call(this, base, options); + }; + + util.inherits(_Box, Box); + + _Box.prototype._context = this; +} + +util.inherits(Hexo, EventEmitter); + +Hexo.prototype.init = function(){ + var self = this; + + this.log.debug('Hexo version: %s', this.version.magenta); + this.log.debug('Working directory: %s', tildify(this.base_dir).magenta); + + // Load internal plugins + require('../plugins/console')(this); + require('../plugins/deployer')(this); + require('../plugins/filter')(this); + require('../plugins/generator')(this); + require('../plugins/helper')(this); + require('../plugins/processor')(this); + require('../plugins/renderer')(this); + require('../plugins/tag')(this); + + // Load config + return require('./load_config')(this).then(function(){ + // Load external plugins & scripts + return Promise.all([ + require('./load_plugins')(self), + require('./load_scripts')(self), + require('./update_package')(self) + ]); + }).then(function(){ + // Load database + return require('./load_database')(self); + }).then(function(){ + // Ready to go! + self.emit('ready'); + }); +}; + +Hexo.prototype.call = function(name, args, callback){ + if (!callback && typeof args === 'function'){ + callback = args; + args = {}; + } + + var self = this; + + return new Promise(function(resolve, reject){ + var c = self.extend.console.get(name); + if (!c) return reject(new Error('Console `' + name + '` is not registered')); + + return c(args); + }).nodeify(callback); +}; + +Hexo.prototype.model = function(name, schema){ + return this.database.model(name, schema); +}; + +Hexo.prototype._createBox = function(base, options){ + return new this.Box(base, options); +}; + +Hexo.lib_dir = Hexo.prototype.lib_dir = libDir; + +Hexo.core_dir = Hexo.prototype.core_dir = pathFn.dirname(libDir); + +Hexo.version = Hexo.prototype.version = pkg.version; + +Hexo.util = Hexo.prototype.util = util; + +module.exports = Hexo; \ No newline at end of file diff --git a/lib/hexo/load_config.js b/lib/hexo/load_config.js new file mode 100644 index 0000000000..e790dd0973 --- /dev/null +++ b/lib/hexo/load_config.js @@ -0,0 +1,49 @@ +var _ = require('lodash'); +var pathFn = require('path'); +var tildify = require('tildify'); +var util = require('../util'); +var fs = util.fs; + +require('colors'); + +module.exports = function(ctx){ + var baseDir = ctx.base_dir; + var configPath = ctx.config_path; + var extname = pathFn.extname(configPath); + var dirname = pathFn.dirname(configPath); + var basename = pathFn.basename(configPath, extname); + + return fs.readdir(dirname).filter(function(item){ + return item.substring(0, basename.length) === basename; + }).then(function(result){ + if (!result.length) return; + + configPath = pathFn.join(pathFn.dirname(configPath), result[0]); + + return ctx.render.render({path: configPath}); + }).then(function(config){ + if (!config) return; + + ctx.log.debug('Config loaded: %s', tildify(configPath).magenta); + + config = _.extend(ctx.config, config); + ctx.config_path = configPath; + ctx.env.init = true; + + if (_.last(config.root) !== '/'){ + config.root += '/'; + } + + if (_.last(config.url) === '/'){ + config.url = config.url.substring(0, config.url.length - 1); + } + + ctx.public_dir = pathFn.join(baseDir, config.public_dir) + pathFn.sep; + ctx.source_dir = pathFn.join(baseDir, config.source_dir) + pathFn.sep; + + if (config.theme){ + ctx.theme_dir = pathFn.join(baseDir, 'themes', config.theme) + pathFn.sep; + ctx.theme_script_dir = pathFn.join(ctx.theme_dir, 'scripts') + pathFn.sep; + } + }); +}; \ No newline at end of file diff --git a/lib/hexo/load_database.js b/lib/hexo/load_database.js new file mode 100644 index 0000000000..1b289c008b --- /dev/null +++ b/lib/hexo/load_database.js @@ -0,0 +1,24 @@ +var semver = require('semver'); +var util = require('../util'); +var fs = util.fs; + +module.exports = function(ctx){ + var db = ctx.database; + var path = db.options.path; + var log = ctx.log; + + return fs.exists(path).then(function(exist){ + if (!exist) return; + + if (semver.lt(ctx.version, '3.0.0')){ + log.debug('Deleting old database.'); + return fs.unlink(path); + } else { + log.debug('Loading database.'); + return db.load(); + } + }).catch(function(){ + log.error('Database log failed. Deleting database.'); + return fs.unlink(path); + }); +}; \ No newline at end of file diff --git a/lib/hexo/load_plugins.js b/lib/hexo/load_plugins.js new file mode 100644 index 0000000000..6c85590c7d --- /dev/null +++ b/lib/hexo/load_plugins.js @@ -0,0 +1,24 @@ +var pathFn = require('path'); +var util = require('../util'); +var fs = util.fs; + +require('colors'); + +module.exports = function(ctx){ + if (!ctx.env.init || ctx.env.safe) return; + + var pluginDir = ctx.plugin_dir; + var currentName = ''; + + return fs.exists(pluginDir).then(function(exist){ + return exist ? fs.readdir(pluginDir) : []; + }).filter(function(name){ + return name.slice(0, 5) === 'hexo-'; + }).map(function(name){ + currentName = name; + require(pathFn.join(pluginDir, name)); + ctx.log.debug('Plugin loaded: %s', name.magenta); + }).catch(function(err){ + ctx.log.error({err: err}, 'Plugin load failed: %s', currentName.magenta); + }); +}; \ No newline at end of file diff --git a/lib/hexo/load_scripts.js b/lib/hexo/load_scripts.js new file mode 100644 index 0000000000..2352083a82 --- /dev/null +++ b/lib/hexo/load_scripts.js @@ -0,0 +1,26 @@ +var Promise = require('bluebird'); +var pathFn = require('path'); +var tildify = require('tildify'); +var util = require('../util'); +var fs = util.fs; + +require('colors'); + +module.exports = function(ctx){ + return Promise.filter([ + ctx.script_dir, + ctx.theme_script_dir + ], function(scriptDir){ + return scriptDir ? fs.exists(scriptDir) : false; + }).map(function(scriptDir){ + var scriptPath = ''; + + return fs.listDir(scriptDir).map(function(name){ + scriptPath = pathFn.join(scriptDir, name); + require(scriptPath); + ctx.log.debug('Script loaded: %s', tildify(scriptPath).magenta); + }).catch(function(err){ + ctx.log.error({err: err}, 'Script load failed: %s', tildify(scriptPath).magenta); + }); + }); +}; \ No newline at end of file diff --git a/lib/hexo/post.js b/lib/hexo/post.js new file mode 100644 index 0000000000..02a4ed0139 --- /dev/null +++ b/lib/hexo/post.js @@ -0,0 +1,21 @@ +function Post(context){ + this.context = context; +} + +Post.prototype.create = function(data, replace, callback){ + // +}; + +Post.prototype.load = function(options, callback){ + // +}; + +Post.prototype.publish = function(data, replace, callback){ + // +}; + +Post.prototype.render = function(source, data, callback){ + // +}; + +module.exports = Post; \ No newline at end of file diff --git a/lib/hexo/render.js b/lib/hexo/render.js new file mode 100644 index 0000000000..1748302133 --- /dev/null +++ b/lib/hexo/render.js @@ -0,0 +1,70 @@ +var pathFn = require('path'), + Promise = require('bluebird'), + util = require('../util'), + fs = util.fs; + +function getExtname(str){ + var extname = pathFn.extname(str); + return extname[0] === '.' ? extname.slice(1) : extname; +} + +function Render(ctx){ + this.renderer = ctx.extend.renderer; +} + +Render.prototype.isRenderable = function(path){ + return this.renderer.isRenderable(path); +}; + +Render.prototype.isRenderableSync = function(path){ + return this.renderer.isRenderableSync(path); +}; + +Render.prototype.getOutput = function(path){ + return this.renderer.getOutput(path); +}; + +Render.prototype.render = function(data, options, callback){ + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; + } + + var self = this; + + return new Promise(function(resolve, reject){ + if (!data) return reject(new TypeError('No input file or string!')); + if (data.text != null) return resolve(data.text); + if (!data.path) return reject(new TypeError('No input file or string!')); + + return fs.readFile(data.path).then(resolve, reject); + }).then(function(text){ + var ext = data.engine || getExtname(data.path); + if (!ext || !self.isRenderable(ext)) return text; + + var renderer = self.renderer.get(ext); + return renderer({path: data.path, text: text}, options); + }).nodeify(callback); +}; + +Render.prototype.renderSync = function(data, options){ + if (!data) throw new TypeError('No input file or string!'); + + var text = ''; + + if (data.text != null){ + text = data.text; + } else if (data.path){ + text = fs.readFileSync(data.path); + } + + if (text == null) throw new TypeError('No input file or string!'); + + var ext = data.engine || getExtname(data.path); + if (!ext || !this.isRenderableSync(ext)) return text; + + var renderer = this.renderer.get(ext, true); + return renderer({path: data.path, text: text}, options); +}; + +module.exports = Render; \ No newline at end of file diff --git a/lib/hexo/scaffold.js b/lib/hexo/scaffold.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/hexo/source.js b/lib/hexo/source.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/hexo/update_package.js b/lib/hexo/update_package.js new file mode 100644 index 0000000000..e18bf8befd --- /dev/null +++ b/lib/hexo/update_package.js @@ -0,0 +1,28 @@ +var pathFn = require('path'); +var util = require('../util'); +var fs = util.fs; + +module.exports = function(ctx){ + if (!ctx.env.init) return; + + var packagePath = pathFn.join(ctx.base_dir, 'package.json'); + var log = ctx.log; + + return fs.exists(packagePath).then(function(exist){ + if (exist) return; + + log.debug('Can\'t found package.json. Rebuilding a new one.'); + return fs.copyFile(pathFn.join(hexo.core_dir, 'assets', 'package.json'), packagePath); + }).then(function(){ + return fs.readFile(packagePath); + }).then(function(content){ + var json = JSON.parse(content); + if (json.version === hexo.version) return; + + json.version = hexo.version; + + log.debug('Updating package.json'); + + return fs.writeFile(packagePath, JSON.stringify(json, null, ' ')); + }); +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000000..c70a303425 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,5 @@ +var Hexo = require('./hexo'); + +module.exports = function(){ + // +}; \ No newline at end of file diff --git a/lib/init.js b/lib/init.js deleted file mode 100644 index 0c7011b2e8..0000000000 --- a/lib/init.js +++ /dev/null @@ -1,96 +0,0 @@ -var async = require('async'), - path = require('path'), - Hexo = require('./core'), - Logger = require('./logger'); - -var defaultCallback = function(err){ - process.exit(err ? 1 : 0); -}; - -module.exports = function(cwd, args, callback){ - if (typeof callback !== 'function') callback = defaultCallback; - - var hexo = global.hexo = new Hexo(), - configfile = args.config || '_config.yml'; - - hexo.bootstrap(cwd, args); - hexo.configfile = path.join(hexo.base_dir, configfile); - - async.eachSeries([ - 'logger', - 'extend', - 'config', - 'update', - 'database', - 'box', - 'plugins', - 'scripts' - ], function(name, next){ - require('./loaders/' + name)(next); - }, function(err){ - if (err){ - if (hexo.log != null){ - return hexo.log.e(err); - } else { - throw err; - } - } - - /** - * Fired when Hexo is ready. - * - * @event ready - * @for Hexo - */ - - hexo.emit('ready'); - - var command = args._.shift(); - - if (command){ - var c = hexo.extend.console.get(command); - - if (!c || (!hexo.env.init && !c.options.init)){ - command = 'help'; - } - } else if (args.v || args.version){ - command = 'version'; - } else { - command = 'help'; - } - - if (hexo.env.silent && command === 'help') return callback(); - - hexo.call(command, args, function(err){ - if (err) hexo.log.e(err); - - /** - * Fired when Hexo is about to exit. - * - * @event exit - * @for Hexo - */ - - hexo.emit('exit'); - - if (!err) return callback(); - - var logPath = path.join(hexo.base_dir, 'debug.log'), - FileStream = Logger.stream.File; - - async.series([ - function(next){ - FileStream.prepare(logPath, next); - }, - function(next){ - FileStream.dump(logPath, hexo.log, next); - } - ], function(err){ - if (err) log.e(err); - callback(err); - }); - }); - }); - - return hexo; -}; \ No newline at end of file diff --git a/lib/loaders/box.js b/lib/loaders/box.js deleted file mode 100644 index 2fe3550a80..0000000000 --- a/lib/loaders/box.js +++ /dev/null @@ -1,71 +0,0 @@ -var fs = require('graceful-fs'), - async = require('async'), - util = require('../util'), - Theme = require('../theme'), - Source = require('../core/source'), - Scaffold = require('../core/scaffold'), - file = util.file2; - -module.exports = function(callback){ - if (!hexo.env.init) return callback(); - - async.parallel([ - function(next){ - fs.exists(hexo.theme_dir, function(exist){ - if (exist){ - /** - * See {% crosslink Theme %}. - * - * @property theme - * @type Theme - * @for Hexo - */ - hexo.theme = new Theme(); - next(); - } else { - next(new Error('Theme ' + hexo.config.theme + ' does not exist.')); - } - }); - }, - function(next){ - fs.exists(hexo.source_dir, function(exist){ - /** - * See {% crosslink Source %}. - * - * @property source - * @type Source - * @for Hexo - */ - hexo.source = new Source(); - - if (exist){ - next(); - } else { - file.mkdirs(hexo.source_dir, next); - } - }); - }, - function(next){ - fs.exists(hexo.scaffold_dir, function(exist){ - /** - * See {% crosslink Scaffold %} - * - * @property scaffold - * @type Scaffold - * @for Hexo - */ - hexo.scaffold = new Scaffold(); - - if (exist){ - hexo.scaffold.process(next); - } else { - file.mkdirs(hexo.scaffold_dir, function(err){ - if (err) return next(err); - - hexo.scaffold.process(next); - }); - } - }); - } - ], callback); -}; \ No newline at end of file diff --git a/lib/loaders/config.js b/lib/loaders/config.js deleted file mode 100644 index 41caa98668..0000000000 --- a/lib/loaders/config.js +++ /dev/null @@ -1,203 +0,0 @@ -var path = require('path'), - fs = require('graceful-fs'), - async = require('async'), - _ = require('lodash'), - HexoError = require('../error'), - util = require('../util'), - file = util.file2; - -var defaults = { - // Site - title: 'Hexo', - subtitle: '', - description: '', - author: 'John Doe', - email: '', - language: '', - // URL - url: 'http://yoursite.com', - root: '/', - permalink: ':year/:month/:day/:title/', - tag_dir: 'tags', - archive_dir: 'archives', - category_dir: 'categories', - code_dir: 'downloads/code', - permalink_defaults: {}, - // Directory - source_dir: 'source', - public_dir: 'public', - // Writing - new_post_name: ':title.md', - default_layout: 'post', - titlecase: false, - external_link: true, - filename_case: 0, - render_drafts: false, - post_asset_folder: false, - relative_link: false, - highlight: { - enable: true, - line_number: true, - tab_replace: '', - }, - // Category & Tag - default_category: 'uncategorized', - category_map: {}, - tag_map: {}, - // Archives - archive: 2, - category: 2, - tag: 2, - // Server - port: 4000, - server_ip: 'localhost', - logger: false, - logger_format: 'dev', - // Date / Time format - date_format: 'MMM D YYYY', - time_format: 'H:mm:ss', - // Pagination - per_page: 10, - pagination_dir: 'page', - // Disqus - disqus_shortname: '', - // Extensions - theme: 'landscape', - exclude_generator: [], - // Deployment - deploy: {} -}; - -var joinPath = function(){ - var str = path.join.apply(this, arguments); - - if (str[str.length - 1] !== path.sep) str += path.sep; - - return str; -}; - -module.exports = function(callback){ - var baseDir = hexo.base_dir, - configPath = hexo.configfile; - - /** - * Configuration. - * - * @property config - * @type Object - * @for Hexo - */ - - hexo.config = {}; - - async.series([ - function(next){ - fs.exists(configPath, function(exist){ - if (exist){ - next(); - } else { - callback(); - } - }); - }, - function(next){ - hexo.render.render({path: configPath}, function(err, result){ - if (err) return next(HexoError.wrap(err, 'Config file load failed')); - - var config = hexo.config = _.extend(defaults, result); - hexo.env.init = true; - - if (_.last(config.root) !== '/'){ - config.root += '/'; - } - - if (_.last(config.url) === '/'){ - config.url = config.url.substring(0, config.url.length - 1); - } - - var baseDir = hexo.base_dir; - - /** - * The path of public directory. - * - * @property public_dir - * @type String - * @for Hexo - */ - - hexo.constant('public_dir', joinPath(baseDir, config.public_dir)); - - /** - * The path of source directory. - * - * @property source_dir - * @type String - * @for Hexo - */ - - hexo.constant('source_dir', joinPath(baseDir, config.source_dir)); - - /** - * The path of plugin directory. - * - * @property plugin_dir - * @type String - * @for Hexo - */ - - hexo.constant('plugin_dir', joinPath(baseDir, 'node_modules')); - - /** - * The path of script directory. - * - * @property script_dir - * @type String - * @for Hexo - */ - - hexo.constant('script_dir', joinPath(baseDir, 'scripts')); - - /** - * The path of scaffold directory. - * - * @property scaffold_dir - * @type String - * @for Hexo - */ - - hexo.constant('scaffold_dir', joinPath(baseDir, 'scaffolds')); - - /** - * The path of theme directory. - * - * @property theme_dir - * @type String - * @for Hexo - */ - - hexo.constant('theme_dir', function(){ - return joinPath(baseDir, 'themes', config.theme); - }); - - /** - * The path of theme script directory. - * - * @property theme_script_dir - * @type String - * @for Hexo - */ - - hexo.constant('theme_script_dir', function(){ - return joinPath(hexo.theme_dir, 'scripts'); - }); - - next(); - }); - } - ], function(err){ - if (err) return callback(err); - - hexo.log.d('Config file loaded from %s', configPath); - callback(); - }); -}; diff --git a/lib/loaders/database.js b/lib/loaders/database.js deleted file mode 100644 index 943a896e47..0000000000 --- a/lib/loaders/database.js +++ /dev/null @@ -1,64 +0,0 @@ -var Database = require('warehouse'), - fs = require('graceful-fs'), - path = require('path'), - async = require('async'), - Model = require('../model'); - -module.exports = function(callback){ - var db = new Database(), - dbPath = path.join(hexo.base_dir, 'db.json'); - - /** - * The model instance. - * - * @property model - * @type Model - * @for Hexo - */ - - var model = hexo.model = new Model(db); - - async.series([ - function(next){ - fs.exists(dbPath, function(exist){ - if (!exist) return next(); - - hexo.log.d('Loading database.'); - - db.load(dbPath, function(err){ - if (!err) return next(); - - hexo.log.e('Database load failed. Deleting database.'); - fs.unlink(dbPath, next); - }); - }); - }, - function(next){ - var schema = require('../model/schema'); - - model.register('Asset', schema.Asset); - model.register('Cache', schema.Cache); - model.register('Category', schema.Category, require('../model/category')); - model.register('Page', schema.Page); - model.register('Post', schema.Post, require('../model/post')); - model.register('Tag', schema.Tag, require('../model/tag')); - - hexo.locals({ - posts: function(){ - return model('Post').populate('categories').populate('tags'); - }, - pages: function(){ - return model('Page'); - }, - categories: function(){ - return model('Category'); - }, - tags: function(){ - return model('Tag'); - } - }); - - next(); - } - ], callback); -}; \ No newline at end of file diff --git a/lib/loaders/extend.js b/lib/loaders/extend.js deleted file mode 100644 index 7a9f61fec6..0000000000 --- a/lib/loaders/extend.js +++ /dev/null @@ -1,34 +0,0 @@ -var Extend = require('../extend'), - HexoError = require('../error'); - -module.exports = function(callback){ - /** - * The extend instance. - * - * @property extend - * @type Extend - * @for Hexo - */ - - var extend = hexo.extend = new Extend(); - - [ - 'console', - 'deployer', - 'filter', - 'generator', - 'helper', - 'migrator', - 'processor', - 'renderer', - 'tag' - ].forEach(function(item){ - extend.module(item, require('../extend/' + item)); - - try { - require('../plugins/' + item); - } catch (e){} - }); - - callback(); -}; \ No newline at end of file diff --git a/lib/loaders/logger.js b/lib/loaders/logger.js deleted file mode 100644 index b542f32159..0000000000 --- a/lib/loaders/logger.js +++ /dev/null @@ -1,44 +0,0 @@ -var colors = require('colors'), - path = require('path'), - Logger = require('../logger'); - -module.exports = function(callback){ - var logger = hexo.log = new Logger({ - levels: { - create: 5, - update: 5, - delete: 5, - skip: 7 - } - }); - - if (hexo.env.silent) return callback(); - - var consoleStream = new Logger.stream.Console(logger, { - colors: { - create: 'green', - update: 'yellow', - delete: 'red', - skip: 'grey' - } - }); - - if (!hexo.env.debug) return callback(); - - consoleStream.setFormat('[:level] ' + ':date'.grey + ' :message'); - consoleStream.setHide(9); - - var logPath = path.join(hexo.base_dir, 'debug.log'), - FileStream = Logger.stream.File; - - FileStream.prepare(logPath, function(err){ - if (err) return log.e(err); - - var fileStream = new FileStream(logger, { - path: logPath, - hide: 9 - }); - - callback(); - }); -}; \ No newline at end of file diff --git a/lib/loaders/plugins.js b/lib/loaders/plugins.js deleted file mode 100644 index 030bd04ffb..0000000000 --- a/lib/loaders/plugins.js +++ /dev/null @@ -1,40 +0,0 @@ -var fs = require('graceful-fs'), - async = require('async'), - path = require('path'), - HexoError = require('../error'); - -module.exports = function(callback){ - if (!hexo.env.init || hexo.env.safe) return callback(); - - var pluginDir = hexo.plugin_dir; - - async.series([ - function(next){ - fs.exists(pluginDir, function(exist){ - if (exist){ - next(); - } else { - callback(); - } - }); - }, - function(next){ - fs.readdir(pluginDir, function(err, files){ - if (err) return hexo.log.e(HexoError.wrap(err, 'Plugin load failed')); - - files.forEach(function(item){ - if (!/^hexo-/.test(item)) return; - - try { - require(path.join(pluginDir, item)); - hexo.log.d('Plugin loaded successfully: ' + item); - } catch (err){ - hexo.log.e(HexoError.wrap(err, 'Plugin load failed: ' + item)); - } - }); - - next(); - }); - } - ], callback); -}; \ No newline at end of file diff --git a/lib/loaders/scripts.js b/lib/loaders/scripts.js deleted file mode 100644 index ae3a01a2e0..0000000000 --- a/lib/loaders/scripts.js +++ /dev/null @@ -1,40 +0,0 @@ -var fs = require('graceful-fs'), - path = require('path'), - async = require('async'), - util = require('../util'), - file = util.file2, - HexoError = require('../error'); - -var loadScript = function(scriptDir, callback){ - fs.exists(scriptDir, function(exist){ - if (!exist) return callback(); - - file.list(scriptDir, function(err, files){ - if (err) return hexo.log.e(HexoError.wrap(err, 'Script load failed')); - - files.forEach(function(item){ - try { - require(path.join(scriptDir, item)); - hexo.log.d('Script load successfully: ' + item); - } catch (err){ - hexo.log.e(HexoError.wrap(err, 'Script load failed: ' + item)); - } - }); - - callback(); - }); - }); -}; - -module.exports = function(callback){ - if (!hexo.env.init || hexo.env.safe) return callback(); - - async.series([ - function(next){ - loadScript(hexo.script_dir, next); - }, - function(next){ - loadScript(hexo.theme_script_dir, next); - } - ], callback); -}; \ No newline at end of file diff --git a/lib/loaders/update.js b/lib/loaders/update.js deleted file mode 100644 index d6f86ac067..0000000000 --- a/lib/loaders/update.js +++ /dev/null @@ -1,56 +0,0 @@ -var pathFn = require('path'), - async = require('async'), - fs = require('graceful-fs'), - util = require('../util'), - file = util.file2, - HexoError = require('../error'); - -module.exports = function(callback){ - if (!hexo.env.init) return callback(); - - var packagePath = pathFn.join(hexo.base_dir, 'package.json'), - log = hexo.log; - - async.waterfall([ - // Check whether package.json exists or not - function(next){ - fs.exists(packagePath, function(exist){ - if (exist) return next(); - - log.d('Can\'t found package.json. Rebuilding a new one'); - file.copyFile(pathFn.join(hexo.core_dir, 'assets', 'package.json'), packagePath, next); - }); - }, - // Update package.json - function(next){ - var json = require(packagePath); - if (json.version === hexo.version) return next(null, false); - - json.version = hexo.version; - - log.d('Updating package.json'); - - file.writeFile(packagePath, JSON.stringify(json, null, ' '), function(err){ - next(err, true); - }); - }, - // Remove db.json if outdated - function(outdated, next){ - if (!outdated) return next(); - - var dbPath = pathFn.join(hexo.base_dir, 'db.json'); - - fs.exists(dbPath, function(exist){ - if (!exist) return next(); - - log.d('Deleting old database'); - fs.unlink(dbPath, next); - }); - } - ], function(err){ - if (err) return hexo.log.e(HexoError.wrap(err, 'Version info check failed')); - - hexo.log.d('Version info checked successfully'); - callback(); - }); -}; \ No newline at end of file diff --git a/lib/logger/console.js b/lib/logger/console.js deleted file mode 100644 index ed9d1e6bce..0000000000 --- a/lib/logger/console.js +++ /dev/null @@ -1,106 +0,0 @@ -var _ = require('lodash'), - colors = require('colors'), - moment = require('moment'), - Stream = require('./stream'); - -/** -* Console stream. -* -* @class Console -* @param {Logger} logger -* @param {Object} options -* @constructor -* @extends Logger.Stream -* @namespace Logger.Stream -* @module hexo -*/ - -var Console = module.exports = function(logger, options){ - options = options || {}; - - Stream.call(this, logger, options); - - /** - * Format. - * - * @property format - * @type String - */ - - this.format = options.format || '[:level] :message'; - - /** - * Colors. - * - * @property colors - * @type Object - */ - - this.colors = _.extend({ - error: 'red', - warn: 'yellow', - info: 'green', - debug: 'grey' - }, options.colors); -}; - -Console.prototype.__proto__ = Stream.prototype; - -/** -* Writes data to process stream. -* -* @method _write -* @param {Object} data -* @private -*/ - -Console.prototype._write = function(data){ - var message = ''; - - if (data.error){ - var err = data.error; - message = err.name + ': ' + err.message + '\n' + err.stack.grey + '\n' + data.message; - } else { - message = data.message; - } - - var str = this.format - .replace(/:level/g, data.level[this.colors[data.level]]) - .replace(/:message/g, message) - .replace(/:date(\[(.+)\])?/g, function(){ - var format = arguments[2] || 'HH:mm:ss.SSS'; - - return moment(data.date).format(format); - }); - - process[data.error ? 'stderr' : 'stdout'].write(str + '\n'); -}; - -/** -* Sets color. -* -* @method setColor -* @param {String} level -* @param {String} color -* @chainable -*/ - -Console.prototype.setColor = function(level, color){ - this.colors[level] = color; - - return this; -}; - -/** -* Sets format. -* -* @method setFormat -* @param {String} format -* @chainable -*/ - -Console.prototype.setFormat = function(format){ - this.format = format; - - return this; -}; \ No newline at end of file diff --git a/lib/logger/file.js b/lib/logger/file.js deleted file mode 100644 index 85530d2ee1..0000000000 --- a/lib/logger/file.js +++ /dev/null @@ -1,138 +0,0 @@ -var moment = require('moment'), - fs = require('graceful-fs'), - Stream = require('./stream'), - os = require('os'), - util = require('../util'), - file = util.file2, - yfm = util.yfm; - -/** -* File stream. -* -* @class File -* @param {Logger} logger -* @param {Object} options -* @constructor -* @extends Logger.Stream -* @namespace Logger.Stream -* @module hexo -*/ - -var File = module.exports = function(logger, options){ - if (!options.path) throw new Error('options.path is not defined'); - - options = options || {}; - - Stream.call(this, logger, options); - - this.format = options.format || '[:level] :date :message'; - var stream = this.stream = fs.createWriteStream(options.path, {flags: 'a'}); - - stream.on('error', function(err){ - if (err) throw err; - }); -}; - -File.prototype.__proto__ = Stream.prototype; - -/** -* Writes file to process stream. -* -* @method _write -* @param {Object} data -* @private -*/ - -File.prototype._write = function(data){ - this.stream.write(toString(this.format, data) + '\n'); -}; - -/** -* Converts data to a string. -* -* @method toString -* @param {String} format -* @param {Object} data -* @private -*/ - -var toString = function(format, data){ - var message = ''; - - if (data.error){ - var err = data.error; - message = err.name + ': ' + err.message + '\n' + err.stack + '\n' + data.message.replace(/\u001b\[(\d+(;\d+)*)?m/g, ''); - } else { - message = data.message; - } - - return format - .replace(/:level/g, data.level) - .replace(/:message/g, message.replace(/\u001b\[(\d+(;\d+)*)?m/g, '')) - .replace(/:date(\[(.+)\])?/g, function(){ - var format = arguments[2] || 'HH:mm:ss.SSS'; - - return moment(data.date).format(format); - }); -}; - -/** -* Sets format. -* -* @method setFormat -* @param {String} format -* @chainable -*/ - -File.prototype.setFormat = function(format){ - this.format = format; - - return this; -}; - -/** -* Prepares starting file stream. -* -* @method prepare -* @param {String} path -* @param {Function} callback -* @static -*/ - -var prepare = File.prepare = function(path, callback){ - var versions = process.versions; - versions.hexo = hexo.version; - - var content = yfm.stringify({ - date: moment().format('YYYY-MM-DD HH:mm:ss.SSS'), - argv: process.argv.join(' '), - os: os.type() + ' ' + os.release() + ' ' + os.platform() + ' ' + os.arch(), - versions: versions - }); - - file.writeFile(path, content, callback); -}; - -/** -* Dumps all logs to the file. -* -* @method dump -* @param {String} path -* @param {Hexo.Logger} log -* @param {Function} callback -* @static -*/ - -File.dump = function(path, log, callback){ - prepare(path, function(err){ - if (err) return callback(err); - - var content = ''; - - log.store.forEach(function(item){ - content += toString('[:level] :date :message', item); - }); - - file.appendFile(path, content, callback); - }); -}; \ No newline at end of file diff --git a/lib/logger/index.js b/lib/logger/index.js deleted file mode 100644 index ed87d62f64..0000000000 --- a/lib/logger/index.js +++ /dev/null @@ -1,228 +0,0 @@ -var format = require('util').format, - _ = require('lodash'), - EventEmitter = require('events').EventEmitter; - -/** -* This module is used for log. -* -* @class Logger -* @constructor -* @extends EventEmitter -* @module hexo -*/ - -var Logger = module.exports = function(options){ - options = options || {}; - - /** - * @property store - * @type Array - */ - - this.store = []; - - /** - * @property levels - * @type Object - */ - - this.levels = _.extend({ - error: 1, - warn: 3, - info: 5, - debug: 7 - }, options.levels); - - /** - * @property alias - * @type Object - */ - - this.alias = _.extend({ - e: 'error', - err: 'error', - w: 'warn', - i: 'info', - d: 'debug' - }, options.alias); - - /** - * @property default - * @type String - * @default info - */ - - this.default = options.default || 'info'; -}; - -Logger.prototype.__proto__ = EventEmitter.prototype; - -/** -* Set a new level. -* -* @method setLevel -* @param {String} name -* @param {Number} level -* @chainable -*/ - -Logger.prototype.setLevel = function(name, level){ - this.levels[name] = +level; - - return this; -}; - -/** -* Set a new alias. -* -* @method setAlias -* @param {String} name -* @param {String} alias -* @chainable -*/ - -Logger.prototype.setAlias = function(name, alias){ - this.alias[name] = alias; - - return this; -}; - -/** -* Set the default level. -* -* @method setDefault -* @param {String} name -* @chainable -*/ - -Logger.prototype.setDefault = function(name){ - this.default = name; - - return this; -}; - -/** -* Create a new log. -* -* @method log -* @param {String} [level] -* @param {String} msg* -* @chainable -*/ - -Logger.prototype.log = function(){ - var args = _.toArray(arguments), - level = args.shift(); - - if (!this.levels.hasOwnProperty(level)){ - var alias = this.alias[level]; - - if (this.levels.hasOwnProperty(alias)){ - level = alias; - } else { - args.unshift(level); - level = this.default; - } - } - - var data = { - level: level, - date: new Date() - }; - - if (args[0] instanceof Error){ - data.error = args.shift(); - data.message = format.apply(null, args); - } else { - data.message = format.apply(null, args); - } - - this.store.push(data); - this._emit(level, data); - - return this; -}; - -/** -* Fires a new event. -* -* @method _emit -* @param {String} level -* @param {Object} data -* @private -*/ - -Logger.prototype._emit = function(level, data){ - /** - * Fires when a new log created. - * - * @event log - */ - - this.emit('log', data); - // this.emit(level, data); -}; - -/** -* Creates a new log on debug level. -* -* `debug` is also alias as `d`. -* -* @method debug -* @param {String} msg* -* @chainable -*/ - -/** -* Creates a new log on info level. -* -* `info` is also alias as `i`. -* -* @method info -* @param {String} msg* -* @chainable -*/ - -/** -* Creates a new log on warn level. -* -* `warn` is also alias as `w`. -* -* @method warn -* @param {String} msg* -* @chainable -*/ - -/** -* Creates a new log on error level. -* -* `error` is also alias as `e`, `err`. -* -* @method error -* @param {String} msg* -* @chainable -*/ - -['debug', 'info', 'warn', 'error'].forEach(function(i){ - Logger.prototype[i] = Logger.prototype[i[0]] = function(){ - var args = _.toArray(arguments); - - args.unshift(i); - this.log.apply(this, args); - - return this; - }; -}); - -Logger.prototype.err = Logger.prototype.error; - -/** -* @property stream -* @type Object -* @static -*/ - -Logger.stream = { - Console: require('./console'), - File: require('./file') -}; \ No newline at end of file diff --git a/lib/logger/stream.js b/lib/logger/stream.js deleted file mode 100644 index 4655b0b9c5..0000000000 --- a/lib/logger/stream.js +++ /dev/null @@ -1,63 +0,0 @@ -var _ = require('lodash'); - -/** -* Logger stream. -* -* This is an abstract class. Every logger stream should inherit this class and implement `_write` method. For example: -* -* ``` js -* var CustomStream = function(logger, options){ -* Stream.apply(this, arguments); -* }; -* -* CustomStream.prototype.__proto__ = Stream.prototype; -* -* CustomStream.prototype._write = function(data){ -* // -* }; -* ``` -* -* @class Stream -* @param {Logger} logger -* @param {Object} options -* @constructor -* @namespace Logger -* @module hexo -*/ - -var Stream = module.exports = function(logger, options){ - options = options || {}; - - /** - * Hide level. - * - * @property hide - * @type Number - */ - - this.hide = options.hide || 7; - - var self = this; - - logger.on('log', function(data){ - if (logger.levels[data.level] < self.hide){ - self._write(data); - } - }); -}; - -// Stream.prototype._write = function(data){}; - -/** -* Set hide level. -* -* @method setHide -* @param {Number} level -* @chainable -*/ - -Stream.prototype.setHide = function(level){ - this.hide = level; - - return this; -}; \ No newline at end of file diff --git a/lib/model/category.js b/lib/model/category.js deleted file mode 100644 index c4b3df0a97..0000000000 --- a/lib/model/category.js +++ /dev/null @@ -1,12 +0,0 @@ -var hooks = require('./hooks/category'); - -exports.hooks = { - pre: { - save: [], - remove: [hooks.removeFromPost] - }, - post: { - save: [], - remove: [] - } -}; \ No newline at end of file diff --git a/lib/model/hooks/category.js b/lib/model/hooks/category.js deleted file mode 100644 index 2ef3465410..0000000000 --- a/lib/model/hooks/category.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.removeFromPost = function(data, next){ - var model = hexo.model, - Post = model('Post'); - - data.posts.forEach(function(post){ - Post.updateById(post, {categories: {$pull: data._id}}); - }); -}; \ No newline at end of file diff --git a/lib/model/hooks/post.js b/lib/model/hooks/post.js deleted file mode 100644 index 0453dbe908..0000000000 --- a/lib/model/hooks/post.js +++ /dev/null @@ -1,158 +0,0 @@ -var _ = require('lodash'); - -exports.createCategory = function(data, next){ - var categories = data.categories; - - if (!categories || !categories.length) return next(); - - var model = hexo.model, - Category = model('Category'), - i = 0; - - categories.forEach(function(name, i){ - var doc = Category.get(name); - - if (!doc){ - var query = { - name: name, - parent: i === 0 ? {$exist: false} : categories[i - 1] - }; - - doc = Category.findOne(query); - } - - if (doc){ - categories[i] = doc._id; - } else { - var data = { - name: name - }; - - if (i > 0){ - data.parent = categories[i - 1]; - } - - Category.insert(data, function(category){ - categories[i] = category._id; - }); - } - }); - - next(); -}; - -exports.createTag = function(data, next){ - var tags = data.tags; - - if (!tags || !tags.length) return next(); - - var model = hexo.model, - Tag = model('Tag'); - - tags.forEach(function(name, i){ - var doc = Tag.get(name) || Tag.findOne({name: name}); - - if (doc){ - tags[i] = doc._id; - } else { - Tag.insert({name: name}, function(tag){ - tags[i] = tag._id; - }); - } - }); - - next(); -}; - -exports.updateCategory = function(data, next){ - if (!data._id) return next(); - - var model = hexo.model, - Post = model('Post'); - Category = model('Category'); - - var doc = Post.get(data._id); - if (!doc) return next(); - - var arr = _.difference(doc.categories, data.categories); - - if (!arr.length) return next(); - - arr.forEach(function(category){ - Category.updateById(category, {posts: {$pull: data._id}}); - }); - - next(); -}; - -exports.updateTag = function(data, next){ - if (!data._id) return next(); - - var model = hexo.model, - Post = model('Post'); - Tag = model('Tag'); - - var doc = Post.get(data._id); - if (!doc) return next(); - - var arr = _.difference(doc.tags, data.tags); - - if (!arr.length) return next(); - - arr.forEach(function(tag){ - Tag.updateById(tag, {posts: {$pull: data._id}}); - }); - - next(); -}; - -exports.addToCategory = function(data){ - var model = hexo.model, - Category = model('Category'); - - data.categories.forEach(function(category){ - Category.updateById(category, {posts: {$addToSet: data._id}}); - }); -}; - -exports.addToTag = function(data){ - var model = hexo.model, - Tag = model('Tag'); - - data.tags.forEach(function(tag){ - Tag.updateById(tag, {posts: {$addToSet: data._id}}); - }); -}; - -exports.removeFromCategory = function(data, next){ - var model = hexo.model, - Category = model('Category'); - - data.categories.forEach(function(category){ - Category.updateById(category, {posts: {$pull: data._id}}); - }); - - next(); -}; - -exports.removeFromTag = function(data, next){ - var model = hexo.model, - Tag = model('Tag'); - - data.tags.forEach(function(tag){ - Tag.updateById(tag, {posts: {$pull: data._id}}); - }); - - next(); -}; - -exports.removeAssets = function(data, next){ - var Asset = hexo.model('Asset'), - route = hexo.route; - - Asset.find({post: data._id}).each(function(asset){ - route.remove(asset.path); - }).remove(); - - next(); -}; \ No newline at end of file diff --git a/lib/model/hooks/tag.js b/lib/model/hooks/tag.js deleted file mode 100644 index e95d150e74..0000000000 --- a/lib/model/hooks/tag.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.removeFromPost = function(data, next){ - var model = hexo.model, - Post = model('Post'); - - data.posts.forEach(function(post){ - Post.updateById(post, {tags: {$pull: data._id}}); - }); -}; \ No newline at end of file diff --git a/lib/model/index.js b/lib/model/index.js deleted file mode 100644 index 1deea6769e..0000000000 --- a/lib/model/index.js +++ /dev/null @@ -1,69 +0,0 @@ -/** -* This module is used to manage all models used in Hexo. -* -* @class Model -* @param {Warehouse} db -* @constructor -* @module hexo -*/ - -var Model = module.exports = function(db){ - var models = {}; - - var model = function(name){ - return models[name]; - }; - - /** - * Register a model. - * - * @method register - * @param {String} name Model name. The model name should be written in CamelCase. - * @param {Schema} [schema] Model schema. - * @param {Object} [method] - * @param {Object} [method.statics] Static methods for the model - * @param {Object} [method.methods] Instance methods for the model - * @param {Object} [method.hooks] Model hooks - */ - - model.register = function(name, schema, method){ - if (!schema) schema = {}; - - var model; - - if (method){ - if (method.hooks){ - if (method.hooks.pre) schema.pres = method.hooks.pre; - if (method.hooks.post) schema.posts = method.hooks.post; - } - - if (method.statics) schema.statics = method.statics; - if (method.methods) schema.methods = method.methods; - - model = db.model(name, schema); - } else { - model = db.model(name, schema); - } - - models[name] = model; - }; - - /** - * Save database. - * - * @method save - * @param {String} dest - * @param {Function} callback - */ - model.save = db.save.bind(db); - - /** - * @property Schema - * @type Schema - * @static - */ - - model.Schema = require('warehouse').Schema; - - return model; -}; \ No newline at end of file diff --git a/lib/model/post.js b/lib/model/post.js deleted file mode 100644 index 05af2b8cc9..0000000000 --- a/lib/model/post.js +++ /dev/null @@ -1,12 +0,0 @@ -var hooks = require('./hooks/post'); - -exports.hooks = { - pre: { - save: [hooks.createCategory, hooks.createTag, hooks.updateCategory, hooks.updateTag], - remove: [hooks.removeFromCategory, hooks.removeFromTag, hooks.removeAssets] - }, - post: { - save: [hooks.addToCategory, hooks.addToTag], - remove: [] - } -}; \ No newline at end of file diff --git a/lib/model/schema.js b/lib/model/schema.js deleted file mode 100644 index e95e263fdb..0000000000 --- a/lib/model/schema.js +++ /dev/null @@ -1,147 +0,0 @@ -var moment = require('moment'), - path = require('path'), - url = require('url'), - util = require('../util'), - escape = util.escape; - -var Schema = require('warehouse').Schema, - Moment = require('./types/moment'); - -var isEndWith = function(str, last){ - return str[str.length - 1] === last; -}; - -var permalinkGetter = function(){ - var url = hexo.config.url; - - return url + (isEndWith(url, '/') ? '' : '/') + this.path; -}; - -var Post = exports.Post = new Schema({ - id: Number, - title: {type: String, default: ''}, - date: {type: Moment, default: moment}, - updated: {type: Moment, default: moment}, - categories: [{type: String, ref: 'Category'}], - tags: [{type: String, ref: 'Tag'}], - comments: {type: Boolean, default: true}, - layout: {type: String, default: 'post'}, - content: {type: String, default: ''}, - excerpt: {type: String, default: ''}, - more: {type: String, default: ''}, - source: {type: String, required: true}, - slug: {type: String, required: true}, - photos: [String], - link: {type: String, default: ''}, - raw: {type: String, default: ''} -}); - -Post.virtual('path', function(){ - return hexo.extend.filter.apply('post_permalink', this); -}); - -Post.virtual('permalink', permalinkGetter); - -Post.virtual('full_source', function(){ - return path.join(hexo.source_dir, this.source); -}); - -Post.virtual('asset_dir', function(){ - var src = this.full_source; - - return src.substring(0, src.length - path.extname(src).length) + path.sep; -}); - -var Page = exports.Page = new Schema({ - title: {type: String, default: ''}, - date: {type: Moment, default: moment}, - updated: {type: Moment, default: moment}, - comments: {type: Boolean, default: true}, - layout: {type: String, default: 'page'}, - content: {type: String, default: ''}, - excerpt: {type: String, default: ''}, - source: {type: String, required: true}, - path: {type: String, required: true}, - raw: {type: String, default: ''} -}); - -Page.virtual('permalink', permalinkGetter); - -Page.virtual('full_source', function(){ - return path.join(hexo.source_dir, this.source); -}); - -var Category = exports.Category = new Schema({ - name: {type: String, required: true}, - parent: {type: String, ref: 'Category'}, - posts: [{type: String, ref: 'Post'}] -}); - -Category.virtual('slug', function(){ - var map = hexo.config.category_map, - name = this.name, - str = ''; - - if (this.parent){ - var model = hexo.model, - Category = model('Category'), - parent = Category.get(this.parent); - - str += parent.slug + '/'; - } - - name = map && map.hasOwnProperty(name) ? map[name] : name; - str += escape.filename(name, hexo.config.filename_case); - - return str; -}); - -Category.virtual('path', function(){ - var catDir = hexo.config.category_dir; - - return catDir + (isEndWith(catDir, '/') ? '' : '/') + this.slug + '/'; -}); - -Category.virtual('permalink', permalinkGetter); - -Category.virtual('length', function(){ - return this.posts.length; -}); - -var Tag = exports.Tag = new Schema({ - name: {type: String, required: true}, - posts: [{type: String, ref: 'Post'}] -}); - -Tag.virtual('slug', function(){ - var map = hexo.config.tag_map, - name = this.name; - - name = map && map.hasOwnProperty(name) ? map[name] : name; - return escape.filename(name, hexo.config.filename_case); -}); - -Tag.virtual('path', function(){ - var tagDir = hexo.config.tag_dir; - - return tagDir + (isEndWith(tagDir, '/') ? '' : '/') + this.slug + '/'; -}); - -Tag.virtual('permalink', permalinkGetter); - -Tag.virtual('length', function(){ - return this.posts.length; -}); - -var Cache = exports.Cache = new Schema({ - _id: {type: String}, - mtime: {type: Number, default: Date.now} -}); - -var Asset = exports.Asset = new Schema({ - _id: {type: String}, - path: {type: String}, - modified: {type: Boolean, default: true}, - post_id: {type: String, ref: 'Post'}, - post_path: {type: String} -}); \ No newline at end of file diff --git a/lib/model/tag.js b/lib/model/tag.js deleted file mode 100644 index 55d3291ecf..0000000000 --- a/lib/model/tag.js +++ /dev/null @@ -1,12 +0,0 @@ -var hooks = require('./hooks/tag'); - -exports.hooks = { - pre: { - save: [], - remove: [hooks.removeFromPost] - }, - post: { - save: [], - remove: [] - } -}; \ No newline at end of file diff --git a/lib/model/types/moment.js b/lib/model/types/moment.js deleted file mode 100644 index c076612ded..0000000000 --- a/lib/model/types/moment.js +++ /dev/null @@ -1,57 +0,0 @@ -var moment = require('moment'), - _ = require('lodash'), - SchemaType = require('warehouse').SchemaType; - -var SchemaMoment = module.exports = function(options){ - SchemaType.call(this, options); -}; - -SchemaMoment.__proto__ = SchemaType; -SchemaMoment.prototype.__proto__ = SchemaType.prototype; - -SchemaMoment.prototype.checkRequired = function(value){ - return moment.isMoment(value); -}; - -var cast = SchemaMoment.prototype.cast = function(value){ - if (!value) return null; - if (moment.isMoment(value)) return value; - - if (hexo.config.language && !_.isArray(hexo.config.language)){ - return moment(value).locale(hexo.config.language.toLowerCase()); - } else { - return moment(value); - } -}; - -SchemaMoment.prototype.save = function(value){ - return value.valueOf(); -}; - -SchemaMoment.prototype.compare = function(vdata, alue){ - return data ? data.valueOf() === cast(value).valueOf() : false; -}; - -SchemaMoment.prototype.q$year = function(data, value){ - return data ? data.year() == value : false; -}; - -SchemaMoment.prototype.q$month = function(data, value){ - return data ? data.month() == value - 1 : false; -}; - -SchemaMoment.prototype.q$day = function(data, value){ - return data ? data.date() == value : false; -}; - -SchemaMoment.prototype.u$inc = function(data, value){ - if (!data) return; - - return moment(data.valueOf() + parseInt(value, 10)); -}; - -SchemaMoment.prototype.u$inc = function(data, value){ - if (!data) return; - - return moment(data.valueOf() - parseInt(value, 10)); -}; diff --git a/lib/models/asset.js b/lib/models/asset.js new file mode 100644 index 0000000000..8956429fb8 --- /dev/null +++ b/lib/models/asset.js @@ -0,0 +1,9 @@ +var Schema = require('warehouse').Schema; + +module.exports = new Schema({ + _id: {type: String, required: true}, + path: {type: String, required: true}, + modified: {type: Boolean, default: true}, + post_id: Schema.Types.CUID, + post_path: String +}); \ No newline at end of file diff --git a/lib/models/cache.js b/lib/models/cache.js new file mode 100644 index 0000000000..3f981d1985 --- /dev/null +++ b/lib/models/cache.js @@ -0,0 +1,6 @@ +var Schema = require('warehouse').Schema; + +module.exports = new Schema({ + _id: {type: String, required: true}, + mtime: {type: Number, default: Date.now} +}); \ No newline at end of file diff --git a/lib/models/category.js b/lib/models/category.js new file mode 100644 index 0000000000..c04330c3d4 --- /dev/null +++ b/lib/models/category.js @@ -0,0 +1,40 @@ +var Schema = require('warehouse').Schema, + util = require('../util'), + escape = util.escape; + +var Category = module.exports = new Schema({ + name: {type: String, required: true}, + parent: {type: Schema.Types.CUID, ref: 'Category'}, + posts: [{type: Schema.Types.CUID, ref: 'Post'}] +}); + +Category.virtual('slug').get(function(){ + var map = hexo.config.category_map, + name = this.name, + str = ''; + + if (this.parent){ + var parent = hexo.model('Category').findById(this.parent); + str += parent.slug + '/'; + } + + name = map && map.hasOwnProperty(name) ? map[name] : name; + str += escape.filename(name, hexo.config.filename_case); + + return str; +}); + +Category.virtual('path').get(function(){ + var catDir = hexo.config.category_dir; + if (catDir[catDir.length - 1] !== '/') catDir += '/'; + + return catDir + this.slug + '/'; +}); + +Category.virtual('permalink').get(function(){ + return hexo.config.url + '/' + this.path; +}); + +Category.virtual('length').get(function(){ + return this.posts.length; +}); \ No newline at end of file diff --git a/lib/models/index.js b/lib/models/index.js new file mode 100644 index 0000000000..9018496595 --- /dev/null +++ b/lib/models/index.js @@ -0,0 +1,6 @@ +exports.Asset = require('./asset'); +exports.Cache = require('./cache'); +exports.Category = require('./category'); +exports.Page = require('./page'); +exports.Post = require('./post'); +exports.Tag = require('./tag'); \ No newline at end of file diff --git a/lib/models/page.js b/lib/models/page.js new file mode 100644 index 0000000000..66c145c11e --- /dev/null +++ b/lib/models/page.js @@ -0,0 +1,25 @@ +var Schema = require('warehouse').Schema; +var pathFn = require('path'); +var Moment = require('./types/moment'); +var moment = require('moment'); + +var Page = module.exports = new Schema({ + title: {type: String, default: ''}, + date: {type: Moment, default: moment}, + updated: {type: Moment, default: moment}, + comments: {type: Boolean, default: true}, + layout: {type: String, default: 'page'}, + content: {type: String, default: ''}, + excerpt: {type: String, default: ''}, + source: {type: String, required: true}, + path: {type: String, required: true}, + raw: {type: String, default: ''} +}); + +Page.virtual('permalink').get(function(){ + return hexo.config.url + '/' + this.path; +}); + +Page.virtual('full_source').get(function(){ + return pathFn.join(hexo.source_dir, this.source); +}); \ No newline at end of file diff --git a/lib/models/post.js b/lib/models/post.js new file mode 100644 index 0000000000..7f1a615147 --- /dev/null +++ b/lib/models/post.js @@ -0,0 +1,41 @@ +var Schema = require('warehouse').Schema; +var moment = require('moment'); +var pathFn = require('path'); +var Promise = require('bluebird'); +var Moment = require('./types/moment'); + +var Post = module.exports = new Schema({ + id: Number, + title: {type: String, default: ''}, + date: {type: Moment, default: moment}, + updated: {type: Moment, default: moment}, + categories: [{type: Schema.Types.CUID, ref: 'Category'}], + tags: [{type: Schema.Types.CUID, ref: 'tags'}], + comments: {type: Boolean, default: true}, + layout: {type: String, default: 'post'}, + content: {type: String, default: ''}, + excerpt: {type: String, default: ''}, + more: {type: String, default: ''}, + source: {type: String, required: true}, + slug: {type: String, required: true}, + photos: [String], + link: {type: String, default: ''}, + raw: {type: String, default: ''} +}); + +Post.virtual('path').get(function(){ + return hexo.extend.filter.apply('post_permalink', this); +}); + +Post.virtual('permalink').get(function(){ + return hexo.config.url + '/' + this.path; +}); + +Post.virtual('full_source').get(function(){ + return pathFn.join(hexo.source_dir, this.source); +}); + +Post.virtual('asset_dir').get(function(){ + var src = this.full_source; + return src.substring(0, src.length - pathFn.extname(src).length) + pathFn.sep; +}); \ No newline at end of file diff --git a/lib/models/tag.js b/lib/models/tag.js new file mode 100644 index 0000000000..e33912b961 --- /dev/null +++ b/lib/models/tag.js @@ -0,0 +1,31 @@ +var Schema = require('warehouse').Schema; +var util = require('../util'); +var escape = util.escape; + +var Tag = module.exports = new Schema({ + name: {type: String, required: true}, + posts: [{type: Schema.Types.CUID, ref: 'Post'}] +}); + +Tag.virtual('slug').get(function(){ + var map = hexo.config.tag_map; + var name = this.name; + + name = map && map.hasOwnProperty(name) ? map[name] : name; + return escape.filename(name, hexo.config.filename_case); +}); + +Tag.virtual('path').get(function(){ + var tagDir = hexo.config.tag_dir; + if (tagDir[tagDir.length - 1] !== '/') tagDir += '/'; + + return tagDir + this.slug + '/'; +}); + +Tag.virtual('permalink').get(function(){ + return hexo.config.url + '/' + this.path; +}); + +Tag.virtual('length').get(function(){ + return this.posts.length; +}); \ No newline at end of file diff --git a/lib/models/types/moment.js b/lib/models/types/moment.js new file mode 100644 index 0000000000..a680ff3a7a --- /dev/null +++ b/lib/models/types/moment.js @@ -0,0 +1,87 @@ +var moment = require('moment'), + SchemaType = require('warehouse').SchemaType, + util = require('../../util'); + +function SchemaTypeMoment(name, options){ + SchemaType.call(this, name, options); +} + +util.inherits(SchemaTypeMoment, SchemaType); + +SchemaTypeMoment.prototype.cast = function(value, data){ + value = SchemaType.prototype.cast.call(this, value, data); + + if (value == null || moment.isMoment(value)) return value; + + var lang = hexo.config.language, + date = moment(value); + + if (lang && !Array.isArray(lang)){ + return date.locale(lang.toLowerCase()); + } else { + return date; + } +}; + +SchemaTypeMoment.prototype.validate = function(value, data){ + value = SchemaType.prototype.validate.call(this, value, data); + if (value instanceof Error) return value; + + if (value != null && (moment.invalid(value))){ + return new Error('`' + value + '` is not a valid date!'); + } + + return value; +}; + +SchemaTypeMoment.prototype.match = function(value, query, data){ + return value ? value.valueOf() === query.valueOf() : false; +}; + +SchemaTypeMoment.prototype.compare = function(a, b){ + if (a){ + if (b){ // a && b + return a - b; + } else { // a && !b + return 1; + } + } else { + if (b){ // !a && b + return -1; + } else { // !a && !b + return 0; + } + } +}; + +SchemaTypeMoment.prototype.parse = function(value, data){ + if (value) return moment(value); +}; + +SchemaTypeMoment.prototype.value = function(value, data){ + return value ? value.toISOString() : value; +}; + +SchemaTypeMoment.prototype.q$day = function(value, query, data){ + return value ? value.date() === query : false; +}; + +SchemaTypeMoment.prototype.q$month = function(value, query, data){ + return value ? value.month() === query : false; +}; + +SchemaTypeMoment.prototype.q$year = function(value, query, data){ + return value ? value.year() === query : false; +}; + +SchemaTypeMoment.prototype.u$inc = function(value, update, data){ + if (value) value.add(update, 'ms'); + return value; +}; + +SchemaTypeMoment.prototype.u$dec = function(value, update, data){ + if (value) value.subtract(update, 'ms'); + return value; +}; + +module.exports = SchemaTypeMoment; \ No newline at end of file diff --git a/lib/plugins/console/clean.js b/lib/plugins/console/clean.js index c373cb492b..ec29f78df6 100644 --- a/lib/plugins/console/clean.js +++ b/lib/plugins/console/clean.js @@ -1,42 +1,37 @@ -var async = require('async'), - fs = require('graceful-fs'), - path = require('path'), - util = require('../../util'), - file = util.file2; +var Promise = require('bluebird'); +var util = require('../../util'); +var fs = util.fs; -module.exports = function(args, callback){ - if (!hexo.env.init) return callback(); +module.exports = function(ctx){ + var log = ctx.log; + var dbPath = ctx.database.options.path; - var log = hexo.log; + function deleteDatabase(){ + return fs.exists(dbPath).then(function(exist){ + if (!exist) return; - async.parallel([ - function(next){ - var cachePath = path.join(hexo.base_dir, 'db.json'); - - fs.exists(cachePath, function(exist){ - if (!exist) return next(); - - fs.unlink(cachePath, function(err){ - if (err) return next(err); - - log.i('Deleted cache file'); - next(); - }); + return fs.unlink(dbPath).then(function(){ + log.info('Deleted database.'); }); - }, - function(next){ - var publicDir = hexo.public_dir; + }); + } - fs.exists(publicDir, function(exist){ - if (!exist) return next(); + function deletePublicDir(){ + var publicDir = ctx.public_dir; - file.rmdir(publicDir, function(err){ - if (err) return next(err); + return fs.exists(publicDir).then(function(exist){ + if (!exist) return; - log.i('Deleted public directory'); - next(); - }); + return fs.rmdir(publicDir).then(function(){ + log.info('Deleted public directory.'); }); - } - ], callback); + }); + } + + return function(args){ + return Promise.all([ + deleteDatabase(), + deletePublicDir() + ]); + }; }; \ No newline at end of file diff --git a/lib/plugins/console/config.js b/lib/plugins/console/config.js deleted file mode 100644 index 770a8b8c04..0000000000 --- a/lib/plugins/console/config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function(args, callback){ - console.log(hexo.config); - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/console/deploy.js b/lib/plugins/console/deploy.js deleted file mode 100644 index d2959b52ca..0000000000 --- a/lib/plugins/console/deploy.js +++ /dev/null @@ -1,84 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - colors = require('colors'), - _ = require('lodash'); - -module.exports = function(args, callback){ - var config = hexo.config.deploy, - log = hexo.log, - extend = hexo.extend, - deployers = extend.deployer.list(); - - if (!config){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Available Types:\n'; - help += ' ' + Object.keys(deployers).join(', ') + '\n\n'; - help += 'For more help, you can check the online docs: ' + 'http://hexo.io/'.underline; - - console.log(help); - - return callback(); - } - - if (!Array.isArray(config)) config = [config]; - - var generate = function(callback){ - if (args.g || args.generate){ - hexo.call('generate', callback); - } else { - fs.exists(hexo.public_dir, function(exist){ - if (exist) return callback(); - - hexo.call('generate', callback); - }); - } - }; - - var onDeployStarted = function() { - /** - * Fired before deployment. - * - * @event deployBefore - * @for Hexo - */ - hexo.emit('deployBefore'); - }; - - var onDeployFinished = function(err) { - /** - * Fired after deployment. - * - * @event deployAfter - * @param {Error} err - * @for Hexo - */ - hexo.emit('deployAfter', err); - callback(err); - }; - - generate(function(err){ - if (err) return callback(err); - - onDeployStarted(); - - async.eachSeries(config, function(item, next){ - var type = item.type; - - if (!deployers.hasOwnProperty(type)){ - log.e('Deployer not found: ' + type); - return next(); - } else { - log.i('Start deploying: ' + type); - } - - deployers[type](_.extend({}, item, args), function(err){ - if (err) return next(err); - - log.i('Deploy done: ' + type); - next(); - }); - }, onDeployFinished); - }); -}; \ No newline at end of file diff --git a/lib/plugins/console/generate.js b/lib/plugins/console/generate.js deleted file mode 100644 index ae1853ddcc..0000000000 --- a/lib/plugins/console/generate.js +++ /dev/null @@ -1,199 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - _ = require('lodash'), - pathFn = require('path'), - colors = require('colors'), - stream = require('stream'), - Stream = stream.Stream, - Readable = stream.Readable, - util = require('../../util'), - file = util.file2, - HexoError = require('../../error'); - -module.exports = function(args, callback){ - var watch = args.w || args.watch, - start = Date.now(); - - var log = hexo.log, - config = hexo.config, - route = hexo.route, - publicDir = hexo.public_dir, - source_dir = hexo.source_dir, - maxRetry = 3, - cache = {}; - - hexo.emit('generateBefore'); - - var generateFile = function(path, content, callback){ - var dest = pathFn.join(publicDir, path), - called = false; - - async.series([ - function(next){ - var parentDir = pathFn.dirname(dest); - - fs.exists(parentDir, function(exist){ - if (exist) return next(); - - file.mkdirs(parentDir, next); - }); - }, - function(next){ - var ws = fs.createWriteStream(dest), - rs; - - ws.on('close', next) - .on('error', next); - - if (content instanceof Stream){ - rs = content; - } else { - cache[path] = content; - rs = new Readable(); - rs.push(content); - rs.push(null); // EOF signal - } - - rs.pipe(ws) - .on('error', next); - } - ], function(err){ - if (called) return; - - called = true; - - callback(err); - }); - }; - - hexo.post.load({watch: watch}, function(err){ - if (err) return callback(err); - - var routes = route.routes, - keys = Object.keys(routes), - finish = Date.now(), - elapsed = (finish - start) / 1000, - count = 0; - - start = Date.now(); - - log.i('Files loaded in %ss', elapsed.toFixed(3)); - - async.auto({ - // Check whether the public folder exists - exist: function(next){ - fs.exists(publicDir, function(exist){ - next(null, exist); - }); - }, - // List all files in the public folder - list: ['exist', function(next, results){ - if (!results.exist) return next(null, []); - - file.list(publicDir, next); - }], - // Generate files - generate: ['list', function(next, results){ - var list = results.list, - tasks = keys.slice(), - retryCount = {}; - - async.whilst(function(){ - return tasks.length; - }, function(next){ - var i = tasks.shift(), - fn = routes[i], - exist = ~list.indexOf(i); - - if (exist && !fn.modified){ - log.log('skip', i); - return next(); - } - - var start = Date.now(); - - var itemCallback = function(err){ - if (err){ - if (err.code === 'EMFILE' && retryCount[i] < maxRetry){ - retryCount[i]++; - log.d('EMFILE: %s (retry %d)', i, retryCount[i]); - tasks.push(i); - } else { - next(HexoError.wrap(err, 'File generate failed: ' + i)); - } - } else { - count++; - log.log(exist ? 'update' : 'create', 'Generated: %s ' + '(%dms)'.grey, i, Date.now() - start); - next(); - } - }; - - fn(function(err, content){ - if (err) return next(HexoError.wrap(err, 'Render failed: ' + i)); - if (content == null) return next(); - - generateFile(i, content, itemCallback); - }); - }, next); - }], - // Clear old files - clear: ['list', function(next, results){ - var list = _.difference(results.list, keys); - - async.each(list, function(i, next){ - var dest = pathFn.join(publicDir, i); - - fs.unlink(dest, function(err){ - if (err && err.code !== 'ENOENT'){ - next(HexoError.wrap(err, 'File delete failed: ' + i)); - } else { - log.log('delete', 'Deleted: %s', i); - next(); - } - }); - }, next); - }] - }, function(err){ - if (err) return callback(err); - - var finish = Date.now(), - elapsed = (finish - start) / 1000; - - hexo.emit('generateAfter'); - log.i('%d files generated in %ss', count, elapsed.toFixed(3)); - - if (watch){ - route.on('update', function(path, fn){ - if (!fn.modified) return; - - fn(function(err, content){ - if (err) return log.e(HexoError.wrap(err, 'Render failed: ' + path)); - if (content == null) return; - - generateFile(path, content, function(err){ - if (err) return log.e(HexoError.wrap(err, 'File generate failed: ' + path)); - - log.log('update', 'Generated: %s', path); - }); - }); - }).on('remove', function(path){ - cache[path] = null; - - fs.unlink(pathFn.join(publicDir, path), function(err){ - if (err && err.code !== 'ENOENT'){ - log.e(HexoError.wrap(err, 'File delete failed: ' + i)); - } else { - log.log('delete', 'Deleted: %s', path); - } - }); - }); - } else { - if (args.d || args.deploy){ - hexo.call('deploy', callback); - } else { - callback(); - } - } - }); - }); -}; \ No newline at end of file diff --git a/lib/plugins/console/help.js b/lib/plugins/console/help.js index 16c48d345a..91baa8cac4 100644 --- a/lib/plugins/console/help.js +++ b/lib/plugins/console/help.js @@ -1,12 +1,67 @@ -var commandList = function(title, list){ +module.exports = function(ctx){ + return function(args){ + var command = args._[0]; + var list = ctx.extend.console.list(); + var str = ''; + var item, options; + + if (list.hasOwnProperty(command) && command !== 'help'){ + item = list[command]; + options = item.options; + + str += 'Usage: hexo ' + command; + if (options.usage) str += ' ' + options.usage; + str += '\n\n'; + str += 'Description:\n'; + str += (options.description || options.desc || item.description || item.desc) + '\n\n'; + + if (options.arguments) str += commandList('Arguments:', options.arguments); + if (options.commands) str += commandList('Commands:', options.commands); + if (options.options) str += commandList('Options:', options.options); + } else { + var keys = Object.keys(list), + commands = []; + + str += 'Usage: hexo \n\n'; + + for (var i = 0, len = keys.length; i < len; i++){ + var key = keys[i]; + item = list[key]; + options = item.options; + + if ((!ctx.env.init && !options.init) || (!ctx.debug && options.debug)) continue; + + commands.push({ + name: key, + desc: (item.description || item.desc) + }); + } + + str += commandList('Commands:', commands); + str += commandList('Global Options:', [ + {name: '--config', desc: 'Specify config file instead of using _config.yml'}, + {name: '--debug', desc: 'Display all verbose messages in the terminal'}, + {name: '--safe', desc: 'Disable all plugins and scripts'}, + {name: '--silent', desc: 'Hide output on console'} + ]); + } + + str += 'For more help, you can use `hexo help [command]` for the detailed information\n'; + str += 'or you can check the docs: ' + 'http://hexo.io/docs/'.underline; + + console.log(str); + }; +}; + +function commandList(title, list){ if (!list.length) return ''; - var str = title + '\n', - length = 0; + var str = title + '\n'; + var length = 0; list = list.sort(function(a, b){ - var nameA = a.name, - nameB = b.name; + var nameA = a.name; + var nameB = b.name; if (nameA.length >= nameB.length && length < nameA.length){ length = nameA.length; @@ -30,59 +85,4 @@ var commandList = function(title, list){ }); return str + '\n'; -}; - -module.exports = function(args, callback){ - var command = args._.shift(), - list = hexo.extend.console.list(), - str = '', - item, - options; - - if (list.hasOwnProperty(command) && command !== 'help'){ - item = list[command]; - options = item.options; - - str += 'Usage: hexo ' + command; - if (options.usage) str += options.usage; - str += '\n\n'; - str += 'Description:\n'; - str += (options.description || options.desc || item.description || item.desc) + '\n\n'; - - if (options.arguments) str += commandList('Arguments:', options.arguments); - if (options.commands) str += commandList('Commands:', options.commands); - if (options.options) str += commandList('Options:', options.options); - } else { - var keys = Object.keys(list), - commands = []; - - str += 'Usage: hexo \n\n'; - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i]; - item = list[key]; - options = item.options; - - if ((!hexo.env.init && !options.init) || (!hexo.debug && options.debug)) continue; - - commands.push({ - name: key, - desc: (item.description || item.desc) - }); - } - - str += commandList('Commands:', commands); - str += commandList('Global Options:', [ - {name: '--config', desc: 'Specify config file instead of using _config.yml'}, - {name: '--debug', desc: 'Display all verbose messages in the terminal'}, - {name: '--safe', desc: 'Disable all plugins and scripts'}, - {name: '--silent', desc: 'Hide output on console'} - ]); - } - - str += 'For more help, you can use `hexo help [command]` for the detailed information\n'; - str += 'or you can check the docs: ' + 'http://hexo.io/docs/'.underline; - - console.log(str); - callback(); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/lib/plugins/console/index.js b/lib/plugins/console/index.js index e9ef707c46..a33658bccf 100644 --- a/lib/plugins/console/index.js +++ b/lib/plugins/console/index.js @@ -1,110 +1,30 @@ -var console = hexo.extend.console; - -console.register('clean', 'Remove generated files and the cache', require('./clean')); - -console.register('config', 'List the current configuration', require('./config')); - -var deployOptions = { - alias: 'd', - options: [ - {name: '--setup', desc: 'Setup without deployment'}, - {name: '-g, --generate', desc: 'Generate before deployment'} - ] -}; - -console.register('deploy', 'Deploy your website', deployOptions, require('./deploy')); - -var generateOptions = { - alias: 'g', - options: [ - {name: '-d, --deploy', desc: 'Deploy after generated'}, - {name: '-w, --watch', desc: 'Watch file changes'} - ] -}; - -console.register('generate', 'Generate static files', generateOptions, require('./generate')); - -console.register('help', 'Get help on a command', {init: true}, require('./help')); - -var initOptions = { - init: true, - desc: 'Create a new Hexo folder at the specified path or the current directory.', - usage: '[destination]', -}; - -console.register('init', 'Create a new Hexo folder', initOptions, require('./init')); - -var listOptions = { - desc: 'List the information of the site.', - usage: '', - arguments: [ - {name: 'type', desc: 'Available types: route'} - ] -}; - -console.register('list', 'List the information of the site', listOptions, require('./list')); - -var migrateOptions = { - init: true, - usage: '' -}; - -console.register('migrate', 'Migrate your site from other system to Hexo', migrateOptions, require('./migrate')); - -var newOptions = { - alias: 'n', - usage: '[layout] ', - arguments: [ - {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'}, - {name: 'title', desc: 'Post title. Wrap it with quotations to escape.'} - ], - options: [ - {name: '-r, --replace', desc: 'Replace the current post if existed.'}, - {name: '-s, --slug', desc: 'Post slug. Customize the URL of the post.'}, - {name: '-p, --path', desc: 'Post path. Customize the path of the post.'} - ] -}; - -console.register('new', 'Create a new post', newOptions, require('./new')); - -var renderOptions = { - desc: 'Render the files with Markdown or other engines and save them at the specified path or the current directory.', - usage: '<file1> [file2] ...', - options: [ - {name: '-o, --output', desc: 'Output destination'} - ] -}; - -console.register('render', 'Render the files with Markdown or other engines', renderOptions, require('./render')); - -var serverOptions = { - alias: 's', - desc: 'Start the server and watch for file changes.', - options: [ - {name: '-i, --ip', desc: 'Override the default server ip. Bind to all ip address by default'}, - {name: '-p, --port', desc: 'Override the default port'}, - {name: '-s, --static', desc: 'Only serve static files'}, - {name: '-l, --log [format]', desc: 'Enable logger. Override the logger format.'}, - {name: '-d, --drafts', desc: 'Serve draft posts.'} - ] -}; - -console.register('server', 'Start the server', serverOptions, require('./server')); - -var versionOptions = { - init: true -}; - -console.register('version', 'Display version information', versionOptions, require('./version')); - -var publishOptions = { - alias: 'p', - desc: 'Moves a draft post from _drafts to _posts folder.', - usage: '[layout] <filename>', - arguments: [ - {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'}, - {name: 'filename', desc: 'Draft filename. "hello-world" for example.'} - ] -}; - -console.register('publish', 'Publish a draft', publishOptions, require('./publish')); +module.exports = function(ctx){ + var console = ctx.extend.console; + + console.register('clean', 'Removed generated files and cache', require('./clean')(ctx)); + + console.register('help', 'Get help on a command', { + init: true + }, require('./help')(ctx)); + + console.register('init', 'Create a new Hexo folder', { + init: true, + desc: 'Create a new Hexo folder at the specified path or the current directory.', + usage: '[destination]' + }, require('./init')(ctx)); + + console.register('render', 'Render files with renderer plugins', { + init: true, + desc: 'Render files with renderer plugins (e.g. Markdown) and save them at the specified path.', + usage: '<file1> [file2] ...', + options: [ + {name: '--output', desc: 'Output destination. Result will be print in the terminal if the output destination is not set.'}, + {name: '--engine', desc: 'Specify render engine'}, + {name: '--pretty', desc: 'Prettify JSON output'} + ] + }, require('./render')(ctx)); + + console.register('version', 'Display version information', { + init: true + }, require('./version')(ctx)); +}; \ No newline at end of file diff --git a/lib/plugins/console/init.js b/lib/plugins/console/init.js index b3c6e37dd1..6896309e76 100644 --- a/lib/plugins/console/init.js +++ b/lib/plugins/console/init.js @@ -1,28 +1,27 @@ -var pathFn = require('path'), - async = require('async'), - fs = require('graceful-fs'), - util = require('../../util'), - file = util.file2; +var pathFn = require('path'); +var tildify = require('tildify'); +var util = require('../../util'); +var fs = util.fs; -module.exports = function(args, callback){ - var target = hexo.base_dir, - log = hexo.log; +require('colors'); - if (args._[0]) target = pathFn.resolve(target, args._[0]); +module.exports = function(ctx){ + var baseDir = ctx.base_dir; + var log = ctx.log; + var assetDir = pathFn.join(ctx.core_dir, 'assets'); - log.i('Copying data'); + return function(args){ + var target = args._[0] ? pathFn.resolve(baseDir, args._[0]) : baseDir; - async.series([ - function(next){ - file.copyDir(pathFn.join(hexo.core_dir, 'assets'), target, next); - }, - function(next){ - fs.rename(pathFn.join(target, 'gitignore'), pathFn.join(target, '.gitignore'), next); - } - ], function(err){ - if (err) return callback(err); + log.info('Copying data to %s', tildify(target).magenta); - log.i('You are almost done! Don\'t forget to run `npm install` before you start blogging with Hexo!'); - callback(); - }); -}; + return fs.copyDir(assetDir, target).then(function(){ + return fs.rename( + pathFn.join(target, 'gitignore'), + pathFn.join(target, '.gitignore')); + }).then(function(){ + log.info('You are almost done! Don\'t forget to run `npm install` ' + + 'before you start blogging with Hexo!'); + }); + }; +}; \ No newline at end of file diff --git a/lib/plugins/console/list/index.js b/lib/plugins/console/list/index.js deleted file mode 100644 index 880fdaf29a..0000000000 --- a/lib/plugins/console/list/index.js +++ /dev/null @@ -1,19 +0,0 @@ -var store = { - route: require('./route') -}; - -module.exports = function(args, callback){ - var type = args._[0]; - - // Display help message if user didn't input any arguments - if (!type || !store.hasOwnProperty(type)){ - hexo.call('help', {_: ['list']}, callback); - return; - } - - hexo.post.load(function(err){ - if (err) return callback(err); - - store[type](args, callback); - }); -}; \ No newline at end of file diff --git a/lib/plugins/console/list/route.js b/lib/plugins/console/list/route.js deleted file mode 100644 index 9364e00820..0000000000 --- a/lib/plugins/console/list/route.js +++ /dev/null @@ -1,79 +0,0 @@ -var colors = require('colors'); - -module.exports = function(args, callback){ - var keys = Object.keys(hexo.route.routes); - - if (args.json){ - console.log(keys); - return callback(); - } - - var routes = {}; - - keys.forEach(function(key){ - setProperty(routes, key, {}); - }); - - console.log('.'.grey); - printTree(routes); - console.log('\nTotal: ' + keys.length); - callback(); -}; - -var setProperty = function(obj, key, data){ - var split = key.split('/'), - cursor = obj; - - for (var i = 0, len = split.length - 1; i < len; i++){ - var name = split[i]; - cursor = cursor[name] = cursor[name] || {}; - } - - cursor[split[i]] = data; -}; - -var printTree = function(obj, indent){ - indent = indent || 0; - - var keys = Object.keys(obj); - - keys.sort(function(a, b){ - if (a < b){ - return -1; - } else if (a > b){ - return 1; - } else { - return 0; - } - }); - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i], - child = obj[key], - childLength = Object.keys(child).length; - - var str = ''; - - for (var j = 0; j < indent; j++){ - str += '| '.grey; - } - - if (i === len - 1 && !childLength){ - str += '└── '.grey; - } else { - str += '├── '.grey; - } - - str += key; - - if (childLength){ - str += (' (' + childLength + ')').grey; - } - - console.log(str); - - if (childLength){ - printTree(child, indent + 1); - } - } -}; \ No newline at end of file diff --git a/lib/plugins/console/migrate.js b/lib/plugins/console/migrate.js deleted file mode 100644 index 8d8adbfbda..0000000000 --- a/lib/plugins/console/migrate.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = function(args, callback){ - // Display help message if user didn't input any arguments - if (!args._.length){ - hexo.call('help', {_: ['migrate']}, callback); - return; - } - - var type = args._.shift(), - extend = hexo.extend, - migrator = extend.migrator.list(); - - if (!migrator.hasOwnProperty(type)){ - var help = ''; - - help += type + ' migrator plugin is not installed.\n\n'; - help += 'Installed migrator plugins:\n'; - help += ' ' + Object.keys(migrator).join(', '); - - console.log(help); - return callback(); - } - - migrator[type](args, callback); -}; \ No newline at end of file diff --git a/lib/plugins/console/new.js b/lib/plugins/console/new.js deleted file mode 100644 index 906cda0438..0000000000 --- a/lib/plugins/console/new.js +++ /dev/null @@ -1,32 +0,0 @@ -var reservedKeys = ['_', 'title', 'layout', 'slug', 'path', 'replace']; - -module.exports = function(args, callback){ - // Display help message if user didn't input any arguments - if (!args._.length){ - hexo.call('help', {_: ['new']}, callback); - return; - } - - var data = { - title: args._.pop(), - layout: args._.length ? args._[0] : hexo.config.default_layout, - slug: args.s || args.slug, - path: args.p || args.path - }; - - var keys = Object.keys(args); - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i]; - if (~reservedKeys.indexOf(key)) continue; - - data[key] = args[key]; - } - - hexo.post.create(data, args.r || args.replace, function(err, target){ - if (err) return callback(err); - - hexo.log.i('File created at %s', target); - callback(); - }); -}; \ No newline at end of file diff --git a/lib/plugins/console/publish.js b/lib/plugins/console/publish.js deleted file mode 100644 index e7eddf8c51..0000000000 --- a/lib/plugins/console/publish.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = function(args, callback){ - // Display help message if user didn't input any arguments - if (!args._.length){ - hexo.call('help', {_: ['publish']}, callback); - return; - } - - hexo.post.publish({ - slug: args._.pop(), - layout: args._.length ? args._[0] : hexo.config.default_layout - }, args.r || args.replace, function(err, target){ - if (err) return callback(err); - - hexo.log.i('Published %s', target); - callback(); - }); -}; \ No newline at end of file diff --git a/lib/plugins/console/render.js b/lib/plugins/console/render.js index ee1ca963ed..d21cb0791c 100644 --- a/lib/plugins/console/render.js +++ b/lib/plugins/console/render.js @@ -1,56 +1,47 @@ -var fs = require('graceful-fs'), - pathFn = require('path'), - async = require('async'), - _ = require('lodash'), - util = require('../../util'), - file = util.file2; - -module.exports = function(args, callback){ - // Display help message if user didn't input any arguments - if (!args._.length){ - hexo.call('help', {_: ['render']}, callback); - return; - } - - var renderFn = hexo.render, - render = renderFn.render, - isRenderable = renderFn.isRenderable, - getOutput = renderFn.getOutput; - - var outputDir = args.o || args.output || hexo.base_dir; - - var renderItem = function(path, callback){ - if (!isRenderable(path)) return callback(); - - render({path: path}, args, function(err, result){ - if (err) return callback(err); - - if (result.toString){ - result = result.toString(); - } else if (_.isObject(result)){ - result = JSON.stringify(result); - } - - var extname = pathFn.extname(path), - dest = pathFn.resolve(outputDir, path.substring(0, path.length - extname.length + 1) + getOutput(path)); - - file.writeFile(dest, result, callback); +var Promise = require('bluebird'); +var pathFn = require('path'); +var tildify = require('tildify'); +var prettyHrtime = require('pretty-hrtime'); +var util = require('../../util'); +var fs = util.fs; + +require('colors'); + +module.exports = function(ctx){ + var render = ctx.render; + var baseDir = hexo.base_dir; + + return function(args){ + var files = args._; + var output = args.o || args.output; + var engine = args.engine; + + return Promise.map(files, function(path){ + var src = pathFn.resolve(baseDir, path); + // var start = Date.now(); + var start = process.hrtime(); + + return render.render({ + path: src, + engine: engine + }).then(function(result){ + if (typeof result === 'object'){ + if (args.pretty){ + result = JSON.stringify(result, null, ' '); + } else { + result = JSON.stringify(result); + } + } + + if (!output) return console.log(result); + + var extname = pathFn.extname(path); + var dest = pathFn.resolve(output, path.substring(0, path.length - extname.length + 1)) + render.getOutput(path); + var interval = prettyHrtime(process.hrtime(start)); + + log.info('Rendered in %s: %s -> %s', interval.cyan, tildify(src).magenta, tildify(dest).magenta); + return fs.writeFile(dest, result); + }); }); }; - - async.each(args._, function(item, next){ - fs.stat(item, function(err, stats){ - if (err) return callback(err); - - if (stats.isDirectory()){ - file.list(item, function(files){ - async.each(files, function(item, next){ - renderItem(pathFn.join(item, i), next); - }, next); - }); - } else { - renderItem(item, next); - } - }); - }, callback); }; \ No newline at end of file diff --git a/lib/plugins/console/server.js b/lib/plugins/console/server.js deleted file mode 100644 index 0335ac60d5..0000000000 --- a/lib/plugins/console/server.js +++ /dev/null @@ -1,64 +0,0 @@ -var connect = require('connect'), - http = require('http'), - colors = require('colors'), - HexoError = require('../../error'); - -module.exports = function(args, callback){ - var config = hexo.config, - log = hexo.log; - - var app = connect(), - serverIp = args.i || args.ip || config.server_ip || 'localhost', - port = parseInt(args.p || args.port || config.port, 10) || 4000, - useDrafts = args.d || args.drafts || config.render_drafts || false, - root = config.root; - - // If the port setting is invalid, set to the default port 4000 - if (port > 65535 || port < 1){ - port = 4000; - } - - // Drafts - if (useDrafts) { - hexo.extend.processor.register('_drafts/*path', require('../processor/post')); - } - - hexo.extend.filter.apply('server_middleware', app); - - // Load source files - hexo.post.load({watch: true}, function(err){ - if (err) return callback(err); - - // Start listening! - var server = http.createServer(app).listen(port, serverIp, function(){ - if (useDrafts){ - log.i('Using drafts.'); - } - - log.i('Hexo is running at ' + 'http://%s:%d%s'.underline + '. Press Ctrl+C to stop.', serverIp, port, root); - - /** - * Fired after server started. - * - * @event server - * @for Hexo - */ - - hexo.emit('server'); - }); - - server.on('error', function(err){ - switch (err.code){ - case 'EADDRINUSE': - err = HexoError.wrap(err, 'Port ' + port + ' has been used. Try other port instead.'); - break; - - case 'EACCES': - err = HexoError.wrap(err, 'Permission denied. You can\'t use port ' + port + '.'); - break; - } - - callback(err); - }); - }); -}; diff --git a/lib/plugins/console/version.js b/lib/plugins/console/version.js index f8f63cb2fb..221d455741 100644 --- a/lib/plugins/console/version.js +++ b/lib/plugins/console/version.js @@ -1,19 +1,19 @@ -var _ = require('lodash'), - os = require('os'); +var _ = require('lodash'); +var os = require('os'); -module.exports = function(args, callback){ - var versions = _.extend({ - hexo: hexo.version, - os: os.type() + ' ' + os.release() + ' ' + os.platform() + ' ' + os.arch() - }, process.versions); +module.exports = function(ctx){ + return function(args){ + var versions = _.extend({ + hexo: ctx.version, + os: os.type() + ' ' + os.release() + ' ' + os.platform() + ' ' + os.arch() + }, process.versions); - if (args.json){ - console.log(versions); - } else { - for (var i in versions){ - console.log(i + ': ' + versions[i]); + if (args.json){ + console.log(versions); + } else { + for (var i in versions){ + console.log(i + ': ' + versions[i]); + } } - } - - callback(); + }; }; \ No newline at end of file diff --git a/lib/plugins/deployer/git.js b/lib/plugins/deployer/git.js deleted file mode 100644 index 0716cfdd2d..0000000000 --- a/lib/plugins/deployer/git.js +++ /dev/null @@ -1,121 +0,0 @@ -var async = require('async'), - fs = require('fs'), - path = require('path'), - moment = require('moment'), - spawn = require('child_process').spawn, - util = require('../../util'), - file = util.file2, - commitMessage = require('./util').commitMessage; - -module.exports = function(args, callback){ - var baseDir = args.deploy_dir || hexo.base_dir, - deployDir = path.join(baseDir, '.deploy'), - publicDir = hexo.public_dir; - - if (!args.repo && !args.repository){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Example:\n'; - help += ' deploy:\n'; - help += ' type: git\n'; - help += ' message: [message]\n'; - help += ' repo:\n'; - help += ' github: <repository url>,<branch>\n'; - help += ' gitcafe: <repository url>,<branch>\n\n'; - help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; - - console.log(help); - return callback(); - } - - var repo = args.repo || args.repository; - - for (var t in repo){ - var s = repo[t].split(','); - repo[t] = {}; - repo[t].url = s[0]; - repo[t].branch = s.length > 1 ? s[1] : 'master'; - } - - var run = function(command, args, callback){ - var cp = spawn(command, args, {cwd: deployDir}); - - cp.stdout.on('data', function(data){ - process.stdout.write(data); - }); - - cp.stderr.on('data', function(data){ - process.stderr.write(data); - }); - - cp.on('close', callback); - }; - - async.series([ - // Set up - function(next){ - fs.exists(deployDir, function(exist){ - if (exist && !args.setup) return next(); - - hexo.log.i('Setting up Git deployment...'); - - var commands = [['init']]; - - if (args.master && repo[args.master]){ - var master = repo[args.master]; - hexo.log.i('fetch from ['+ args.master.green + ']:', master.url.cyan); - commands.push(['remote', 'add', 'origin', '-t', master.branch, master.url]); - commands.push(['pull']); - } else { - commands.push(['add', '-A', '.']); - commands.push(['commit', '-m', 'First commit']); - } - - for (var t in repo){ - commands.push(['remote', 'add', t, '-t', repo[t].branch, repo[t].url]); - } - - file.writeFile(path.join(deployDir, 'placeholder'), '', function(err){ - if (err) callback(err); - - async.eachSeries(commands, function(item, next){ - run('git', item, function(code){ - if (code === 0) next(); - }); - }, function(){ - if (!args.setup) next(); - }); - }); - }); - }, - - function(next){ - hexo.log.i('Clearing .deploy folder...'); - - file.emptyDir(deployDir, next); - }, - function(next){ - hexo.log.i('Copying files from public folder...'); - - file.copyDir(publicDir, deployDir, next); - }, - - function(next){ - var commands = [ - ['add', '-A'], - ['commit', '-m', commitMessage(args)], - ]; - - for (var t in repo){ - commands.push(['push', '-u', t, 'master:' + repo[t].branch, '--force']); - } - - async.eachSeries(commands, function(item, next){ - run('git', item, function(){ - next(); - }); - }, next); - } - ], callback); -}; diff --git a/lib/plugins/deployer/github.js b/lib/plugins/deployer/github.js deleted file mode 100644 index de1207b5e8..0000000000 --- a/lib/plugins/deployer/github.js +++ /dev/null @@ -1,125 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - path = require('path'), - colors = require('colors'), - swig = require('swig'), - spawn = require('child_process').spawn, - util = require('../../util'), - file = util.file2, - commitMessage = require('./util').commitMessage; - -// http://git-scm.com/docs/git-clone -var rRepo = /(:|\/)([^\/]+)\/([^\/]+)\.git\/?$/; - -module.exports = function(args, callback){ - var baseDir = hexo.base_dir, - deployDir = path.join(baseDir, '.deploy'), - publicDir = hexo.public_dir; - - if (!args.repo && !args.repository){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Example:\n'; - help += ' deploy:\n'; - help += ' type: github\n'; - help += ' repo: <repository url>\n'; - help += ' branch: [branch]\n'; - help += ' message: [message]\n\n'; - help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; - - console.log(help); - return callback(); - } - - var url = args.repo || args.repository; - - if (!rRepo.test(url)){ - hexo.log.e(url + ' is not a valid repository URL!'); - return callback(); - } - - var branch = args.branch; - - if (!branch){ - var match = url.match(rRepo), - username = match[2], - repo = match[3], - rGh = new RegExp('^' + username + '\\.github\\.[io|com]', 'i'); - - // https://help.github.com/articles/user-organization-and-project-pages - if (repo.match(rGh)){ - branch = 'master'; - } else { - branch = 'gh-pages'; - } - } - - var run = function(command, args, callback){ - var cp = spawn(command, args, {cwd: deployDir}); - - cp.stdout.on('data', function(data){ - process.stdout.write(data); - }); - - cp.stderr.on('data', function(data){ - process.stderr.write(data); - }); - - cp.on('close', callback); - }; - - async.series([ - // Set up - function(next){ - fs.exists(deployDir, function(exist){ - if (exist && !args.setup) return next(); - - hexo.log.i('Setting up GitHub deployment...'); - - var commands = [ - ['init'], - ['add', '-A', '.'], - ['commit', '-m', 'First commit'] - ]; - - if (branch !== 'master') commands.push(['branch', '-M', branch]); - - file.writeFile(path.join(deployDir, 'placeholder'), '', function(err){ - if (err) callback(err); - - async.eachSeries(commands, function(item, next){ - run('git', item, function(code){ - if (code === 0) next(); - }); - }, function(){ - if (!args.setup) next(); - }); - }); - }); - }, - function(next){ - hexo.log.i('Clearing .deploy folder...'); - - file.emptyDir(deployDir, next); - }, - function(next){ - hexo.log.i('Copying files from public folder...'); - - file.copyDir(publicDir, deployDir, next); - }, - function(next){ - var commands = [ - ['add', '-A'], - ['commit', '-m', commitMessage(args)], - ['push', '-u', url, branch, '--force'] - ]; - - async.eachSeries(commands, function(item, next){ - run('git', item, function(){ - next(); - }); - }, next); - } - ], callback); -}; diff --git a/lib/plugins/deployer/heroku/Procfile b/lib/plugins/deployer/heroku/Procfile deleted file mode 100644 index 5106273001..0000000000 --- a/lib/plugins/deployer/heroku/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node app \ No newline at end of file diff --git a/lib/plugins/deployer/heroku/app.js b/lib/plugins/deployer/heroku/app.js deleted file mode 100644 index 79b3a8116c..0000000000 --- a/lib/plugins/deployer/heroku/app.js +++ /dev/null @@ -1,10 +0,0 @@ -var connect = require('connect'), - app = connect.createServer(), - port = process.env.PORT; - -app.use(connect.static(__dirname + '/public')); -app.use(connect.compress()); - -app.listen(port, function(){ - console.log('Hexo is running on port %d', port); -}); \ No newline at end of file diff --git a/lib/plugins/deployer/heroku/index.js b/lib/plugins/deployer/heroku/index.js deleted file mode 100644 index 4fcde667a5..0000000000 --- a/lib/plugins/deployer/heroku/index.js +++ /dev/null @@ -1,137 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - path = require('path'), - colors = require('colors'), - spawn = require('child_process').spawn, - util = require('../../../util'), - file = util.file2, - commitMessage = require('../util').commitMessage; - -module.exports = function(args, callback){ - if (!args.repo && !args.repository){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Example:\n'; - help += ' deploy:\n'; - help += ' type: heroku\n'; - help += ' repo: <repository url>\n'; - help += ' message: [message]\n\n'; - help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; - - console.log(help); - return callback(); - } - - var url = args.repo || args.repository, - baseDir = hexo.base_dir; - - var run = function(command, args, callback){ - var cp = spawn(command, args, {cwd: baseDir}); - - cp.stdout.on('data', function(data){ - process.stdout.write(data); - }); - - cp.stderr.on('data', function(data){ - process.stderr.write(data); - }); - - cp.on('close', callback); - }; - - async.series([ - function(next){ - var files = ['app.js', 'Procfile']; - - async.each(files, function(item, next){ - var src = path.join(__dirname, item), - dest = path.join(baseDir, item); - - fs.exists(dest, function(exist){ - if (exist){ - next(); - } else { - hexo.log.d('Copying %s...', item); - file.copyFile(src, dest, next); - } - }); - }, next); - }, - function(next){ - var packagePath = path.join(baseDir, 'package.json'); - - var defaultPackage = JSON.stringify({ - name: 'hexo', - version: hexo.version, - private: true, - dependencies: { - connect: '2.x' - } - }, ' '); - - fs.exists(packagePath, function(exist){ - if (exist){ - try { - var content = require(packagePath); - - if (content.dependencies){ - if (content.dependencies.connect){ - return next(); - } else { - content.dependencies.connect = '2.x'; - } - } else { - content.dependencies = { - connect: '2.x' - }; - } - - hexo.log.d('Updating package.json...'); - file.writeFile(packagePath, JSON.stringify(content, ' '), next); - } catch (e){ - hexo.log.d('Creating package.json...'); - file.writeFile(packagePath, defaultPackage, next); - } - } else { - hexo.log.d('Creating package.json...'); - file.writeFile(packagePath, defaultPackage, next); - } - }); - }, - function(next){ - var gitPath = path.join(baseDir, '.git'); - - fs.exists(gitPath, function(exist){ - if (exist) return next(); - - var commands = [ - ['init'] - ]; - - hexo.log.d('Initializing git...'); - - async.eachSeries(commands, function(item, next){ - run('git', item, function(code){ - if (code === 0) next(); - }); - }, next); - }); - }, - function(next){ - if (args.setup) return callback(); - - var commands = [ - ['add', '-A', '.'], - ['commit', '-m', commitMessage(args)], - ['push', '-u', url, 'master', '--force'] - ]; - - async.eachSeries(commands, function(item, next){ - run('git', item, function(){ - next(); - }); - }, next); - } - ], callback); -}; \ No newline at end of file diff --git a/lib/plugins/deployer/index.js b/lib/plugins/deployer/index.js index 795a024131..98d1fc1cc0 100644 --- a/lib/plugins/deployer/index.js +++ b/lib/plugins/deployer/index.js @@ -1,7 +1,3 @@ -var deployer = hexo.extend.deployer; - -deployer.register('git', require('./git')); -deployer.register('github', require('./github')); -deployer.register('heroku', require('./heroku')); -deployer.register('openshift', require('./openshift')); -deployer.register('rsync', require('./rsync')); \ No newline at end of file +module.exports = function(){ + // +}; \ No newline at end of file diff --git a/lib/plugins/deployer/openshift.js b/lib/plugins/deployer/openshift.js deleted file mode 100644 index dbb2630dc1..0000000000 --- a/lib/plugins/deployer/openshift.js +++ /dev/null @@ -1,77 +0,0 @@ -var colors = require('colors'), - path = require('path'), - moment = require('moment'), - spawn = require('child_process').spawn, - util = require('../../util'), - file = util.file2, - commitMessage = require('./util').commitMessage, - async = require('async'), - fs = require('graceful-fs'); //required for chmod on testrubyserver.rb; - - -var run = function(command, args, callback){ - var cp = spawn(command, args); - - cp.stdout.on('data', function(data){ - process.stdout.write(data); - }); - - cp.stderr.on('data', function(data){ - process.stderr.write(data); - }); - - cp.on('close', callback); -}; - -module.exports = function(args, callback){ - var baseDir = hexo.base_dir, - publicDir = hexo.public_dir; - - if (!args.remote){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Example:\n'; - help += ' deploy:\n'; - help += ' type: openshift\n'; - help += ' root:<root directory>\n'; - help += ' remote: <upstream git remote>\n'; - help += ' branch: [upstraem git branch] # Default is master\n'; - help += ' message: [message]\n\n'; - help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; - - console.log(help); - return callback(); - } - - var blogDir = path.join(baseDir, './diy', args.root), - remote = args.remote, - branch = args.branch || 'master'; - - async.series([ - function(next){ - file.copyFile(blogDir+'/testrubyserver.rb', publicDir); - file.rmdir(blogDir, next); - }, - function(next){ - file.copyDir(publicDir, blogDir, next); - }, - function(next){ - fs.chmod(blogDir+'/testrubyserver.rb',0777,next); - }, - function(next){ - var commands = [ - ['add', '-A', baseDir], - ['add', '-A', blogDir], - ['commit', '-m', commitMessage(args)], - ['push', remote, branch, '--force'] - ]; - - async.eachSeries(commands, function(item, next){ - run('git', item, function(){ - next(); - }); - }, next); - } - ], callback); -}; diff --git a/lib/plugins/deployer/rsync.js b/lib/plugins/deployer/rsync.js deleted file mode 100644 index 5cdeeb32e6..0000000000 --- a/lib/plugins/deployer/rsync.js +++ /dev/null @@ -1,63 +0,0 @@ -var colors = require('colors'), - spawn = require('child_process').spawn; - -var run = function(command, args, callback){ - var cp = spawn(command, args); - - cp.stdout.on('data', function(data){ - process.stdout.write(data); - }); - - cp.stderr.on('data', function(data){ - process.stderr.write(data); - }); - - cp.on('close', callback); -}; - -module.exports = function(args, callback){ - if (!args.host || !args.user || !args.root){ - var help = ''; - - help += 'You should configure deployment settings in _config.yml first!\n\n'; - help += 'Example:\n'; - help += ' deploy:\n'; - help += ' type: rsync\n'; - help += ' host: <host>\n'; - help += ' user: <user>\n'; - help += ' root: <root>\n'; - help += ' port: [port] # Default is 22\n'; - help += ' delete: [true|false] # Default is true\n'; - help += ' verbose: [true|false] # Default is true\n'; - help += ' ignore_errors: [true|false] # Default is false\n\n'; - help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; - - console.log(help); - return callback(); - } - - if (!args.hasOwnProperty('delete')) args.delete = true; - if (!args.port) args.port = 22; - if (!args.hasOwnProperty('verbose')) args.verbose = true; - - if (args.port > 65535 || args.port < 1){ - args.port = 22; - } - - var params = [ - args.delete === true ? '--delete' : '', - args.verbose === true ? '-v' : '', - args.ignore_errors === true ? '--ignore-errors' : '', - '-az', - 'public/', - '-e', - 'ssh -p ' + args.port, - args.user + '@' + args.host + ':' + args.root - ].filter(function(arg){ - return arg; - }); - - run('rsync', params, function(code){ - callback(code ? 'Deploy failed: rsync (code ' + code + ')' : null); - }); -}; \ No newline at end of file diff --git a/lib/plugins/deployer/util.js b/lib/plugins/deployer/util.js deleted file mode 100644 index 5eab42c9d3..0000000000 --- a/lib/plugins/deployer/util.js +++ /dev/null @@ -1,18 +0,0 @@ -var swig = require('swig'), - moment = require('moment'); - -var helpers = { - now: function(format){ - return moment().format(format); - } -}; - -exports.commitMessage = function(args){ - var message = args.m || args.msg || args.message; - - if (!message){ - message = 'Site updated: {{ now(\'YYYY-MM-DD HH:mm:ss\') }}'; - } - - return swig.compile(message)(helpers); -}; diff --git a/lib/plugins/filter/backtick_code_block.js b/lib/plugins/filter/backtick_code_block.js deleted file mode 100644 index 8d6ba58e26..0000000000 --- a/lib/plugins/filter/backtick_code_block.js +++ /dev/null @@ -1,47 +0,0 @@ -var stripIndent = require('strip-indent'), - util = require('../../util'), - highlight = util.highlight; - -var rBacktick = /\n*(`{3,}|~{3,}) *(.+)? *\n([\s\S]+?)\s*\1\n*/g, - rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/i, - rLangCaption = /([^\s]+)\s*(.+)?/i; - -module.exports = function(data, callback){ - var config = hexo.config.highlight || {}; - - if (!config.enable) return callback(); - - data.content = data.content.replace(rBacktick, function(){ - var args = arguments[2], - str = arguments[3]; - - var options = { - gutter: config.line_number, - tab: config.tab_replace - }; - - var match; - - if (args){ - if (rAllOptions.test(args)){ - match = args.match(rAllOptions); - } else if (rLangCaption.test(args)){ - match = args.match(rLangCaption); - } - - options.lang = match[1]; - - if (match[2]){ - options.caption = '<span>' + match[2] + '</span>'; - - if (match[3]){ - options.caption += '<a href="' + match[3] + '">' + (match[4] ? match[4] : 'link') + '</a>'; - } - } - } - - return '\n\n<escape>' + highlight(stripIndent(str), options).replace(/&/g, '&') + '</escape>\n\n'; - }); - - callback(null, data); -}; \ No newline at end of file diff --git a/lib/plugins/filter/excerpt.js b/lib/plugins/filter/excerpt.js deleted file mode 100644 index 535300f3fb..0000000000 --- a/lib/plugins/filter/excerpt.js +++ /dev/null @@ -1,18 +0,0 @@ -var rExcerpt = /<!--\s*more\s*-->/g; - -module.exports = function(data, callback){ - var content = data.content; - - if (rExcerpt.test(content)){ - data.content = content.replace(rExcerpt, function(match, index){ - data.excerpt = content.substring(0, index); - data.more = content.substring(index, content.length - 1 - index); - return '<a id="more"></a>'; - }); - } else { - data.excerpt = ''; - data.more = content; - } - - callback(null, data); -}; \ No newline at end of file diff --git a/lib/plugins/filter/external_link.js b/lib/plugins/filter/external_link.js deleted file mode 100644 index 615298f528..0000000000 --- a/lib/plugins/filter/external_link.js +++ /dev/null @@ -1,35 +0,0 @@ -var url = require('url'), - cheerio = require('cheerio'); - -module.exports = function(data, callback){ - var config = hexo.config; - - if (!config.external_link) return callback(); - - var $ = cheerio.load(data.content, {decodeEntities: false}); - - $('a').each(function(){ - // Exit if the link has target attribute - if ($(this).attr('target')) return; - - // Exit if the href attribute doesn't exists - var href = $(this).attr('href'); - if (!href) return; - - var data = url.parse(href); - - // Exit if the link doesn't have protocol, which means it's a internal link - if (!data.protocol) return; - - // Exit if the url is started with site url - if (data.hostname === config.url) return match; - - $(this) - .attr('target', '_blank') - .attr('rel', 'external'); - }); - - data.content = $.html(); - - callback(null, data); -}; \ No newline at end of file diff --git a/lib/plugins/filter/index.js b/lib/plugins/filter/index.js index 3b0803a5d4..98d1fc1cc0 100644 --- a/lib/plugins/filter/index.js +++ b/lib/plugins/filter/index.js @@ -1,18 +1,3 @@ -var filter = hexo.extend.filter; - -filter.register('before_post_render', require('./backtick_code_block')); -filter.register('before_post_render', require('./titlecase')); - -filter.register('after_post_render', require('./external_link')); -filter.register('after_post_render', require('./excerpt')); - -filter.register('new_post_path', require('./new_post_path')); - -filter.register('post_permalink', require('./post_permalink')); - -filter.register('server_middleware', require('./middlewares/logger')); -filter.register('server_middleware', require('./middlewares/header')); -filter.register('server_middleware', require('./middlewares/route')); -filter.register('server_middleware', require('./middlewares/static')); -filter.register('server_middleware', require('./middlewares/redirect')); -filter.register('server_middleware', require('./middlewares/gzip')); \ No newline at end of file +module.exports = function(){ + // +}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/gzip.js b/lib/plugins/filter/middlewares/gzip.js deleted file mode 100644 index 37201edd22..0000000000 --- a/lib/plugins/filter/middlewares/gzip.js +++ /dev/null @@ -1,5 +0,0 @@ -var compress = require('compression'); - -module.exports = function(app){ - app.use(compress()); -}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/header.js b/lib/plugins/filter/middlewares/header.js deleted file mode 100644 index dab3efde0a..0000000000 --- a/lib/plugins/filter/middlewares/header.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function(app){ - app.use(function(req, res, next){ - res.setHeader('X-Powered-By', 'Hexo'); - next(); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/logger.js b/lib/plugins/filter/middlewares/logger.js deleted file mode 100644 index 386f2b281c..0000000000 --- a/lib/plugins/filter/middlewares/logger.js +++ /dev/null @@ -1,16 +0,0 @@ -var morgan = require('morgan'); - -module.exports = function(app){ - var config = hexo.config, - args = hexo.env.args, - logger = args.l || args.log; - - // Fix #746 - if (typeof config.logger_format !== 'string') config.logger_format = 'dev'; - - if (logger){ - app.use(morgan(typeof logger === 'string' ? logger : config.logger_format)); - } else if (config.logger || hexo.env.debug){ - app.use(morgan(config.logger_format)); - } -}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/redirect.js b/lib/plugins/filter/middlewares/redirect.js deleted file mode 100644 index dcba441768..0000000000 --- a/lib/plugins/filter/middlewares/redirect.js +++ /dev/null @@ -1,15 +0,0 @@ -var serverUtil = require('../../../util/server'); - -module.exports = function(app){ - var config = hexo.config, - root = config.root; - - if (root === '/') return; - - // If root url is not `/`, redirect to the correct root url - app.use(function(req, res, next){ - if (req.method !== 'GET' || req.url !== '/') return next(); - - serverUtil.redirect(res, root); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/route.js b/lib/plugins/filter/middlewares/route.js deleted file mode 100644 index 87e17e47f3..0000000000 --- a/lib/plugins/filter/middlewares/route.js +++ /dev/null @@ -1,42 +0,0 @@ -var pathFn = require('path'), - Readable = require('stream').Readable, - serverUtil = require('../../../util/server'); - -module.exports = function(app){ - var config = hexo.config, - args = hexo.env.args, - root = config.root, - route = hexo.route; - - if (args.s || args.static) return; - - app.use(root, function(req, res, next){ - var method = req.method; - if (method !== 'GET' && method !== 'HEAD') return next(); - - var url = route.format(decodeURIComponent(req.url)), - target = route.get(url); - - // When the URL is `foo/index.html` but users access `foo`, redirect to `foo/`. - if (!target){ - if (pathFn.extname(url)) return next(); - - return serverUtil.redirect(res, root + url + '/'); - } - - target(function(err, result){ - if (err) return next(err); - if (result == null) return next(); - - serverUtil.contentType(res, pathFn.extname(url)); - - if (method === 'HEAD') return res.end(); - - if (result instanceof Readable){ - result.pipe(res).on('error', next); - } else { - res.end(result); - } - }); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/middlewares/static.js b/lib/plugins/filter/middlewares/static.js deleted file mode 100644 index 7ec5f4d19b..0000000000 --- a/lib/plugins/filter/middlewares/static.js +++ /dev/null @@ -1,5 +0,0 @@ -var serveStatic = require('serve-static'); - -module.exports = function(app){ - app.use(hexo.config.root, serveStatic(hexo.public_dir)); -}; \ No newline at end of file diff --git a/lib/plugins/filter/new_post_path.js b/lib/plugins/filter/new_post_path.js deleted file mode 100644 index a235485220..0000000000 --- a/lib/plugins/filter/new_post_path.js +++ /dev/null @@ -1,102 +0,0 @@ -var fs = require('graceful-fs'), - moment = require('moment'), - path = require('path'), - _ = require('lodash'), - util = require('../../util'), - escape = util.escape, - Permalink = util.permalink, - permalink; - -var reservedKeys = ['year', 'month', 'i_month', 'day', 'i_day', 'title']; - -module.exports = function(data, replace, callback){ - var sourceDir = hexo.source_dir, - config = hexo.config, - newPostName = config.new_post_name, - layout = data.layout, - date = data.date, - slug = data.slug, - target = ''; - - if (layout === 'page'){ - if (data.path){ - target = path.join(sourceDir, data.path); - } else { - target = path.join(sourceDir, slug, 'index.md'); - } - } else if (layout === 'draft'){ - target = path.join(sourceDir, '_drafts', data.path || (slug + path.extname(newPostName))); - } else { - var postDir = path.join(sourceDir, '_posts'), - filename = ''; - - if (data.path){ - filename = data.path; - } else { - if (!permalink || permalink.rule !== newPostName){ - permalink = new Permalink(newPostName); - } - - var filenameData = { - year: date.format('YYYY'), - month: date.format('MM'), - i_month: date.format('M'), - day: date.format('DD'), - i_day: date.format('D'), - title: slug - }; - - var keys = Object.keys(data); - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i]; - if (~reservedKeys.indexOf(key)) continue; - - filenameData[key] = data[key]; - } - - filename = permalink.stringify(_.extend({}, config.permalink_defaults, filenameData)); - } - - target = path.join(postDir, filename); - } - - if (!path.extname(target)) target += '.md'; - - if (replace) return callback(null, target); - - fs.exists(target, function(exist){ - if (!exist) return callback(null, target); - - // If the target exists, check the parent folder and rename the file. e.g. target-1.md - fs.readdir(path.dirname(target), function(err, files){ - if (err) return callback(err); - - var extname = path.extname(target), - basename = path.basename(target, extname), - regex = new RegExp('^' + escape.regex(basename) + '-?(\\d+)?'), - max = 0; - - files.forEach(function(item){ - var match = path.basename(item, path.extname(item)).match(regex); - - if (match){ - var num = match[1]; - - if (num){ - if (num >= max){ - max = parseInt(num, 10) + 1; - } - } else { - if (max === 0){ - max = 1; - } - } - } - }); - - target = target.substring(0, target.length - extname.length) + '-' + max + extname; - callback(null, target); - }); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/post_permalink.js b/lib/plugins/filter/post_permalink.js deleted file mode 100644 index c3740f4a39..0000000000 --- a/lib/plugins/filter/post_permalink.js +++ /dev/null @@ -1,38 +0,0 @@ -var _ = require('lodash'), - Permalink = require('../../util').permalink, - permalink; - -module.exports = function(data){ - var config = hexo.config; - - if (!permalink || permalink.rule !== config.permalink){ - permalink = new Permalink(config.permalink); - } - - var meta = { - id: data.id || data._id, - title: data.slug, - year: data.date.format('YYYY'), - month: data.date.format('MM'), - day: data.date.format('DD'), - i_month: data.date.format('M'), - i_day: data.date.format('D') - }; - - var categories = data.categories; - - if (categories.length){ - var category = categories[categories.length - 1], - Category = hexo.model('Category'); - - meta.category = Category.get(category).slug; - } else { - meta.category = config.default_category; - } - - _.each(data, function(value, i){ - if (!meta.hasOwnProperty(i)) meta[i] = value; - }); - - return permalink.stringify(_.extend({}, config.permalink_defaults, meta)); -}; \ No newline at end of file diff --git a/lib/plugins/filter/titlecase.js b/lib/plugins/filter/titlecase.js deleted file mode 100644 index 70d39a26fa..0000000000 --- a/lib/plugins/filter/titlecase.js +++ /dev/null @@ -1,10 +0,0 @@ -var util = require('../../util'), - titlecase = util.titlecase; - -module.exports = function(data, callback){ - if (!hexo.config.titlecase || !data.title) return callback(); - - data.title = titlecase(data.title); - - callback(null, data); -}; \ No newline at end of file diff --git a/lib/plugins/generator/archive.js b/lib/plugins/generator/archive.js deleted file mode 100644 index 97f3a5352c..0000000000 --- a/lib/plugins/generator/archive.js +++ /dev/null @@ -1,54 +0,0 @@ -var paginator = require('./paginator'), - _ = require('lodash'); - -module.exports = function(locals, render, callback){ - var config = hexo.config, - mode = +config.archive, - archiveDir = config.archive_dir + '/'; - - if (!mode) return callback(); - - var generate = function(path, posts, options){ - if (mode === 2){ - paginator(path, posts, 'archive', render, options); - } else { - render(path, ['archive', 'index'], _.extend({posts: posts}, options)); - } - }; - - var posts = locals.posts.sort('date', -1); - - if (!posts.length) return callback(); - - generate(archiveDir, posts, {archive: true}); - - var newest = posts.first().date, - oldest = posts.last().date; - - // Yearly - for (var i = oldest.year(); i <= newest.year(); i++){ - var yearly = posts.find({date: {$year: i}}); - - if (!yearly.length) continue; - - generate(archiveDir + i + '/', yearly, { - archive: true, - year: i - }); - - // Monthly - for (var j = 1; j <= 12; j++){ - var monthly = yearly.find({date: {$year: i, $month: j}}); - - if (!monthly.length) continue; - - generate(archiveDir + i + '/' + (j < 10 ? '0' + j : j) + '/', monthly, { - archive: true, - year: i, - month: j - }); - } - } - - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/generator/asset.js b/lib/plugins/generator/asset.js deleted file mode 100644 index 9aebc31a2b..0000000000 --- a/lib/plugins/generator/asset.js +++ /dev/null @@ -1,56 +0,0 @@ -var fs = require('graceful-fs'), - pathFn = require('path'), - async = require('async'), - HexoError = require('../../error'); - -module.exports = function(locals, _render, callback){ - var baseDir = hexo.base_dir, - renderFn = hexo.render, - render = renderFn.render, - isRenderable = renderFn.isRenderable, - getOutput = renderFn.getOutput; - - async.each(hexo.model('Asset').toArray(), function(asset, next){ - var path = asset.path, - source = pathFn.join(baseDir, asset._id); - - fs.exists(source, function(exist){ - if (!exist){ - asset.remove(function(){ - next(); - }); - - return; - } - - var dest = '', - content; - - if (isRenderable(path)){ - var extname = pathFn.extname(path), - filename = path.substring(0, path.length - extname.length), - subext = pathFn.extname(filename); - - dest = filename + '.' + (subext ? subext.substring(1) : getOutput(path)); - - content = function(callback){ - render({path: source}, function(err, result){ - if (err) return callback(HexoError.wrap(err, 'Asset render failed: ' + path)); - - callback(null, result); - }); - }; - } else { - dest = path; - - content = function(fn){ - fn(null, fs.createReadStream(source)); - }; - } - - content.modified = asset.modified; - hexo.route.set(dest, content); - next(); - }); - }, callback); -}; \ No newline at end of file diff --git a/lib/plugins/generator/category.js b/lib/plugins/generator/category.js deleted file mode 100644 index 4130820d68..0000000000 --- a/lib/plugins/generator/category.js +++ /dev/null @@ -1,24 +0,0 @@ -var _ = require('lodash'), - paginator = require('./paginator'); - -module.exports = function(locals, render, callback){ - var config = hexo.config, - mode = +config.category; - - if (!mode) return callback(); - - locals.categories.populate('posts').each(function(cat){ - if (!cat.length) return; - - var posts = cat.posts.sort('date', -1).populate('categories').populate('tags'), - path = cat.path; - - if (mode === 2){ - paginator(path, posts, 'category', render, {category: cat.name}); - } else { - render(path, ['category', 'archive', 'index'], _.extend({posts: posts}, {category: cat.name})); - } - }); - - callback(); -}; diff --git a/lib/plugins/generator/home.js b/lib/plugins/generator/home.js deleted file mode 100644 index 5adacd054c..0000000000 --- a/lib/plugins/generator/home.js +++ /dev/null @@ -1,7 +0,0 @@ -var paginator = require('./paginator'); - -module.exports = function(locals, render, callback){ - var posts = locals.posts.sort('date', -1); - paginator('', posts, 'index', render); - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/generator/index.js b/lib/plugins/generator/index.js index 8d17b3852b..98d1fc1cc0 100644 --- a/lib/plugins/generator/index.js +++ b/lib/plugins/generator/index.js @@ -1,9 +1,3 @@ -var generator = hexo.extend.generator; - -generator.register('archive', require('./archive')); -generator.register('category', require('./category')); -generator.register('home', require('./home')); -generator.register('page', require('./page')); -generator.register('post', require('./post')); -generator.register('tag', require('./tag')); -generator.register('asset', require('./asset')); \ No newline at end of file +module.exports = function(){ + // +}; \ No newline at end of file diff --git a/lib/plugins/generator/page.js b/lib/plugins/generator/page.js deleted file mode 100644 index 46f03830db..0000000000 --- a/lib/plugins/generator/page.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = function(locals, render, callback){ - var route = hexo.route; - - locals.pages.each(function(item){ - var layout = item.layout, - path = item.path; - - if (!layout || layout === 'false'){ - route.set(item.path, function(fn){ - fn(null, item.content); - }); - } else { - render(item.path, [layout, 'page', 'post', 'index'], item); - } - }); - - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/generator/paginator.js b/lib/plugins/generator/paginator.js deleted file mode 100644 index 80e97eed36..0000000000 --- a/lib/plugins/generator/paginator.js +++ /dev/null @@ -1,52 +0,0 @@ -var _ = require('lodash'); - -var format = function(base, i){ - return base + (i == 1 ? '' : hexo.config.pagination_dir + '/' + i + '/'); -}; - -var Paginator = function(base, posts, num, total){ - var config = hexo.config, - perPage = this.per_page = config.per_page; - - this.base = config.root + base; - this.total = total; - this.current = num; - this.current_url = format(base, num); - this.posts = posts.slice(perPage * (num - 1), perPage * num); - - if (num == 1){ - this.prev = 0; - this.prev_link = ''; - } else { - this.prev = num - 1; - this.prev_link = format(base, num - 1); - } - - if (num == total){ - this.next = 0; - this.next_link = ''; - } else { - this.next = num + 1; - this.next_link = format(base, num + 1); - } -}; - -module.exports = function(base, posts, layout, render, options){ - var config = hexo.config; - - if (config.per_page && posts.length){ - var total = Math.ceil(posts.length / config.per_page); - - var renderData = function(i){ - var data = _.extend(new Paginator(base, posts, i, total), options); - - render(data.current_url, [layout, 'archive', 'index'], data); - }; - - for (var i = 1; i <= total; i++){ - renderData(i); - } - } else { - render(base, [layout, 'archive', 'index'], _.extend({posts: posts}, options)); - } -}; \ No newline at end of file diff --git a/lib/plugins/generator/post.js b/lib/plugins/generator/post.js deleted file mode 100644 index 3d7f8fdd9f..0000000000 --- a/lib/plugins/generator/post.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = function(locals, render, callback){ - var route = hexo.route, - arr = locals.posts.sort('date', -1).toArray(), - length = arr.length; - - arr.forEach(function(post, i){ - var layout = post.layout, - path = post.path; - - if (!layout || layout === 'false'){ - route.set(path, function(fn){ - fn(null, post.content); - }); - } else { - post.prev = i === 0 ? null : arr[i - 1]; - post.next = i === length - 1 ? null : arr[i + 1]; - - render(path, [layout, 'post', 'page', 'index'], post); - } - }); - - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/generator/tag.js b/lib/plugins/generator/tag.js deleted file mode 100644 index d213b9fcde..0000000000 --- a/lib/plugins/generator/tag.js +++ /dev/null @@ -1,24 +0,0 @@ -var _ = require('lodash'), - paginator = require('./paginator'); - -module.exports = function(locals, render, callback){ - var config = hexo.config, - mode = +config.tag; - - if (!mode) return callback(); - - locals.tags.populate('posts').each(function(tag){ - if (!tag.length) return; - - var posts = tag.posts.sort('date', -1).populate('categories').populate('tags'), - path = tag.path; - - if (mode === 2){ - paginator(path, posts, 'tag', render, {tag: tag.name}); - } else { - render(path, ['tag', 'archive', 'index'], _.extend({posts: posts}, {tag: tag.name})); - } - }); - - callback(); -}; \ No newline at end of file diff --git a/lib/plugins/helper/index.js b/lib/plugins/helper/index.js index 851c582f09..2ff1dce6fd 100644 --- a/lib/plugins/helper/index.js +++ b/lib/plugins/helper/index.js @@ -1,80 +1,82 @@ -var helper = hexo.extend.helper; +module.exports = function(ctx){ + var helper = ctx.extend.helper; -var date = require('./date'); + var date = require('./date'); -helper.register('date', date.date); -helper.register('date_xml', date.date_xml); -helper.register('time', date.time); -helper.register('full_date', date.full_date); -helper.register('time_tag', date.time_tag); -helper.register('moment', date.moment); + helper.register('date', date.date); + helper.register('date_xml', date.date_xml); + helper.register('time', date.time); + helper.register('full_date', date.full_date); + helper.register('time_tag', date.time_tag); + helper.register('moment', date.moment); -var form = require('./form'); + var form = require('./form'); -helper.register('search_form', form.search_form); + helper.register('search_form', form.search_form); -var format = require('./format'); + var format = require('./format'); -helper.register('strip_html', format.strip_html); -helper.register('trim', format.trim); -helper.register('titlecase', format.titlecase); -helper.register('markdown', format.markdown); -helper.register('word_wrap', format.word_wrap); -helper.register('truncate', format.truncate); -helper.register('render', format.render); + helper.register('strip_html', format.strip_html); + helper.register('trim', format.trim); + helper.register('titlecase', format.titlecase); + helper.register('markdown', format.markdown); + helper.register('word_wrap', format.word_wrap); + helper.register('truncate', format.truncate); + helper.register('render', format.render); -helper.register('fragment_cache', require('./fragment_cache')); + helper.register('fragment_cache', require('./fragment_cache')); -helper.register('gravatar', require('./gravatar')); + helper.register('gravatar', require('./gravatar')); -var is = require('./is'); + var is = require('./is'); -helper.register('is_current', is.is_current); -helper.register('is_home', is.is_home); -helper.register('is_post', is.is_post); -helper.register('is_archive', is.is_archive); -helper.register('is_year', is.is_year); -helper.register('is_month', is.is_month); -helper.register('is_category', is.is_category); -helper.register('is_tag', is.is_tag); + helper.register('is_current', is.is_current); + helper.register('is_home', is.is_home); + helper.register('is_post', is.is_post); + helper.register('is_archive', is.is_archive); + helper.register('is_year', is.is_year); + helper.register('is_month', is.is_month); + helper.register('is_category', is.is_category); + helper.register('is_tag', is.is_tag); -var list = require('./list'), - listPost = require('./list_post'); + var list = require('./list'), + listPost = require('./list_post'); -helper.register('list_categories', list.list_categories); -helper.register('list_tags', list.list_tags); -helper.register('list_archives', list.list_archives); -helper.register('list_posts', listPost.list_posts); -helper.register('get_posts', listPost.get_posts); + helper.register('list_categories', list.list_categories); + helper.register('list_tags', list.list_tags); + helper.register('list_archives', list.list_archives); + helper.register('list_posts', listPost.list_posts); + helper.register('get_posts', listPost.get_posts); -helper.register('open_graph', require('./open_graph')); + helper.register('open_graph', require('./open_graph')); -var number = require('./number'); + var number = require('./number'); -helper.register('number_format', number.number_format); + helper.register('number_format', number.number_format); -helper.register('paginator', require('./paginator')); + helper.register('paginator', require('./paginator')); -helper.register('partial', require('./partial')); + helper.register('partial', require('./partial')); -var tag = require('./tag'); + var tag = require('./tag'); -helper.register('css', tag.css); -helper.register('js', tag.js); -helper.register('link_to', tag.link_to); -helper.register('mail_to', tag.mail_to); -helper.register('image_tag', tag.image_tag); -helper.register('favicon_tag', tag.favicon_tag); -helper.register('feed_tag', tag.feed_tag); + helper.register('css', tag.css); + helper.register('js', tag.js); + helper.register('link_to', tag.link_to); + helper.register('mail_to', tag.mail_to); + helper.register('image_tag', tag.image_tag); + helper.register('favicon_tag', tag.favicon_tag); + helper.register('feed_tag', tag.feed_tag); -var tagcloud = require('./tagcloud'); + var tagcloud = require('./tagcloud'); -helper.register('tagcloud', tagcloud); -helper.register('tag_cloud', tagcloud); + helper.register('tagcloud', tagcloud); + helper.register('tag_cloud', tagcloud); -helper.register('toc', require('./toc')); + helper.register('toc', require('./toc')); -var url = require('./url'); + var url = require('./url'); -helper.register('relative_url', url.relative_url); -helper.register('url_for', url.url_for); \ No newline at end of file + helper.register('relative_url', url.relative_url); + helper.register('url_for', url.url_for); +}; \ No newline at end of file diff --git a/lib/plugins/processor/asset.js b/lib/plugins/processor/asset.js deleted file mode 100644 index ef50211f71..0000000000 --- a/lib/plugins/processor/asset.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = function(data, callback){ - var Asset = hexo.model('Asset'), - source = data.source.substring(hexo.base_dir.length), - doc = Asset.get(source); - - if (data.type === 'delete'){ - if (doc){ - hexo.route.remove(data.path); - doc.remove(); - } - - return callback(); - } - - if (doc){ - doc.path = data.path; - doc.modified = data.type === 'update'; - - doc.save(function(){ - callback(); - }); - } else { - Asset.insert({ - _id: source, - path: data.path, - modified: true - }, function(){ - callback(); - }); - } -}; \ No newline at end of file diff --git a/lib/plugins/processor/index.js b/lib/plugins/processor/index.js index 13c5cfeeed..98d1fc1cc0 100644 --- a/lib/plugins/processor/index.js +++ b/lib/plugins/processor/index.js @@ -1,51 +1,3 @@ -var pathFn = require('path'); - -var processor = hexo.extend.processor, - rHiddenFile = /^[^_](?:(?!\/_).)*$/, - rTmpFile = /[~%]$/, - postDir = '_posts/', - postDirLength = postDir.length; - -exports.regex = { - hiddenFile: rHiddenFile, - tmpFile: rTmpFile -}; - -processor.register(function(path){ - if (!hexo.render.isRenderable(path)) return false; - if (rTmpFile.test(path)) return false; - if (path.substring(0, postDirLength) !== postDir) return false; - - var str = path.substring(postDirLength); - if (!rHiddenFile.test(str)) return false; - - return {path: str}; -}, require('./post')); - -processor.register(function(path){ - if (!hexo.config.post_asset_folder) return false; - if (hexo.render.isRenderable(path)) return false; - if (rTmpFile.test(path)) return false; - if (path.substring(0, postDirLength) !== postDir) return false; - - var str = path.substring(postDirLength); - if (!rHiddenFile.test(str)) return false; - - return {path: str}; -}, require('./post_asset')); - -processor.register(function(path){ - if (rTmpFile.test(path)) return false; - if (!rHiddenFile.test(path)) return false; - if (!hexo.render.isRenderable(path)) return false; - - return true; -}, require('./page')); - -processor.register(function(path){ - if (rTmpFile.test(path)) return false; - if (!rHiddenFile.test(path)) return false; - if (hexo.render.isRenderable(path)) return false; - - return true; -}, require('./asset')); \ No newline at end of file +module.exports = function(){ + // +}; \ No newline at end of file diff --git a/lib/plugins/processor/page.js b/lib/plugins/processor/page.js deleted file mode 100644 index d81870cedb..0000000000 --- a/lib/plugins/processor/page.js +++ /dev/null @@ -1,90 +0,0 @@ -var async = require('async'), - pathFn = require('path'), - moment = require('moment'), - util = require('../../util'), - yfm = util.yfm; - -module.exports = function(data, callback){ - var Page = hexo.model('Page'), - path = data.path, - doc = Page.findOne({source: path}), - getOutput = hexo.render.getOutput; - - if (data.type === 'skip' && doc){ - return callback(); - } - - if (data.type === 'delete'){ - if (doc){ - hexo.route.remove(doc.path); - doc.remove(); - } - - return callback(); - } - - async.auto({ - stat: function(next){ - data.stat(next); - }, - read: function(next){ - data.read({cache: true}, next); - } - }, function(err, results){ - if (err) return callback(err); - - var stat = results.stat, - link = '', - meta = yfm(results.read); - - meta.content = meta._content; - delete meta._content; - - meta.source = path; - meta.raw = results.read; - - if (meta.date){ - if (!(meta.date instanceof Date)){ - meta.date = moment(meta.date, 'YYYY-MM-DD HH:mm:ss').toDate(); - } - } else { - meta.date = stat.ctime; - } - - if (meta.updated){ - if (!(meta.updated instanceof Date)){ - meta.updated = moment(meta.updated, 'YYYY-MM-DD HH:mm:ss').toDate(); - } - } else { - meta.updated = stat.mtime; - } - - if (meta.permalink){ - link = meta.permalink; - delete meta.permalink; - - if (!pathFn.extname(link)){ - link += (link[link.length - 1] === '/' ? '' : '/') + 'index.' + getOutput(path); - } - } else { - var extname = pathFn.extname(path); - link = path.substring(0, path.length - extname.length + 1) + getOutput(path); - } - - meta.path = link; - - hexo.post.render(data.source, meta, function(err, meta){ - if (err) return callback(err); - - if (doc){ - doc.replace(meta, function(){ - callback(); - }); - } else { - Page.insert(meta, function(){ - callback(); - }); - } - }); - }); -}; diff --git a/lib/plugins/processor/post.js b/lib/plugins/processor/post.js deleted file mode 100644 index 704105b91c..0000000000 --- a/lib/plugins/processor/post.js +++ /dev/null @@ -1,213 +0,0 @@ -var async = require('async'), - moment = require('moment'), - fs = require('graceful-fs'), - pathFn = require('path'), - _ = require('lodash'), - url = require('url'), - util = require('../../util'), - yfm = util.yfm, - escape = util.escape, - Permalink = util.permalink, - file = util.file2; - -var rBasename = /((.*)\/)?([^\/]+)\.(\w+)$/, - regex = require('./index').regex, - preservedKeys = ['title', 'year', 'month', 'day', 'i_month', 'i_day'], - permalink; - -var scanAssetDir = function(post, callback){ - var assetDir = post.asset_dir, - postPath = post.path, - baseDir = hexo.base_dir, - baseDirLength = baseDir.length, - Asset = hexo.model('Asset'), - Post = hexo.model('Post'); - - fs.exists(assetDir, function(exist){ - if (!exist) return callback(); - - file.list(assetDir, function(err, files){ - if (err) return callback(err); - - async.each(files, function(item, next){ - if (!regex.hiddenFile.test(item)) return next(); - if (regex.tmpFile.test(item)) return next(); - - var source = pathFn.join(assetDir, item).substring(baseDirLength), - asset = Asset.get(source); - - if (asset){ - if (asset.post_path === postPath) return next(); - - hexo.route.remove(asset.path); - - asset.update({ - path: url.resolve(postPath, item), - modified: true, - post_id: post._id, - postPath: postPath - }, function(){ - next(); - }); - } else { - Asset.insert({ - _id: source, - path: url.resolve(postPath, item), - modified: true, - post_id: post._id, - post_path: postPath - }, function(){ - next(); - }); - } - }, callback); - }); - }); -}; - -module.exports = function(data, callback){ - var config = hexo.config, - path = data.params.path; - - var Post = hexo.model('Post'), - doc = Post.findOne({source: data.path}); - - if (data.type === 'skip' && doc){ - return callback(); - } - - if (data.type === 'delete'){ - if (doc){ - hexo.route.remove(doc.path); - doc.remove(); - } - - return callback(); - } - - async.auto({ - stat: function(next){ - data.stat(next); - }, - read: function(next){ - data.read(next); - }, - post: ['stat', 'read', function(next, results){ - var stat = results.stat, - meta = yfm(results.read); - - meta.content = meta._content; - delete meta._content; - - meta.source = data.path; - meta.raw = results.read; - meta.slug = escape.path(path.match(rBasename)[3]); - - var newPostName = config.new_post_name; - newPostName = newPostName.substring(0, newPostName.length - pathFn.extname(newPostName).length); - - if (!permalink || permalink.rule !== newPostName){ - permalink = new Permalink(newPostName, { - segments: { - year: /(\d{4})/, - month: /(\d{2})/, - day: /(\d{2})/, - i_month: /(\d{1,2})/, - i_day: /(\d{1,2})/ - } - }); - } - - var filenameInfo = permalink.parse(path.substring(0, path.length - pathFn.extname(path).length)); - - if (filenameInfo){ - if (filenameInfo.title) meta.slug = filenameInfo.title; - - _.difference(Object.keys(filenameInfo), preservedKeys).forEach(function(i){ - meta[i] = filenameInfo[i]; - }); - } - - if (meta.date){ - if (!(meta.date instanceof Date)){ - meta.date = moment(meta.date, 'YYYY-MM-DD HH:mm:ss').toDate(); - } - } else { - if (filenameInfo && filenameInfo.year && (filenameInfo.month || filenameInfo.i_month) && (filenameInfo.day || filenameInfo.i_day)){ - meta.date = new Date( - filenameInfo.year, - parseInt(filenameInfo.month || filenameInfo.i_month, 10) - 1, - parseInt(filenameInfo.day || filenameInfo.i_day, 10) - ); - } else { - meta.date = stat.ctime; - } - } - - if (meta.updated){ - if (!(meta.updated instanceof Date)){ - meta.updated = moment(meta.updated, 'YYYY-MM-DD HH:mm:ss').toDate(); - } - } else { - meta.updated = stat.mtime; - } - - if (meta.permalink){ - var link = meta.slug = escape.path(meta.permalink); - if (!pathFn.extname(link) && link[link.length - 1] !== '/') link += '/'; - delete meta.permalink; - } - - if (meta.category){ - meta.categories = meta.category; - delete meta.category; - } - - if (meta.categories && !Array.isArray(meta.categories)){ - meta.categories = [meta.categories]; - } - - if (meta.tag){ - meta.tags = meta.tag; - delete meta.tag; - } - - if (meta.tags && !Array.isArray(meta.tags)){ - meta.tags = [meta.tags]; - } - - if (meta.photo){ - meta.photos = meta.photo; - delete meta.photo; - } - - if (meta.photos && !Array.isArray(meta.photos)){ - meta.photos = [meta.photos]; - } - - if (meta.link && !meta.title){ - meta.title = meta.link - .replace(/^https?:\/\/|\/$/g, ''); - } - - hexo.post.render(data.source, meta, function(err, meta){ - if (err) return next(err); - - if (doc){ - doc.replace(meta, function(post){ - next(null, post); - }); - } else { - Post.insert(meta, function(post){ - next(null, post); - }); - } - }); - }], - asset: ['post', function(next, results){ - if (!config.post_asset_folder) return next(); - - scanAssetDir(results.post, next); - }] - }, callback); -}; \ No newline at end of file diff --git a/lib/plugins/processor/post_asset.js b/lib/plugins/processor/post_asset.js deleted file mode 100644 index f36b491bb1..0000000000 --- a/lib/plugins/processor/post_asset.js +++ /dev/null @@ -1,71 +0,0 @@ -var url = require('url'), - util = require('../../util'), - escape = util.escape; - -module.exports = function(data, callback){ - var path = data.params.path, - source = data.source.substring(hexo.base_dir.length); - - var Asset = hexo.model('Asset'), - Post = hexo.model('Post'), - doc = Asset.get(source); - - if (data.type === 'skip' && doc){ - return callback(); - } - - if (data.type === 'delete'){ - if (doc){ - hexo.route.remove(doc.path); - doc.remove(); - } - - return callback(); - } - - var post, - postPath; - - if (doc){ - post = Post.get(doc.post_id); - postPath = post.path; - - if (post){ - doc.update({ - path: url.resolve(postPath, data.source.substring(post.asset_dir.length)), - modified: data.type === 'update', - post_path: postPath - }, function(){ - callback(); - }); - } else { - doc.remove(function(){ - callback(); - }); - } - } else { - var posts = Post.toArray(); - - for (var i = 0, len = posts.length; i < len; i++){ - post = posts[i]; - - if (new RegExp('^' + escape.regex(post.asset_dir)).test(data.source)){ - break; - } - } - - if (!post) return callback(); - - postPath = post.path; - - Asset.insert({ - _id: source, - path: url.resolve(postPath, data.source.substring(post.asset_dir.length)), - modified: true, - post_path: postPath, - post_id: post._id - }, function(){ - callback(); - }); - } -}; \ No newline at end of file diff --git a/lib/plugins/renderer/index.js b/lib/plugins/renderer/index.js index 2d24588227..9f26879d0b 100644 --- a/lib/plugins/renderer/index.js +++ b/lib/plugins/renderer/index.js @@ -1,13 +1,15 @@ -var renderer = hexo.extend.renderer; +module.exports = function(ctx){ + var renderer = ctx.extend.renderer; -var html = require('./html'); + var html = require('./html'); -renderer.register('htm', 'html', html, true); -renderer.register('html', 'html', html, true); + renderer.register('htm', 'html', html, true); + renderer.register('html', 'html', html, true); -renderer.register('swig', 'html', require('./swig'), true); + renderer.register('swig', 'html', require('./swig'), true); -var yml = require('./yaml'); + var yml = require('./yaml'); -renderer.register('yml', 'json', yml, true); -renderer.register('yaml', 'json', yml, true); \ No newline at end of file + renderer.register('yml', 'json', yml, true); + renderer.register('yaml', 'json', yml, true); +}; \ No newline at end of file diff --git a/lib/plugins/tag/index.js b/lib/plugins/tag/index.js index 6674cb6c34..f81fab784b 100644 --- a/lib/plugins/tag/index.js +++ b/lib/plugins/tag/index.js @@ -1,41 +1,48 @@ -var tag = hexo.extend.tag; +/* +module.exports = function(ctx){ + var tag = ctx.extend.tag; -var blockquote = require('./blockquote'); + var blockquote = require('./blockquote'); -tag.register('quote', blockquote, {ends: true, escape: false}); -tag.register('blockquote', blockquote, {ends: true, escape: false}); + tag.register('quote', blockquote, {ends: true, escape: false}); + tag.register('blockquote', blockquote, {ends: true, escape: false}); -var code = require('./code'); + var code = require('./code'); -tag.register('code', code, true); -tag.register('codeblock', code, true); + tag.register('code', code, true); + tag.register('codeblock', code, true); -tag.register('gist', require('./gist')); + tag.register('gist', require('./gist')); -tag.register('iframe', require('./iframe')); + tag.register('iframe', require('./iframe')); -var img = require('./img'); + var img = require('./img'); -tag.register('img', img); -tag.register('image', img); + tag.register('img', img); + tag.register('image', img); -var include_code = require('./include_code'); + var include_code = require('./include_code'); -tag.register('include_code', include_code); -tag.register('include-code', include_code); + tag.register('include_code', include_code); + tag.register('include-code', include_code); -tag.register('jsfiddle', require('./jsfiddle')); + tag.register('jsfiddle', require('./jsfiddle')); -var link = require('./link'); + var link = require('./link'); -tag.register('a', link); -tag.register('link', link); -tag.register('anchor', link); + tag.register('a', link); + tag.register('link', link); + tag.register('anchor', link); -tag.register('pullquote', require('./pullquote'), {ends: true, escape: false}); + tag.register('pullquote', require('./pullquote'), {ends: true, escape: false}); -tag.register('rawblock', require('./raw'), true); + tag.register('rawblock', require('./raw'), true); -tag.register('vimeo', require('./vimeo')); + tag.register('vimeo', require('./vimeo')); -tag.register('youtube', require('./youtube')); \ No newline at end of file + tag.register('youtube', require('./youtube')); +};*/ + +module.exports = function(ctx){ + // +}; \ No newline at end of file diff --git a/lib/post/create.js b/lib/post/create.js deleted file mode 100644 index a99c3870df..0000000000 --- a/lib/post/create.js +++ /dev/null @@ -1,125 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - path = require('path'), - moment = require('moment'), - swig = require('swig'), - util = require('../util'), - file = util.file2, - yfm = util.yfm, - escape = util.escape; - -var preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content']; - -swig.setDefaults({ - autoescape: false -}); - -/** -* Creates a new post. -* -* @method create -* @param {Object} data -* @param {String} data.title Post title -* @param {String} [data.slug] Post slug. If not defined, the value will be escaped `data.title`. -* @param {String} [data.path] Post path. If not defined, the value will be `data.slug`. -* @param {String} [data.layout] Post layout. If not defined, the value will be `default_layout` in global configuration. -* @param {Date} [data.date] Post date. If not defined, the value will be `Date.now()`. -* @param {String} [data.content] Post content. -* @param {Boolean} [replace=false] Determines whether to replace existing data. -* @param {Function} callback -* @for post -* @static -*/ - -module.exports = function(data, replace, callback){ - if (!callback){ - if (typeof replace === 'function'){ - callback = replace; - replace = false; - } else { - callback = function(){}; - } - } - - var config = hexo.config, - slug = data.slug = escape.filename(data.slug || data.title, config.filename_case), - layout = data.layout = (data.layout || config.default_layout).toLowerCase(), - date = data.date = data.date ? moment(data.date) : moment(); - - async.auto({ - filename: function(next){ - hexo.extend.filter.apply('new_post_path', [data, replace], function(err, results){ - if (err) return next(err); - next(null, results[results.length - 1]); - }); - }, - scaffold: function(next){ - hexo.scaffold.get(layout, function(err, result){ - if (err) return next(err); - if (result != null) return next(null, result); - - hexo.scaffold.get('normal', next); - }); - }, - content: ['filename', 'scaffold', function(next, results){ - data.title = '"' + data.title + '"'; - data.date = date.format('YYYY-MM-DD HH:mm:ss'); - - var split = yfm.split(results.scaffold), - frontMatter = swig.compile(split.data)(data), - compiled = yfm([frontMatter, '---', split.content].join('\n')); - - for (var i in data){ - if (!~preservedKeys.indexOf(i)){ - compiled[i] = data[i]; - } - } - - if (data.content){ - compiled._content += '\n' + data.content; - } - - var content = yfm.stringify(compiled); - - file.writeFile(results.filename, content, function(err){ - if (err) return callback(err); - - next(null, content); - }); - }], - folder: ['filename', function(next, results){ - if (!config.post_asset_folder) return next(); - - var filename = results.filename, - target = filename.substring(0, filename.length - path.extname(filename).length); - - fs.exists(target, function(exist){ - if (exist){ - file.rmdir(target, function(err){ - if (err) return next(err); - - file.mkdirs(target, next); - }); - } else { - file.mkdirs(target, next); - } - }); - }], - event: ['filename', 'content', 'folder', function(next, results){ - /** - * Fired when a new post created. - * - * @event new - * @param {String} path The full path of the new post - * @param {String} content The content of the new post - * @for Hexo - */ - hexo.emit('new', results.filename, results.content); - next(); - }] - }, function(err, results){ - if (err) return callback(err); - - callback(null, results.filename, results.content); - }); -}; \ No newline at end of file diff --git a/lib/post/index.js b/lib/post/index.js deleted file mode 100644 index e16aae209e..0000000000 --- a/lib/post/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** -* Post functions. -* -* @class post -* @module hexo -* @static -*/ - -exports.create = require('./create'); -exports.render = require('./render'); -exports.load = require('./load'); -exports.publish = require('./publish'); \ No newline at end of file diff --git a/lib/post/load.js b/lib/post/load.js deleted file mode 100644 index adb6a4437a..0000000000 --- a/lib/post/load.js +++ /dev/null @@ -1,82 +0,0 @@ -var _ = require('lodash'), - async = require('async'); - -/** -* Loads post files. -* -* This function does: -* -* - {% crosslink theme/process %}: Processes theme files -* - {% crosslink theme/watch %}: Watches theme files (When `options.watch` is true) -* - {% crosslink source/process %}: Processes source files -* - {% crosslink source/watch %}: Watches source files (When `options.watch` is true) -* - {% crosslink theme/generate %}: Runs generators -* -* @method load -* @param {Object} [options] -* @param {Boolean} [options.watch=false] Watch source files -* @param {Function} [callback] -* @for post -* @static -*/ - -module.exports = function(options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - watch: false - }, options); - - var isReady = false; - - async.auto({ - process_theme: function(next){ - hexo.theme.process(next); - }, - watch_theme: ['process_theme', function(next){ - if (options.watch){ - hexo.theme.watch(); - - hexo.on('processAfter', function(path){ - if (isReady && path === hexo.theme_dir){ - hexo.theme.generate(options); - } - }); - } - - next(); - }], - process_source: function(next){ - hexo.source.process(next); - }, - watch_source: ['process_source', function(next){ - if (options.watch){ - hexo.source.watch(); - - hexo.on('processAfter', function(path){ - if (isReady && path === hexo.source_dir){ - hexo.theme.generate(options); - } - }); - } - - next(); - }] - }, function(err){ - if (err) return callback(err); - - hexo.theme.generate(options, function(err){ - if (err) return callback(err); - - isReady = true; - callback(); - }); - }); -}; \ No newline at end of file diff --git a/lib/post/publish.js b/lib/post/publish.js deleted file mode 100644 index f7323b8f12..0000000000 --- a/lib/post/publish.js +++ /dev/null @@ -1,117 +0,0 @@ -var async = require('async'), - path = require('path'), - fs = require('graceful-fs'), - _ = require('lodash'), - util = require('../util'), - file = util.file2, - yfm = util.yfm, - escape = util.escape; - -var removeExtname = function(str){ - return str.substring(0, str.length - path.extname(str).length); -}; - -/** -* Publish a draft. -* -* @method publish -* @param {Object} data -* @param {String} data.slug -* @param {String} [data.layout] -*/ - -module.exports = function(data, replace, callback){ - if (!callback){ - if (typeof replace === 'function'){ - callback = replace; - replace = false; - } else { - callback = function(){}; - } - } - - if (data.layout === 'draft') data.layout = 'post'; - - var config = hexo.config, - draftDir = path.join(hexo.source_dir, '_drafts'), - slug = data.slug = escape.filename(data.slug, config.filename_case), - layout = data.layout = (data.layout || config.default_layout).toLowerCase(); - - async.auto({ - // Find draft - src: function(next){ - file.list(draftDir, function(err, files){ - if (err) return next(err); - - var regex = new RegExp('^' + escape.regex(slug)); - src = ''; - - for (var i = 0, len = files.length; i < len; i++){ - var name = files[i]; - - if (regex.test(name)){ - src = path.join(draftDir, name); - break; - } - } - - if (src){ - next(null, src); - } else { - next(new Error('Draft "' + slug + '" does not exist.')); - } - }); - }, - // Create post - post: ['src', function(next, results){ - file.readFile(results.src, function(err, content){ - if (err) return next(err); - - _.extend(data, yfm(content)); - - data.content = data._content; - delete data._content; - - hexo.post.create(data, replace, function(err, src, content){ - if (err) return next(err); - - next(null, { - path: src, - content: content - }); - }); - }); - }], - // Remove draft file - removePost: ['post', function(next, results){ - fs.unlink(results.src, next); - }], - // Copy assets - asset: ['src', 'post', function(next, results){ - if (!config.post_asset_folder) return next(); - - var src = removeExtname(results.src), - dest = removeExtname(results.post.path); - - fs.exists(src, function(exist){ - if (!exist) return next(); - - file.copyDir(src, dest, function(err){ - if (err) return next(err); - - next(null, src); - }); - }); - }], - // Remove original assets - removeAsset: ['asset', function(next, results){ - if (!results.asset) return next(); - - file.rmdir(results.asset, next); - }] - }, function(err, results){ - if (err) return callback(err); - - callback(null, results.src, results.post.content); - }); -}; \ No newline at end of file diff --git a/lib/post/render.js b/lib/post/render.js deleted file mode 100644 index 59424a50d9..0000000000 --- a/lib/post/render.js +++ /dev/null @@ -1,127 +0,0 @@ -var async = require('async'), - swig = require('swig'), - _ = require('lodash'), - isReady = false; - -var rEscapeContent = /<escape( indent=['"](\d+)['"])?>([\s\S]+?)<\/escape>/g, - rUnescape = /<hexoescape>(\d+)<\/hexoescape>/g; - -swig.setDefaults({autoescape: false}); - -/** -* Renders post contents. -* -* Rendering flow: -* -* - Compiles with Swig -* - Runs before_post_render filter -* - Compiles with Markdown (or other render engines, depends on the extension name of source files) -* - Runs after_post_render filter -* -* @method render -* @param {String} source The path of source file -* @param {Object} data -* @param {Function} callback -* @for post -* @static -*/ - -module.exports = function(source, data, callback){ - var extend = hexo.extend, - filter = extend.filter, - render = hexo.render.render; - - // Loads tag plugins - if (!isReady){ - extend.tag.list().forEach(function(tag){ - swig.setTag(tag.name, tag.parse, tag.compile, tag.ends, true); - }); - - isReady = true; - } - - // Replaces <escape>content</escape> in raw content with `<hexoescape>cache id</hexoescape>` - var escapeContent = function(){ - var indent = parseInt(arguments[2], 10), - str = arguments[3], - out = ''; - - out += '<hexoescape>' + cache.length + '</hexoescape>'; - cache.push(str); - - return out; - }; - - var cache = []; - - async.series([ - // Renders content with Swig - function(next){ - try { - data.content = swig.render(data.content, { - locals: data, - filename: source - }); - } catch (err){ - return callback(err); - } - - // Replaces contents in `<escape>` tag and saves them in cache - data.content = data.content.replace(rEscapeContent, escapeContent); - - next(); - }, - function(next){ - // Pre filters - async.eachSeries(filter.list('before_post_render'), function(filter, next){ - filter(data, function(err, result){ - if (err) return callback(err); - - if (result){ - // Replaces contents in `<escape>` tag and saves them in cache - result.content = result.content.replace(rEscapeContent, escapeContent); - - data = result; - } - - next(); - }); - }, next); - }, - function(next){ - var options = data.markdown || {}; - - if (!hexo.config.highlight.enable){ - options.highlight = null; - } - - // Renders with Markdown or other render engines. - render({text: data.content, path: source, engine: data.engine}, options, function(err, result){ - if (err) return callback(err); - - // Replaces cache data with real contents. - data.content = result.replace(rUnescape, function(match, number){ - return cache[number]; - }); - - // Clean cache - cache.length = 0; - - // Post filters - async.eachSeries(filter.list('after_post_render'), function(filter, next){ - filter(data, function(err, result){ - if (err) return callback(err); - - if (result){ - data = result; - } - - next(); - }); - }, next); - }); - } - ], function(err){ - callback(err, data); - }); -}; \ No newline at end of file diff --git a/lib/theme/index.js b/lib/theme/index.js deleted file mode 100644 index d199ae4c67..0000000000 --- a/lib/theme/index.js +++ /dev/null @@ -1,265 +0,0 @@ -var pathFn = require('path'), - _ = require('lodash'), - async = require('async'), - colors = require('colors'), - domain = require('domain'), - Box = require('../box'), - i18n = require('../core/i18n'), - HexoError = require('../error'); - -/** -* This module manages all files in the theme folder. -* -* @class Theme -* @constructor -* @module hexo -* @extend Box -*/ - -var Theme = module.exports = function Theme(){ - Box.call(this, hexo.theme_dir); - - /** - * Theme configuration. - * - * @property config - * @type Object - */ - this.config = {}; - - /** - * The i18n instance of the theme. - * - * @property i18n - * @type i18n - */ - this.i18n = new i18n({ - code: hexo.config.language - }); - - /** - * The view collection of the theme. - * - * @property views - * @type Object - */ - this.views = {}; - - this.processors = [ - require('./processors/config'), - require('./processors/i18n'), - require('./processors/view'), - require('./processors/source') - ]; -}; - -Theme.prototype.__proto__ = Box.prototype; - -Theme.prototype.load = Box.prototype.process; - -Theme.prototype._generate = function(options, callback){ - options = _.extend({ - watch: false - }, options); - - var model = hexo.model, - config = hexo.config, - route = hexo.route, - siteLocals = hexo.locals._generate(), - themeLocals = _.extend({}, config, this.config, config.theme_config), - env = hexo.env, - i18n = this.i18n, - layoutDir = pathFn.join(this.base, 'layout') + pathFn.sep, - self = this, - d = domain.create(), - called = false; - - hexo._themeConfig = themeLocals; - - // calculate a language tag out off 'page', 'path' and config - var ensureLanguage = function(page, path) { - var lang = page.lang || ''; - - // if a post or page defines a 'lang' value, then use it - if (!lang) { - // if not, calculate a value - if (config.language) { - if (_.isArray(config.language)) { - // the first configured language is the default language - lang = config.language[0]; - - // detection only makes sence if we have more than one language - if (config.language_detect_in_path) { - // ensure path starts with a '/' - var _path = path; - if (_path.indexOf('/') !== 0) _path = '/' + _path; - - // search for all configured languages as '/[language]/' in path - var length = config.language.length; - var _lang = ''; - var only_first_level = config.language_detect_first_level || 0; - - for (var i = 0; i < length; i++){ - var l = config.language[i]; - if (only_first_level) { - // search only the first level in the path - if (_path.indexOf('/'+l+'/') === 0) { - _lang = l; - break; - } - } else { - // search all level in the path - if (_path.indexOf('/'+l+'/') >= 0) { - _lang = l; - break; - } - } - } - - // only change default language if we have a positive detection - if (_lang) lang = _lang; - } - } else { - lang = config.language; - } - } - } - - // still no language found? - // use 'default' for compatibility with languages/defaul.yml - if (lang === '') { lang = 'default'; } - - return lang; - }; - - var Locals = function(path, locals){ - this.page = _.extend({path: path}, locals); - // every page should have a 'lang' value - this.page.lang = ensureLanguage(this.page, path); - this.path = path; - this.url = config.url + config.root + path; - this.site = siteLocals; - this.config = config; - this.theme = themeLocals; - this._ = _; - this.__ = i18n.__(); - this._p = i18n._p(); - this.layout = 'layout'; - this.cache = !options.watch; - this.env = hexo.env; - this.view_dir = layoutDir; - }; - - d.on('error', function(err){ - !called && callback(err); - called = true; - }); - - var generators = hexo.extend.generator.list(), - keys = Object.keys(generators), - excludeGenerator = config.exclude_generator || []; - - async.each(keys, function(key, next){ - if (~excludeGenerator.indexOf(key)) return next(); - - var start = Date.now(), - generator = generators[key]; - - d.add(generator); - - d.run(function(){ - generator(siteLocals, function(path, layouts, locals){ - if (!Array.isArray(layouts)) layouts = [layouts]; - - layouts = _.uniq(layouts); - - var newLocals = new Locals(path, locals); - - route.set(path, function(fn){ - var view; - - for (var i = 0, len = layouts.length; i < len; i++){ - view = self.getView(layouts[i]); - if (view) break; - } - - if (view){ - hexo.log.d('Render %s: %s', layouts[i], path); - view.render(newLocals, fn); - } else { - hexo.log.w('No layout: %s', path); - fn(); - } - }); - }, function(err){ - d.remove(generator); - hexo.log.d('Generator: %s ' + '(%dms)'.grey, key, Date.now() - start); - next(err); - }); - }); - }, function(err){ - !called && callback(err); - called = true; - }); -}; - -/** -* Runs generators. -* -* @method generate -* @param {Object} [options] -* @param {Boolean} [options.watch=false] -* @param {Function} [callback] -* @async -*/ -Theme.prototype.generate = function(options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - var self = this; - - async.series([ - // Save database - function(next){ - hexo.model.save(pathFn.join(hexo.base_dir, 'db.json'), function(err){ - if (err) return next(HexoError.wrap(err, 'Database save failed')); - - next(); - }); - }, - // Run generators - function(next){ - self._generate(options, callback); - } - ], callback); -}; - -/** -* Gets a view. -* -* @method getView -* @param {String} path -* @return {Theme.View} -*/ -Theme.prototype.getView = function(path){ - // Replace backslashes on Windows - path = path.replace(/\\/g, '/'); - - var extname = pathFn.extname(path), - name = path.substring(0, path.length - extname.length), - views = this.views[name]; - - if (!views) return; - - if (extname){ - return views[extname]; - } else { - return views[Object.keys(views)[0]]; - } -}; diff --git a/lib/theme/processors/config.js b/lib/theme/processors/config.js deleted file mode 100644 index e0e6d832f9..0000000000 --- a/lib/theme/processors/config.js +++ /dev/null @@ -1,20 +0,0 @@ -var HexoError = require('../../error'), - Pattern = require('../../box/pattern'); - -exports.process = function(data, callback){ - if (data.type === 'delete'){ - data.box.config = {}; - return callback(); - } - - data.render(function(err, result){ - if (err) return callback(HexoError.wrap(err, 'Theme config load failed')); - - data.box.config = result; - - hexo.log.d('Theme config loaded'); - callback(); - }); -}; - -exports.pattern = new Pattern(/^_config\.(yml|yaml)$/); \ No newline at end of file diff --git a/lib/theme/processors/i18n.js b/lib/theme/processors/i18n.js deleted file mode 100644 index 943ca466d8..0000000000 --- a/lib/theme/processors/i18n.js +++ /dev/null @@ -1,23 +0,0 @@ -var pathFn = require('path'), - Pattern = require('../../box/pattern'); - -exports.process = function(data, callback){ - var path = data.params.path, - extname = pathFn.extname(path), - name = path.substring(0, path.length - extname.length), - i18n = data.box.i18n; - - if (data.type === 'delete'){ - i18n.remove(name); - return callback(); - } - - data.render(function(err, result){ - if (err) return callback(err); - - i18n.set(name, result); - callback(); - }); -}; - -exports.pattern = new Pattern('languages/*path'); \ No newline at end of file diff --git a/lib/theme/processors/source.js b/lib/theme/processors/source.js deleted file mode 100644 index 5d4d6bfb2a..0000000000 --- a/lib/theme/processors/source.js +++ /dev/null @@ -1,41 +0,0 @@ -var HexoError = require('../../error'), - Pattern = require('../../box/pattern'); - -var rHiddenFile = /\/_/; - -exports.process = function(data, callback){ - if (rHiddenFile.test(data.path)) return callback(); - - var Asset = hexo.model('Asset'), - source = data.source.substring(hexo.base_dir.length), - path = data.params.path, - doc = Asset.get(source); - - if (data.type === 'delete'){ - if (doc){ - hexo.route.remove(path); - doc.remove(); - } - - return callback(); - } - - if (doc){ - doc.path = path; - doc.modified = data.type === 'update'; - - doc.save(function(){ - callback(); - }); - } else { - Asset.insert({ - _id: source, - path: path, - modified: true - }, function(){ - callback(); - }); - } -}; - -exports.pattern = new Pattern('source/*path'); \ No newline at end of file diff --git a/lib/theme/processors/view.js b/lib/theme/processors/view.js deleted file mode 100644 index aec95be0dd..0000000000 --- a/lib/theme/processors/view.js +++ /dev/null @@ -1,33 +0,0 @@ -var pathFn = require('path'), - Pattern = require('../../box/pattern'), - View = require('../view'), - util = require('../../util'), - yfm = util.yfm; - -exports.process = function(data, callback){ - var path = data.params.path, - extname = pathFn.extname(path), - name = path.substring(0, path.length - extname.length), - views = data.box.views; - - if (!views.hasOwnProperty(name)) views[name] = {}; - - var view = views[name][extname]; - - if (data.type === 'delete'){ - view = null; - return callback(); - } - - data.read(function(err, result){ - if (err) return callback(err); - if (!view) view = views[name][extname] = new View(data.source, path, data.box); - - view.data = yfm(result); - view.invalidate(); - - callback(); - }); -}; - -exports.pattern = new Pattern('layout/*path'); \ No newline at end of file diff --git a/lib/theme/view.js b/lib/theme/view.js deleted file mode 100644 index 1e055617ac..0000000000 --- a/lib/theme/view.js +++ /dev/null @@ -1,209 +0,0 @@ -var async = require('async'), - pathFn = require('path'), - _ = require('lodash'); - -/** -* The view object of the Theme class. -* -* @class View -* @param {String} source The full source of the view file. -* @param {String} path The relative path of the view file. -* @param {Theme} theme -* @constructor -* @namespace Theme -* @module hexo -*/ -var View = module.exports = function View(source, path, theme){ - /** - * The full path of the view file. - * - * @property source - * @type {String} - */ - this.source = source; - - /** - * The relative path of the view file. - * - * @property path - * @type {String} - */ - this.path = path; - - /** - * The extension name of the view file. (With a prefixed dot) - * - * @property extname - * @type {String} - */ - this.extname = pathFn.extname(path); - - /** - * The theme object. - * - * @property theme - * @type {Theme} - */ - this.theme = theme; - - /** - * View data. - * - * @property data - * @type {Object} - */ - this.data = null; - - /** - * View cache. - * - * @property cache - * @type {Object} - * @private - */ - this.cache = null; -}; - -/** -* Renders the view. -* -* @method render -* @param {Object} [options] -* @param {Boolean} [options.cache=true] -* @param {Function} callback -*/ -View.prototype.render = function(options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - cache: true - }, options); - - var data = this.data; - if (!data) return callback(); - - var layout = data.hasOwnProperty('layout') ? data.layout : options.layout, - locals = _.extend(this._buildLocals(options), _.omit(data, 'layout', '_content'), {filename: this.source}), - self = this; - - hexo.render.render({ - path: this.source, - text: data._content - }, locals, function(err, result){ - if (err) return callback(err); - if (!layout) return callback(null, result); - - var layoutView = self._resolveLayout(layout); - - if (layoutView){ - var layoutLocals = _.extend({}, locals, {body: result, layout: false}); - - layoutView.render(layoutLocals, callback); - } else { - callback(null, result); - } - }); -}; - -/** -* Resolves the layout path. -* -* @method _resolveLayout -* @param {String} name -* @return {View} -* @private -*/ -View.prototype._resolveLayout = function(name){ - // Relative path - var layoutView = this.theme.getView(pathFn.join(pathFn.dirname(this.path), name)); - - if (layoutView && layoutView.source === this.source) layoutView = null; - - // Absolute path - if (!layoutView){ - layoutView = this.theme.getView(name); - - if (layoutView && layoutView.source === this.source) layoutView = null; - } - - return layoutView; -}; - -/** -* Clones the object and binds the helper functions. -* -* @method _buildLocals -* @param {Object} locals -* @return {Object} -* @private -*/ -View.prototype._buildLocals = function(locals){ - var helpers = hexo.extend.helper.list(), - obj = {}; - - _.each(helpers, function(helper, name){ - obj[name] = helper.bind(obj); - }); - - _.each(locals, function(fn, name){ - if (!helpers.hasOwnProperty(name) || helpers[name].toString() === fn.toString()){ - obj[name] = fn; - } - }); - - return obj; -}; - -/** -* Renders the view synchronizedly. -* -* @method renderSync -* @param {Object} [options] -* @param {Boolean} [options.cache=true] -* @return {String} -*/ -View.prototype.renderSync = function(options){ - options = _.extend({ - cache: true - }, options); - - var data = this.data; - if (!data) return; - - var layout = data.hasOwnProperty('layout') ? data.layout : options.layout, - locals = _.extend(this._buildLocals(options), _.omit(data, 'layout', '_content'), {filename: this.source}); - - var result = hexo.render.renderSync({ - path: this.source, - text: data._content - }, locals); - - if (!result) return; - if (!layout) return result; - - var layoutView = this._resolveLayout(layout); - - if (layoutView){ - var layoutLocals = _.extend({}, locals, {body: result, layout: false}); - - return layoutView.renderSync(layoutLocals); - } else { - return result; - } -}; - -/** -* Invalidates the cache. -* -* @method invalidate -*/ -View.prototype.invalidate = function(){ - this.cache = null; -}; \ No newline at end of file diff --git a/lib/util/escape.js b/lib/util/escape.js index 96ee987dc4..7589a86169 100644 --- a/lib/util/escape.js +++ b/lib/util/escape.js @@ -19,15 +19,18 @@ * @static */ -var FILE_NAME_ESCAPE_REGEX = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g; -var CONTINUES_DASH_REGEX = /-{1,}/g; -var TAIL_DASH_REGEX = /-+$/; +var rFilenameEscape = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g, + rContinuesDash = /-{1,}/g, + rTailDash = /-+$/; + +var EOL = require('os').EOL, + rEOL = new RegExp(EOL, 'g'); exports.filename = function(str, transform){ var result = exports.diacritic(str.toString()) - .replace(FILE_NAME_ESCAPE_REGEX, '-') - .replace(CONTINUES_DASH_REGEX, '-') - .replace(TAIL_DASH_REGEX, ''); + .replace(rFilenameEscape, '-') + .replace(rContinuesDash, '-') + .replace(rTailDash, ''); transform = parseInt(transform, 10); @@ -56,8 +59,8 @@ exports.filename = function(str, transform){ exports.path = function(str, transform){ var result = str.toString() - .replace(FILE_NAME_ESCAPE_REGEX, '-') - .replace(CONTINUES_DASH_REGEX, '-'); + .replace(rFilenameEscape, '-') + .replace(rContinuesDash, '-'); transform = parseInt(transform, 10); @@ -206,4 +209,12 @@ exports.diacritic = function(str){ return str.replace(/[^\u0000-\u007E]/g, function(a){ return diacriticsMap[a] || a; }); +}; + +exports.eol = function(str){ + return EOL === '\n' ? str : str.replace(rEOL, '\n'); +}; + +exports.bom = function(str){ + return str.replace(/^\uFEFF/, ''); }; \ No newline at end of file diff --git a/lib/util/exec.js b/lib/util/exec.js deleted file mode 100644 index 40d20089ba..0000000000 --- a/lib/util/exec.js +++ /dev/null @@ -1,40 +0,0 @@ -var exec = require('child_process').exec; - -/** -* Runs a command in a shell and buffers the output. -* -* @method exec -* @param {Object} options -* @param {String} options.command The command to run, with space-separated arguments -* @param {Object} [options.options] See [child_process.exec](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) -* @param {Function} options.callback -* @for util -* @static -*/ - -module.exports = function(options){ - var comm = exec(options.command, options.options, options.callback); - - comm.stdout.setEncoding('utf8'); - comm.stderr.setEncoding('utf8'); - - if (options.stdout){ - comm.stdout.on('data', options.stdout); - } else { - comm.stdout.on('data', function(data){ - process.stdout.write(data); - }); - } - - if (options.stderr){ - comm.stderr.on('data', options.stderr); - } else { - comm.stderr.on('data', function(data){ - process.stderr.write(data); - }); - } - - if (options.exit){ - comm.on('exit', options.exit); - } -}; diff --git a/lib/util/format.js b/lib/util/format.js index e761792862..f750ac5d2d 100644 --- a/lib/util/format.js +++ b/lib/util/format.js @@ -44,7 +44,7 @@ exports.trim = function(content){ * @static */ -exports.titlecase = require('./inflector').titlecase; +exports.titlecase = require('inflection').titleize; /** * Wraps the `text` into lines no longer than `width`. diff --git a/lib/util/fs.js b/lib/util/fs.js new file mode 100644 index 0000000000..50fd3bab03 --- /dev/null +++ b/lib/util/fs.js @@ -0,0 +1,403 @@ +var Promise = require('bluebird'); +var fs = require('graceful-fs'); +var pathFn = require('path'); +var escape = require('./escape'); + +var dirname = pathFn.dirname; +var join = pathFn.join; +var escapeEOL = escape.eol; +var escapeBOM = escape.bom; + +var statAsync = Promise.promisify(fs.stat); +var readdirAsync = Promise.promisify(fs.readdir); +var unlinkAsync = Promise.promisify(fs.unlink); +var mkdirAsync = Promise.promisify(fs.mkdir); +var renameAsync = Promise.promisify(fs.rename); +var writeFileAsync = Promise.promisify(fs.writeFile); +var appendFileAsync = Promise.promisify(fs.appendFile); +var rmdirAsync = Promise.promisify(fs.rmdir); +var readFileAsync = Promise.promisify(fs.readFile); +var createReadStream = fs.createReadStream; +var createWriteStream = fs.createWriteStream; + +function exists(path){ + return new Promise(function(resolve, reject){ + fs.exists(path, resolve); + }); +} + +function mkdirs(path){ + var parent = dirname(path); + + return exists(parent).then(function(exist){ + if (!exist) return mkdirs(parent); + }).then(function(){ + return mkdirAsync(path); + }); +} + +function mkdirsSync(path){ + var parent = dirname(path); + var exist = fs.existsSync(parent); + + if (!exist) mkdirsSync(parent); + fs.mkdirSync(path); +} + +function checkParent(path){ + var parent = dirname(path); + + return exists(parent).then(function(exist){ + if (!exist) return mkdirs(parent); + }).catch(function(err){ + if (err.cause.code !== 'EEXIST') throw err; + }); +} + +function checkParentSync(path){ + var parent = dirname(path); + var exist = fs.existsSync(parent); + + if (exist) return; + + try { + mkdirsSync(parent); + } catch (err){ + if (err.code !== 'EEXIST') throw err; + } +} + +function writeFile(path, data, options){ + return checkParent(path).then(function(){ + return writeFileAsync(path, data, options); + }); +} + +function writeFileSync(path, data, options){ + checkParentSync(path); + fs.writeFileSync(path, data, options); +} + +function appendFile(path, data, options){ + return checkParent(path).then(function(){ + return appendFileAsync(path, data, options); + }); +} + +function appendFileSync(path, data, options){ + checkParentSync(path); + fs.appendFileSync(path, data, options); +} + +function copyFile(src, dest){ + return checkParent(dest).then(function(){ + return new Promise(function(resolve, reject){ + var rs = createReadStream(src); + var ws = createWriteStream(dest); + + rs.pipe(ws) + .on('error', reject); + + ws.on('close', resolve) + .on('error', reject); + }); + }); +} + +function trueFn(){ + return true; +} + +function ignoreHiddenFiles(ignore){ + if (ignore){ + return function(item){ + return item[0] !== '.'; + }; + } else { + return trueFn; + } +} + +function ignoreFilesRegex(regex){ + if (regex){ + return function(item){ + return !regex.test(item); + }; + } else { + return trueFn; + } +} + +function ignoreExcludeFiles(arr, parent){ + if (arr && arr.length){ + var len = arr.length; + + return function(item){ + var path = join(parent, item); + + for (var i = 0; i < len; i++){ + if (arr[i] === path) return false; + } + + return true; + }; + } else { + return trueFn; + } +} + +function reduceFiles(result, item){ + if (Array.isArray(item)){ + return result.concat(item); + } else { + result.push(item); + return result; + } +} + +function copyDir(src, dest, options, parent){ + options = options || {}; + parent = parent || ''; + + return checkParent(dest).then(function(){ + return readdirAsync(src); + }) + .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) + .filter(ignoreFilesRegex(options.ignorePattern)) + .map(function(item){ + var childSrc = join(src, item); + var childDest = join(dest, item); + + return statAsync(childSrc).then(function(stats){ + if (stats.isDirectory()){ + return copyDir(childSrc, childDest, options); + } else { + return copyFile(childSrc, childDest, options).then(function(){ + return join(parent, item); + }); + } + }); + }).reduce(reduceFiles, []); +} + +function listDir(path, options, parent){ + options = options || {}; + parent = parent || ''; + + return readdirAsync(path) + .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) + .filter(ignoreFilesRegex(options.ignorePattern)) + .map(function(item){ + var childPath = join(path, item); + + return statAsync(childPath).then(function(stats){ + if (stats.isDirectory()){ + return listDir(childPath, options, join(parent, item)); + } else { + return join(parent, item); + } + }); + }).reduce(reduceFiles, []); +} + +function listDirSync(path, options, parent){ + options = options || {}; + parent = parent || ''; + + return fs.readdirSync(path) + .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) + .filter(ignoreFilesRegex(options.ignorePattern)) + .map(function(item){ + var childPath = join(path, item); + var stats = fs.statSync(childPath); + + if (stats.isDirectory()){ + return listDirSync(childPath, options, join(parent, item)); + } else { + return join(parent, item); + } + }).reduce(reduceFiles, []); +} + +function escapeFileContent(content){ + return escapeBOM(escapeEOL(content)); +} + +function readFile(path, options){ + options = options || {}; + if (options.encoding == null) options.encoding = 'utf8'; + + return readFileAsync(path, options).then(function(content){ + if (options.escape == null || options.escape){ + return escapeFileContent(content); + } else { + return content; + } + }); +} + +function readFileSync(path, options){ + options = options || {}; + if (options.encoding == null) options.encoding = 'utf8'; + + var content = fs.readFileSync(path, options); + + if (options.escape == null || options.escape){ + return escapeFileContent(content); + } else { + return content; + } +} + +function emptyDir(path, options, parent){ + options = options || {}; + parent = parent || ''; + + return readdirAsync(path) + .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) + .filter(ignoreFilesRegex(options.ignorePattern)) + .filter(ignoreExcludeFiles(options.exclude, parent)) + .map(function(item){ + var childPath = join(path, item); + + return statAsync(childPath).then(function(stats){ + return { + isDirectory: stats.isDirectory(), + path: join(parent, item), + fullPath: childPath + }; + }); + }).map(function(item){ + var fullPath = item.fullPath; + + if (item.isDirectory){ + return emptyDir(fullPath, options, item.path).then(function(removed){ + return rmdirAsync(fullPath).then(function(){ + return removed; + }, function(err){ + if (err.code !== 'ENOTEMPTY') throw err; + return removed; + }); + }); + } else { + return unlinkAsync(fullPath).then(function(){ + return item.path; + }); + } + }).reduce(reduceFiles, []); +} + +function emptyDirSync(path, options, parent){ + options = options || {}; + parent = parent || ''; + + return fs.readdirSync(path) + .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) + .filter(ignoreFilesRegex(options.ignorePattern)) + .filter(ignoreExcludeFiles(options.exclude, parent)) + .map(function(item){ + var childPath = join(path, item); + var stats = fs.statSync(childPath); + + if (stats.isDirectory()){ + var removed = emptyDirSync(childPath, options, join(parent, item)); + + try { + rmdirSync(childPath); + } catch (err){ + if (err.code !== 'ENOTEMPTY') throw err; + } + + return removed; + } else { + fs.unlinkSync(childPath); + return join(parent, item); + } + }).reduce(reduceFiles, []); +} + +function rmdir(path){ + return readdirAsync(path).map(function(item){ + var childPath = join(path, item); + + return statAsync(childPath).then(function(stats){ + if (stats.isDirectory()){ + return rmdir(childPath); + } else { + return unlinkAsync(childPath); + } + }); + }).then(function(){ + return rmdirAsync(path); + }); +} + +function rmdirSync(path){ + var files = fs.readdirSync(path); + var childPath; + var stats; + + for (var i = 0, len = files.length; i < len; i++){ + childPath = join(path, files[i]); + stats = fs.statSync(childPath); + + if (stats.isDirectory()){ + rmdirSync(childPath); + } else { + fs.unlinkSync(childPath); + } + } + + fs.rmdirSync(path); +} + +exports.appendFile = appendFile; +exports.appendFileSync = appendFileSync; + +exports.chmod = Promise.promisify(fs.chmod); +exports.chmodSync = fs.chmodSync; + +exports.chown = Promise.promisify(fs.chown); +exports.chownSync = fs.chownSync; + +exports.copyDir = copyDir; +exports.copyFile = copyFile; + +exports.createReadStream = createReadStream; +exports.createWriteStream = createWriteStream; + +exports.emptyDir = emptyDir; +exports.emptyDirSync = emptyDirSync; + +exports.exists = exists; +exports.existsSync = fs.existsSync; + +exports.listDir = listDir; +exports.listDirSync = listDirSync; + +exports.mkdir = mkdirAsync; +exports.mkdirSync = fs.mkdirSync; + +exports.mkdirs = mkdirs; +exports.mkdirsSync = mkdirsSync; + +exports.readdir = readdirAsync; +exports.readdirSync = fs.readdirSync; + +exports.readFile = readFile; +exports.readFileSync = readFileSync; + +exports.rename = renameAsync; +exports.renameSync = fs.renameSync; + +exports.rmdir = rmdir; +exports.rmdirSync = rmdirSync; + +exports.stat = statAsync; +exports.statSync = fs.statSync; + +exports.unlink = unlinkAsync; +exports.unlinkSync = fs.unlinkSync; + +exports.writeFile = writeFile; +exports.writeFileSync = writeFileSync; \ No newline at end of file diff --git a/lib/util/highlight.js b/lib/util/highlight.js index 293ef55faf..aab2158030 100644 --- a/lib/util/highlight.js +++ b/lib/util/highlight.js @@ -70,14 +70,9 @@ module.exports = function(str, options){ compiled = str; } else { var lang = options.lang.toLowerCase(); - if (keys.indexOf(lang) !== -1) lang = alias[lang]; - try { - compiled = hljs.highlight(lang, str).value; - } catch (e){ - compiled = hljs.highlightAuto(str).value; - } + compiled = highlight(lang, str).value; } if (!options.wrap) return compiled; @@ -107,4 +102,12 @@ module.exports = function(str, options){ result += '</figure>'; return result; -}; \ No newline at end of file +}; + +function highlight(lang, str){ + try { + return hljs.highlight(lang, str).value; + } catch (err){ + return hljs.highlightAuto(str).value; + } +} \ No newline at end of file diff --git a/lib/core/i18n.js b/lib/util/i18n.js similarity index 100% rename from lib/core/i18n.js rename to lib/util/i18n.js diff --git a/lib/util/index.js b/lib/util/index.js index cc3c7acb5a..f485bb0d9f 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -1,103 +1,18 @@ -/** -* Utilities. -* -* @class util -* @module hexo -* @static -*/ - -module.exports = { - /** - * See {% crosslink util.file %} - * - * @property file - * @type util.file - * @deprecated Use hexo.util.file2 or hexo.file instead. - */ - - file: require('./file'), - - /** - * See [front-matter](https://github.com/hexojs/front-matter) - * - * @property yfm - * @type util.yfm - */ - - yfm: require('hexo-front-matter'), - spawn: require('./spawn'), - exec: require('./exec'), - highlight: require('./highlight'), - - /** - * See {% crosslink util.file2 %} - * - * @property file2 - * @type util.file2 - */ - - file2: require('./file2'), - - /** - * See {% crosslink util.escape %} - * - * @property escape - * @type util.escape - */ - - escape: require('./escape'), - - /** - * See {% crosslink util.Pool %} - * - * @property pool - * @type util.Pool - */ - - pool: require('./pool'), - html_tag: require('./html_tag'), - - /** - * See {% crosslink util.inflector %} - * - * @property inflector - * @type util.inflector - */ - - inflector: require('./inflector'), - - /** - * See {% crosslink util.inflector/titleize %}. - * - * @method titlecase - * @param {String} str - * @return {String} - */ - - titlecase: require('./inflector').titlecase, - - /** - * See {% crosslink util.format %}. - * - * @property format - * @type util.format - */ - - format: require('./format'), - - /** - * See {% crosslink util.server %} - * - * @property server - * @type util.server - */ - server: require('./server'), - - /** - * See {% crosslink util.Permalink %} - * - * @property permalink - * @type util.Permalink - */ - permalink: require('./permalink') -}; \ No newline at end of file +var util = require('util'), + inflection = require('inflection'); + +exports.inherits = util.inherits; + +exports.escape = require('./escape'); +exports.file = require('./file'); +exports.file2 = require('./file2'); +exports.format = require('./format'); +exports.fs = require('./fs'); +exports.highlight = require('./highlight'); +exports.htmlTag = exports.html_tag = require('./html_tag'); +exports.permalink = require('./permalink'); +exports.server = require('./server'); +exports.yfm = require('hexo-front-matter'); +exports.inflector = inflection; +exports.titlecase = inflection.titleize; +exports.Router = require('./router'); \ No newline at end of file diff --git a/lib/util/inflector.js b/lib/util/inflector.js deleted file mode 100644 index 83fdee89e0..0000000000 --- a/lib/util/inflector.js +++ /dev/null @@ -1,402 +0,0 @@ -/** -* Inflector. -* -* @class inflector -* @namespace util -* @since 2.4.0 -* @module hexo -*/ - -var plurals = [], - singulars = [], - uncountables = []; - -var words = [ - 'a', 'an', 'and', 'as', 'at', 'but', 'by', 'en', 'for', 'if', 'in', 'of', 'on', - 'or', 'the', 'to', 'v', 'v.', 'via', 'vs', 'vs.' -]; - -var plural = function(key, value){ - plurals.push([key, value]); -}; - -var singular = function(key, value){ - singulars.push([key, value]); -}; - -var irregular = function(key, value){ - plural(new RegExp('(' + key + ')$', 'i'), value); - singular(new RegExp('(' + value + ')$', 'i'), key); -}; - -var uncountable = function(word){ - uncountables.push(new RegExp('(' + word + ')$', 'i')); -}; - -plural(/$/, 's'); -plural(/s$/i, 's'); -plural(/(ax|test)is$/i, '$1es'); -plural(/(octop|vir)us$/i, '$1i'); -plural(/(alias|status)$/i, '$1es'); -plural(/(bu)s$/i, '$1ses'); -plural(/(buffal|tomat)o$/i, '$1oes'); -plural(/([ti])um$/i, '$1a'); -plural(/sis$/i, 'ses'); -plural(/(?:([^f])fe|([lr])f)$/i, '$1$2ves'); -plural(/(hive)$/i, '$1s'); -plural(/([^aeiouy]|qu)y$/i, '$1ies'); -plural(/(x|ch|ss|sh)$/i, '$1es'); -plural(/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'); -plural(/([m|l])ouse$/i, '$1ice'); -plural(/^(ox)$/i, '$1en'); -plural(/(quiz)$/i, '$1zes'); - -singular(/s$/i, ''); -singular(/(n)ews$/i, '$1ews'); -singular(/([ti])a$/i, '$1um'); -singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '$1$2sis'); -singular(/(analy)ses$/i, '$1sis'); -singular(/([^f])ves$/i, '$1fe'); -singular(/(hive)s$/i, '$1'); -singular(/(tive)s$/i, '$1'); -singular(/([lr])ves$/i, '$1f'); -singular(/([^aeiouy]|qu)ies$/i, '$1y'); -singular(/(s)eries$/i, '$1eries'); -singular(/(m)ovies$/i, '$1ovie'); -singular(/(x|ch|ss|sh)es$/i, '$1'); -singular(/([m|l])ice$/i, '$1ouse'); -singular(/(bus)es$/i, '$1'); -singular(/(o)es$/i, '$1'); -singular(/(shoe)s$/i, '$1'); -singular(/(cris|ax|test)es$/i, '$1is'); -singular(/(octop|vir)i$/i, '$1us'); -singular(/(alias|status)es$/i, '$1'); -singular(/^(ox)en/i, '$1'); -singular(/(vert|ind)ices$/i, '$1ex'); -singular(/(matr)ices$/i, '$1ix'); -singular(/(quiz)zes$/i, '$1'); -singular(/(database)s$/i, '$1'); - -irregular('person', 'people'); -irregular('man', 'men'); -irregular('child', 'children'); -irregular('sex', 'sexes'); -irregular('move', 'moves'); -irregular('cow', 'kine'); -irregular('zombie', 'zombies'); -irregular('genus', 'genera'); - -uncountable('equipment'); -uncountable('information'); -uncountable('rice'); -uncountable('money'); -uncountable('species'); -uncountable('series'); -uncountable('fish'); -uncountable('sheep'); -uncountable('jeans'); -uncountable('police'); -uncountable('status'); - -/** -* Returns the plural form of the string. -* -* @method pluralize -* @param {String} str -* @return {String} -* @static -*/ - -var pluralize = exports.pluralize = function(str){ - str = str.toLowerCase(); - - var i; - - for (i = uncountables.length - 1; i >= 0; i--){ - if (uncountables[i].test(str)){ - return str; - } - } - - for (i = plurals.length - 1; i >= 0; i--){ - var item = plurals[i], - rule = item[0]; - - if (item[1] === str) return str; - - if (rule.test(str)){ - return str.replace(rule, item[1]); - } - } - - return str; -}; - -/** -* Returns the singular form of the string. -* -* @method singularize -* @param {String} str -* @return {String} -* @static -*/ - -var singularize = exports.singularize = function(str){ - str = str.toLowerCase(); - - var i; - - for (i = uncountables.length - 1; i >= 0; i--){ - if (uncountables[i].test(str)){ - return str; - } - } - - for (i = singulars.length - 1; i >= 0; i--){ - var item = singulars[i], - rule = item[0]; - - if (rule.test(str)){ - return str.replace(rule, item[1]); - } - } - - return str; -}; - -/** -* Converts the string to CamelCase. -* -* @method singularize -* @param {String} str -* @param {Boolean} uppercase -* @return {String} -* @static -*/ - -var camelize = exports.camelize = function(str, uppercase){ - if (uppercase == null) uppercase = true; - - str = str.replace(/(?:_|(\/))([a-z\d]*)/g, function(){ - var word = arguments[2]; - - return word[0].toUpperCase() + word.substring(1); - }); - - if (uppercase) str = str[0].toUpperCase() + str.substring(1); - - return str; -}; - -/** -* Returns an underscored, lowercased form of the string. -* -* @method underscore -* @param {String} str -* @return {String} -* @static -*/ - -var underscore = exports.underscore = function(str){ - return str - .replace(/([A-Z\d]+)([A-Z][a-z])/g, function(){ - return arguments[1] + '_' + arguments[2].toLowerCase(); - }) - .replace(/([a-z\d])([A-Z])/g, function(){ - return arguments[1] + '_' + arguments[2].toLowerCase(); - }) - .replace(/-/g, '_') - .toLowerCase(); -}; - -/** -* Capitalizes the first word, turns underscores into spaces and strip a trailing "_id". -* -* @method humanize -* @param {String} str -* @return {String} -* @static -*/ - -var humanize = exports.humanize = function(str){ - str = str - .replace(/_id$/, '') - .replace(/_/g, ' ') - .toLowerCase(); - - return str[0].toUpperCase() + str.substring(1); -}; - -/** -* Capitalizes all the words. -* -* @method startcase -* @param {String} str -* @return {String} -* @static -*/ - -var startcase = exports.startcase = function(str){ - return humanize(underscore(str)) - .replace(/\b(['’`])?([a-z])/g, function(match){ - if (arguments[1]){ - return match; - } else { - return arguments[2].toUpperCase(); - } - }); -}; - -/** -* Capitalizes all the words, except for articles, prepositions, and conjunctions. -* -* `titleize` is also aliased as `titlecase`. -* -* @method titleize -* @param {String} str -* @return {String} -* @static -*/ - -var titleize = exports.titleize = exports.titlecase = function(str){ - return startcase(str) - .replace(/(['’`])?(\w+)/g, function(match, mark, str){ - if (mark){ - return match; - } else { - str = str.toLowerCase(); - - if (words.indexOf(str) == -1){ - return str[0].toUpperCase() + str.substring(1); - } else { - return str; - } - } - }); -}; - -/** -* Creates a name of a table. -* -* @method tableize -* @param {String} str -* @return {String} -* @static -*/ - -var tableize = exports.tableize = function(str){ - return pluralize(underscore(str)); -}; - -/** -* Creates a class name. -* -* @method classify -* @param {String} str -* @return {String} -* @static -*/ - -var classify = exports.classify = function(str){ - return camelize(singularize(str.replace(/.*\./g, ''))); -}; - -/** -* Replaces underscores with dashes in the string. -* -* @method dasherize -* @param {String} str -* @return {String} -* @static -*/ - -var dasherize = exports.dasherize = function(str){ - return str.replace(/_/g, '-'); -}; - -/** -* Replaces special characters in a string so that it may be used as part of a ‘pretty’ URL. -* -* @method parameterize -* @param {String} str -* @return {String} -* @static -*/ - -var parameterize = exports.parameterize = function(str, sep){ - if (sep == null) sep = '-'; - - str = str - .toLowerCase() - .replace(/[^a-z0-9\-_]+/g, sep); - - if (sep){ - str = str - // Remove repeated separators - .replace(new RegExp(sep + '{2,}', 'g'), sep) - // Remove leading/trailing separators - .replace(new RegExp('^' + sep + '|' + sep + '$'), ''); - } - - return str; -}; - -/** -* Creates a foreign key name from a class name. `separate_class_name_and_id_with_underscore` sets -* whether the method should put '_' between the name and 'id'. -* -* @method foreignKey -* @param {String} str -* @param {Boolean} sep -* @return {String} -* @static -*/ - -var foreignKey = exports.foreignKey = exports.foreign_key = function(str, sep){ - if (sep == null) sep = true; - - return underscore(singularize(str) + (sep ? '_id' : 'id')); -}; - -/** -* Returns the suffix that should be added to a number to denote the position in -* an ordered sequence such as 1st, 2nd, 3rd, 4th. -* -* @method ordinal -* @param {Number} num -* @return {String} -* @static -*/ - -var ordinal = exports.ordinal = function(num){ - num = Math.abs(+num) % 100; - - if (num >= 11 && num <= 13){ - return 'th'; - } else { - switch (num % 10){ - case 1: - return 'st'; - case 2: - return 'nd'; - case 3: - return 'rd'; - default: - return 'th'; - } - } -}; - -/** -* Turns a number into an ordinal string used to denote the position in an ordered sequence -* such as 1st, 2nd, 3rd, 4th. -* -* @method ordinalize -* @param {Number} num -* @return {String} -* @static -*/ - -var ordinalize = exports.ordinalize = function(num){ - return num + ordinal(num); -}; \ No newline at end of file diff --git a/lib/util/locals.js b/lib/util/locals.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/util/pool.js b/lib/util/pool.js deleted file mode 100644 index dc56d9636e..0000000000 --- a/lib/util/pool.js +++ /dev/null @@ -1,208 +0,0 @@ -var child_process = require('child_process'), - cpus = require('os').cpus().length, - nextTick; - -if (setImmediate != null){ - nextTick = function(fn){ - setImmediate(fn); - }; -} else { - nextTick = function(fn){ - process.nextTick(fn); - }; -} - -/** -* Thread pool. -* -* @class Pool -* @param {String} worker -* @param {Number} [concurrency] Default to the number of CPUs. -* @namespace util -* @constructor -* @module hexo -*/ - -var Pool = module.exports = function Pool(worker, concurrency){ - /** - * Tasks. - * - * @property tasks - * @type Array - * @private - */ - - this.tasks = []; - - /** - * Workers. - * - * @property workers - * @type Array - * @private - */ - - this.workers = []; - - /** - * Concurrency. - * - * @property concurrency - * @type Number - */ - - this.concurrency = concurrency || cpus; - - /** - * This function is invoked when the last task in the pool is done. - * - * @property drain - * @type Function - */ - - this.drain = function(){}; - - /** - * This function is invoked when the number of pending tasks equals to the concurrency of the pool. - * - * @property saturated - * @type Function - */ - - this.saturated = function(){}; - - /** - * This function is invoked when the last task in the pool is started. - * - * @property empty - * @type Function - */ - - this.empty = function(){}; - - for (var i = 0; i < this.concurrency; i++){ - this.workers.push(child_process.fork(worker)); - } -}; - -/** -* Inserts a task to the pool. -* -* @method _insert -* @param {Array|Object} tasks -* @param {Boolean} first -* @param {Function} [callback] -* @private -*/ - -Pool.prototype._insert = function(tasks, first, callback){ - if (!Array.isArray(tasks)) tasks = [tasks]; - - var self = this; - - tasks.forEach(function(task){ - var item = { - data: task, - callback: typeof callback === 'function' ? callback : function(){} - }; - - if (first){ - self.tasks.unshift(item); - } else { - self.tasks.push(item); - } - - if (self.tasks.length === self.concurrency){ - self.saturated(); - } - - nextTick(function(){ - self.process(); - }); - }); -}; - -/** -* Starts distributing tasks to each worker. -* -* @method process -* @private -*/ - -Pool.prototype.process = function(){ - if (this.tasks.length && this.workers.length){ - var task = this.tasks.shift(), - worker = this.workers.shift(), - self = this; - - if (!this.tasks.length) this.empty(); - - var removeAllListeners = function(){ - worker.removeAllListeners('error') - .removeAllListeners('message'); - }; - - worker - .on('error', function(err){ - task.callback(err); - removeAllListeners(); - }) - .on('message', function(){ - task.callback.apply(task, arguments); - self.workers.push(worker); - - if (!self.tasks.length && self.workers.length == self.concurrency) self.drain(); - - removeAllListeners(); - self.process(); - }) - .send(task.data); - } -}; - -/** -* Adds a task to last of queue. -* -* @method push -* @param {Array|Object} tasks -* @param {Function} [callback] -*/ - -Pool.prototype.push = function(tasks, callback){ - this._insert(tasks, false, callback); -}; - -/** -* Adds a task to first of queue. -* -* @method unshift -* @param {Array|Object} tasks -* @param {Function} [callback] -*/ - -Pool.prototype.unshift = function(tasks, callback){ - this._insert(tasks, true, callback); -}; - -/** -* Terminates all workers. -* -* @method end -*/ - -Pool.prototype.end = function(){ - this.workers.forEach(function(worker){ - worker.kill(); - }); -}; - -/** -* Returns the number of pending tasks. -* -* @method length -* @return {Number} -*/ - -Pool.prototype.length = function(){ - return this.tasks.length; -}; \ No newline at end of file diff --git a/lib/core/router.js b/lib/util/router.js similarity index 100% rename from lib/core/router.js rename to lib/util/router.js diff --git a/lib/util/spawn.js b/lib/util/spawn.js deleted file mode 100644 index bd8bb0f6e7..0000000000 --- a/lib/util/spawn.js +++ /dev/null @@ -1,39 +0,0 @@ -var spawn = require('child_process').spawn; - -/** -* Launches a new process. -* -* @method spawn -* @param {Object} options -* @param {String} options.command The command to run -* @param {Array} options.args The list of string arguments -* @param {Object} [options.options] See [child_process.spawn](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) -* @for util -*/ - -module.exports = function(options){ - var comm = spawn(options.command, options.args, options.options); - - comm.stdout.setEncoding('utf8'); - comm.stderr.setEncoding('utf8'); - - if (options.stdout){ - comm.stdout.on('data', options.stdout); - } else { - comm.stdout.on('data', function(data){ - process.stdout.write(data); - }); - } - - if (options.stderr){ - comm.stderr.on('data', options.stderr); - } else { - comm.stderr.on('data', function(data){ - process.stderr.write(data); - }); - } - - if (options.exit){ - comm.on('exit', options.exit); - } -}; \ No newline at end of file diff --git a/package.json b/package.json index c88a8ea1b9..d5feb19cc0 100644 --- a/package.json +++ b/package.json @@ -34,34 +34,40 @@ "author": "Tommy Chen <tommy351@gmail.com> (http://zespia.tw)", "license": "MIT", "dependencies": { - "async": "^0.9.0", + "abbrev": "^1.0.5", + "bluebird": "^2.3.10", + "bunyan": "^1.2.1", "cheerio": "0.17.0", - "chokidar": "0.8.2", - "colors": "0.6.2", + "chokidar": "0.10.4", + "colors": "1.0.3", "compression": "^1.0.3", "connect": "3.x", "graceful-fs": "^3.0.2", "hexo-front-matter": "0.0.4", - "highlight.js": "8.1.0", + "highlight.js": "8.3.0", + "inflection": "^1.5.1", "js-yaml": "^3.1.0", "lodash": "^2.4.1", "mime": "^1.2.11", - "minimist": "0.2.0", + "minimist": "1.1.0", "moment": "^2.8.1", "morgan": "^1.1.1", + "pretty-hrtime": "^0.2.2", + "semver": "^4.1.0", "serve-static": "^1.2.0", "sprintf-js": "0.0.7", "strip-indent": "^0.1.3", - "swig": "1.4.1", - "warehouse": "0.2.2" + "swig": "1.4.2", + "tildify": "^1.0.0", + "warehouse": "1.0.0-rc.2" }, "devDependencies": { "chai": "^1.9.1", - "gulp": "^3.6.2", - "gulp-jshint": "^1.5.5", + "gulp": "^3.8.9", + "gulp-jshint": "^1.8.6", "gulp-load-plugins": "^0.7.0", - "gulp-mocha": "^0.5.1", - "jshint-stylish": "^0.4.0", - "mocha": "^1.20.1" + "gulp-mocha": "^1.1.1", + "jshint-stylish": "^1.0.0", + "mocha": "^2.0.1" } } diff --git a/test/fixtures/test.yml b/test/fixtures/test.yml new file mode 100644 index 0000000000..6a54f9f7e3 --- /dev/null +++ b/test/fixtures/test.yml @@ -0,0 +1,9 @@ +name: + first: John + last: Doe + +age: 23 + +list: +- Apple +- Banana \ No newline at end of file diff --git a/test/index.js b/test/index.js index f7e441144e..124a19a9aa 100644 --- a/test/index.js +++ b/test/index.js @@ -1,28 +1,5 @@ -var file = require('../lib/util/file2'), - fs = require('graceful-fs'), - pathFn = require('path'); - -describe('Hexo test', function(){ - before(require('./scripts/init')); - - require('./scripts/core'); +describe('Hexo', function(){ + require('./scripts/box'); + require('./scripts/hexo'); require('./scripts/util'); - require('./scripts/i18n'); - require('./scripts/filter'); - require('./scripts/helper'); - require('./scripts/tag'); - require('./scripts/post'); - require('./scripts/scaffold'); - - after(function(done){ - var blogDir = pathFn.join(__dirname, 'blog'); - - fs.exists(blogDir, function(exist){ - if (exist){ - file.rmdir(blogDir, done); - } else { - done(); - } - }); - }); }); \ No newline at end of file diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js new file mode 100644 index 0000000000..60a61e27ef --- /dev/null +++ b/test/scripts/box/box.js @@ -0,0 +1,27 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var Hexo = require('../../../lib/hexo'); +var util = Hexo.util; +var fs = util.fs; + +describe('Box', function(){ + var hexo = new Hexo(__dirname, {}); + var base = pathFn.join(__dirname, 'tmp'); + var box = hexo._createBox(base); + + before(function(){ + return fs.mkdir(base); + }); + + it.skip('addProcessor()'); + + it.skip('process()'); + + it.skip('watch()'); + + it.skip('unwatch()'); + + after(function(){ + return fs.rmdir(base); + }); +}); \ No newline at end of file diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js new file mode 100644 index 0000000000..b5af8c1f49 --- /dev/null +++ b/test/scripts/box/file.js @@ -0,0 +1,53 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var Promise = require('bluebird'); +var File = require('../../../lib/box/file'); +var util = require('../../../lib/util'); +var fs = util.fs; + +describe('File', function(){ + var target = pathFn.join(__dirname, '../../fixtures/test.yml'); + var body; + + var file = new File({ + source: target, + path: target, + type: 'create', + params: {foo: 'bar'} + }); + + before(function(){ + return fs.readFile(target).then(function(result){ + body = result; + }); + }); + + it('read()', function(){ + return file.read().then(function(content){ + content.should.eql(body); + }); + }); + + it('readSync()', function(){ + file.readSync().should.eql(body); + }); + + it('stat()', function(){ + return Promise.all([ + fs.stat(target), + file.stat() + ]).then(function(stats){ + stats[0].should.eql(stats[1]); + }); + }); + + it('statSync()', function(){ + return fs.stat(target).then(function(stats){ + file.statSync().should.eql(stats); + }); + }); + + it.skip('render()'); + + it.skip('renderSync()'); +}); \ No newline at end of file diff --git a/test/scripts/box/index.js b/test/scripts/box/index.js new file mode 100644 index 0000000000..ca806a7f77 --- /dev/null +++ b/test/scripts/box/index.js @@ -0,0 +1,5 @@ +describe('Box', function(){ + require('./box'); + require('./file'); + require('./pattern'); +}); \ No newline at end of file diff --git a/test/scripts/box/pattern.js b/test/scripts/box/pattern.js new file mode 100644 index 0000000000..77c29a6d20 --- /dev/null +++ b/test/scripts/box/pattern.js @@ -0,0 +1,58 @@ +var should = require('chai').should(); +var Pattern = require('../../../lib/box/pattern'); + +describe('Pattern', function(){ + it('String - posts/:id', function(){ + var pattern = new Pattern('posts/:id'); + var result = pattern.match('/posts/89'); + + result.should.eql({ + 0: 'posts/89', + 1: '89', + id: '89' + }); + }); + + it('String - posts/*path', function(){ + var pattern = new Pattern('posts/*path'); + var result = pattern.match('posts/2013/hello-world'); + + result.should.eql({ + 0: 'posts/2013/hello-world', + 1: '2013/hello-world', + path: '2013/hello-world' + }); + }); + + it('String - posts/:id?', function(){ + var pattern = new Pattern('posts/:id?'); + + pattern.match('posts/').should.eql({ + 0: 'posts/', + 1: undefined, + id: undefined + }); + + pattern.match('posts/89').should.eql({ + 0: 'posts/89', + 1: '89', + id: '89' + }); + }); + + it('RegExp', function(){ + var pattern = new Pattern(/ab?cd/); + + pattern.match('abcd').should.be.ok; + pattern.match('acd').should.be.ok; + }); + + it('Function', function(){ + var pattern = new Pattern(function(str){ + str.should.eql('foo'); + return {}; + }); + + pattern.match('foo').should.eql({}); + }); +}); \ No newline at end of file diff --git a/test/scripts/core/index.js b/test/scripts/core/index.js deleted file mode 100644 index edc60a7155..0000000000 --- a/test/scripts/core/index.js +++ /dev/null @@ -1,3 +0,0 @@ -describe('Core', function(){ - require('./router'); -}); \ No newline at end of file diff --git a/test/scripts/core/router.js b/test/scripts/core/router.js deleted file mode 100644 index 32116eee57..0000000000 --- a/test/scripts/core/router.js +++ /dev/null @@ -1,83 +0,0 @@ -var should = require('chai').should(); - -describe('router', function(){ - var Router = require('../../../lib/core/router'), - router; - - before(function(){ - router = new Router(); - }); - - it('format', function(){ - router.format('foo').should.eql('foo'); - - // Remove prefixed slashes - router.format('/foo').should.eql('foo'); - router.format('///foo').should.eql('foo'); - - // Append `index.html` to the URL with trailing slash - router.format('foo/').should.eql('foo/index.html'); - - // '' => `index.html - router.format('').should.eql('index.html'); - router.format().should.eql('index.html'); - - // Remove backslashes - router.format('foo\\bar').should.eql('foo/bar'); - router.format('foo\\bar\\').should.eql('foo/bar/index.html'); - - // Remove query string - router.format('foo?a=1&b=2').should.eql('foo'); - }); - - it('set: string', function(done){ - router.once('update', function(source, route){ - source.should.eql('foo'); - - route(function(err, content){ - should.not.exist(err); - content.should.eql('bar'); - }); - - done(); - }); - - router.set('foo', 'bar'); - router.get('foo', function(err, content){ - should.not.exist(err); - content.should.eql('bar'); - }); - }); - - it('set: function', function(done){ - router.once('update', function(source, route){ - source.should.eql('hello'); - - route(function(err, content){ - should.not.exist(err); - content.should.eql('world'); - }); - - done(); - }); - - router.set('hello', function(fn){ - fn(null, 'world'); - }); - - router.get('hello', function(err, content){ - should.not.exist(err); - content.should.eql('world'); - }); - }); - - it('remove', function(done){ - router.once('remove', function(source){ - source.should.eql('hello'); - done(); - }); - - router.remove('hello'); - should.not.exist(router.get('hello')); - }); -}); \ No newline at end of file diff --git a/test/scripts/filter/excerpt.js b/test/scripts/filter/excerpt.js deleted file mode 100644 index a8977dea39..0000000000 --- a/test/scripts/filter/excerpt.js +++ /dev/null @@ -1,25 +0,0 @@ -var should = require('chai').should(); - -describe('excerpt', function(){ - var excerpt = require('../../../lib/plugins/filter/excerpt'); - - it('with tag', function(){ - excerpt({ - content: '12345<!-- more -->67890' - }, function(err, data){ - should.not.exist(err); - data.content.should.eql('12345<a id="more"></a>67890'); - data.excerpt.should.eql('12345'); - }); - }); - - it('no tags', function(){ - excerpt({ - content: '1234567890' - }, function(err, data){ - should.not.exist(err); - data.content.should.eql('1234567890'); - data.excerpt.should.eql(''); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/filter/external_link.js b/test/scripts/filter/external_link.js deleted file mode 100644 index 5a9893c16e..0000000000 --- a/test/scripts/filter/external_link.js +++ /dev/null @@ -1,40 +0,0 @@ -var should = require('chai').should(); - -describe('external_link', function(){ - var external_link = require('../../../lib/plugins/filter/external_link'); - - before(function(){ - hexo.config.external_link = true; - }); - - it('internal link', function(){ - external_link({ - content: '<a href="foo.html">foo</a>' - }, function(err, data){ - should.not.exist(err); - data.content.should.eql('<a href="foo.html">foo</a>'); - }); - }); - - it('external link', function(){ - external_link({ - content: '<a href="http://zespia.tw">Zespia</a>' - }, function(err, data){ - should.not.exist(err); - data.content.should.eql('<a href="http://zespia.tw" target="_blank" rel="external">Zespia</a>'); - }); - }); - - it('external link but already has target="_blank" attribute', function(){ - external_link({ - content: '<a href="http://zespia.tw" target="_blank">Zespia</a>' - }, function(err, data){ - should.not.exist(err); - data.content.should.eql('<a href="http://zespia.tw" target="_blank">Zespia</a>'); - }); - }); - - after(function(){ - hexo.config.external_link = false; - }); -}); \ No newline at end of file diff --git a/test/scripts/filter/index.js b/test/scripts/filter/index.js deleted file mode 100644 index ce332a7cd9..0000000000 --- a/test/scripts/filter/index.js +++ /dev/null @@ -1,4 +0,0 @@ -describe('Filter', function(){ - require('./excerpt'); - require('./external_link'); -}); \ No newline at end of file diff --git a/test/scripts/helper/date.js b/test/scripts/helper/date.js deleted file mode 100644 index 53bc2c47b8..0000000000 --- a/test/scripts/helper/date.js +++ /dev/null @@ -1,110 +0,0 @@ -var moment = require('moment'), - should = require('chai').should(); - -describe('date', function(){ - var date = require('../../../lib/plugins/helper/date'); - - var genTimeTag = function(date, format){ - if (!(date instanceof Date)){ - if (moment.isMoment(date)){ - date = date.toDate(); - } else { - date = new Date(date); - } - } - - return '<time datetime="' + date.toISOString() + '">' + moment(date).format(format) + '</time>'; - }; - - it('date', function(){ - var format = hexo.config.date_format, - custom = 'YYYY-MM-DD'; - - moment(date.date(), format).isValid().should.be.true; - - var nowDate = new Date(); - date.date(nowDate).should.eql(moment(nowDate).format(format)); - date.date(nowDate, custom).should.eql(moment(nowDate).format(custom)); - - var nowMoment = moment(); - date.date(nowMoment).should.eql(nowMoment.format(format)); - date.date(nowMoment, custom).should.eql(nowMoment.format(custom)); - - var nowMs = Date.now(); - date.date(nowMs).should.eql(moment(nowMs).format(format)); - date.date(nowMs, custom).should.eql(moment(nowMs).format(custom)); - }); - - it('date_xml', function(){ - date.date_xml().should.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); - - var nowDate = new Date(); - date.date_xml(nowDate).should.eql(nowDate.toISOString()); - - var nowMoment = moment(); - date.date_xml(nowMoment).should.eql(nowMoment.toDate().toISOString()); - - var nowMs = Date.now(); - date.date_xml(nowMs).should.eql(new Date(nowMs).toISOString()); - }); - - it('time', function(){ - var format = hexo.config.time_format, - custom = 'H:mm'; - - moment(date.time(), format).isValid().should.be.true; - - var nowDate = new Date(); - date.time(nowDate).should.eql(moment(nowDate).format(format)); - date.time(nowDate, custom).should.eql(moment(nowDate).format(custom)); - - var nowMoment = moment(); - date.time(nowMoment).should.eql(nowMoment.format(format)); - date.time(nowMoment, custom).should.eql(nowMoment.format(custom)); - - var nowMs = Date.now(); - date.time(nowMs).should.eql(moment(nowMs).format(format)); - date.time(nowMs, custom).should.eql(moment(nowMs).format(custom)); - - }); - - it('full_date', function(){ - var format = hexo.config.date_format + ' ' + hexo.config.time_format, - custom = 'YYYY-MM-DD H:mm'; - - moment(date.full_date(), format).isValid().should.be.true; - - var nowDate = new Date(); - date.full_date(nowDate).should.eql(moment(nowDate).format(format)); - date.full_date(nowDate, custom).should.eql(moment(nowDate).format(custom)); - - var nowMoment = moment(); - date.full_date(nowMoment).should.eql(nowMoment.format(format)); - date.full_date(nowMoment, custom).should.eql(nowMoment.format(custom)); - - var nowMs = Date.now(); - date.full_date(nowMs).should.eql(moment(nowMs).format(format)); - date.full_date(nowMs, custom).should.eql(moment(nowMs).format(custom)); - }); - - it('time_tag', function(){ - var format = hexo.config.date_format, - custom = 'YYYY-MM-DD'; - - var nowDate = new Date(); - date.time_tag(nowDate).should.eql(genTimeTag(nowDate, format)); - date.time_tag(nowDate, custom).should.eql(genTimeTag(nowDate, custom)); - - var nowMoment = moment(); - date.time_tag(nowMoment).should.eql(genTimeTag(nowMoment, format)); - date.time_tag(nowMoment, custom).should.eql(genTimeTag(nowMoment, custom)); - - var nowMs = Date.now(); - date.time_tag(nowMs).should.eql(genTimeTag(nowMs, format)); - date.time_tag(nowMs, custom).should.eql(genTimeTag(nowMs, custom)); - }); - - it('moment', function(){ - moment.isMoment(new date.moment()).should.be.true; - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/gravatar.js b/test/scripts/helper/gravatar.js deleted file mode 100644 index 673c1e9f56..0000000000 --- a/test/scripts/helper/gravatar.js +++ /dev/null @@ -1,29 +0,0 @@ -var crypto = require('crypto'), - should = require('chai').should(); - -describe('gravatar', function(){ - var gravatar = require('../../../lib/plugins/helper/gravatar'); - - var md5 = function(str){ - return crypto.createHash('md5').update(str).digest('hex'); - }; - - var email = 'abc@abc.com', - hash = md5(email); - - it('default', function(){ - gravatar(email).should.eql('http://www.gravatar.com/avatar/' + hash); - }); - - it('size', function(){ - gravatar(email, 100).should.eql('http://www.gravatar.com/avatar/' + hash + '?s=100'); - }); - - it('options', function(){ - gravatar(email, { - s: 200, - r: 'pg', - d: 'mm' - }).should.eql('http://www.gravatar.com/avatar/' + hash + '?s=200&r=pg&d=mm'); - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/index.js b/test/scripts/helper/index.js deleted file mode 100644 index 5e61330660..0000000000 --- a/test/scripts/helper/index.js +++ /dev/null @@ -1,9 +0,0 @@ -describe('Helper', function(){ - require('./date'); - require('./gravatar'); - require('./is'); - require('./number'); - require('./tag'); - require('./toc'); - require('./url'); -}); \ No newline at end of file diff --git a/test/scripts/helper/is.js b/test/scripts/helper/is.js deleted file mode 100644 index 1e4f97fab9..0000000000 --- a/test/scripts/helper/is.js +++ /dev/null @@ -1,50 +0,0 @@ -var should = require('chai').should(); - -describe('is', function(){ - var is = require('../../../lib/plugins/helper/is'); - - it('is_current', function(){ - is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo').should.be.true; - is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo/bar').should.be.true; - is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo/baz').should.be.false; - }); - - it('is_home', function(){ - var paginationDir = hexo.config.pagination_dir; - - is.is_home.call({path: '', config: hexo.config}).should.be.true; - is.is_home.call({path: paginationDir + '/2/', config: hexo.config}).should.be.true; - }); - - it('is_post', function(){ - var config = { - permalink: ':id/:category/:year/:month/:day/:title' - }; - - is.is_post.call({path: '123/foo/bar/2013/08/12/foo-bar', config: config}).should.be.true; - }); - - it('is_archive', function(){ - var archiveDir = hexo.config.archive_dir; - - is.is_archive.call({path: archiveDir + '/', config: hexo.config}).should.be.true; - is.is_archive.call({path: archiveDir + '/2013', config: hexo.config}).should.be.true; - is.is_archive.call({path: archiveDir + '/2013/08', config: hexo.config}).should.be.true; - }); - - it('is_year', function(){ - is.is_archive.call({path: hexo.config.archive_dir + '/2013', config: hexo.config}).should.be.true; - }); - - it('is_month', function(){ - is.is_archive.call({path: hexo.config.archive_dir + '/2013/08', config: hexo.config}).should.be.true; - }); - - it('is_category', function(){ - is.is_category.call({path: hexo.config.category_dir + '/foo', config: hexo.config}).should.be.true; - }); - - it('is_tag', function(){ - is.is_tag.call({path: hexo.config.tag_dir + '/foo', config: hexo.config}).should.be.true; - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/number.js b/test/scripts/helper/number.js deleted file mode 100644 index 6237c0176e..0000000000 --- a/test/scripts/helper/number.js +++ /dev/null @@ -1,28 +0,0 @@ -var should = require('chai').should(); - -describe('number', function(){ - var number = require('../../../lib/plugins/helper/number'); - - describe('number_format', function(){ - it('default', function(){ - number.number_format(1234.567).should.eql('1,234.567'); - }); - - it('precision', function(){ - number.number_format(1234.567, {precision: false}).should.eql('1,234.567'); - number.number_format(1234.567, {precision: 0}).should.eql('1,234'); - number.number_format(1234.567, {precision: 1}).should.eql('1,234.6'); - number.number_format(1234.567, {precision: 2}).should.eql('1,234.57'); - number.number_format(1234.567, {precision: 3}).should.eql('1,234.567'); - number.number_format(1234.567, {precision: 4}).should.eql('1,234.5670'); - }); - - it('delimiter', function(){ - number.number_format(1234.567, {delimiter: ' '}).should.eql('1 234.567'); - }); - - it('separator', function(){ - number.number_format(1234.567, {separator: '*'}).should.eql('1,234*567'); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/tag.js b/test/scripts/helper/tag.js deleted file mode 100644 index 487d48032a..0000000000 --- a/test/scripts/helper/tag.js +++ /dev/null @@ -1,372 +0,0 @@ -var should = require('chai').should(), - qs = require('querystring'), - htmlTag = require('../../../lib/util/html_tag'); - -describe('tag', function(){ - var tag = require('../../../lib/plugins/helper/tag'), - context = require('../../../lib/plugins/helper/url'); - - describe('css', function(){ - var css = tag.css.bind(context); - - var genResult = function(arr){ - var result = ''; - - arr.forEach(function(item){ - result += htmlTag('link', {rel: 'stylesheet', href: item + '.css', type: 'text/css'}) + '\n'; - }); - - return result; - }; - - it('a string', function(){ - var result = genResult(['/style']); - - css('style').should.eql(result); - css('style.css').should.eql(result); - - css('http://zespia.tw/style.css').should.eql(genResult(['http://zespia.tw/style'])); - css('//zespia.tw/style.css').should.eql(genResult(['//zespia.tw/style'])); - }); - - it('an array', function(){ - var result = genResult(['/foo', '/bar', '/baz']); - - css(['foo', 'bar', 'baz']).should.eql(result); - }); - - it('multiple strings', function(){ - var result = genResult(['/foo', '/bar', '/baz']); - - css('foo', 'bar', 'baz').should.eql(result); - }); - - it('multiple arrays', function(){ - var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); - - css(['s1', 's2', 's3'], ['s4', 's5'], ['s6']).should.eql(result); - }); - - it('mixed', function(){ - var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); - - css(['s1', 's2'], 's3', 's4', ['s5'], 's6').should.eql(result); - }); - }); - - describe('js', function(){ - var js = tag.js.bind(context); - - var genResult = function(arr){ - var result = ''; - - arr.forEach(function(item){ - result += htmlTag('script', {src: item + '.js', type: 'text/javascript'}, '') + '\n'; - }); - - return result; - }; - - it('a string', function(){ - var result = genResult(['/script']); - - js('script').should.eql(result); - js('script.js').should.eql(result); - - js('http://code.jquery.com/jquery-2.0.3.min.js').should.eql(genResult(['http://code.jquery.com/jquery-2.0.3.min'])); - js('//code.jquery.com/jquery-2.0.3.min.js').should.eql(genResult(['//code.jquery.com/jquery-2.0.3.min'])); - }); - - it('an array', function(){ - var result = genResult(['/foo', '/bar', '/baz']); - - js(['foo', 'bar', 'baz']).should.eql(result); - }); - - it('multiple strings', function(){ - var result = genResult(['/foo', '/bar', '/baz']); - - js('foo', 'bar', 'baz').should.eql(result); - }); - - it('multiple arrays', function(){ - var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); - - js(['s1', 's2', 's3'], ['s4', 's5'], ['s6']).should.eql(result); - }); - - it('mixed', function(){ - var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); - - js(['s1', 's2'], 's3', 's4', ['s5'], 's6').should.eql(result); - }); - }); - - describe('link_to', function(){ - var link_to = tag.link_to.bind(context), - url = 'http://zespia.tw/', - text = 'Zespia'; - - it('path', function(){ - var text = url.replace(/^https?:\/\//, ''); - - link_to(url).should.eql(htmlTag('a', { - href: url, - title: text - }, text)); - }); - - it('title', function(){ - link_to(url, text).should.eql(htmlTag('a', { - href: url, - title: text - }, text)); - }); - - it('external (boolean)', function(){ - link_to(url, text, true).should.eql(htmlTag('a', { - href: url, - title: text, - target: '_blank', - rel: 'external' - }, text)); - }); - - it('external (options)', function(){ - link_to(url, text, {external: true}).should.eql(htmlTag('a', { - href: url, - title: text, - target: '_blank', - rel: 'external' - }, text)); - }); - - it('class (string)', function(){ - link_to(url, text, {class: 'foo bar'}).should.eql(htmlTag('a', { - href: url, - title: text, - class: 'foo bar' - }, text)); - }); - - it('class (array)', function(){ - link_to(url, text, {class: ['foo', 'bar']}).should.eql(htmlTag('a', { - href: url, - title: text, - class: 'foo bar' - }, text)); - }); - - it('id', function(){ - link_to(url, text, {id: 'foo'}).should.eql(htmlTag('a', { - href: url, - title: text, - id: 'foo' - }, text)); - }); - }); - - describe('mail_to', function(){ - var mail_to = tag.mail_to.bind(context), - url = 'abc@abc.com', - text = 'Email'; - - it('path', function(){ - mail_to(url).should.eql(htmlTag('a', { - href: 'mailto:' + url, - title: url - }, url)); - }); - - it('text', function(){ - mail_to(url, text).should.eql(htmlTag('a', { - href: 'mailto:' + url, - title: text - }, text)); - }); - - it('class (string)', function(){ - mail_to(url, text, {class: 'foo bar'}).should.eql(htmlTag('a', { - href: 'mailto:' + url, - title: text, - class: 'foo bar' - }, text)); - }); - - it('class (array)', function(){ - mail_to(url, text, {class: ['foo', 'bar']}).should.eql(htmlTag('a', { - href: 'mailto:' + url, - title: text, - class: 'foo bar' - }, text)); - }); - - it('id', function(){ - mail_to(url, text, {id: 'foo'}).should.eql(htmlTag('a', { - href: 'mailto:' + url, - title: text, - id: 'foo' - }, text)); - }); - - it('subject', function(){ - var data = {subject: 'Hello World'}, - querystring = qs.stringify(data); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - - it('cc (string)', function(){ - var data = {cc: 'abc@abc.com'}, - querystring = qs.stringify(data); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - - it('cc (array)', function(){ - var data = {cc: 'abc@abc.com,bcd@bcd.com'}, - querystring = qs.stringify(data); - - data.cc = data.cc.split(','); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - - it('bcc (string)', function(){ - var data = {bcc: 'abc@abc.com'}, - querystring = qs.stringify(data); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - - it('bcc (array)', function(){ - var data = {bcc: 'abc@abc.com,bcd@bcd.com'}, - querystring = qs.stringify(data); - - data.bcc = data.bcc.split(','); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - - it('body', function(){ - var data = {body: 'Lorem Ipsum'}, - querystring = qs.stringify(data); - - mail_to(url, text, data).should.eql(htmlTag('a', { - href: 'mailto:' + url + '?' + querystring, - title: text - }, text)); - }); - }); - - describe('image_tag', function(){ - var image_tag = tag.image_tag.bind(context), - url = 'http://haha.com/some_img.jpg', - text = 'An image'; - - it('path', function(){ - image_tag(url).should.eql(htmlTag('img', { - src: url - })); - }); - - it('alt', function(){ - image_tag(url, {alt: text}).should.eql(htmlTag('img', { - src: url, - alt: text - })); - }); - - it('class (string)', function(){ - image_tag(url, {class: 'foo bar'}).should.eql(htmlTag('img', { - src: url, - class: 'foo bar' - })); - }); - - it('class (array)', function(){ - image_tag(url, {class: ['foo', 'bar']}).should.eql(htmlTag('img', { - src: url, - class: 'foo bar' - })); - }); - - it('id', function(){ - image_tag(url, {id: 'foo'}).should.eql(htmlTag('img', { - src: url, - id: 'foo' - })); - }); - - it('width', function(){ - image_tag(url, {width: 100}).should.eql(htmlTag('img', { - src: url, - width: 100 - })); - }); - - it('height', function(){ - image_tag(url, {height: 100}).should.eql(htmlTag('img', { - src: url, - height: 100 - })); - }); - }); - - describe('favicon_tag', function(){ - var favicon_tag = tag.favicon_tag.bind(context), - path = '/favicon.ico'; - - it('path', function(){ - favicon_tag(path).should.eql(htmlTag('link', { - rel: 'shortcut icon', - href: path - })); - }); - }); - - describe('feed_tag', function(){ - var feed_tag = tag.feed_tag.bind(context), - url = '/atom.xml', - text = 'Feed Title'; - - var attrs = { - rel: 'alternative', - href: url, - title: '', - type: 'application/atom+xml' - }; - - it('path', function(){ - attrs.title = hexo.config.title; - feed_tag(url).should.eql(htmlTag('link', attrs)); - }); - - it('title', function(){ - attrs.title = text; - feed_tag(url, {title: text}).should.eql(htmlTag('link', attrs)); - }); - - it('type', function(){ - attrs.title = hexo.config.title; - attrs.type = 'application/rss+xml'; - - feed_tag(url, {type: 'rss'}).should.eql(htmlTag('link', attrs)); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/toc.js b/test/scripts/helper/toc.js deleted file mode 100644 index b7f11f5fc1..0000000000 --- a/test/scripts/helper/toc.js +++ /dev/null @@ -1,110 +0,0 @@ -var should = require('chai').should(), - _ = require('lodash'); - -describe('toc', function(){ - var toc = require('../../../lib/plugins/helper/toc'); - - var html = [ - '<h1 id="title_1">Title 1</h1>', - '<h2 id="title_1_1">Title 1.1</h2>', - '<h3 id="title_1_1_1">Title 1.1.1</h3>', - '<h2 id="title_1_2">Title 1.2</h2>', - '<h2 id="title_1_3">Title 1.3</h2>', - '<h3 id="title_1_3_1">Title 1.3.1</h3>', - '<h1 id="title_2">Title 2</h1>', - '<h2 id="title_2_1">Title 2.1</h2>' - ].join(''); - - var genResult = function(options){ - options = _.extend({ - class: 'toc', - list_number: true - }, options); - - var className = options.class, - listNumber = options.list_number; - - var result = [ - '<ol class="' + className + '">', - '<li class="' + className + '-item ' + className + '-level-1">', - '<a class="' + className + '-link" href="#title_1">', - (listNumber ? '<span class="' + className + '-number">1.</span> ' : ''), - '<span class="' + className + '-text">Title 1</span>', - '</a>', - '<ol class="' + className + '-child">', - '<li class="' + className + '-item ' + className + '-level-2">', - '<a class="' + className + '-link" href="#title_1_1">', - (listNumber ? '<span class="' + className + '-number">1.1.</span> ' : ''), - '<span class="' + className + '-text">Title 1.1</span>', - '</a>', - '<ol class="' + className + '-child">', - '<li class="' + className + '-item ' + className + '-level-3">', - '<a class="' + className + '-link" href="#title_1_1_1">', - (listNumber ? '<span class="' + className + '-number">1.1.1.</span> ' : ''), - '<span class="' + className + '-text">Title 1.1.1</span>', - '</a>', - '</li>', - '</ol>', - '</li>', - '<li class="' + className + '-item ' + className + '-level-2">', - '<a class="' + className + '-link" href="#title_1_2">', - (listNumber ? '<span class="' + className + '-number">1.2.</span> ' : ''), - '<span class="' + className + '-text">Title 1.2</span>', - '</a>', - '</li>', - '<li class="' + className + '-item ' + className + '-level-2">', - '<a class="' + className + '-link" href="#title_1_3">', - (listNumber ? '<span class="' + className + '-number">1.3.</span> ' : ''), - '<span class="' + className + '-text">Title 1.3</span>', - '</a>', - '<ol class="' + className + '-child">', - '<li class="' + className + '-item ' + className + '-level-3">', - '<a class="' + className + '-link" href="#title_1_3_1">', - (listNumber ? '<span class="' + className + '-number">1.3.1.</span> ' : ''), - '<span class="' + className + '-text">Title 1.3.1</span>', - '</a>', - '</li>', - '</ol>', - '</li>', - '</ol>', - '</li>', - '<li class="' + className + '-item ' + className + '-level-1">', - '<a class="' + className + '-link" href="#title_2">', - (listNumber ? '<span class="' + className + '-number">2.</span> ' : ''), - '<span class="' + className + '-text">Title 2</span>', - '</a>', - '<ol class="' + className + '-child">', - '<li class="' + className + '-item ' + className + '-level-2">', - '<a class="' + className + '-link" href="#title_2_1">', - (listNumber ? '<span class="' + className + '-number">2.1.</span> ' : ''), - '<span class="' + className + '-text">Title 2.1</span>', - '</a>', - '</li>', - '</ol>', - '</li>', - '</ol>' - ].join(''); - - return result; - }; - - it('default', function(){ - genResult().should.eql(toc(html)); - }); - - it('class', function(){ - var options = { - class: 'foo' - }; - - genResult(options).should.eql(toc(html, options)); - }); - - it('list_number', function(){ - var options = { - list_number: false - }; - - genResult(options).should.eql(toc(html, options)); - }); -}); \ No newline at end of file diff --git a/test/scripts/helper/url.js b/test/scripts/helper/url.js deleted file mode 100644 index 7ffbb16f0a..0000000000 --- a/test/scripts/helper/url.js +++ /dev/null @@ -1,60 +0,0 @@ -var should = require('chai').should(), - urlHelper = require('../../../lib/plugins/helper/url'), - relative_url = urlHelper.relative_url; - -describe('relative_url', function(){ - it('from root', function(){ - relative_url('', 'foo/').should.eql('foo'); - relative_url('/', 'bar/').should.eql('bar'); - }); - - it('from same root', function(){ - relative_url('foo/', 'foo/bar/').should.eql('bar'); - }); - - it('from different root', function(){ - relative_url('foo/', 'bar/baz/').should.eql('../bar/baz'); - }); -}); - -describe('url_for', function(){ - var url_for = require('../../../lib/plugins/helper/url').url_for; - - it('internal url (relative off)', function(){ - url_for.call({ - config: {root: '/'}, - relative_url: relative_url - }, 'index.html').should.eql('/index.html'); - - url_for.call({ - config: {root: '/blog/'}, - relative_url: relative_url - }, 'index.html').should.eql('/blog/index.html'); - }); - - it('internal url (relative on)', function(){ - url_for.call({ - config: {root: '/', relative_link: true}, - path: '', - relative_url: relative_url - }, 'index.html').should.eql('index.html'); - - url_for.call({ - config: {root: '/', relative_link: true}, - path: 'foo/bar/', - relative_url: relative_url - }, 'index.html').should.eql('../../index.html'); - }); - - it('external url', function(){ - [ - 'http://zespia.tw/', - '//google.com/' - ].forEach(function(url){ - url_for.call({ - config: {}, - relative_url: relative_url - }, url).should.eql(url); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/hexo/hexo.js b/test/scripts/hexo/hexo.js new file mode 100644 index 0000000000..91f0ae512f --- /dev/null +++ b/test/scripts/hexo/hexo.js @@ -0,0 +1,3 @@ +describe('Hexo', function(){ + // +}); \ No newline at end of file diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js new file mode 100644 index 0000000000..4a90369804 --- /dev/null +++ b/test/scripts/hexo/index.js @@ -0,0 +1,4 @@ +describe('Hexo', function(){ + require('./hexo'); + require('./render'); +}); \ No newline at end of file diff --git a/test/scripts/hexo/render.js b/test/scripts/hexo/render.js new file mode 100644 index 0000000000..a0f8279929 --- /dev/null +++ b/test/scripts/hexo/render.js @@ -0,0 +1,198 @@ +var Hexo = require('../../../lib/hexo'); +var util = Hexo.util; +var fs = util.fs; +var pathFn = require('path'); +var fixtureDir = pathFn.join(__dirname, '../../fixtures'); + +describe('Render', function(){ + var hexo; + + before(function(){ + hexo = new Hexo(__dirname); + return hexo.init(); + }); + + it('isRenderable()', function(){ + hexo.render.isRenderable('test.txt').should.be.false; + + // html + hexo.render.isRenderable('test.htm').should.be.true; + hexo.render.isRenderable('test.html').should.be.true; + + // swig + hexo.render.isRenderable('test.swig').should.be.true; + + // yaml + hexo.render.isRenderable('test.yml').should.be.true; + hexo.render.isRenderable('test.yaml').should.be.true; + }); + + it('isRenderableSync()', function(){ + hexo.render.isRenderableSync('test.txt').should.be.false; + + // html + hexo.render.isRenderableSync('test.htm').should.be.true; + hexo.render.isRenderableSync('test.html').should.be.true; + + // swig + hexo.render.isRenderableSync('test.swig').should.be.true; + + // yaml + hexo.render.isRenderableSync('test.yml').should.be.true; + hexo.render.isRenderableSync('test.yaml').should.be.true; + }); + + it('getOutput()', function(){ + hexo.render.getOutput('test.txt').should.not.ok; + + // html + hexo.render.getOutput('test.htm').should.eql('html'); + hexo.render.getOutput('test.html').should.eql('html'); + + // swig + hexo.render.getOutput('test.swig').should.eql('html'); + + // yaml + hexo.render.getOutput('test.yml').should.eql('json'); + hexo.render.getOutput('test.yaml').should.eql('json'); + }); + + it('render() - path', function(){ + return hexo.render.render({ + path: pathFn.join(fixtureDir, 'test.yml') + }).then(function(result){ + result.should.eql({ + name: { + first: 'John', + last: 'Doe' + }, + age: 23, + list: ['Apple', 'Banana'] + }); + }); + }); + + it('render() - text (without engine)', function(){ + var path = pathFn.join(fixtureDir, 'test.yml'); + var content; + + return fs.readFile(path).then(function(raw){ + content = raw; + return hexo.render.render({ + text: raw + }); + }).then(function(result){ + result.should.eql(content); + }); + }); + + it('render() - text (with engine)', function(){ + var path = pathFn.join(fixtureDir, 'test.yml'); + + return fs.readFile(path).then(function(raw){ + return hexo.render.render({ + text: raw, + engine: 'yaml' + }); + }).then(function(result){ + result.should.eql({ + name: { + first: 'John', + last: 'Doe' + }, + age: 23, + list: ['Apple', 'Banana'] + }); + }); + }); + + it('render() - no path and text', function(){ + hexo.render.render().catch(function(err){ + err.should.have.property('message', 'No input file or string!'); + }); + }); + + it('render() - options', function(){ + hexo.render.render({ + text: [ + '<title>{{ title }}', + '{{ content }}' + ].join('\n'), + engine: 'swig' + }, { + title: 'Hello world', + content: 'foobar' + }).then(function(result){ + result.should.eql([ + 'Hello world', + 'foobar' + ].join('\n')); + }); + }); + + it('renderSync() - path', function(){ + var result = hexo.render.renderSync({ + path: pathFn.join(fixtureDir, 'test.yml') + }); + + result.should.eql({ + name: { + first: 'John', + last: 'Doe' + }, + age: 23, + list: ['Apple', 'Banana'] + }); + }); + + it('renderSync() - text (without engine)', function(){ + var path = pathFn.join(fixtureDir, 'test.yml'); + + return fs.readFile(path).then(function(raw){ + hexo.render.renderSync({text: raw}).should.eql(raw); + }); + }); + + it('renderSync() - text (with engine)', function(){ + var path = pathFn.join(fixtureDir, 'test.yml'); + + return fs.readFile(path).then(function(raw){ + var result = hexo.render.renderSync({text: raw, engine: 'yaml'}); + + result.should.eql({ + name: { + first: 'John', + last: 'Doe' + }, + age: 23, + list: ['Apple', 'Banana'] + }); + }); + }); + + it('renderSync() - no path and text', function(){ + try { + hexo.render.renderSync(); + } catch (err){ + err.should.have.property('message', 'No input file or string!'); + } + }); + + it('renderSync() - options', function(){ + var result = hexo.render.renderSync({ + text: [ + '{{ title }}', + '{{ content }}' + ].join('\n'), + engine: 'swig' + }, { + title: 'Hello world', + content: 'foobar' + }); + + result.should.eql([ + 'Hello world', + 'foobar' + ].join('\n')); + }); +}); \ No newline at end of file diff --git a/test/scripts/i18n/default.json b/test/scripts/i18n/default.json deleted file mode 100644 index 7685d2f6dd..0000000000 --- a/test/scripts/i18n/default.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "index": { - "title": "Home", - "add": "Add", - "video": { - "zero": "No videos", - "one": "One video", - "other": "%d videos" - } - } -} \ No newline at end of file diff --git a/test/scripts/i18n/index.js b/test/scripts/i18n/index.js deleted file mode 100644 index 2887f86e6c..0000000000 --- a/test/scripts/i18n/index.js +++ /dev/null @@ -1,50 +0,0 @@ -var should = require('chai').should(); - -describe('i18n', function(){ - var I18n = require('../../../lib/core/i18n'), - i18n = new I18n({code: 'zh-TW'}); - - ['default', 'zh-TW'].forEach(function(name){ - i18n.set(name, require('./' + name + '.json')); - }); - - it('constructor', function(){ - i18n.options.code.should.eql(['zh-TW', 'zh']); - }); - - it('__', function(){ - var __ = i18n.__(); - - __.languages.should.eql(['zh-TW', 'zh', 'default']); - - __('index').should.eql('index'); - __('index.title').should.eql('首頁'); - __('index.add').should.eql('新增'); - - __ = i18n.__('default'); - - __.languages.should.eql(['default', 'zh-TW', 'zh']); - - __('index.title').should.eql('Home'); - __('index.add').should.eql('Add'); - }); - - it('_p', function(){ - var _p = i18n._p(); - - _p.languages.should.eql(['zh-TW', 'zh', 'default']); - - _p('index', 0).should.eql('index'); - _p('index.video', 0).should.eql('沒有影片'); - _p('index.video', 1).should.eql('1 部影片'); - _p('index.video', 10).should.eql('10 部影片'); - - _p = i18n._p('default'); - - _p.languages.should.eql(['default', 'zh-TW', 'zh']); - - _p('index.video', 0).should.eql('No videos'); - _p('index.video', 1).should.eql('One video'); - _p('index.video', 10).should.eql('10 videos'); - }); -}); \ No newline at end of file diff --git a/test/scripts/i18n/zh-TW.json b/test/scripts/i18n/zh-TW.json deleted file mode 100644 index c62f2099f5..0000000000 --- a/test/scripts/i18n/zh-TW.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "index": { - "title": "首頁", - "add": "新增", - "video": { - "zero": "沒有影片", - "other": "%d 部影片" - } - } -} \ No newline at end of file diff --git a/test/scripts/init.js b/test/scripts/init.js deleted file mode 100644 index 46074c8ec7..0000000000 --- a/test/scripts/init.js +++ /dev/null @@ -1,61 +0,0 @@ -var pathFn = require('path'), - async = require('async'), - should = require('chai').should(), - spawn = require('child_process').spawn, - file = require('../../lib/util/file2'); - -var compareFile = function(a, b, callback){ - async.parallel([ - function(next){ - file.readFile(a, {encoding: null}, next); - }, - function(next){ - file.readFile(b, {encoding: null}, next); - } - ], function(err, results){ - if (err) return callback(err); - - if (results[0].toString() === results[1].toString()){ - callback(); - } else { - callback(new Error(a + ' is not equal to ' + b)); - } - }); -}; - -module.exports = function(callback){ - var hexo = require('../../lib/hexo'); - - async.series([ - function(next){ - var cmd = spawn('./bin/hexo', ['init', 'test/blog'], { - cwd: pathFn.join(__dirname, '../..') - }); - - cmd.on('close', next); - }, - function(next){ - var blogDir = pathFn.join(__dirname, '../blog'), - assetDir = pathFn.join(__dirname, '../../assets'); - - async.parallel([ - function(next){ - file.list(blogDir, function(err, files){ - async.each(files, function(file, next){ - compareFile(pathFn.join(blogDir, file), pathFn.join(assetDir, file), next); - }, next); - }); - }, - function(next){ - compareFile(pathFn.join(blogDir, '.gitignore'), pathFn.join(assetDir, 'gitignore'), next); - } - ], next); - }, - function(next){ - hexo.init({ - cwd: pathFn.join(__dirname, '../blog'), - silent: true - }, next); - } - ], callback); -}; \ No newline at end of file diff --git a/test/scripts/post/assets/287.jpg b/test/scripts/post/assets/287.jpg deleted file mode 100644 index 88dede91b68bce2fef8b02d577821383d6d4ab51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16361 zcmbWebySqm7cTq`0}Lfd3P{Ny9g@-vAl=54Ns+VQw}*gG=$@$)`oV)ylSc6Ve_QB`76)mBhq@)Hr@;AZmlwSML76UZdR zBq~hsZw`irfD!NDH6ttA2q|{HS zY3b=185zkRF|#l;u+T9uGW_QvAk3){TnI599x($2DFws-cKg=>km7+DK^b5WGk`@3 z0+WLN^#F_*kT{_KHsJqiAS^I8<|*;;2?#L*>K+1EATSsU^ZXb9n9;$Q-vMk=9I_|; zvbf|r)(~b-3W2c1Ts){;btk3nuOk*g8?Sfx1XPcxX=tCavaxe;3JHrm7Znqif1#kL zq^zQ}j)YjEEG&VJNb@%l4^$!dVO-!Pure|hSx3+hVPfpLyFD|dHZ~lV| z1c3i1EX?tL0{j2qBE{gs!n_u2$bWEwu>3I{Op1;3gddkoRtIA3NzN<~hDRZnm|NY6 z4;9osqO|e)ML@+OwEXn=KhXY*?0*f|yZ<9({|~VL7Z(~J0)sFw4@?Tk0L$1=hT1?V zy%-Wi+OMh@kA@4rE%&9HOvH8h{pm*#8LuboVQF?#Za9O2g|TeEy59ZRHjE}hAZU|f z?NesE+dqK+g-6T{E$~i=DUQ?krKXF1!I>_nyISCCA>inZqtJ>Ll9}VYkm(=rXcG{?>+l6`dqIh9ITNyzZxr#!CAN&Jib)wZP^`?=~w;c{}7SiyUaMd9XXYVm7XO-3B zUckr$zq9n$l@LRO&Y<8v)wNjKTeA$*&g( z0#}+bN=YXdPm(&-lDJs}o?Ie2tG^2lR8xZ?5-J5)8XL4m*9F1 z8t+kduyF2v%w!U18(nd2Qxx%(70%iJI&q?qs-K?UlwXd&qohYIE~^-fH%FRd`XLwX z9aq=n32+({ls+Mjk;i&N0Q~Hm2E{>a4Xr<@9$=H|nDfp?xf@Bwt5(|kU5x4-_G!`V z!)QI_@Ow=nW^HqZ@@re_zQ%}5mmS}4t%pIc$H8Q?wi88a9uQ5=c`Omo2a{;tTQBJp zkg7!R0;g7uo6JP5+lta#I>*J-eGB`6HEmq?iE@fFHl&Ksn~6?R@5J@9jXA2u zag_7tn{W$B>GZ1=Nxd_xX-j}h1VJ7*(UM*&M91&~p!Vq~-)%{W3~LF8QRz9;JJV2T!YB$lHjzdNX*6sMsqROwXi9Zb90|mDZnSYSwC(IR%5y^kN~8m7%U!0Z%nwT(Ll%a>+9x}1)4EzZf|IwEKsYlL+mdNaA-lo|HW zdw^2aS$<(}jBq+xyVcm1H2;I&UmSp^r)7<^2(K-8Cv{=$1q3+JLX__=n6f8t*r(xzn3zfdwr&F7?P%MHNUuF<+z8E!x&U>4yGIvJGnzahKMAwEV*^~Zrn!8f`5m& zK@aN&mGL~$T|Uy=@P_dBJ1?v_fdB+SmoOBH#C@hDdp*@nRutq6Q}l4cN=DEZbMkjk zIw$xQ;7O3eDC;-A)Xj7fIt)rwqUe&oT zm>r9oXk4jH874H%fC7<&6u1c3u}V{+Op{-zrNl~A#N)nYvoX|y?DZYL>Q~#+bLI_+ zmaL=%jpJMe`IZqN;!R745C$o!!R#P9>+0;fkENgY98PVAp}v3@m!A#2xEGiBq1{o& zV7K2ng4p5UMkS+MHfs(6Mw(32YB=nxiGT9%Gcfn)YU5k}_jpN!8tDHeoJ&5}%b8um?BxWG9luj@^g;A3qt0ozEi_V53SlN-j%{1 zFS+g-*Npf}cgsH0yo5>`K|RX{R$smHzr>z4Z}>SkHG7;ST?_hc<{A^@rA4>WAXG`g zWge)03#WG-syL^Ni~*k(j|o+~#3UcU>CS{ed(8(wu+Z!#09#yJ8E8SFvGc{<`fxBbJ_Amb6Imc{WU|KO@9x0 z-`=Bjy+as7nI}@Q;fdN(5NgvK2PCT{NgZyRrN04XF(9OTCSrPu{kSUmlT^ zbx)&ZA%)>>sC%@pT?&Dey6%Z5ed<-t1<(_R`Tj@S&ZI`I*e6}(`ov$ShYUx&<-Ps2srAEYJI&apo- zn()$r=pDwrD`pfc6r5jhvqkwBTrlL6R7xK|>WSoDGz)&3iuwmsv?Rb^Zg0tCdE6Am zRlM&0uE8?&V`1QBQ3-;?ujQC3%iih!#Shoi05q~gdZWn|X}ED6lLUw7q&`Aa5GtS! zG?QWl@P5YsDXC~sVuo? z2E4$<5&lRi$Vcz|Az%akL~N|?NuSh?A@xeWl@vRc2t&%`bhf9mQX8Py9qmexO#Lmp z7DRZK_w`I00w&JSSt--`^G?j=bk_Iv#ShaKhO(HqDA1zPTZx8Uz21>+vy8iz!AC3Wp>=Du zUIYX0gI)Q0j>f1;xIN-oyx%EzB1q&~uF{LtXXDTwKlTv=cd_Q*kKFs*r0cm@jXz2+3Ja=e_cD(%85Pi`8OTUL+WEf5QL+4jbiA3uC5t?E* zh6?wIC6S-GD|&vBWO$EQ&g47Y2QQIS$d*%|s~~6b3OHr2o0g)1{$Q`0K{R24t@V@^ zr6$+*(?%I-omls)Oth4jpa8px4IQbuTU%Ri>u+BxqihHTXgspuEt%hvFGYQIL{{RQ z=%%oAj*n?aR06No&o^Pl{tX&#K_-vIR#}tD#L8pl7o`t8qkh^SeTib3Jh1;` z#Tt9!eF|`qZ5W9xk5betj{s(wp^Igk^YO1xQKBYlPli@Kq|ZnGyi~T0N1aPgxlEYQ zavuy@Vg~w5?>+r&6bL@#FTMQUl`f#rn2qu5!}x-aGIt2w);1rzGGFMm4#J=F;#dCjeb>qEv|@HUan(EcUttc{G*|wqGfILf@WIh@Akl)uzeVPU0m~@ zSKcg5gr^eEWi4dxo;DXuhuTe`=3dMT^;pksTbenFhS53)ip|TO?;D(Jk&ZPCJ$g*v zk5^O&_?uR=6gt1J+xzjV8S+=R{yup2_}WOU+!YE%-!l6VH1AzaWeE=^Rcm zzZpZrdi|zFx*k#xdm7ZL3CE$CB5^4|{lX1&)|ka9r$^D%Iu!wF=Xn3d2L9ytm?j57 zkp3)%mF*&&iQT!p@vICdxTH`K6SykAREj>ni0;NWocpbum1kO4zQc`v1 zZ(Q3tbpY&77DeTG9$Z>4`b0aFwyODk^fPahWlz*Zz4t$08u36FHA`oQ`n6Nw-CRu) zie$q0m95pOxImX0s>=Sw3Vrg~%aW9a%VKbKxN)C!=yc`W&Y}awbK$OB_h)~Q7IlG? z{bSL<*&(4TiF29R&z);S#%Go4$4|fiG5b`Ms>`ZoJ>G3^F z_$49i#LeFT-79Vb7o$0wGmG)G^~t|V)8gqr6Sg?x5lW!?iyUA)onv|9!Ras#eH3$K z{yp|LI3`qlwqXKIDQMnhw@1Pz6AO5=sj2x9K3M2ESOC8*yUtYE&%$$!v|4R+>j^RV zHLt{7EyOQ-VK0Sk8-6wO+&fp(fjevkn$*uG1l*oEy9tuJYq?MTv@jb@-oy4DLm8>Q zI^a(KdGaadu3M5e;*Iips{0e&f>4kcf`BSv-ra`s-S^Xe3avt#0wX5-pp{x4cjW}R z0N_lxjqSH3#K(BGW7BDE970lz{$!@;4nI6#+W;S02^F62_;_gifkPcv0xbA5V3VO6DI=eyQ3J01`nJ{3vQF}k zQ#S{vis%J`NW)auE=)%&E!5K0WoSVmDmgR_Z-A!!Q|~_@-h{s0d=v1Y^a?~^GFpS(gIchq$EC>y#oAwsFc*rD6*zaZdpbi7%PJcem`DZJU@J4lQo2 zmt74)@r1?-J~fM^+#i$R#1LzfAoG|>(CY*V{(Ijo2KqOTA0}%wD{2O9vckE|uK0A1 z89IHveEtgK5k!i74Sk7pb`fuQ*UmWin1^BXG4cf;x3nJwl{qjYsaCc#TQr ztLZN`hz#ahHZ$vzo)oWnzWDTtaz4PZ+b`$^bU2ajrUK&zaX-7(lkUc=He2=U=s)-e z%%UE>Kx;xq2Fv_zQjDk}oLZ`YZBd=vmY`|2uK2+~=|m4MCMu8X;2`GorWeY^5Xv#t z*v69MXj`}wRtudT^=6&=uudu#{E0B>jZKmL4t{Z#CyewtB{^+sXD{p;fwvBo)KL$? zxb49^hr}j|BI<$x1a6|nowLeDvgit&E>P&IgDqKys=tts^#S0dK+P@Uh;&jQ!+x9r z)<64f>xiX+2uhCLUixL#5-C0ybJ{Ni5{b3>{^2#hrj+Y!J3WgAs8*m1*W6Zky;(8I zI)N{vQ+Dkn6vj2)D#b!;gWo&ML{9_P4i$twp1E=B`|PFqVxmvWTDP}E!(C3JDCy(h zG>mV4y>%0pjGTjC{+#fUFpHkZQJu8aN>TxVx17M>TP5`XnLyI8c%Yf|Zdc^^tVDK> zll`+Zq~e60NRkcb3uh1nZALb_ASOJ3k7XH_*=pWt#dVmV{oA z^Sn2^Hw|V&!l_cpb*yjAOOukEe2yS&r)vK6nB`THfXcpU$kPbQIzcw33N_-!vkKyg z9ajfd{u{#{@vk$6hYn+XqZo3fk(wI~L$Llj)Zs3p`rIkgy5+4k#nYjj%Z9D^csqM4 zor#$OS3N;_?k^hHlN1v)+jiqBs-T{xLIx65oIDw3;LlE#y!oPFWBu}agMy%!v;~pO z$frJzo1N@+4f_6u4^h!c*Xu^-4t}<_x!AbRHB8=zj4d17X#S#-w8H(Idme|rZ42wH zK{6NTJ4lp%M7e>7rn67WJlo|{qX3?p?5p6-^9u%4Qi6l=;NzRG-qFDJ*={}o)+m^N zd)v&UV0^p8b?X@P3Ba66AP#f&iID@?*||VC!)@ujuVjwxhL(s z+s&P<;3Z~Fve!W+{q7BnF>Mw3X=mN^Q5H32q1BsC*7r+^PY4NfDF>2_hO0-l^eoiTk!imkCXOKi-y7V@ z2i+lmKhI6eY4|m}=5lUn2jh5Hl7DZoS9BoiYvWRj`%S9Yy8`Fy=A!Jh)w6{nIfsd# zQHF97MT=Vz08umO3ce+Vp1*|(2 zmFUJH1p$0}6LVO`T}I;!ByAQbNlmnV?`wCh->2}MFJ-AmqXh|kY0D9B^1XGh-fc`c zsp~l?xW6yL@b8L#i-tDci_F-szZrdENcGPj55j_X$^CJ>?}a;J8N@>0*m@-HGx}sT z-Pj;Id5d0&h`gk8^xFZd0|TElGMFyQZmus)1;bdXX>yrl|POU9Ft{?tXlcni4z_s$+Cc#RW`b)@)Pi9haS&@9*U16R_ra$}pumjep<{JsI8b0&Zq3R&K4@ zsot4ecj8Z0h>t@^37Z>d16hoe+%NG;hm&Gw7<)pys)d}SCrjjs@#hyJTz^TLJgrF0 zQzB5JRo0`~>OVpd8~8dVt(5|lp@4=9T7^{3B4MEWn`=_EVv>2q!|b|W#vK&g*}(fXo^aDEl3 zhk}vqzo!cuHvBs-#&;4H*VyZgMo#n)pX>l7h8YGHK_R*Srt)+9O}mjzW?Gx zUkskKowBwKV-2NEzSWUwCt0<$z^(5Lxmn%Tt&DyS839a zLux@&g8@Ztl)dfWW!e;gw-+xLGhfyRlrCBq@M-|Yjw@eUgYJc2dgimj*%k$-!vwW8%>@c)w{a>~pq7@$yN)Lb&$$vOtp!rNxOE%owP zyqZ%IDyu40eo+}CvB>;Z;H=IJ&#^_6>Ykpj)%20`gwLN*BrEuxEODINCe^u%+89W@ zQzmf%kXDH$kP)lk2nBaD!B+w*oH3p!*)DZ;@k93Yk(HibX=m#`NLEeAnKLv&GJ%Li z;xBHNe;N%}b5sxRgVsDQCr)MHn-9n*E?~vqAX;9PY12$j9zcJ_}Vnt*JDpF<7L|GRmtD z&2}w5`Uk|+jvM~bsq=*=(BRKE-ur+mZzWqvUHwU(5$xn}u)ULrKDcMzu5u|)egSnb za5VQ}EA*G9ymDQ>9U}~VeWjA$J8zbn0uGPM(J`l=8?g<#cplxoS3U^!Ly$Z?i{Wsk&IsUK?)JVKg{C1PN)Ctgmw6_ z_7`^acP17FlF1*Z$WW@ew$Y4>hpd-SQk+e;HVsee=-*ExS708BLDBo|q+6uBrakF1 zoRx0w`ic^`JD6Ft+WhFFuo+)8h~yS@>wr`3=A|dNzV{@QUR}Q{zLqa8W3C@D^Br3*M%^_BDw_ zc*0q{u1c0pi$d#u!Yh>A#4ppJZO1$!+>+-qz3cAp!Tsa|~KaqW(~mWK2n6=kv{&DF(~oxsz!$0Axci`nOFcP#n$LHBiQc_&MB zhhjy+?VGn}pq;T5+4%ER>}=>iz`eD5|B`XB;CW5X3W7vAxd)>=F>tnAh5!B0FTD9} z$0p>j(7bwR%sAxj&3$N$jyWWY?{b6!L{Fgu!}-6v7>FC7`G%UFyyit#J;Q}dkU*Th zm4}~MLXbh(sjg7D#tt1zwSs_7B;Ly!IK+Cfr47=j<`Y@?A`s(vZzdjoiX!#gQn25l zI{K7kAP4?KM>{p?4sEF+Mv%w3O6bSQvQEk6?`8raA31n!AbuqB6U`X~agw!>{N@H_ z)N8!@UxR#wzpT6@8^e~ccEI*d9z+ng=%K8`ekKk{makR*Kze6x#?+$f?E(z~?si9f zbI5lDt7F$#o^P5X=bz7bB`FPOORv;s zC6VhttkX33yUE(!1+0lnQM?(ClD1X9V&PqkxW5%gxtG`aHU4_NQTMgaHk4zi8rzGQ z9n98eY7}0}7wBTvhC|D{5{}`xkcHI7Jt6IP`{u-V0SIEo-Z}0iEfyOE?fL8nZ}>VR^Qw+sJfEP9E%^CSs;oy+!&PW2 zDdafc5FoxYDm1gydVO?{mz(7_IWz}ty4Lp}E201`hf)Z8Bky2SbyO0m6$B)C$A3BN ztAjmxlj->3X>05716Ss|jhm&9r-EE>uW#qi%Wf=@Yi4!GuEWSnC<_7L7*UWa{M7in zKe2+-Rj1v_N+3mrCIJ14qVh-E$MgkIE(~rr6l$X?E9%U8xe3WUBE=97KDp{Q9d)ZP z!wukueH!+`kxVSOZ1o3RWo^nrmBe$i>bVZDBtJv0F;r`1g9A`*x8IKwgptl37~5B z90}=!H=g$finItX^z7+>C5KUK7E+h&aMWItj^8a!vIBY1|7*SMLOkW-3e?O%Qm9=B zW}^V@F5(~i&89sr0z=ns52jmivk=LD9AL4L&k~6Ii&BJ_1*-?pkl{&+jeW(^-`?weRLf$NJu zou=&{v1!v93K!-MJZw$Kofyt{e-MT+o4l-Tey}n~r~RU%{qLgdbHH|zQt#sZRYO6* z#)i~$<+?QVg7g;Y{fVJ;QB@`TQEwUA^CuSk1NKDw=??#G+fEGh5v#-knCho@xwaJkB-4$szWPVRaP2hk)!z{!F< z)i~a_cuq0y-!#%wO1+Ch^`ST#c`9H?%BvwdhsvuyyGA2WwYi51-gm9Op~#Akexg`rv~;~vR;RV-d9@c6^R^WC&wX3qS=zChP-3^cF-Z$;w)8hR z_jd6NJCWZA(Byz@fSTs7wPviOWiaXskx5rlYvQiyh4zT` zrbxbWa6hr zetIk+5kdyKn!36a1W|qu+l5hI$T$n^2hSGA3&AanSMrXDKoCW9AwIou-|e3$ zSIT2N2uT6`#9>1SEkDkVc16a^*@SH0m7+}Xm-aAb>62NY*_Dey)#sLw)CKw6(CQn5 zfm+538CBqPZStvftx=&8;*#_}MH`az%ha&hF6kMLmT(Qphr&epSEAA=&~V>}W`joS zu_1kL3;UuE<_vmgWv`b%#!|jZgwgq5m?frKKG1S48txyGrCe~nk$z-bv~3s{PungR z4XnA<*82|gZR@tbGY)S!t3XfpLLPe?C3RAR!(en$MIY%ZxpKN{-CCvfk5XsR-E=S3 zh$U<)SojAyGZG#KVaC*F%e(xdYU6 zHp#@RK~k=to43PNj60#KXDwyMP}_}Bz!Hmn^Ms!BeT0xl6j@)mwdaX4l@aLnA zv}xL}?)4(Cnno88{{YFEyj^F%`o@~tBn}pH8Y=#iH=sW!rKa|oX(kW4@j*lV6mR~V z?B5t!=gwOOwy?CR*@ zGkpR2)%5$A)gj3@45Fo`1(~rm@MKfXpq%s^j3_z%RZ{!H@HpJs{ioJQ-N#KBT~2=` z*eRPeLM6As_yEGezN1yt_SSQS>v1KCOF9)FdG5SlH67{xOS`MNUL&LW6Q$R*a){t1 z@tM(o0JYt%Z}Q`ed=m^;GVH(<(Y!zMi}GL5aPuS-VwX*=?w@F{Ix0*FVu(!GZuSx? zZGF&)eKQk&_wCWZUv7YP z_Al&=7;;Duu2Bw6QvxC}Rj)`7Jk9(CkUgahkNjy4Ypu1WYkkUinDN3+85Cs}*-Fk4 z1VQe})-DE2IivPE;qZ=o)}nSYY<|_9M_00PoLg}$Hf6LOvQXDb$d*f8-vwmz35nz5 z^iu?pM6vzYFokYWu~;z~g$Fn`Muy06pf2)ZM}}FoWPXS+OJcETHh)gYM~reIjb|f! zYF+)OO+~1p`k+2lmcHb4PHsn?r}{_pRI;z5m+`w^5+tD4it?pQS?|Vj6}d6lh;5fM zo-Ur;2*es94sF{ttrw^BvQ1-~tOEkGdO!S(QBzdCo$@Y#5($Y)Pwg6sQ-eXG7nCZs z!fcV%SK5$_SUg`mkKmHwT*oUJ)w8Mb_)8hpR3AR&e*k6`Q;bXO{pIu-54g=K46@m> zs1HdM*m@g0im?hCDwsKkS@0QWPqLwZxh9h3=?OwoIh{iH2!PX!TIn3NZNvQWAG4JT zpA^8DOIKy`dY)j;+thJ-yHv@jT1WM9a_ukIc(UR@z@|up5iW9{0_sr6`;QSRuGPXA zkwe=FrOQy&ETNnWC-8@Og@1rmh`tfxTk0P-3eah6S}NVE!UWHKi->sJS4aD3L6Zi4 zioSVoQ(E$7RnQnl&{MIe({)30xbx(($BZKNbR;{QOWjcb>797Bnf+ZYK- zz_Cc8(Uaj-koCR9JHP?#qgtB)Nc2?wXJFoMM$BhqgG?Z>B)2IA5Wi=Jiz`HMMZx1> z6$_v(*KtY6@-yvui()3P2VVaG;xmRbs; zPcvy66dyi%lIsnr?vIbPPMgHVO^{?Yp0*g>ko60OSy2RWgGRPe=;zs6umx|9GtVmC z+rG%>cc(E)p$t=f0lLDER2@=M#*o1{OZ^i+Y98`-EqN&b9%!X7Uz7cVf`%BmrrGb* z1Gc*{r{?!<%W7>*?yko3B909IN_IPaw3*|Z?v1ofOs;i< z!fci|jb_!+nV9+Af=A`y$;IWaUORK6awT(yx?mM&s=@`sj}O6&c~`GBBm9|1*^l_M zT2ZEl5lGgUuvf@=I;-$q7z6o*S~plgTJ~;ccgL0YP%BM%{yG0zPSAyG4#AW6EU&tB zJ?8qn&wFjPmO1tfGWYt|{hT^q-D7J*!mi$(TnJ^bn89}Xn<^lB6KkkXQbU4kYpZR+ zq2W<3K#UY_XY$_L21ID=U? zZ2yW$#iNB*FNqVA>y4tcuEkL!dj*|8Sa6Qt+tEfoaJw7S2AWWboRTQEn}8Tm{Z-&V zO9x#lbtZ5aEhgAfz2^i%|7y1&XG10`3CM|Vh^2o;FjT!>S<`h z;02s=baJTbfUB_TGw&*v34z+3K+rAYG2+3999miWZJOSISovqZ!gzvJ4=KUFoKtP* zJz}*#aFw-ybLc#6Zi(FZ@H>InQ;t2QV#EUrOpY$hkhl$XPC4yt=m>_c-<(zO$-PfE zA&rzOn|e!Aq~uvOFw0hqfXq2D`w-#oO%)!s2X$wj(Q_$9i}p0Q6#ukCM)iep)Pe&6WzrAQP4f$y$t_$PQnQjV94`|oQ0a!rPQ@5iiZ`NDrd z4UED0AApCly*If}IX~JVR`=cd2mH3Z{ZaU2QmPm{+80L6xPvi60pkgwxH1H1BzwPR z+@9YG*&d8TaQ;SzU*U*44gW3T0xG+ zA#_52bQFA%HjtxVFh*wQn^z4_3{p1he9%S_UjbFR^HI)?$m*^RfwS|9`mT#f2Rsl) zM~a2~Y+yG^+%>*zww=TJpvg7P-1xpeptJM_nc@%j0ArTjL$u<>pJ$yFj>aDcz25j# zberk95EP5jVP+eB!?S9-1a@$E`)9JXGGhGQZ9|_n>4S}ZY*se&_?DP^1DjK|Q;Tf5 zsCc~*pPUD0iaGkRX;1337FlvIa>wh=v_G@WL=G=NL#Mr-GGxMroTqkH46?$rSKWH? z>JzFhR;4Sgk1X3!k_yGtc@7V8JNtux>7%Kh=M571Up6013eWD;SSUGA(jD|OL^KK? z{apx`_jbbbrI9|L-fHfir+9ZVdqWPRV~g@_)9n3OT(0DqS&|pf#A{~%w>4U(EiQG} z=$>ZskbFctE=CMeTpsn7u&^{O)`0qwz8Fh}*hPC_$f}X3sNQw%o@o*T!ImNMQM@4s%7;;~HL!w0?RooW0ikpY( z;P>0n0A%n}GP3a(h5BAjSPSiH61D_dlpH|JL%hN~xL|%wY-Ofg4*RzqKSJiUVlGn{ z@C=v3*v@Jk@;r3;6IYo%7Ucj#>gLU17;nYFd+qQ2)WUn=VLDQ+>bFCuFb15GD}hT@ z2aoTX-gh$hrE9+XSnJleFRB)xq*-mDg~5hy2u$D;epIP)4uPdCsmdT0S-TowDjv51M_@5 zzkcYX01cKBMi3Fpm#hVIrJ;lxAM2!4c&tUNMq_ptEE60{rO#KVTn937jJjeSb}e#b zJ8~46bFi^ITVg~4_Qu;(+f-fhIM*pT>v|>1qq8%BwxP%)@Lu{CzjOJV=FqMtHRaVDGB@;_XjD%Za5Q+z zw1lamRH91!W-?e!j4!je(tL>DkL~?!&^YF1CgpL}FaruGb7)UFtgb9H)yz2HaKidr z!|*P1+IFH+2S(F39;By%I73Pjs1pFgBHP17%VT19AN(GOZ->xPLp?2PK}P+Tzh0USL9L8^X)2y~8*;e+swWx! zEmpqZ@Mc>*DL<)*4SOn-Bp{De0Bw;K_wk(*c*@;RG4Ug8NXgp#Po%|u)p&=;0v=_F zWAW1oCPiY2nCUx1!SLGv;B3!umgU5Y-4du`z#>_UEEhu zJL99f!(flqI~7$5iz}c4slXCAS$7<4(Um}gQ3=NRu`h&n0QS6SfW2eXbA&=0{4~~% zE-c3s=~-kp+L#Es<3wl8$EDPQJ`+dTbfRi&xDe=sZV~SXP7-@YgJzbi zl#rCX`eM4_gxh|f>mK`C2^$a(+1jn4W#uxVT29J;ZU$Vx)T%a1WO|s=|5Ko?_qCJX z{%f!A@2PDwFLuYU<~kVtBTm%IK zuxT!P5ywP9%#L-nxq+3Xu88g7d5_yr>XM?qd1oSecY?VS^-K$o(}AsH$J zqKHyZMrlKOCe1$EY^&Eiry8q^Q))s!*6riVFfiL<Fd*mPU@ zfnyA9RQ%m{632G2;2SL4oHJ*Ex+pR8zmt`N`U8y=hx2y; zrZ+zh4jULz^6Is=5kD*8#AS9CA&&Oa093G2qhxoZgDJZH0qvxiVy-Pj0ENbShM_+&j(*D_Vm2gxJ~e4E)Xom5B8oG~%H0>TNfsGJj&*jPhBc@~+8K6CAOp zue;zvmn5bFoCU8ulhu=gF&fVs<`mjO$_}ZWp@E_{KuyC|r79V31XDRV|^p*0Ed-YH{NB)VHuB{5BvI{(eAbZ+qnIQsA#{JF2G!4T|;ZU z7io@*oyZJTcZ~QAZKty@kLcTuf$Y4ml7^J2th()IpqG8x{hsaydFZK^vb6z1j@sgJ zqDYC|**d&8xV^ATjHgeO#tlFa?Niiftcfo<@&=dr<{CSiKy_+~L1*Q$Pq2-iKliHg z*d#5f$n2D!{sdhL>%i>M@~UB_8N;67t{ToLKvN$HE*2b9K=yve*uM6(3=S-{RPrck ze8m{DD)$mabD<+b`B=mL0xfme>c^%P2_LM+;y+j;Wy3) zfRI<(IH5!VUXRs8*JI^H1iu_eH#}5`+kkWHLfyxzdwH1IwP6R| zFmRPlvML9?d<6gmdHDjqrR&1s?qbW(_ zFOY4UT#3hIhlbWLnz3&M&88Z!6O%b^T z$K;JtBDA}6Y0BBxO)Ba^1JbF@`2-LI0-NO;ZX2Ul+DcY4|EwRYTpN_hN$BU0rm+B8 z`QraXNhiA&XotDxJUgSb$*r^LNY-p*VU@*H`aXqXCbHbuV(BR(9d1g$n{n)0`K-;O z(wPQDO3qnI7jQ<(tr7o9)PlVzW^gG_@4R_ulc8}q7ztN zE%~itg`q=u4$Een^EgO>BBoQ(3FJhH?+^k}&G5izvtOWY=D*0P)H4C!Cros@C|S+B zaqL>mFby8Z9l=yx2sHx7xnZ~;X({`Grrbg}Cduw*dI6C20`6Q|`e=ejRD6D?u?Bfd6Z6$p%wVo|CH8<>weTg7;H9wYD@)`Q>L4vd&TYZFo vUv=-><2qAu$?ZK@5wdZh`6U(zvO125StZLEjXsqyVUb+B_tA#@`~5!v{rivl diff --git a/test/scripts/post/assets/test.txt b/test/scripts/post/assets/test.txt deleted file mode 100644 index efbfa09b5c..0000000000 --- a/test/scripts/post/assets/test.txt +++ /dev/null @@ -1,9 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit varius dictum. Morbi semper volutpat nulla sed laoreet. Donec vulputate blandit eros, sed faucibus nisi facilisis vitae. Fusce eget augue dignissim, placerat urna in, consequat tellus. Morbi placerat mi sit amet lectus ornare posuere. Sed vitae hendrerit velit. Aenean imperdiet vestibulum arcu, consectetur ullamcorper orci aliquam non. Vestibulum et cursus nisi. Ut vel tellus libero. Cras dapibus tempus feugiat. - -Maecenas sit amet metus elementum, sodales velit sit amet, commodo urna. In convallis massa in lorem ornare bibendum. Donec sit amet placerat mi. Quisque nec pellentesque massa. Praesent varius massa nibh, sed imperdiet urna consequat at. Suspendisse lacinia sed lectus at consequat. Nulla at quam tristique, rhoncus quam ac, vestibulum sem. Ut venenatis vitae mi vitae rutrum. Nunc at velit id tellus lacinia dapibus ut nec mi. Morbi ac nulla sit amet eros blandit tristique. - -In non est nec nunc iaculis mattis. Proin tempor tristique augue sed scelerisque. Praesent quis nunc interdum, consequat mauris eget, feugiat velit. Nulla aliquam ante a lacus commodo iaculis. Vestibulum eget aliquam massa. Duis bibendum lobortis nibh, id blandit odio. Nam convallis neque purus, eu euismod tellus imperdiet a. - -Mauris convallis congue diam, eget volutpat dolor tempor non. Nulla consectetur semper dui, vel tempor nunc dictum ultricies. Nulla mollis nisi at dolor convallis, semper faucibus metus vulputate. Curabitur consequat massa quis massa venenatis, id ultrices odio congue. Nullam sit amet sem nibh. Aenean adipiscing ante et nunc dignissim hendrerit. Praesent tempus non nisi at tempus. - -Mauris tempor mollis felis nec adipiscing. In nec nunc malesuada, accumsan nibh quis, aliquet nunc. Nulla sit amet massa urna. Ut sit amet ipsum tincidunt, ultricies eros eu, sodales velit. Nulla pharetra risus magna. Phasellus vitae tristique tellus, ac sodales turpis. In nisl ligula, vehicula a erat sed, gravida egestas justo. Quisque quis venenatis diam, nec sagittis nibh. Maecenas luctus, est quis convallis gravida, lacus mi faucibus dui, nec malesuada tortor ante eget orci. Curabitur ac facilisis mauris. Sed quis suscipit elit. \ No newline at end of file diff --git a/test/scripts/post/create.js b/test/scripts/post/create.js deleted file mode 100644 index 8bbeff2f7b..0000000000 --- a/test/scripts/post/create.js +++ /dev/null @@ -1,350 +0,0 @@ -var should = require('chai').should(), - fs = require('graceful-fs'), - async = require('async'), - pathFn = require('path'); - -var check = function(args, results, callback){ - async.parallel([ - function(next){ - hexo.once('new', function(path, content){ - path.should.eql(results.path); - next(); - }); - }, - function(next){ - args.push(function(err, path, content){ - if (err) return next(err); - - path.should.eql(results.path); - - next(null, { - path: path, - content: content - }); - }); - - hexo.post.create.apply(hexo, args); - } - ], function(err, results){ - callback(err, results[1]); - }); -}; - -describe('create', function(){ - var posts = []; - - it('default', function(done){ - var data = { - title: 'Test Post' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('slug', function(done){ - var data = { - title: 'Test Post', - slug: 'WTF' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'WTF.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('path', function(done){ - var data = { - title: 'Test Post', - slug: 'WTF', - path: 'mypath' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'mypath.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('layout: post', function(done){ - var data = { - title: 'Test Post', - layout: 'post' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('layout: page', function(done){ - var data = { - title: 'Test Post', - layout: 'page' - }; - - var results = { - path: pathFn.join(hexo.source_dir, 'Test-Post', 'index.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('layout: draft', function(done){ - var data = { - title: 'Test Post', - layout: 'draft' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_drafts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('layout: photo', function(done){ - var data = { - title: 'Test Post', - layout: 'photo' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - hexo.util.yfm(data.content).layout.should.eql('photo'); - done(); - }); - }); - - it('customized default_layout', function(done){ - var data = { - title: 'Test Post' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - hexo.config.default_layout = 'hahaha'; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - hexo.config.default_layout = 'post'; - hexo.util.yfm(data.content).layout.should.eql('hahaha'); - done(); - }); - }); - - it('date', function(done){ - var date = new Date(2014, 7, 1); - - var data = { - title: 'Test Post', - date: date - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - hexo.util.yfm(data.content).date.should.eql(date); - done(); - }); - }); - - it('content', function(done){ - var content = 'hahaha'; - - var data = { - title: 'Test Post', - content: content - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - hexo.util.yfm(data.content)._content.should.eql(content); - done(); - }); - }); - - it('rename duplicated files', function(done){ - var data = { - title: 'Test Post' - }; - - async.series([ - function(next){ - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, next); - }, - function(next){ - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post-1.md') - }; - - posts.push(results.path); - check([data], results, next); - } - ], done); - }); - - it('replace duplicated files', function(done){ - var data = { - title: 'Test Post' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - - async.series([ - function(next){ - check([data], results, next); - }, - function(next){ - check([data, true], results, next); - } - ], done); - }); - - it('custom variables', function(done){ - var data = { - title: 'Test Post', - foo: 1, - bar: 2 - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - var yfm = hexo.util.yfm(data.content); - yfm.foo.should.eql(1); - yfm.bar.should.eql(2); - - done(); - }); - }); - - it('build files based on a specified scaffold', function(done){ - var layout = 'custom', - scaffold = ''; - - async.series([ - // Read the fixture - function(next){ - fs.readFile(pathFn.join(__dirname, 'scaffold.md'), 'utf8', function(err, content){ - if (err) return next(err); - - scaffold = content; - next(); - }); - }, - // Create a scaffold - function(next){ - hexo.scaffold.set(layout, scaffold, next); - }, - // Create a post - function(next){ - var data = { - title: 'Test Post', - layout: layout - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - var yfm = hexo.util.yfm(data.content); - yfm.layout.should.eql('custom'); - yfm._content.should.eql('scaffold content'); - - next(); - }); - }, - function(next){ - hexo.scaffold.remove(layout, next); - } - ], done); - }); - - it('asset folder', function(done){ - hexo.config.post_asset_folder = true; - - var data = { - title: 'Test Post' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'Test-Post.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - var assetFolder = data.path.substring(0, data.path.length - pathFn.extname(data.path).length); - - fs.exists(assetFolder, function(exist){ - hexo.config.post_asset_folder = false; - exist.should.be.true; - done(); - }); - }); - }); - - afterEach(function(done){ - async.each(posts, function(post, next){ - fs.unlink(post, next); - }, function(err){ - posts.length = 0; - done(err); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/post/index.js b/test/scripts/post/index.js deleted file mode 100644 index 6babfbad40..0000000000 --- a/test/scripts/post/index.js +++ /dev/null @@ -1,4 +0,0 @@ -describe('Post', function(){ - require('./create'); - require('./publish'); -}); \ No newline at end of file diff --git a/test/scripts/post/publish.js b/test/scripts/post/publish.js deleted file mode 100644 index c6ad8e9bbe..0000000000 --- a/test/scripts/post/publish.js +++ /dev/null @@ -1,192 +0,0 @@ -var should = require('chai').should(), - fs = require('graceful-fs'), - async = require('async'), - pathFn = require('path'), - file = require('../../../lib/util/file2'); - -var check = function(args, results, callback){ - args.push(function(err, path, content){ - if (err) return callback(err); - - async.parallel([ - // Check the content of post - function(next){ - fs.readFile(results.path, 'utf8', function(err, post){ - if (err) return next(err); - - post.should.eql(content); - next(); - }); - }, - // Check whether the draft was deleted - function(next){ - fs.exists(path, function(exist){ - exist.should.be.false; - next(); - }); - } - ], function(err){ - callback(err, { - path: path, - content: content - }); - }); - }); - - hexo.post.publish.apply(hexo, args); -}; - -describe('publish', function(){ - var posts = []; - - beforeEach(function(done){ - hexo.post.create({ - title: 'Draft Test', - slug: 'draft-test', - layout: 'draft' - }, done); - }); - - it('normal', function(done){ - var data = { - slug: 'draft-test' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'draft-test.md') - }; - - posts.push(results.path); - check([data], results, done); - }); - - it('custom layout', function(done){ - var data = { - slug: 'draft-test', - layout: 'photo' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'draft-test.md') - }; - - posts.push(results.path); - check([data], results, function(err, data){ - if (err) return done(err); - - hexo.util.yfm(data.content).layout.should.eql('photo'); - done(); - }); - }); - - it('rename duplicated files', function(done){ - async.series([ - function(next){ - hexo.post.create({slug: 'draft-test'}, function(err, path, content){ - if (err) return next(err); - - posts.push(path); - next(); - }); - }, - function(next){ - var data = { - slug: 'draft-test' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'draft-test-1.md') - }; - - posts.push(results.path); - check([data], results, next); - } - ], done); - }); - - it('replace duplicated files', function(done){ - async.series([ - function(next){ - hexo.post.create({slug: 'draft-test'}, function(err, path, content){ - if (err) return next(err); - - posts.push(path); - next(); - }); - }, - function(next){ - var data = { - slug: 'draft-test' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'draft-test.md') - }; - - check([data, true], results, next); - } - ], done); - }); - - it('asset folder', function(done){ - hexo.config.post_asset_folder = true; - - var fixtureDir = pathFn.join(__dirname, 'assets'), - fixtureList = []; - - async.series([ - function(next){ - file.list(fixtureDir, function(err, files){ - if (err) return next(err); - - var assetDir = pathFn.join(hexo.source_dir, '_drafts', 'draft-test'); - fixtureList = files; - - async.each(files, function(item, next){ - file.copyFile(pathFn.join(fixtureDir, item), pathFn.join(assetDir, item), next); - }, next); - }); - }, - // Publish the draft - function(next){ - var data = { - slug: 'draft-test' - }; - - var results = { - path: pathFn.join(hexo.source_dir, '_posts', 'draft-test.md') - }; - - posts.push(results.path); - check([data], results, next); - }, - // Check assets - function(next){ - var assetDir = pathFn.join(hexo.source_dir, '_posts', 'draft-test'); - hexo.config.post_asset_folder = false; - - async.each(fixtureList, function(item, next){ - async.map([ - pathFn.join(fixtureDir, item), - pathFn.join(assetDir, item) - ], fs.readFile, function(err, results){ - if (err) return next(err); - - results[0].should.eql(results[1]); - posts.push(pathFn.join(assetDir, item)); - next(); - }); - }, next); - } - ], done); - }); - - afterEach(function(done){ - async.each(posts, function(post, next){ - fs.unlink(post, next); - }, function(err){ - posts.length = 0; - done(err); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/post/scaffold.md b/test/scripts/post/scaffold.md deleted file mode 100644 index 2178d16237..0000000000 --- a/test/scripts/post/scaffold.md +++ /dev/null @@ -1,4 +0,0 @@ -title: {{ title }} -layout: {{ layout }} ---- -scaffold content \ No newline at end of file diff --git a/test/scripts/scaffold/fixture.md b/test/scripts/scaffold/fixture.md deleted file mode 100644 index 1bfbbaa7dc..0000000000 --- a/test/scripts/scaffold/fixture.md +++ /dev/null @@ -1,4 +0,0 @@ -title: {{ title }} -layout: {{ layout }} ---- -foo \ No newline at end of file diff --git a/test/scripts/scaffold/index.js b/test/scripts/scaffold/index.js deleted file mode 100644 index 3d3854fbab..0000000000 --- a/test/scripts/scaffold/index.js +++ /dev/null @@ -1,83 +0,0 @@ -var async = require('async'), - fs = require('graceful-fs'), - pathFn = require('path'); - -describe('Scaffold', function(){ - var fixture = ''; - - before(function(done){ - fs.readFile(pathFn.join(__dirname, 'fixture.md'), 'utf8', function(err, content){ - if (err) return done(err); - - fixture = content; - done(); - }); - }); - - it('get', function(done){ - hexo.scaffold.get('post', function(err, content){ - if (err) return done(err); - - content.replace(/\n+$/, '').should.eql([ - 'title: {{ title }}', - 'date: {{ date }}', - 'tags:', - '---' - ].join('\n')); - done(); - }); - }); - - it('get the default layout', function(){ - hexo.scaffold.get('blah', function(err, content){ - if (err) return done(err); - - content.replace(/\n+$/, '').should.eql([ - 'layout: {{ layout }}', - 'title: {{ title }}', - 'date: {{ date }}', - 'tags:', - '---' - ].join('\n')); - done(); - }); - }); - - it('set', function(done){ - async.series([ - function(next){ - hexo.scaffold.set('foo', fixture, next); - }, - function(next){ - hexo.scaffold.get('foo', function(err, content){ - if (err) return next(err); - - content.should.eql(fixture); - next(); - }); - }, - function(next){ - fs.readFile(pathFn.join(hexo.scaffold_dir, 'foo.md'), 'utf8', function(err, content){ - if (err) return next(err); - - content.should.eql(fixture); - next(); - }); - } - ], done); - }); - - it('remove', function(done){ - async.series([ - function(next){ - hexo.scaffold.remove('foo', next); - }, - function(next){ - fs.exists(pathFn.join(hexo.scaffold_dir, 'foo.md'), function(exist){ - exist.should.be.false; - next(); - }); - } - ], done); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/blockquote.js b/test/scripts/tag/blockquote.js deleted file mode 100644 index 77113bb4e5..0000000000 --- a/test/scripts/tag/blockquote.js +++ /dev/null @@ -1,44 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('blockquote', function(){ - var blockquote = require('../../../lib/plugins/tag/blockquote'); - - var bq = function(args){ - var result = blockquote(args.split(' '), '123456 **bold** and *italic*'); - - return result.replace(/(.*?)<\/escape>/g, '$1'); - }; - - it('author', function(){ - var $ = cheerio.load(bq('John Doe')); - - $('blockquote footer strong').html().should.eql('John Doe'); - }); - - it('author + source', function(){ - var $ = cheerio.load(bq('John Doe, A book')); - - $('blockquote footer strong').html().should.eql('John Doe'); - $('blockquote footer cite').html().should.eql('A book'); - }); - - it('author + link', function(){ - var $ = cheerio.load(bq('John Doe http://zespia.tw')); - - $('blockquote footer strong').html().should.eql('John Doe'); - $('blockquote footer cite').html().should.eql('zespia.tw/'); - - $ = cheerio.load(bq('John Doe http://zespia.tw/this/is/a/fucking/long/url')); - - $('blockquote footer strong').html().should.eql('John Doe'); - $('blockquote footer cite').html().should.eql('zespia.tw/this/is/a/fucking/…'); - }); - - it('author + link + title', function(){ - var $ = cheerio.load(bq('John Doe http://zespia.tw My Blog')); - - $('blockquote footer strong').html().should.eql('John Doe'); - $('blockquote footer cite').html().should.eql('My Blog'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/code.js b/test/scripts/tag/code.js deleted file mode 100644 index 6bb0102bd0..0000000000 --- a/test/scripts/tag/code.js +++ /dev/null @@ -1,50 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(), - highlight = require('../../../lib/util/highlight'); - -describe('code', function(){ - var code = require('../../../lib/plugins/tag/code'); - - var dummy = [ - 'var dummy = function(){', - ' alert("dummy");', - '});' - ].join('\n'); - - var content = cheerio.load(highlight(dummy))('table').html(); - - it('content', function(){ - var $ = cheerio.load(code([], dummy)); - - $('figure').attr('class').should.eql('highlight'); - $('figure table').html().should.eql(content); - }); - - it('lang', function(){ - var $ = cheerio.load(code('lang:js'.split(' '), '')); - - $('figure').attr('class').should.eql('highlight js'); - }); - - it('title', function(){ - var $ = cheerio.load(code('Code block test'.split(' '), '')); - - $('figcaption span').html().should.eql('Code block test'); - }); - - it('title + url', function(){ - var $ = cheerio.load(code('Code block test http://zespia.tw'.split(' '), '')); - - $('figcaption span').html().should.eql('Code block test'); - $('figcaption a').attr('href').should.eql('http://zespia.tw'); - $('figcaption a').html().should.eql('link'); - }); - - it('title + url + link', function(){ - var $ = cheerio.load(code('Code block test http://zespia.tw My blog'.split(' '), '')); - - $('figcaption span').html().should.eql('Code block test'); - $('figcaption a').attr('href').should.eql('http://zespia.tw'); - $('figcaption a').html().should.eql('My blog'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/gist.js b/test/scripts/tag/gist.js deleted file mode 100644 index 1808ea53c3..0000000000 --- a/test/scripts/tag/gist.js +++ /dev/null @@ -1,18 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('gist', function(){ - var gist = require('../../../lib/plugins/tag/gist'); - - it('id', function(){ - var $ = cheerio.load(gist(['foo'])); - - $('script').attr('src').should.eql('https://gist.github.com/foo.js'); - }); - - it('file', function(){ - var $ = cheerio.load(gist(['foo', 'bar'])); - - $('script').attr('src').should.eql('https://gist.github.com/foo.js?file=bar'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/iframe.js b/test/scripts/tag/iframe.js deleted file mode 100644 index 8466c96801..0000000000 --- a/test/scripts/tag/iframe.js +++ /dev/null @@ -1,36 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('iframe', function(){ - var iframe = require('../../../lib/plugins/tag/iframe'); - - it('url', function(){ - var $ = cheerio.load(iframe(['http://zespia.tw'])); - - $('iframe').attr('src').should.eql('http://zespia.tw'); - $('iframe').attr('width').should.eql('100%'); - $('iframe').attr('height').should.eql('300'); - $('iframe').attr('frameborder').should.eql('0'); - $('iframe').attr('allowfullscreen').should.eql(''); - }); - - it('width', function(){ - var $ = cheerio.load(iframe(['http://zespia.tw', '500'])); - - $('iframe').attr('src').should.eql('http://zespia.tw'); - $('iframe').attr('width').should.eql('500'); - $('iframe').attr('height').should.eql('300'); - $('iframe').attr('frameborder').should.eql('0'); - $('iframe').attr('allowfullscreen').should.eql(''); - }); - - it('height', function(){ - var $ = cheerio.load(iframe(['http://zespia.tw', '500', '600'])); - - $('iframe').attr('src').should.eql('http://zespia.tw'); - $('iframe').attr('width').should.eql('500'); - $('iframe').attr('height').should.eql('600'); - $('iframe').attr('frameborder').should.eql('0'); - $('iframe').attr('allowfullscreen').should.eql(''); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/img.js b/test/scripts/tag/img.js deleted file mode 100644 index cad9b79d9d..0000000000 --- a/test/scripts/tag/img.js +++ /dev/null @@ -1,81 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('img', function(){ - var img = require('../../../lib/plugins/tag/img'); - - it('src', function(){ - var $ = cheerio.load(img(['http://placekitten.com/200/300'])); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - }); - - it('class + src', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - }); - - it('multiple classes + src', function(){ - var $ = cheerio.load(img('left top http://placekitten.com/200/300'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left top'); - }); - - it('class + src + width', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 200'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('width').should.eql('200'); - }); - - it('class + src + width + height', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('width').should.eql('200'); - $('img').attr('height').should.eql('300'); - }); - - it('class + src + title', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 Place Kitten'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('title').should.eql('Place Kitten'); - }); - - it('class + src + width + title', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 200 Place Kitten'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('width').should.eql('200'); - $('img').attr('title').should.eql('Place Kitten'); - }); - - it('class + src + width + height + title', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300 Place Kitten'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('width').should.eql('200'); - $('img').attr('height').should.eql('300'); - $('img').attr('title').should.eql('Place Kitten'); - }); - - it('class + src + width + height + title + alt', function(){ - var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300 "Place Kitten" "A cute kitten"'.split(' '))); - - $('img').attr('src').should.eql('http://placekitten.com/200/300'); - $('img').attr('class').should.eql('left'); - $('img').attr('width').should.eql('200'); - $('img').attr('height').should.eql('300'); - $('img').attr('title').should.eql('Place Kitten'); - $('img').attr('alt').should.eql('A cute kitten'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/include_code.js b/test/scripts/tag/include_code.js deleted file mode 100644 index 65a1ef1aae..0000000000 --- a/test/scripts/tag/include_code.js +++ /dev/null @@ -1,50 +0,0 @@ -var cheerio = require('cheerio'), - path = require('path'), - should = require('chai').should(), - file = require('../../../lib/util/file2'), - highlight = require('../../../lib/util/highlight'); - -// Fixture -function unique(arr){ - var a = [], - l = arr.length; - - for (var i = 0; i < l; i++){ - for (var j = i + 1; j < l; j++){ - if (arr[i] === arr[j]) j = ++i; - } - - a.push(arr[i]); - } - - return a; -} - -describe('include_code', function(){ - var include_code = require('../../../lib/plugins/tag/include_code'), - raw = unique.toString(), - content = cheerio.load(highlight(raw))('table').html(); - - before(function(done){ - file.writeFile(path.join(hexo.source_dir, 'downloads', 'code', 'test.js'), raw, done); - }); - - it('file', function(){ - var $ = cheerio.load(include_code('test.js'.split(' '))); - - $('figure').attr('class').should.eql('highlight js'); - $('figure table').html().should.eql(content); - }); - - it('title', function(){ - var $ = cheerio.load(include_code('Code block title test.js'.split(' '))); - - $('figcaption span').html().should.eql('Code block title'); - }); - - it('lang', function(){ - var $ = cheerio.load(include_code('lang:javascript test.js'.split(' '))); - - $('figure').attr('class').should.eql('highlight javascript'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/index.js b/test/scripts/tag/index.js deleted file mode 100644 index e9081d0b1a..0000000000 --- a/test/scripts/tag/index.js +++ /dev/null @@ -1,14 +0,0 @@ -describe('Tag', function(){ - require('./blockquote'); - require('./code'); - require('./gist'); - require('./iframe'); - require('./img'); - require('./include_code'); - require('./jsfiddle'); - require('./link'); - require('./pullquote'); - require('./raw'); - require('./vimeo'); - require('./youtube'); -}); \ No newline at end of file diff --git a/test/scripts/tag/jsfiddle.js b/test/scripts/tag/jsfiddle.js deleted file mode 100644 index 59f84967b7..0000000000 --- a/test/scripts/tag/jsfiddle.js +++ /dev/null @@ -1,52 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('jsfiddle', function(){ - var jsfiddle = require('../../../lib/plugins/tag/jsfiddle'); - - it('id', function(){ - var $ = cheerio.load(jsfiddle(['foo'])); - - $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); - }); - - it('tabs', function(){ - var $ = cheerio.load(jsfiddle(['foo', 'default'])); - - $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); - - $ = cheerio.load(jsfiddle(['foo', 'html,css'])); - - $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/html,css/light'); - }); - - it('skin', function(){ - var $ = cheerio.load(jsfiddle(['foo', 'default', 'default'])); - - $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); - - $ = cheerio.load(jsfiddle(['foo', 'default', 'dark'])); - - $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/dark'); - }); - - it('width', function(){ - var $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default'])); - - $('iframe').attr('width').should.eql('100%'); - - $ = cheerio.load(jsfiddle(['foo', 'default', 'default', '500'])); - - $('iframe').attr('width').should.eql('500'); - }); - - it('height', function(){ - var $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default', 'default'])); - - $('iframe').attr('height').should.eql('300'); - - $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default', '500'])); - - $('iframe').attr('height').should.eql('500'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/link.js b/test/scripts/tag/link.js deleted file mode 100644 index 6b9a4b78d0..0000000000 --- a/test/scripts/tag/link.js +++ /dev/null @@ -1,51 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('link', function(){ - var link = require('../../../lib/plugins/tag/link'); - - it('text + url', function(){ - var $ = cheerio.load(link('Click here to Google http://google.com'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - }); - - it('text + url + external', function(){ - var $ = cheerio.load(link('Click here to Google http://google.com true'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - $('a').attr('target').should.eql('_blank'); - - $ = cheerio.load(link('Click here to Google http://google.com false'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - should.not.exist($('a').attr('target')); - }); - - it('text + url + title', function(){ - var $ = cheerio.load(link('Click here to Google http://google.com Google link'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - $('a').attr('title').should.eql('Google link'); - }); - - it('text + url + external + title', function(){ - var $ = cheerio.load(link('Click here to Google http://google.com true Google link'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - $('a').attr('target').should.eql('_blank'); - $('a').attr('title').should.eql('Google link'); - - $ = cheerio.load(link('Click here to Google http://google.com false Google link'.split(' '))); - - $('a').attr('href').should.eql('http://google.com'); - $('a').html().should.eql('Click here to Google'); - should.not.exist($('a').attr('target')); - $('a').attr('title').should.eql('Google link'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/pullquote.js b/test/scripts/tag/pullquote.js deleted file mode 100644 index 535f9e5430..0000000000 --- a/test/scripts/tag/pullquote.js +++ /dev/null @@ -1,24 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('pullquote', function(){ - var pullquote = require('../../../lib/plugins/tag/pullquote'); - - var raw = '123456 **bold** and *italic*'; - - it('content', function(){ - var $ = cheerio.load(pullquote([], raw)); - - $('blockquote').attr('class').should.eql('pullquote'); - }); - - it('class', function(){ - var $ = cheerio.load(pullquote(['foo'], raw)); - - $('blockquote').attr('class').should.eql('pullquote foo'); - - $ = cheerio.load(pullquote(['foo', 'bar'], raw)); - - $('blockquote').attr('class').should.eql('pullquote foo bar'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/raw.js b/test/scripts/tag/raw.js deleted file mode 100644 index a21eb2cae3..0000000000 --- a/test/scripts/tag/raw.js +++ /dev/null @@ -1,9 +0,0 @@ -var should = require('chai').should(); - -describe('raw', function(){ - var raw = require('../../../lib/plugins/tag/raw'); - - it('content', function(){ - raw([], '123456789strong987654321').should.eql('123456789strong987654321'); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/vimeo.js b/test/scripts/tag/vimeo.js deleted file mode 100644 index bc8502aa2f..0000000000 --- a/test/scripts/tag/vimeo.js +++ /dev/null @@ -1,15 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('vimeo', function(){ - var vimeo = require('../../../lib/plugins/tag/vimeo'); - - it('id', function(){ - var $ = cheerio.load(vimeo(['foo'])); - - $('.video-container').html().should.be.ok; - $('iframe').attr('src').should.eql('//player.vimeo.com/video/foo'); - $('iframe').attr('frameborder').should.eql('0'); - $('iframe').attr('allowfullscreen').should.eql(''); - }); -}); \ No newline at end of file diff --git a/test/scripts/tag/youtube.js b/test/scripts/tag/youtube.js deleted file mode 100644 index 1701652ee0..0000000000 --- a/test/scripts/tag/youtube.js +++ /dev/null @@ -1,15 +0,0 @@ -var cheerio = require('cheerio'), - should = require('chai').should(); - -describe('youtube', function(){ - var youtube = require('../../../lib/plugins/tag/youtube'); - - it('id', function(){ - var $ = cheerio.load(youtube(['foo'])); - - $('.video-container').html().should.be.ok; - $('iframe').attr('src').should.eql('//www.youtube.com/embed/foo'); - $('iframe').attr('frameborder').should.eql('0'); - $('iframe').attr('allowfullscreen').should.eql(''); - }); -}); \ No newline at end of file diff --git a/test/scripts/util/fs.js b/test/scripts/util/fs.js new file mode 100644 index 0000000000..aac01bfd83 --- /dev/null +++ b/test/scripts/util/fs.js @@ -0,0 +1,449 @@ +var should = require('chai').should(), + pathFn = require('path'), + Promise = require('bluebird'), + fs = require('../../../lib/util').fs; + +function createDummyFolder(path){ + // TODO nested dummy files + return Promise.all([ + fs.writeFile(pathFn.join(path, 'a.txt'), 'a'), + fs.writeFile(pathFn.join(path, 'b.js'), 'b'), + fs.writeFile(pathFn.join(path, '.c'), 'c') + ]);} + +describe('fs', function(){ + var tmpDir = pathFn.join(__dirname, 'fs_tmp'); + + before(function(){ + return fs.mkdir(tmpDir); + }); + + it('exists()', function(){ + return fs.exists(tmpDir).then(function(exist){ + exist.should.be.true; + }); + }); + + it('mkdirs()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'c'); + + return fs.mkdirs(target).then(function(){ + return fs.exists(target); + }).then(function(exist){ + exist.should.be.true; + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('mkdirsSync()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'c'); + + fs.mkdirsSync(target); + + return fs.exists(target).then(function(exist){ + exist.should.be.true; + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('writeFile()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); + var body = 'foo'; + + return fs.writeFile(target, body).then(function(){ + return fs.readFile(target); + }).then(function(content){ + content.should.eql(body); + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('writeFileSync()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); + var body = 'foo'; + + fs.writeFileSync(target, body); + + return fs.readFile(target).then(function(content){ + content.should.eql(body); + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('appendFile()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); + var body = 'foo'; + var body2 = 'bar'; + + return fs.writeFile(target, body).then(function(){ + return fs.appendFile(target, body2); + }).then(function(){ + return fs.readFile(target); + }).then(function(content){ + content.should.eql(body + body2); + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('appendFileSync()', function(){ + var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); + var body = 'foo'; + var body2 = 'bar'; + + return fs.writeFile(target, body).then(function(){ + fs.appendFileSync(target, body2); + return fs.readFile(target); + }).then(function(content){ + content.should.eql(body + body2); + return fs.rmdir(pathFn.join(tmpDir, 'a')); + }); + }); + + it('copyFile()', function(){ + var src = pathFn.join(tmpDir, 'test.txt'); + var dest = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); + var body = 'foo'; + + return fs.writeFile(src, body).then(function(){ + return fs.copyFile(src, dest); + }).then(function(){ + return fs.readFile(dest); + }).then(function(content){ + content.should.eql(body); + + return Promise.all([ + fs.unlink(src), + fs.rmdir(pathFn.join(tmpDir, 'a')) + ]); + }); + }); + + it('copyDir()', function(){ + var src = pathFn.join(tmpDir, 'a'); + var dest = pathFn.join(tmpDir, 'b'); + + return createDummyFolder(src).then(function(){ + return fs.copyDir(src, dest); + }).then(function(files){ + files.should.eql(['a.txt', 'b.js']); + + return Promise.all([ + fs.readFile(pathFn.join(dest, 'a.txt')), + fs.readFile(pathFn.join(dest, 'b.js')) + ]); + }).then(function(result){ + result.should.eql(['a', 'b']); + }).then(function(){ + return Promise.all([ + fs.rmdir(src), + fs.rmdir(dest) + ]); + }); + }); + + it('copyDir() - ignoreHidden off', function(){ + var src = pathFn.join(tmpDir, 'a'); + var dest = pathFn.join(tmpDir, 'b'); + + return createDummyFolder(src).then(function(){ + return fs.copyDir(src, dest, {ignoreHidden: false}); + }).then(function(files){ + files.should.have.members(['a.txt', 'b.js', '.c']); + + return Promise.all([ + fs.readFile(pathFn.join(dest, 'a.txt')), + fs.readFile(pathFn.join(dest, 'b.js')), + fs.readFile(pathFn.join(dest, '.c')) + ]); + }).then(function(result){ + result.should.eql(['a', 'b', 'c']); + }).then(function(){ + return Promise.all([ + fs.rmdir(src), + fs.rmdir(dest) + ]); + }); + }); + + it('copyDir() - ignorePattern', function(){ + var src = pathFn.join(tmpDir, 'a'); + var dest = pathFn.join(tmpDir, 'b'); + + return createDummyFolder(src).then(function(){ + return fs.copyDir(src, dest, {ignorePattern: /\.js/}); + }).then(function(files){ + files.should.have.members(['a.txt']); + + return Promise.all([ + fs.readFile(pathFn.join(dest, 'a.txt')) + ]); + }).then(function(result){ + result.should.eql(['a']); + }).then(function(){ + return Promise.all([ + fs.rmdir(src), + fs.rmdir(dest) + ]); + }); + }); + + it('listDir()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.listDir(target); + }).then(function(files){ + files.should.have.members(['a.txt', 'b.js']); + return fs.rmdir(target); + }); + }); + + it('listDir() - ignoreHidden off', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.listDir(target, {ignoreHidden: false}); + }).then(function(files){ + files.should.have.members(['a.txt', 'b.js', '.c']); + return fs.rmdir(target); + }); + }); + + it('listDir() - ignorePattern', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.listDir(target, {ignorePattern: /\.js/}); + }).then(function(files){ + files.should.have.members(['a.txt']); + return fs.rmdir(target); + }); + }); + + it('listDirSync()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.listDirSync(target); + files.should.have.members(['a.txt', 'b.js']); + return fs.rmdir(target); + }); + }); + + it('listDirSync() - ignoreHidden off', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.listDirSync(target, {ignoreHidden: false}); + files.should.have.members(['a.txt', 'b.js', '.c']); + return fs.rmdir(target); + }); + }); + + it('listDirSync() - ignorePattern', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.listDirSync(target, {ignorePattern: /\.js/}); + files.should.have.members(['a.txt']); + return fs.rmdir(target); + }); + }); + + it('readFile()', function(){ + var target = pathFn.join(tmpDir, 'test.txt'); + var body = 'test'; + + return fs.writeFile(target, body).then(function(){ + return fs.readFile(target); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(target); + }); + }); + + it('readFileSync()', function(){ + var target = pathFn.join(tmpDir, 'test.txt'); + var body = 'test'; + + return fs.writeFile(target, body).then(function(){ + fs.readFileSync(target).should.eql(body); + return fs.unlink(target); + }); + }); + + it('emptyDir()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.emptyDir(target); + }).then(function(files){ + files.should.have.members(['a.txt', 'b.js']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, false, true]); + return fs.rmdir(target); + }); + }); + + it('emptyDir() - ignoreHidden off', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.emptyDir(target, {ignoreHidden: false}); + }).then(function(files){ + files.should.have.members(['a.txt', 'b.js', '.c']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, false, false]); + return fs.rmdir(target); + }); + }); + + it('emptyDir() - ignorePattern', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.emptyDir(target, {ignorePattern: /\.js/}); + }).then(function(files){ + files.should.have.members(['a.txt']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, true, true]); + return fs.rmdir(target); + }); + }); + + it('emptyDir() - exclude', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.emptyDir(target, {exclude: ['a.txt']}); + }).then(function(files){ + files.should.have.members(['b.js']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([true, false, true]); + return fs.rmdir(target); + }); + }); + + it('emptyDirSync()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.emptyDirSync(target); + files.should.have.members(['a.txt', 'b.js']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, false, true]); + return fs.rmdir(target); + }); + }); + + it('emptyDirSync() - ignoreHidden off', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.emptyDirSync(target, {ignoreHidden: false}); + files.should.have.members(['a.txt', 'b.js', '.c']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, false, false]); + return fs.rmdir(target); + }); + }); + + it('emptyDirSync() - ignorePattern', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.emptyDirSync(target, {ignorePattern: /\.js/}); + files.should.have.members(['a.txt']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([false, true, true]); + return fs.rmdir(target); + }); + }); + + it('emptyDirSync() - exclude', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + var files = fs.emptyDirSync(target, {exclude: ['a.txt']}); + files.should.have.members(['b.js']); + + return Promise.all([ + fs.exists(pathFn.join(target, 'a.txt')), + fs.exists(pathFn.join(target, 'b.js')), + fs.exists(pathFn.join(target, '.c')) + ]); + }).then(function(result){ + result.should.eql([true, false, true]); + return fs.rmdir(target); + }); + }); + + it('rmdir()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + return fs.rmdir(target); + }).then(function(){ + return fs.exists(target); + }).then(function(exist){ + exist.should.be.false; + }) + }); + + it('rmdirSync()', function(){ + var target = pathFn.join(tmpDir, 'test'); + + return createDummyFolder(target).then(function(){ + fs.rmdirSync(target); + return fs.exists(target); + }).then(function(exist){ + exist.should.be.false; + }); + }); + + after(function(){ + return fs.rmdir(tmpDir); + }); +}); \ No newline at end of file diff --git a/test/scripts/util/index.js b/test/scripts/util/index.js index 4f6a3b83f5..8fc9b37b0f 100644 --- a/test/scripts/util/index.js +++ b/test/scripts/util/index.js @@ -1,5 +1,6 @@ describe('Utilities', function(){ require('./file2'); + require('./fs'); require('./html_tag'); require('./permalink'); }); \ No newline at end of file From 3323364f3a81b539f303f6dec4ae666bb3c34dcc Mon Sep 17 00:00:00 2001 From: Tommy Chen Date: Tue, 25 Nov 2014 23:23:31 +0800 Subject: [PATCH 02/15] Rewrite models. Add model tests. --- .jshintrc | 3 +- .travis.yml | 3 +- gulpfile.js | 29 +- lib/box/file.js | 1 - lib/box/index.js | 27 +- lib/cli/init.js | 2 +- lib/extend/console.js | 4 +- lib/extend/deployer.js | 4 + lib/extend/filter.js | 51 +- lib/extend/generator.js | 6 +- lib/extend/helper.js | 4 + lib/extend/migrator.js | 4 + lib/extend/processor.js | 4 +- lib/extend/renderer.js | 4 +- lib/extend/tag.js | 162 +++-- lib/hexo/create_logger.js | 3 +- lib/hexo/index.js | 38 +- lib/hexo/load_config.js | 8 + lib/hexo/post.js | 256 +++++++- lib/hexo/register_models.js | 14 + lib/hexo/render.js | 8 +- lib/hexo/scaffold.js | 89 +++ lib/hexo/source.js | 10 + lib/hexo/update_package.js | 6 +- lib/index.js | 5 - lib/models/asset.js | 15 +- lib/models/cache.js | 10 +- lib/models/category.js | 122 ++-- lib/models/index.js | 2 + lib/models/page.js | 40 +- lib/models/post.js | 184 ++++-- lib/models/post_category.js | 10 + lib/models/post_tag.js | 10 + lib/models/tag.js | 79 ++- lib/models/types/moment.js | 29 +- lib/plugins/console/clean.js | 47 +- lib/plugins/console/config.js | 3 + lib/plugins/console/deploy.js | 55 ++ lib/plugins/console/generate.js | 9 + lib/plugins/console/help.js | 94 ++- lib/plugins/console/index.js | 76 ++- lib/plugins/console/init.js | 31 +- lib/plugins/console/migrate.js | 25 + lib/plugins/console/new.js | 43 ++ lib/plugins/console/publish.js | 19 + lib/plugins/console/render.js | 61 +- lib/plugins/console/server.js | 3 + lib/plugins/console/version.js | 15 + .../filter/after_post_render/excerpt.js | 16 + .../filter/after_post_render/external_link.js | 32 + lib/plugins/filter/after_post_render/index.js | 6 + .../before_post_render/backtick_code_block.js | 41 ++ .../filter/before_post_render/index.js | 6 + .../filter/before_post_render/titlecase.js | 8 + lib/plugins/filter/index.js | 11 +- lib/plugins/filter/new_post_path.js | 104 ++++ lib/plugins/filter/post_permalink.js | 49 ++ lib/plugins/filter/server_middleware/gzip.js | 5 + .../filter/server_middleware/header.js | 6 + lib/plugins/filter/server_middleware/index.js | 10 + .../filter/server_middleware/logger.js | 18 + .../filter/server_middleware/redirect.js | 13 + lib/plugins/filter/server_middleware/route.js | 42 ++ .../filter/server_middleware/static.js | 5 + lib/plugins/generator/index.js | 2 +- lib/plugins/helper/date.js | 12 +- lib/plugins/helper/form.js | 2 +- lib/plugins/helper/format.js | 15 +- lib/plugins/helper/gravatar.js | 4 +- lib/plugins/helper/index.js | 11 +- lib/plugins/helper/is.js | 18 +- lib/plugins/helper/list.js | 64 +- lib/plugins/helper/list_post.js | 42 +- lib/plugins/helper/number.js | 26 +- lib/plugins/helper/open_graph.js | 18 +- lib/plugins/helper/paginator.js | 30 +- lib/plugins/helper/partial.js | 52 +- lib/plugins/helper/render.js | 12 + lib/plugins/helper/tag.js | 35 +- lib/plugins/helper/tagcloud.js | 113 ++-- lib/plugins/helper/toc.js | 19 +- lib/plugins/helper/url.js | 24 +- lib/plugins/tag/blockquote.js | 108 ++-- lib/plugins/tag/code.js | 58 +- lib/plugins/tag/include_code.js | 75 +-- lib/plugins/tag/index.js | 11 +- lib/theme/index.js | 140 +++++ lib/theme/processors/config.js | 20 + lib/theme/processors/i18n.js | 20 + lib/theme/processors/source.js | 16 + lib/theme/processors/view.js | 7 + lib/theme/view.js | 129 ++++ lib/util/escape.js | 7 +- lib/util/file.js | 282 --------- lib/util/file2.js | 569 ------------------ lib/util/fs.js | 55 +- lib/util/index.js | 5 +- lib/util/locals.js | 0 lib/util/permalink.js | 16 +- lib/util/router.js | 13 +- package.json | 13 +- test/index.js | 2 + test/scripts/box/box.js | 3 +- test/scripts/extend/console.js | 104 ++++ test/scripts/extend/deployer.js | 60 ++ test/scripts/extend/filter.js | 202 +++++++ test/scripts/extend/generator.js | 52 ++ test/scripts/extend/helper.js | 45 ++ test/scripts/extend/index.js | 11 + test/scripts/extend/migrator.js | 60 ++ test/scripts/extend/processor.js | 34 ++ test/scripts/extend/renderer.js | 128 ++++ test/scripts/extend/tag.js | 5 + test/scripts/models/assets.js | 33 + test/scripts/models/cache.js | 24 + test/scripts/models/category.js | 191 ++++++ test/scripts/models/index.js | 9 + test/scripts/models/moment.js | 90 +++ test/scripts/models/page.js | 62 ++ test/scripts/models/post.js | 189 ++++++ test/scripts/models/tag.js | 157 +++++ test/scripts/util/file2.js | 470 --------------- test/scripts/util/index.js | 2 +- test/scripts/util/permalink.js | 12 +- test/scripts/util/router.js | 69 +++ 125 files changed, 3945 insertions(+), 2211 deletions(-) create mode 100644 lib/hexo/register_models.js delete mode 100644 lib/index.js create mode 100644 lib/models/post_category.js create mode 100644 lib/models/post_tag.js create mode 100644 lib/plugins/console/config.js create mode 100644 lib/plugins/console/deploy.js create mode 100644 lib/plugins/console/generate.js create mode 100644 lib/plugins/console/migrate.js create mode 100644 lib/plugins/console/new.js create mode 100644 lib/plugins/console/publish.js create mode 100644 lib/plugins/console/server.js create mode 100644 lib/plugins/filter/after_post_render/excerpt.js create mode 100644 lib/plugins/filter/after_post_render/external_link.js create mode 100644 lib/plugins/filter/after_post_render/index.js create mode 100644 lib/plugins/filter/before_post_render/backtick_code_block.js create mode 100644 lib/plugins/filter/before_post_render/index.js create mode 100644 lib/plugins/filter/before_post_render/titlecase.js create mode 100644 lib/plugins/filter/new_post_path.js create mode 100644 lib/plugins/filter/post_permalink.js create mode 100644 lib/plugins/filter/server_middleware/gzip.js create mode 100644 lib/plugins/filter/server_middleware/header.js create mode 100644 lib/plugins/filter/server_middleware/index.js create mode 100644 lib/plugins/filter/server_middleware/logger.js create mode 100644 lib/plugins/filter/server_middleware/redirect.js create mode 100644 lib/plugins/filter/server_middleware/route.js create mode 100644 lib/plugins/filter/server_middleware/static.js create mode 100644 lib/plugins/helper/render.js create mode 100644 lib/theme/index.js create mode 100644 lib/theme/processors/config.js create mode 100644 lib/theme/processors/i18n.js create mode 100644 lib/theme/processors/source.js create mode 100644 lib/theme/processors/view.js create mode 100644 lib/theme/view.js delete mode 100644 lib/util/file.js delete mode 100644 lib/util/file2.js delete mode 100644 lib/util/locals.js create mode 100644 test/scripts/extend/console.js create mode 100644 test/scripts/extend/deployer.js create mode 100644 test/scripts/extend/filter.js create mode 100644 test/scripts/extend/generator.js create mode 100644 test/scripts/extend/helper.js create mode 100644 test/scripts/extend/index.js create mode 100644 test/scripts/extend/migrator.js create mode 100644 test/scripts/extend/processor.js create mode 100644 test/scripts/extend/renderer.js create mode 100644 test/scripts/extend/tag.js create mode 100644 test/scripts/models/assets.js create mode 100644 test/scripts/models/cache.js create mode 100644 test/scripts/models/category.js create mode 100644 test/scripts/models/index.js create mode 100644 test/scripts/models/moment.js create mode 100644 test/scripts/models/page.js create mode 100644 test/scripts/models/post.js create mode 100644 test/scripts/models/tag.js delete mode 100644 test/scripts/util/file2.js create mode 100644 test/scripts/util/router.js diff --git a/.jshintrc b/.jshintrc index 6cabfd079b..1e998ff268 100644 --- a/.jshintrc +++ b/.jshintrc @@ -8,7 +8,6 @@ "undef": true, "unused": "vars", "globals": { - "Promise": true, - "hexo": true + "Promise": true } } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f41c25616b..944cc6d1d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,5 @@ node_js: - "0.10" - "0.11" before_script: - - npm install -g gulp \ No newline at end of file + - npm install -g gulp + - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 54577dd5b3..c32b3a8d18 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,16 +1,25 @@ -var gulp = require('gulp'), - $ = require('gulp-load-plugins')(), - path = require('path'); +var gulp = require('gulp'); +var $ = require('gulp-load-plugins')(); +var del = require('del'); -var lib = 'lib/**/*.js', - test = 'test/scripts/**/*.js'; +var lib = 'lib/**/*.js'; +var test = 'test/scripts/**/*.js'; -gulp.task('mocha', function(){ +gulp.task('coverage', function(){ + return gulp.src(lib) + .pipe($.istanbul()); +}); + +gulp.task('coverage:clean', function(callback){ + del(['coverage/**/*'], callback); +}); + +gulp.task('mocha', ['coverage'], function(){ return gulp.src('test/index.js') .pipe($.mocha({ - reporter: 'spec', - ignoreLeaks: true - })); + reporter: 'spec' + })) + .pipe($.istanbul.writeReports()); }); gulp.task('jshint', function(){ @@ -21,7 +30,7 @@ gulp.task('jshint', function(){ }); gulp.task('watch', function(){ - gulp.watch(lib, ['mocha', 'jshint']); + gulp.watch(lib, e['mocha', 'jshint']); gulp.watch(['test/index.js', test], ['mocha']); }); diff --git a/lib/box/file.js b/lib/box/file.js index 78a3816510..0abc72e68f 100644 --- a/lib/box/file.js +++ b/lib/box/file.js @@ -1,4 +1,3 @@ -var Promise = require('bluebird'); var util = require('../util'); var fs = util.fs; diff --git a/lib/box/index.js b/lib/box/index.js index e8f87bf998..f45e7792f1 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -9,7 +9,8 @@ var fs = util.fs; require('colors'); -function Box(base, options){ +function Box(context, base, options){ + this.context = context; this.base = base; this.processors = []; this.processingFiles = {}; @@ -27,8 +28,8 @@ function Box(base, options){ util.inherits(_File, File); - _File.prototype._box = this; - _File.prototype._context = this._context; + _File.prototype.box = this; + _File.prototype._context = context; } Box.prototype.addProcessor = function(pattern, fn){ @@ -46,15 +47,16 @@ Box.prototype.addProcessor = function(pattern, fn){ }); }; -Box.prototype.process = function(files){ +Box.prototype.process = function(files, callback){ var self = this; var base = this.base; + var ctx = this.context; return new Promise(function(resolve, reject){ if (self.isProcessing) return reject(new Error('Box is processing!')); self.isProcessing = true; - self._context.emit('processBefore', base); + ctx.emit('processBefore', base); if (files){ return Array.isArray(files) ? files : [files]; @@ -72,15 +74,18 @@ Box.prototype.process = function(files){ } }).finally(function(){ self.isProcessing = false; - }) + }).nodeify(callback); }; +Box.prototype.load = Box.prototype.process; + Box.prototype._dispatch = function(item){ var path = item.path.replace(/\\/g, '/'); var start = Date.now(); var base = this.base; var self = this; - var log = this._context.log; + var ctx = this.context; + var log = ctx.log; var executed = false; if (this.processingFiles[path]) return; @@ -100,14 +105,14 @@ Box.prototype._dispatch = function(item){ executed = true; - return processor.process(file); + return processor.process.call(self, file); }).then(function(){ if (!executed) return; log.debug('Processed: %s in ' + '%dms'.cyan, path.magenta, Date.now() - start); }, function(err){ log.error('Process failed: %s', path.magenta); - return err; + throw err; }).finally(function(){ self.processingFiles = false; }); @@ -121,14 +126,14 @@ var chokidarEventMap = { add: 'create', change: 'update', unlink: 'delete' -} +}; Box.prototype.watch = function(){ if (this.watcher) throw new Error('Watcher has already started.'); var base = this.base; var baseLength = base.length; - var log = this._context.log; + var self = this; this.watcher = chokidar.watch(this.base, this.options) .on('all', function(event, src){ diff --git a/lib/cli/init.js b/lib/cli/init.js index 83d5786a95..6b09840f6d 100644 --- a/lib/cli/init.js +++ b/lib/cli/init.js @@ -60,7 +60,7 @@ module.exports = function(args){ hexo.log.fatal( {err: err}, - 'Something went wrong. Maybe you can find the solution here: %s', + 'Something\'s wrong. Maybe you can find the solution here: %s', 'http://hexo.io/docs/troubleshooting.html'.underline ); }); diff --git a/lib/extend/console.js b/lib/extend/console.js index 3b6564bf74..eccd6ea296 100644 --- a/lib/extend/console.js +++ b/lib/extend/console.js @@ -1,5 +1,5 @@ -var Promise = require('bluebird'), - abbrev = require('abbrev'); +var Promise = require('bluebird'); +var abbrev = require('abbrev'); function Console(){ this.store = {}; diff --git a/lib/extend/deployer.js b/lib/extend/deployer.js index fddbca954a..ca52e6a38a 100644 --- a/lib/extend/deployer.js +++ b/lib/extend/deployer.js @@ -8,6 +8,10 @@ Deployer.prototype.list = function(){ return this.store; }; +Deployer.prototype.get = function(name){ + return this.store[name]; +}; + Deployer.prototype.register = function(name, fn){ if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/filter.js b/lib/extend/filter.js index b8cc3693e3..e21e5ba994 100644 --- a/lib/extend/filter.js +++ b/lib/extend/filter.js @@ -15,17 +15,18 @@ Filter.prototype.list = function(type){ }; Filter.prototype.register = function(type, fn, priority){ - if (!fn){ + if (!priority){ if (typeof type === 'function'){ + priority = fn; fn = type; type = 'after_post_render'; - } else { - throw new TypeError('fn must be a function'); } } + if (typeof fn !== 'function') throw new TypeError('fn must be a function'); + type = typeAlias[type] || type; - priority = priority == null ? priority : 10; + priority = priority == null ? 10 : priority; var store = this.store[type] = this.store[type] || []; @@ -37,25 +38,41 @@ Filter.prototype.register = function(type, fn, priority){ }); }; -Filter.prototype.apply = function(type, args, sync, context){ - if (!args) args = []; - if (!Array.isArray(args)) args = [args]; +Filter.prototype.exec = function(type, data, options){ + options = options || {}; var filters = this.list(type); + var ctx = options.context; + var args = options.args || []; - if (sync){ - var result; - - for (var i = 0, len = filters.length; i < len; i++){ - result = filters[i].apply(context, args); - } + args.unshift(data); - return result; - } else { - return Promise.map(filters, function(filter){ - return filter.apply(context, args); + return Promise.map(filters, function(filter){ + return Promise.method(filter).apply(ctx, args).then(function(result){ + args[0] = result == null ? data : result; + return args[0]; }); + }, {concurrency: 1}).then(function(result){ + return args[0]; + }); +}; + +Filter.prototype.execSync = function(type, data, options){ + options = options || {}; + + var filters = this.list(type); + var ctx = options.context; + var args = options.args || []; + var result; + + args.unshift(data); + + for (var i = 0, len = filters.length; i < len; i++){ + result = filters[i].apply(ctx, args); + args[0] = result == null ? data : result; } + + return args[0]; }; module.exports = Filter; \ No newline at end of file diff --git a/lib/extend/generator.js b/lib/extend/generator.js index 5416902a6a..3ec6771953 100644 --- a/lib/extend/generator.js +++ b/lib/extend/generator.js @@ -9,11 +9,15 @@ Generator.prototype.list = function(){ return this.store; }; +Generator.prototype.get = function(name){ + return this.store[name]; +}; + Generator.prototype.register = function(name, fn){ if (!fn){ if (typeof name === 'function'){ fn = name; - name = 'generator=' + this.id++; + name = 'generator-' + this.id++; } else { throw new TypeError('fn must be a function'); } diff --git a/lib/extend/helper.js b/lib/extend/helper.js index 47a57fc388..48d0601d63 100644 --- a/lib/extend/helper.js +++ b/lib/extend/helper.js @@ -6,6 +6,10 @@ Helper.prototype.list = function(){ return this.store; }; +Helper.prototype.get = function(name){ + return this.store[name]; +}; + Helper.prototype.register = function(name, fn){ if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/migrator.js b/lib/extend/migrator.js index d95069d70b..e8947580d5 100644 --- a/lib/extend/migrator.js +++ b/lib/extend/migrator.js @@ -8,6 +8,10 @@ Migrator.prototype.list = function(){ return this.store; }; +Migrator.prototype.get = function(name){ + return this.store[name]; +}; + Migrator.prototype.register = function(name, fn){ if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/processor.js b/lib/extend/processor.js index 509c77b43f..0ee4216f3b 100644 --- a/lib/extend/processor.js +++ b/lib/extend/processor.js @@ -1,5 +1,5 @@ -var Promise = require('bluebird'), - Pattern = require('../box/pattern'); +var Promise = require('bluebird'); +var Pattern = require('../box/pattern'); function Processor(){ this.store = []; diff --git a/lib/extend/renderer.js b/lib/extend/renderer.js index ccf5992b5a..7e269caf51 100644 --- a/lib/extend/renderer.js +++ b/lib/extend/renderer.js @@ -1,5 +1,5 @@ -var pathFn = require('path'), - Promise = require('bluebird'); +var pathFn = require('path'); +var Promise = require('bluebird'); function getExtname(str){ var extname = pathFn.extname(str) || str; diff --git a/lib/extend/tag.js b/lib/extend/tag.js index 40784fcbb7..05b6abfcc1 100644 --- a/lib/extend/tag.js +++ b/lib/extend/tag.js @@ -1,16 +1,15 @@ var stripIndent = require('strip-indent'); +var swig = require('swig'); -var placeholder = String.fromCharCode(65535), - rPlaceholder = new RegExp(placeholder + '(\\d+)' + placeholder, 'g'); +var placeholder = String.fromCharCode(65535); +var rPlaceholder = new RegExp(placeholder + '(\\d+)' + placeholder, 'g'); function Tag(){ - this.store = []; + this.swig = new swig.Swig({ + autoescape: false + }); } -Tag.prototype.list = function(){ - return this.store; -}; - Tag.prototype.register = function(name, fn, options){ if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); @@ -19,77 +18,70 @@ Tag.prototype.register = function(name, fn, options){ options = {ends: options}; } - var tag = { - name: name, - ends: options.ends - }; + this.swig.setTag(name, tagParse, tagCompile(fn, options)); +}; - tag.parse = function(str, line, parser, types, stack, opts){ - // Hack: Don't let Swig parse tokens - parser.on('*', function(token){ - var prevToken = this.prevToken, - prevTokenType = prevToken ? prevToken.type : 0, - isString = true; - - switch (token.type){ - case types.WHITESPACE: - for (var i = 0, len = token.length; i < len; i++){ - this.out.push(' '); - } - - break; - - case types.DOTKEY: - this.out.push('.'); - break; - - case types.FILTER: - case types.FILTEREMPTY: - this.out.push('| '); - break; - - case types.COMPARATOR: - case types.LOGIC: - isString = false; - break; - - case types.FUNCTION: - isString = false; - token.type = types.UNKNOWN; - this.out.push(token.match + '('); - break; - - case types.FUNCTIONEMPTY: - isString = false; - token.type = types.UNKNOWN; - this.out.push(token.match + '()'); - break; - } +function tagParse(str, line, parser, types, stack, opts){ + // Hack: Don't let Swig parse tokens + parser.on('*', function(token){ + var prevToken = this.prevToken, + prevTokenType = prevToken ? prevToken.type : 0, + isString = true; - switch (prevTokenType){ - case types.COMPARATOR: - case types.LOGIC: + switch (token.type){ + case types.WHITESPACE: + for (var i = 0, len = token.length; i < len; i++){ this.out.push(' '); - break; - } - - if (isString){ - token.type = types.STRING; - } - - return true; - }); - - // TODO What's this for? - parser.on('start', function(){ - tag._line = line; - }); + } + + break; + + case types.DOTKEY: + this.out.push('.'); + break; + + case types.FILTER: + case types.FILTEREMPTY: + this.out.push('| '); + break; + + case types.COMPARATOR: + case types.LOGIC: + isString = false; + break; + + case types.FUNCTION: + isString = false; + token.type = types.UNKNOWN; + this.out.push(token.match + '('); + break; + + case types.FUNCTIONEMPTY: + isString = false; + token.type = types.UNKNOWN; + this.out.push(token.match + '()'); + break; + } + + switch (prevTokenType){ + case types.COMPARATOR: + case types.LOGIC: + this.out.push(' '); + break; + } + + if (isString){ + token.type = types.STRING; + } return true; - }; + }); +} - tag.compile = function(compiler, args, content, parents, opts, blockName){ +function tagCompile(fn, options){ + return function(compiler, args, content, parents, opts, blockName){ var tmp = {}; + var out = '(function(){'; content = content.map(function(line, i){ if (line.compile){ @@ -101,7 +93,6 @@ Tag.prototype.register = function(name, fn, options){ }); var result = fn(args.join('').split(' '), content.join(''), opts); - if (!result) return ''; result = result @@ -109,31 +100,22 @@ Tag.prototype.register = function(name, fn, options){ .replace(/\n|\r/g, '\\n') .replace(/"/g, '\\"'); - var out = [ - '(function(){', - (options.escape ? '_output += "' + result + '";' : '_output += "' + result + '";'), - '})();' - ].join('\n'); + if (options.escape){ + out += '_output += "' + result + '"'; + } else { + out += '_output += "' + result + '";'; + } out = out.replace(rPlaceholder, function(){ - var line = tmp[arguments[1]], - result = line.compile(compiler, line.args, line.content, parents, opts, line.name); - + var line = tmp[arguments[1]]; + var result = line.compile(compiler, line.args, line.content, parents, opts, line.name); if (!result) return ''; - return [ - '";', - '})();', - result, - '(function(){', - '_output += "' - ].join('\n'); + return '";})();' + result + '(function(){_output += "'; }); return out; }; - - this.store.push(tag); -}; +} module.exports = Tag; \ No newline at end of file diff --git a/lib/hexo/create_logger.js b/lib/hexo/create_logger.js index 5e4e444264..b8388148c1 100644 --- a/lib/hexo/create_logger.js +++ b/lib/hexo/create_logger.js @@ -52,7 +52,6 @@ ConsoleStream.prototype.write = function(data){ function createLogger(env){ var streams = []; - var timer = {}; if (!env.silent){ streams.push({ @@ -69,7 +68,7 @@ function createLogger(env){ }); } - var logger = this.log = bunyan.createLogger({ + var logger = bunyan.createLogger({ name: 'hexo', streams: streams, serializers: { diff --git a/lib/hexo/index.js b/lib/hexo/index.js index 8c8c4e6c09..b82b3b0895 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -6,11 +6,11 @@ var Database = require('warehouse'); var EventEmitter = require('events').EventEmitter; var pkg = require('../../package.json'); var createLogger = require('./create_logger'); -var Box = require('../box'); var extend = require('../extend'); var Render = require('./render'); -var models = require('../models'); +var registerModels = require('./register_models'); var Post = require('./post'); +var Scaffold = require('./scaffold'); var Router = util.Router; var libDir = pathFn.dirname(__dirname); @@ -126,25 +126,29 @@ function Hexo(base, args){ this.post = new Post(this); + this.scaffold = new Scaffold(this); + var db = this.database = new Database({ version: dbVersion, path: pathFn.join(base, 'db.json') }); - db.model('Asset', models.Asset); - db.model('Cache', models.Cache); - db.model('Category', models.Category); - db.model('Page', models.Page); - db.model('Post', models.Post); - db.model('Tag', models.Tag); + registerModels(this); - var _Box = this.Box = function(base, options){ - Box.call(this, base, options); + this.locals = { + get posts(){ + return db.model('Post').populate('categories tags'); + }, + get pages(){ + return db.model('Page'); + }, + get categories(){ + return db.model('Category'); + }, + get tags(){ + return db.model('Tag'); + } }; - - util.inherits(_Box, Box); - - _Box.prototype._context = this; } util.inherits(Hexo, EventEmitter); @@ -194,7 +198,7 @@ Hexo.prototype.call = function(name, args, callback){ var c = self.extend.console.get(name); if (!c) return reject(new Error('Console `' + name + '` is not registered')); - return c(args); + return c.call(self, args); }).nodeify(callback); }; @@ -202,10 +206,6 @@ Hexo.prototype.model = function(name, schema){ return this.database.model(name, schema); }; -Hexo.prototype._createBox = function(base, options){ - return new this.Box(base, options); -}; - Hexo.lib_dir = Hexo.prototype.lib_dir = libDir; Hexo.core_dir = Hexo.prototype.core_dir = pathFn.dirname(libDir); diff --git a/lib/hexo/load_config.js b/lib/hexo/load_config.js index e790dd0973..ba43f06b7e 100644 --- a/lib/hexo/load_config.js +++ b/lib/hexo/load_config.js @@ -2,6 +2,8 @@ var _ = require('lodash'); var pathFn = require('path'); var tildify = require('tildify'); var util = require('../util'); +var Theme = require('../theme'); +var Source = require('./source'); var fs = util.fs; require('colors'); @@ -41,9 +43,15 @@ module.exports = function(ctx){ ctx.public_dir = pathFn.join(baseDir, config.public_dir) + pathFn.sep; ctx.source_dir = pathFn.join(baseDir, config.source_dir) + pathFn.sep; + ctx.source = new Source(ctx); + if (config.theme){ ctx.theme_dir = pathFn.join(baseDir, 'themes', config.theme) + pathFn.sep; ctx.theme_script_dir = pathFn.join(ctx.theme_dir, 'scripts') + pathFn.sep; + + ctx.theme = new Theme(ctx); + } else { + throw new Error('Theme has not been set. Please set the theme in _config.yml.'); } }); }; \ No newline at end of file diff --git a/lib/hexo/post.js b/lib/hexo/post.js index 02a4ed0139..f1edfce731 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -1,21 +1,269 @@ +var moment = require('moment'); +var swig = require('swig'); +var Promise = require('bluebird'); +var pathFn = require('path'); +var _ = require('lodash'); +var util = require('../util'); + +var escape = util.escape; +var yfm = util.yfm; +var fs = util.fs; + +var rEscapeContent = /]*)>([\s\S]+?)<\/escape>/g; +var rUnescape = /(\d+)<\/hexoescape>/g; + +var preservedKeys = { + title: true, + slug: true, + path: true, + layout: true, + date: true, + content: true +}; + +swig.setDefaults({ + autoescape: false +}); + function Post(context){ this.context = context; } Post.prototype.create = function(data, replace, callback){ - // + if (!callback && typeof replace === 'function'){ + callback = replace; + replace = false; + } + + var ctx = this.context; + var config = ctx.config; + + data.slug = escape.filename(data.slug || data.title, config.filename_case); + data.layout = (data.layout || config.default_layout).toLowerCase(); + data.date = data.date ? moment(data.date) : moment(); + + return Promise.all([ + // Get the post path + ctx.extend.filter.exec('new_post_path', data, { + args: [replace], + context: ctx + }), + // Get the scaffold + this._getScaffold(data.layout) + ]).spread(function(path, scaffold){ + // Wrap title with quotations + data.title = '"' + data.title + '"'; + data.date = data.date.format('YYYY-MM-DD HH:mm:ss'); + + // Split data part from the raw scaffold + var split = yfm.split(scaffold); + + // Compile front-matter with data + var frontMatter = swig.compile(split.data)(data); + + // Concat compiled front-matter and the content part of the raw scaffold + var compiled = yfm.parse(frontMatter + '\n---\n' + split.content); + + var keys = Object.keys(data); + var key; + + // Add data which are not in the raw scaffold + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + if (!preservedKeys[key]) compiled[key] = data[key]; + } + + // Append content + if (data.content) compiled._content += '\n' + data.content; + + // Stringify front-matter + var content = yfm.stringify(compiled); + + var result = { + path: path, + content: content + }; + + return Promise.all([ + // Write content to file + fs.writeFile(path, content), + // Create asset folder + createAssetFolder(path, config.post_asset_folder) + ]).then(function(){ + ctx.emit('new', result); + }).thenReturn(result); + }).nodeify(callback); }; +Post.prototype._getScaffold = function(layout){ + var ctx = this.context; + + return ctx.scaffold.get(layout).then(function(result){ + if (result != null) return result; + return ctx.scaffold.get('normal'); + }); +}; + +function createAssetFolder(path, assetFolder){ + if (!assetFolder) return Promise.resolve(); + + var target = removeExtname(path); + + return fs.exists(target).then(function(exist){ + if (!exist) return fs.mkdirs(target); + }); +} + +function removeExtname(str){ + return str.substring(0, str.length - pathFn.extname(str).length); +} + Post.prototype.load = function(options, callback){ - // + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; + } + + options = options || {}; + + var ctx = this.context; + + return Promise.all([ + ctx.theme.process(), + ctx.source.process() + ]).then(function(){ + return ctx.theme.generate(); + }).then(function(){ + if (!options.watch) return; + + ctx.theme.watch(); + ctx.source.watch(); + + ctx.on('processAfter', function(){ + ctx.theme.generate(); + }); + }).nodeify(callback); }; Post.prototype.publish = function(data, replace, callback){ - // + if (!callback && typeof replace === 'function'){ + callback = replace; + replace = false; + } + + if (data.layout === 'draft') data.layout = 'post'; + + var ctx = this.context; + var config = ctx.config; + var draftDir = pathFn.join(ctx.source_dir, '_drafts'); + var slug = data.slug = escape.filename(data.slug, config.filename_case); + var regex = new RegExp('^' + escape.regex(slug)); + var self = this; + var src = ''; + var dest = ''; + var content = ''; + + data.layout = (data.layout || config.default_layout).toLowerCase(); + + // Find the draft + return fs.listDir(draftDir).any(function(item){ + return regex.test(item); + }).then(function(item){ + if (!item) throw new Error('Draft "' + slug + '" does not exist.'); + + // Read the content + src = pathFn.join(draftDir, item); + return fs.readFile(src); + }).then(function(content){ + // Create post + _.extend(data, yfm(content)); + data.content = data._content; + delete data._content; + + return self.create(data, replace).then(function(post){ + dest = post.path; + content = post.content; + }); + }).then(function(){ + // Remove the original draft file + return fs.unlink(src); + }).then(function(){ + if (!config.post_asset_folder) return; + + // Copy assets + var assetSrc = removeExtname(src); + var assetDest = removeExtname(dest); + + return fs.exists(assetSrc).then(function(exist){ + if (!exist) return; + + return fs.copyDir(assetSrc, assetDest).then(function(){ + return fs.rmdir(assetSrc); + }); + }); + }).thenReturn({ + path: dest, + content: content + }).nodeify(callback); }; Post.prototype.render = function(source, data, callback){ - // + if (!callback && typeof data === 'function'){ + callback = data; + data = source; + source = null; + } + + var ctx = this.context; + var config = ctx.config; + var swig = ctx.extend.tag.swig; + var filter = ctx.extend.filter; + var cache = []; + + // Replaces content in raw content with `cache id` + function escapeContent(match, content){ + return '' + (cache.push(content) - 1) + ''; + } + + return new Promise(function(resolve, reject){ + if (data.content != null) return resolve(data.content); + if (!source) return reject(new Error('No input file or string!')); + + // Read content from files + fs.readFile(source).then(resolve, reject); + }).then(function(content){ + // Render content with Swig + data.content = swig.render(content, { + locals: data, + filename: source + }).replace(rEscapeContent, escapeContent); + }).then(function(){ + // Run "before_post_render" filters + return filter.exec('before_post_render', data, {context: ctx}).then(function(){ + data.content = data.content.replace(rEscapeContent, escapeContent); + }); + }).then(function(){ + var options = data.markdown || {}; + if (!config.highlight.enable) options.highlight = null; + + // Renders with Markdown or other render engines. + return ctx.render.render({ + text: data.content, + path: source, + engine: data.engine + }, options); + }).then(function(content){ + // Replaces cache data with real contents. + data.content = content.replace(rUnescape, function(match, number){ + return cache[number]; + }); + + // Clean cache + cache.length = 0; + + // Run "after_post_render" filters + return filter.exec('after_post_render', data, {context: ctx}); + }); }; module.exports = Post; \ No newline at end of file diff --git a/lib/hexo/register_models.js b/lib/hexo/register_models.js new file mode 100644 index 0000000000..77af19e284 --- /dev/null +++ b/lib/hexo/register_models.js @@ -0,0 +1,14 @@ +var models = require('../models'); + +module.exports = function(ctx){ + var db = ctx.database; + + db.model('Asset', models.Asset(ctx)); + db.model('Cache', models.Cache(ctx)); + db.model('Category', models.Category(ctx)); + db.model('Page', models.Page(ctx)); + db.model('Post', models.Post(ctx)); + db.model('PostCategory', models.PostCategory(ctx)); + db.model('PostTag', models.PostTag(ctx)); + db.model('Tag', models.Tag(ctx)); +}; \ No newline at end of file diff --git a/lib/hexo/render.js b/lib/hexo/render.js index 1748302133..2d45d36c2e 100644 --- a/lib/hexo/render.js +++ b/lib/hexo/render.js @@ -1,7 +1,7 @@ -var pathFn = require('path'), - Promise = require('bluebird'), - util = require('../util'), - fs = util.fs; +var pathFn = require('path'); +var Promise = require('bluebird'); +var util = require('../util'); +var fs = util.fs; function getExtname(str){ var extname = pathFn.extname(str); diff --git a/lib/hexo/scaffold.js b/lib/hexo/scaffold.js index e69de29bb2..f9fd5b1db5 100644 --- a/lib/hexo/scaffold.js +++ b/lib/hexo/scaffold.js @@ -0,0 +1,89 @@ +var pathFn = require('path'); +var Promise = require('bluebird'); +var util = require('../util'); + +var fs = util.fs; + +function Scaffold(context){ + this.context = context; + this.scaffoldDir = context.scaffold_dir; + this.assetDir = pathFn.join(context.core_dir, 'assets', 'scaffolds'); +} + +Scaffold.prototype.defaults = { + normal: [ + 'layout: {{ layout }}', + 'title: {{ title }}', + 'date: {{ date }}', + 'tags:', + '---' + ].join('\n') +}; + +Scaffold.prototype._listDir = function(){ + var scaffoldDir = this.scaffoldDir; + + return fs.listDir(scaffoldDir, { + ignoreFilesRegex: /^_|\/_/ + }).map(function(item){ + return { + name: item.substring(0, item.length - pathFn.extname(item).length), + path: pathFn.join(scaffoldDir, item) + }; + }); +}; + +Scaffold.prototype._getScaffold = function(name){ + return this._listDir().any(function(item){ + return item.name === name; + }); +}; + +Scaffold.prototype._getDefaultScaffold = function(name){ + var defaults = this.defaults; + if (defaults[name]) return Promise.resolve(defaults[name]); + + var path = pathFn.join(this.assetDir, name + '.md'); + + return fs.exists(path).then(function(exist){ + if (!exist) return; + + return fs.readFile(path).then(function(content){ + defaults[name] = content; + return content; + }); + }); +}; + +Scaffold.prototype.get = function(name, callback){ + var self = this; + + return this._getScaffold(name).then(function(item){ + if (item){ + return fs.readFile(item.path); + } else { + return self._getDefaultScaffold(name); + } + }).nodeify(callback); +}; + +Scaffold.prototype.set = function(name, content, callback){ + var scaffoldDir = this.scaffoldDir; + + return this._getScaffold(name).then(function(item){ + var path = item.path || pathFn.join(scaffoldDir, name); + if (!pathFn.extname(path)) path += '.md'; + + return fs.writeFile(path, content); + }).nodeify(callback); +}; + +Scaffold.prototype.remove = function(name, callback){ + return this._getScaffold(name).then(function(item){ + if (!item) return; + + return fs.unlink(item.path); + }).nodeify(callback); +}; + +module.exports = Scaffold; \ No newline at end of file diff --git a/lib/hexo/source.js b/lib/hexo/source.js index e69de29bb2..a9f4019347 100644 --- a/lib/hexo/source.js +++ b/lib/hexo/source.js @@ -0,0 +1,10 @@ +var Box = require('../box'); +var util = require('../util'); + +function Source(ctx){ + Box.call(this, ctx, ctx.source_dir); + + this.processors = ctx.extend.processor.list(); +} + +util.inherits(Source, Box); \ No newline at end of file diff --git a/lib/hexo/update_package.js b/lib/hexo/update_package.js index e18bf8befd..c050591a90 100644 --- a/lib/hexo/update_package.js +++ b/lib/hexo/update_package.js @@ -12,14 +12,14 @@ module.exports = function(ctx){ if (exist) return; log.debug('Can\'t found package.json. Rebuilding a new one.'); - return fs.copyFile(pathFn.join(hexo.core_dir, 'assets', 'package.json'), packagePath); + return fs.copyFile(pathFn.join(ctx.core_dir, 'assets', 'package.json'), packagePath); }).then(function(){ return fs.readFile(packagePath); }).then(function(content){ var json = JSON.parse(content); - if (json.version === hexo.version) return; + if (json.version === ctx.version) return; - json.version = hexo.version; + json.version = ctx.version; log.debug('Updating package.json'); diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index c70a303425..0000000000 --- a/lib/index.js +++ /dev/null @@ -1,5 +0,0 @@ -var Hexo = require('./hexo'); - -module.exports = function(){ - // -}; \ No newline at end of file diff --git a/lib/models/asset.js b/lib/models/asset.js index 8956429fb8..7bc236f0ca 100644 --- a/lib/models/asset.js +++ b/lib/models/asset.js @@ -1,9 +1,10 @@ var Schema = require('warehouse').Schema; -module.exports = new Schema({ - _id: {type: String, required: true}, - path: {type: String, required: true}, - modified: {type: Boolean, default: true}, - post_id: Schema.Types.CUID, - post_path: String -}); \ No newline at end of file +module.exports = function(ctx){ + return new Schema({ + _id: {type: String, required: true}, + path: {type: String, required: true}, + modified: {type: Boolean, default: true}, + post: {type: Schema.Types.CUID, ref: 'Post'} + }); +}; \ No newline at end of file diff --git a/lib/models/cache.js b/lib/models/cache.js index 3f981d1985..616e67845f 100644 --- a/lib/models/cache.js +++ b/lib/models/cache.js @@ -1,6 +1,8 @@ var Schema = require('warehouse').Schema; -module.exports = new Schema({ - _id: {type: String, required: true}, - mtime: {type: Number, default: Date.now} -}); \ No newline at end of file +module.exports = function(ctx){ + return new Schema({ + _id: {type: String, required: true}, + mtime: {type: Number, default: Date.now} + }); +}; \ No newline at end of file diff --git a/lib/models/category.js b/lib/models/category.js index c04330c3d4..a8dfb1fede 100644 --- a/lib/models/category.js +++ b/lib/models/category.js @@ -1,40 +1,82 @@ -var Schema = require('warehouse').Schema, - util = require('../util'), - escape = util.escape; - -var Category = module.exports = new Schema({ - name: {type: String, required: true}, - parent: {type: Schema.Types.CUID, ref: 'Category'}, - posts: [{type: Schema.Types.CUID, ref: 'Post'}] -}); - -Category.virtual('slug').get(function(){ - var map = hexo.config.category_map, - name = this.name, - str = ''; - - if (this.parent){ - var parent = hexo.model('Category').findById(this.parent); - str += parent.slug + '/'; - } - - name = map && map.hasOwnProperty(name) ? map[name] : name; - str += escape.filename(name, hexo.config.filename_case); - - return str; -}); - -Category.virtual('path').get(function(){ - var catDir = hexo.config.category_dir; - if (catDir[catDir.length - 1] !== '/') catDir += '/'; - - return catDir + this.slug + '/'; -}); - -Category.virtual('permalink').get(function(){ - return hexo.config.url + '/' + this.path; -}); - -Category.virtual('length').get(function(){ - return this.posts.length; -}); \ No newline at end of file +var Schema = require('warehouse').Schema; +var util = require('../util'); +var escape = util.escape; + +module.exports = function(ctx){ + var Category = new Schema({ + name: {type: String, required: true}, + parent: {type: Schema.Types.CUID, ref: 'Category'} + }); + + Category.virtual('slug').get(function(){ + var map = ctx.config.category_map || {}; + var name = this.name; + var str = ''; + + if (!name) return; + + if (this.parent){ + var parent = ctx.model('Category').findById(this.parent); + str += parent.slug + '/'; + } + + name = map[name] || name; + str += escape.filename(name, ctx.config.filename_case); + + return str; + }); + + Category.virtual('path').get(function(){ + var catDir = ctx.config.category_dir; + if (catDir[catDir.length - 1] !== '/') catDir += '/'; + + return catDir + this.slug + '/'; + }); + + Category.virtual('permalink').get(function(){ + return ctx.config.url + '/' + this.path; + }); + + Category.virtual('posts').get(function(){ + var PostCategory = ctx.model('PostCategory'); + var Post = ctx.model('Post'); + + var ids = PostCategory.find({category_id: this._id}).map(function(item){ + return item.post_id; + }); + + return Post.find({ + _id: {$in: ids}, + published: true + }); + }); + + Category.virtual('length').get(function(){ + return this.posts.length; + }); + + // Check whether a category exists + Category.pre('save', function(data){ + var name = data.name; + var parent = data.parent; + if (!name) return; + + var Category = ctx.model('Category'); + var cat = Category.findOne({ + name: name, + parent: parent || {$exists: false} + }); + + if (cat && cat._id === data._id){ + throw new Error('Category `' + name + '` has already existed!'); + } + }); + + // Remove PostCategory references + Category.pre('remove', function(data){ + var PostCategory = ctx.model('PostCategory'); + return PostCategory.remove({category_id: data._id}); + }); + + return Category; +}; \ No newline at end of file diff --git a/lib/models/index.js b/lib/models/index.js index 9018496595..04af414fbc 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -3,4 +3,6 @@ exports.Cache = require('./cache'); exports.Category = require('./category'); exports.Page = require('./page'); exports.Post = require('./post'); +exports.PostCategory = require('./post_category'); +exports.PostTag = require('./post_tag'); exports.Tag = require('./tag'); \ No newline at end of file diff --git a/lib/models/page.js b/lib/models/page.js index 66c145c11e..ec8e76b680 100644 --- a/lib/models/page.js +++ b/lib/models/page.js @@ -3,23 +3,27 @@ var pathFn = require('path'); var Moment = require('./types/moment'); var moment = require('moment'); -var Page = module.exports = new Schema({ - title: {type: String, default: ''}, - date: {type: Moment, default: moment}, - updated: {type: Moment, default: moment}, - comments: {type: Boolean, default: true}, - layout: {type: String, default: 'page'}, - content: {type: String, default: ''}, - excerpt: {type: String, default: ''}, - source: {type: String, required: true}, - path: {type: String, required: true}, - raw: {type: String, default: ''} -}); +module.exports = function(ctx){ + var Page = new Schema({ + title: {type: String, default: ''}, + date: {type: Moment, default: moment}, + updated: {type: Moment, default: moment}, + comments: {type: Boolean, default: true}, + layout: {type: String, default: 'page'}, + content: {type: String, default: ''}, + excerpt: {type: String, default: ''}, + source: {type: String, required: true}, + path: {type: String, required: true}, + raw: {type: String, default: ''} + }); -Page.virtual('permalink').get(function(){ - return hexo.config.url + '/' + this.path; -}); + Page.virtual('permalink').get(function(){ + return ctx.config.url + '/' + this.path; + }); -Page.virtual('full_source').get(function(){ - return pathFn.join(hexo.source_dir, this.source); -}); \ No newline at end of file + Page.virtual('full_source').get(function(){ + return pathFn.join(ctx.source_dir, this.source || ''); + }); + + return Page; +}; \ No newline at end of file diff --git a/lib/models/post.js b/lib/models/post.js index 7f1a615147..8881625e79 100644 --- a/lib/models/post.js +++ b/lib/models/post.js @@ -4,38 +4,152 @@ var pathFn = require('path'); var Promise = require('bluebird'); var Moment = require('./types/moment'); -var Post = module.exports = new Schema({ - id: Number, - title: {type: String, default: ''}, - date: {type: Moment, default: moment}, - updated: {type: Moment, default: moment}, - categories: [{type: Schema.Types.CUID, ref: 'Category'}], - tags: [{type: Schema.Types.CUID, ref: 'tags'}], - comments: {type: Boolean, default: true}, - layout: {type: String, default: 'post'}, - content: {type: String, default: ''}, - excerpt: {type: String, default: ''}, - more: {type: String, default: ''}, - source: {type: String, required: true}, - slug: {type: String, required: true}, - photos: [String], - link: {type: String, default: ''}, - raw: {type: String, default: ''} -}); - -Post.virtual('path').get(function(){ - return hexo.extend.filter.apply('post_permalink', this); -}); - -Post.virtual('permalink').get(function(){ - return hexo.config.url + '/' + this.path; -}); - -Post.virtual('full_source').get(function(){ - return pathFn.join(hexo.source_dir, this.source); -}); - -Post.virtual('asset_dir').get(function(){ - var src = this.full_source; - return src.substring(0, src.length - pathFn.extname(src).length) + pathFn.sep; -}); \ No newline at end of file +module.exports = function(ctx){ + var Post = new Schema({ + id: Number, + title: {type: String, default: ''}, + date: {type: Moment, default: moment}, + updated: {type: Moment, default: moment}, + comments: {type: Boolean, default: true}, + layout: {type: String, default: 'post'}, + content: {type: String, default: ''}, + excerpt: {type: String, default: ''}, + more: {type: String, default: ''}, + source: {type: String, required: true}, + slug: {type: String, required: true}, + photos: [String], + link: {type: String, default: ''}, + raw: {type: String, default: ''}, + published: {type: Boolean, default: true} + }); + + Post.virtual('path').get(function(){ + var path = ctx.extend.filter.execSync('post_permalink', this, {context: ctx}); + return typeof path === 'string' ? path : ''; + }); + + Post.virtual('permalink').get(function(){ + return ctx.config.url + '/' + this.path; + }); + + Post.virtual('full_source').get(function(){ + return pathFn.join(ctx.source_dir, this.source || ''); + }); + + Post.virtual('asset_dir').get(function(){ + var src = this.full_source; + return src.substring(0, src.length - pathFn.extname(src).length) + pathFn.sep; + }); + + Post.virtual('tags').get(function(){ + var PostTag = ctx.model('PostTag'); + var Tag = ctx.model('Tag'); + + var ids = PostTag.find({post_id: this._id}).map(function(item){ + return item.tag_id; + }); + + return Tag.find({_id: {$in: ids}}); + }); + + Post.method('setTags', function(tags){ + var PostTag = ctx.model('PostTag'); + var Tag = ctx.model('Tag'); + var id = this._id; + + return Promise.map(tags, function(tag){ + // Find the tag by name + var data = Tag.findOne({name: tag}); + if (data) return data; + + // Insert the tag if not exist + return Tag.insert({name: tag}); + }).map(function(tag){ + // Find the reference + var ref = PostTag.findOne({post_id: id, tag_id: tag._id}); + if (ref) return ref; + + // Insert the reference if not exist + return PostTag.insert({ + post_id: id, + tag_id: tag._id + }); + }); + }); + + Post.virtual('categories').get(function(){ + var PostCategory = ctx.model('PostCategory'); + var Category = ctx.model('Category'); + + var ids = PostCategory.find({post_id: this._id}).map(function(item){ + return item.category_id; + }); + + return Category.find({_id: {$in: ids}}); + }); + + Post.method('setCategories', function(cats){ + var PostCategory = ctx.model('PostCategory'); + var Category = ctx.model('Category'); + var id = this._id; + var arr = []; + + // Don't use "Promise.map". It doesn't run in series. + // MUST USE "Promise.each". + return Promise.each(cats, function(cat, i){ + // Find the category by name + var data = Category.findOne({ + name: cat, + parent: i ? cats[i - 1] : {$exists: false} + }); + + if (data){ + arr.push(data._id); + return data; + } + + // Insert the category if not exist + var obj = {name: cat}; + if (i) obj.parent = arr[i - 1]; + + return Category.insert(obj).then(function(data){ + arr.push(data._id); + return data; + }); + }).each(function(){ + // Get the index from the second argument + // and get the category id from arr. + var cat = arr[arguments[1]]; + + // Find the reference + var ref = PostCategory.findOne({post_id: id, tag_id: cat}); + if (ref) return ref; + + // Insert the reference if not exist + return PostCategory.insert({ + post_id: id, + category_id: cat + }); + }); + }); + + // Remove PostTag references + Post.pre('remove', function(data){ + var PostTag = ctx.model('PostTag'); + return PostTag.remove({post_id: data._id}); + }); + + // Remove PostCategory references + Post.pre('remove', function(data){ + var PostCategory = ctx.model('PostCategory'); + return PostCategory.remove({post_id: data._id}); + }); + + // Remove assets + Post.pre('remove', function(data){ + var Asset = ctx.model('Asset'); + return Asset.remove({post: data._id}); + }); + + return Post; +}; \ No newline at end of file diff --git a/lib/models/post_category.js b/lib/models/post_category.js new file mode 100644 index 0000000000..d4370a82c7 --- /dev/null +++ b/lib/models/post_category.js @@ -0,0 +1,10 @@ +var Schema = require('warehouse').Schema; + +module.exports = function(ctx){ + var PostCategory = new Schema({ + post_id: {type: Schema.Types.CUID, ref: 'Post'}, + category_id: {type: Schema.Types.CUID, ref: 'Category'} + }); + + return PostCategory; +}; \ No newline at end of file diff --git a/lib/models/post_tag.js b/lib/models/post_tag.js new file mode 100644 index 0000000000..a2a26353d4 --- /dev/null +++ b/lib/models/post_tag.js @@ -0,0 +1,10 @@ +var Schema = require('warehouse').Schema; + +module.exports = function(ctx){ + var PostTag = new Schema({ + post_id: {type: Schema.Types.CUID, ref: 'Post'}, + tag_id: {type: Schema.Types.CUID, ref: 'Tag'} + }); + + return PostTag; +}; \ No newline at end of file diff --git a/lib/models/tag.js b/lib/models/tag.js index e33912b961..37d5f3b62b 100644 --- a/lib/models/tag.js +++ b/lib/models/tag.js @@ -2,30 +2,67 @@ var Schema = require('warehouse').Schema; var util = require('../util'); var escape = util.escape; -var Tag = module.exports = new Schema({ - name: {type: String, required: true}, - posts: [{type: Schema.Types.CUID, ref: 'Post'}] -}); +module.exports = function(ctx){ + var Tag = new Schema({ + name: {type: String, required: true} + }); -Tag.virtual('slug').get(function(){ - var map = hexo.config.tag_map; - var name = this.name; + Tag.virtual('slug').get(function(){ + var map = ctx.config.tag_map || {}; + var name = this.name; + if (!name) return; - name = map && map.hasOwnProperty(name) ? map[name] : name; - return escape.filename(name, hexo.config.filename_case); -}); + name = map[name] || name; + return escape.filename(name, ctx.config.filename_case); + }); -Tag.virtual('path').get(function(){ - var tagDir = hexo.config.tag_dir; - if (tagDir[tagDir.length - 1] !== '/') tagDir += '/'; + Tag.virtual('path').get(function(){ + var tagDir = ctx.config.tag_dir; + if (tagDir[tagDir.length - 1] !== '/') tagDir += '/'; - return tagDir + this.slug + '/'; -}); + return tagDir + this.slug + '/'; + }); -Tag.virtual('permalink').get(function(){ - return hexo.config.url + '/' + this.path; -}); + Tag.virtual('permalink').get(function(){ + return ctx.config.url + '/' + this.path; + }); -Tag.virtual('length').get(function(){ - return this.posts.length; -}); \ No newline at end of file + Tag.virtual('posts').get(function(){ + var PostTag = ctx.model('PostTag'); + var Post = ctx.model('Post'); + + var ids = PostTag.find({tag_id: this._id}).map(function(item){ + return item.post_id; + }); + + return Post.find({ + _id: {$in: ids}, + published: true + }); + }); + + Tag.virtual('length').get(function(){ + return this.posts.length; + }); + + // Check whether a tag exists + Tag.pre('save', function(data){ + var name = data.name; + if (!name) return; + + var Tag = ctx.model('Tag'); + var tag = Tag.findOne({name: name}); + + if (tag && tag._id === data._id){ + throw new Error('Tag `' + name + '` has already existed!'); + } + }); + + // Remove PostTag references + Tag.pre('remove', function(data){ + var PostTag = ctx.model('PostTag'); + return PostTag.remove({tag_id: data._id}); + }); + + return Tag; +}; \ No newline at end of file diff --git a/lib/models/types/moment.js b/lib/models/types/moment.js index a680ff3a7a..cf361e3e53 100644 --- a/lib/models/types/moment.js +++ b/lib/models/types/moment.js @@ -1,6 +1,6 @@ -var moment = require('moment'), - SchemaType = require('warehouse').SchemaType, - util = require('../../util'); +var moment = require('moment'); +var SchemaType = require('warehouse').SchemaType; +var util = require('../../util'); function SchemaTypeMoment(name, options){ SchemaType.call(this, name, options); @@ -12,22 +12,17 @@ SchemaTypeMoment.prototype.cast = function(value, data){ value = SchemaType.prototype.cast.call(this, value, data); if (value == null || moment.isMoment(value)) return value; - - var lang = hexo.config.language, - date = moment(value); - - if (lang && !Array.isArray(lang)){ - return date.locale(lang.toLowerCase()); - } else { - return date; - } + return moment(value); }; SchemaTypeMoment.prototype.validate = function(value, data){ value = SchemaType.prototype.validate.call(this, value, data); if (value instanceof Error) return value; - if (value != null && (moment.invalid(value))){ + // NEED FIX: Why value sometimes don't have "isValid" method? + value = moment(value); + + if (value != null && (!moment.isMoment(value) || !value.isValid())){ return new Error('`' + value + '` is not a valid date!'); } @@ -75,13 +70,13 @@ SchemaTypeMoment.prototype.q$year = function(value, query, data){ }; SchemaTypeMoment.prototype.u$inc = function(value, update, data){ - if (value) value.add(update, 'ms'); - return value; + if (!value) return value; + return value.add(update); }; SchemaTypeMoment.prototype.u$dec = function(value, update, data){ - if (value) value.subtract(update, 'ms'); - return value; + if (!value) return value; + return value.subtract(update); }; module.exports = SchemaTypeMoment; \ No newline at end of file diff --git a/lib/plugins/console/clean.js b/lib/plugins/console/clean.js index ec29f78df6..14afbcc3ae 100644 --- a/lib/plugins/console/clean.js +++ b/lib/plugins/console/clean.js @@ -2,36 +2,33 @@ var Promise = require('bluebird'); var util = require('../../util'); var fs = util.fs; -module.exports = function(ctx){ - var log = ctx.log; +module.exports = function(args){ + return Promise.all([ + deleteDatabase(this), + deletePublicDir(this) + ]); +}; + +function deleteDatabase(ctx){ var dbPath = ctx.database.options.path; - function deleteDatabase(){ - return fs.exists(dbPath).then(function(exist){ - if (!exist) return; + return fs.exists(dbPath).then(function(exist){ + if (!exist) return; - return fs.unlink(dbPath).then(function(){ - log.info('Deleted database.'); - }); + return fs.unlink(dbPath).then(function(){ + ctx.log.info('Deleted database.'); }); - } + }); +} - function deletePublicDir(){ - var publicDir = ctx.public_dir; +function deletePublicDir(ctx){ + var publicDir = ctx.public_dir; - return fs.exists(publicDir).then(function(exist){ - if (!exist) return; + return fs.exists(publicDir).then(function(exist){ + if (!exist) return; - return fs.rmdir(publicDir).then(function(){ - log.info('Deleted public directory.'); - }); + return fs.rmdir(publicDir).then(function(){ + ctx.log.info('Deleted public directory.'); }); - } - - return function(args){ - return Promise.all([ - deleteDatabase(), - deletePublicDir() - ]); - }; -}; \ No newline at end of file + }); +} \ No newline at end of file diff --git a/lib/plugins/console/config.js b/lib/plugins/console/config.js new file mode 100644 index 0000000000..6849b0a804 --- /dev/null +++ b/lib/plugins/console/config.js @@ -0,0 +1,3 @@ +module.exports = function(args){ + console.log(this.config); +}; \ No newline at end of file diff --git a/lib/plugins/console/deploy.js b/lib/plugins/console/deploy.js new file mode 100644 index 0000000000..2ceb2ea204 --- /dev/null +++ b/lib/plugins/console/deploy.js @@ -0,0 +1,55 @@ +var _ = require('lodash'); +var util = require('../../util'); + +var fs = util.fs; + +require('colors'); + +module.exports = function(args){ + var config = this.config.deploy; + var deployers = this.extend.deployer.list(); + var self = this; + + if (!config){ + var help = ''; + + help += 'You should configure deployment settings in _config.yml first!\n\n'; + help += 'Available deployer plugins:\n'; + help += ' ' + Object.keys(deployers).join(', ') + '\n\n'; + help += 'For more help, you can check the online docs: ' + 'http://hexo.io/'.underline; + + console.log(help); + return; + } + + return new Promise(function(resolve, reject){ + if (args.g || args.generate){ + self.call('generate').then(resolve, reject); + } else { + fs.exists(self.public_dir, function(exist){ + if (exist) return resolve(); + self.call('generate').then(resolve, reject); + }); + } + }).then(function(){ + self.emit('deployBefore'); + + if (!Array.isArray(config)) config = [config]; + return config; + }).map(function(item){ + var type = item.type; + + if (!deployers[type]){ + self.log.error('Deployer not found: %s', type.magenta); + return; + } + + self.log.info('Deploying: %s', type.magenta); + + return deployers[type].call(self, _.extend({}, item, args)).then(function(){ + self.log.info('Deploy done: %s', type.magenta); + }); + }).then(function(){ + self.emit('deployAfter'); + }); +}; \ No newline at end of file diff --git a/lib/plugins/console/generate.js b/lib/plugins/console/generate.js new file mode 100644 index 0000000000..af5e1d970f --- /dev/null +++ b/lib/plugins/console/generate.js @@ -0,0 +1,9 @@ +module.exports = function(args){ + var watch = args.w || args.watch; + + this.emit('generateBefore'); + + return this.post.load({watch: watch}).then(function(){ + // + }); +}; \ No newline at end of file diff --git a/lib/plugins/console/help.js b/lib/plugins/console/help.js index 91baa8cac4..8341c4c18c 100644 --- a/lib/plugins/console/help.js +++ b/lib/plugins/console/help.js @@ -1,56 +1,54 @@ -module.exports = function(ctx){ - return function(args){ - var command = args._[0]; - var list = ctx.extend.console.list(); - var str = ''; - var item, options; - - if (list.hasOwnProperty(command) && command !== 'help'){ - item = list[command]; +module.exports = function(args){ + var command = args._[0]; + var list = this.extend.console.list(); + var str = ''; + var item, options; + + if (list.hasOwnProperty(command) && command !== 'help'){ + item = list[command]; + options = item.options; + + str += 'Usage: hexo ' + command; + if (options.usage) str += ' ' + options.usage; + str += '\n\n'; + str += 'Description:\n'; + str += (options.description || options.desc || item.description || item.desc) + '\n\n'; + + if (options.arguments) str += commandList('Arguments:', options.arguments); + if (options.commands) str += commandList('Commands:', options.commands); + if (options.options) str += commandList('Options:', options.options); + } else { + var keys = Object.keys(list); + var commands = []; + + str += 'Usage: hexo \n\n'; + + for (var i = 0, len = keys.length; i < len; i++){ + var key = keys[i]; + item = list[key]; options = item.options; - str += 'Usage: hexo ' + command; - if (options.usage) str += ' ' + options.usage; - str += '\n\n'; - str += 'Description:\n'; - str += (options.description || options.desc || item.description || item.desc) + '\n\n'; - - if (options.arguments) str += commandList('Arguments:', options.arguments); - if (options.commands) str += commandList('Commands:', options.commands); - if (options.options) str += commandList('Options:', options.options); - } else { - var keys = Object.keys(list), - commands = []; - - str += 'Usage: hexo \n\n'; - - for (var i = 0, len = keys.length; i < len; i++){ - var key = keys[i]; - item = list[key]; - options = item.options; - - if ((!ctx.env.init && !options.init) || (!ctx.debug && options.debug)) continue; - - commands.push({ - name: key, - desc: (item.description || item.desc) - }); - } - - str += commandList('Commands:', commands); - str += commandList('Global Options:', [ - {name: '--config', desc: 'Specify config file instead of using _config.yml'}, - {name: '--debug', desc: 'Display all verbose messages in the terminal'}, - {name: '--safe', desc: 'Disable all plugins and scripts'}, - {name: '--silent', desc: 'Hide output on console'} - ]); + if ((!this.env.init && !options.init) || (!this.env.debug && options.debug)) continue; + + commands.push({ + name: key, + desc: (item.description || item.desc) + }); } - str += 'For more help, you can use `hexo help [command]` for the detailed information\n'; - str += 'or you can check the docs: ' + 'http://hexo.io/docs/'.underline; + str += commandList('Commands:', commands); + str += commandList('Global Options:', [ + {name: '--config', desc: 'Specify config file instead of using _config.yml'}, + {name: '--debug', desc: 'Display all verbose messages in the terminal'}, + {name: '--safe', desc: 'Disable all plugins and scripts'}, + {name: '--silent', desc: 'Hide output on console'} + ]); + } + + str += 'For more help, you can use `hexo help [command]` for the detailed information\n'; + str += 'or you can check the docs: ' + 'http://hexo.io/docs/'.underline; - console.log(str); - }; + console.log(str); }; function commandList(title, list){ diff --git a/lib/plugins/console/index.js b/lib/plugins/console/index.js index a33658bccf..4af250aca6 100644 --- a/lib/plugins/console/index.js +++ b/lib/plugins/console/index.js @@ -1,30 +1,86 @@ module.exports = function(ctx){ var console = ctx.extend.console; - console.register('clean', 'Removed generated files and cache', require('./clean')(ctx)); + console.register('clean', 'Removed generated files and cache.', require('./clean')); - console.register('help', 'Get help on a command', { + console.register('config', 'List the current configuration.', require('./config')); + + console.register('deploy', 'Deploy your website.', { + options: [ + {name: '--setup', desc: 'Setup without deployment'}, + {name: '-g, --generate', desc: 'Generate before deployment'} + ] + }, require('./deploy')); + + console.register('generate', 'Generate static files.', { + options: [ + {name: '-d, --deploy', desc: 'Deploy after generated'}, + {name: '-w, --watch', desc: 'Watch file changes'} + ] + }, require('./generate')); + + console.register('help', 'Get help on a command.', { init: true - }, require('./help')(ctx)); + }, require('./help')); - console.register('init', 'Create a new Hexo folder', { + console.register('init', 'Create a new Hexo folder.', { init: true, desc: 'Create a new Hexo folder at the specified path or the current directory.', usage: '[destination]' - }, require('./init')(ctx)); + }, require('./init')); + + console.register('migrate', 'Migrate your site from other system to Hexo.', { + init: true, + usage: '', + arguments: [ + {name: 'type', desc: 'Migrator type.'} + ] + }, require('./migrate')); - console.register('render', 'Render files with renderer plugins', { + console.register('new', 'Create a new post.', { + usage: '[layout] ', + arguments: [ + {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'}, + {name: 'title', desc: 'Post title. Wrap it with quotations to escape.'} + ], + options: [ + {name: '-r, --replace', desc: 'Replace the current post if existed.'}, + {name: '-s, --slug', desc: 'Post slug. Customize the URL of the post.'}, + {name: '-p, --path', desc: 'Post path. Customize the path of the post.'} + ] + }, require('./new')); + + console.register('publish', 'Moves a draft post from _drafts to _posts folder.', { + usage: '[layout] <filename>', + arguments: [ + {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'}, + {name: 'filename', desc: 'Draft filename. "hello-world" for example.'} + ] + }, require('./publish')); + + console.register('render', 'Render files with renderer plugins.', { init: true, desc: 'Render files with renderer plugins (e.g. Markdown) and save them at the specified path.', usage: '<file1> [file2] ...', options: [ - {name: '--output', desc: 'Output destination. Result will be print in the terminal if the output destination is not set.'}, + {name: '--output', desc: 'Output destination. Result will be printed in the terminal if the output destination is not set.'}, {name: '--engine', desc: 'Specify render engine'}, {name: '--pretty', desc: 'Prettify JSON output'} ] - }, require('./render')(ctx)); + }, require('./render')); + + console.register('server', 'Start the server.', { + desc: 'Start the server and watch for file changes.', + options: [ + {name: '-i, --ip', desc: 'Override the default server IP. Bind to all IP address by default.'}, + {name: '-p, --port', desc: 'Override the default port.'}, + {name: '-s, --static', desc: 'Only serve static files.'}, + {name: '-l, --log [format]', desc: 'Enable logger. Override the logger format.'}, + {name: '-d, --drafts', desc: 'Serve draft posts.'} + ] + }, require('./server')); - console.register('version', 'Display version information', { + console.register('version', 'Display version information.', { init: true - }, require('./version')(ctx)); + }, require('./version')); }; \ No newline at end of file diff --git a/lib/plugins/console/init.js b/lib/plugins/console/init.js index 6896309e76..412455ec85 100644 --- a/lib/plugins/console/init.js +++ b/lib/plugins/console/init.js @@ -5,23 +5,20 @@ var fs = util.fs; require('colors'); -module.exports = function(ctx){ - var baseDir = ctx.base_dir; - var log = ctx.log; - var assetDir = pathFn.join(ctx.core_dir, 'assets'); +module.exports = function(args){ + var baseDir = this.base_dir; + var log = this.log; + var assetDir = pathFn.join(this.core_dir, 'assets'); + var target = args._[0] ? pathFn.resolve(baseDir, args._[0]) : baseDir; - return function(args){ - var target = args._[0] ? pathFn.resolve(baseDir, args._[0]) : baseDir; + log.info('Copying data to %s', tildify(target).magenta); - log.info('Copying data to %s', tildify(target).magenta); - - return fs.copyDir(assetDir, target).then(function(){ - return fs.rename( - pathFn.join(target, 'gitignore'), - pathFn.join(target, '.gitignore')); - }).then(function(){ - log.info('You are almost done! Don\'t forget to run `npm install` ' + - 'before you start blogging with Hexo!'); - }); - }; + return fs.copyDir(assetDir, target).then(function(){ + return fs.rename( + pathFn.join(target, 'gitignore'), + pathFn.join(target, '.gitignore')); + }).then(function(){ + log.info('You are almost done! Don\'t forget to run `npm install` ' + + 'before you start blogging with Hexo!'); + }); }; \ No newline at end of file diff --git a/lib/plugins/console/migrate.js b/lib/plugins/console/migrate.js new file mode 100644 index 0000000000..2741ef7323 --- /dev/null +++ b/lib/plugins/console/migrate.js @@ -0,0 +1,25 @@ +require('colors'); + +module.exports = function(args){ + // Display help message if user didn't input any arguments + if (!args._.length){ + return this.call('help', {_: ['migrate']}); + } + + var type = args._.shift(); + var migrators = this.extend.migrator.list(); + + if (!migrators[type]){ + var help = ''; + + help += type.magenta + ' migrator plugin is not installed.\n\n'; + help += 'Installed migrator plugins:\n'; + help += ' ' + Object.keys(migrators).join(', ') + '\n\n'; + help += 'For more help, you can check the online docs: ' + 'http://hexo.io/'.underline; + + console.log(help); + return; + } + + return migrators[type].call(this, args); +}; \ No newline at end of file diff --git a/lib/plugins/console/new.js b/lib/plugins/console/new.js new file mode 100644 index 0000000000..e4f929eaa9 --- /dev/null +++ b/lib/plugins/console/new.js @@ -0,0 +1,43 @@ +var tildify = require('tildify'); + +require('colors'); + +var reservedKeys = { + _: true, + title: true, + layout: true, + slug: true, + path: true, + // Global options + config: true, + debug: true, + safe: true, + silent: true +}; + +module.exports = function(args){ + // Display help message if user didn't input any arguments + if (!args._.length){ + return this.call('help', {_: ['new']}); + } + + var data = { + title: args._.pop(), + layout: args._.length ? args._[0] : this.config.default_layout, + slug: args.s || args.slug, + path: args.p || args.path + }; + + var keys = Object.keys(args); + var key = ''; + var self = this; + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + if (!reservedKeys[key]) data[key] = args[key]; + } + + return this.post.create(data, args.r || args.replace).then(function(post){ + self.log.info('Created: %s', tildify(post.path).magenta); + }); +}; \ No newline at end of file diff --git a/lib/plugins/console/publish.js b/lib/plugins/console/publish.js new file mode 100644 index 0000000000..7580b3f980 --- /dev/null +++ b/lib/plugins/console/publish.js @@ -0,0 +1,19 @@ +var tildify = require('tildify'); + +require('colors'); + +module.exports = function(args){ + // Display help message if user didn't input any arguments + if (!args._.length){ + return this.call('help', {_: ['publish']}); + } + + var self = this; + + return this.post.publish({ + slug: args._.pop(), + layout: args._.length ? args._[0] : this.config.default_layout + }, args.r || args.replace).then(function(post){ + self.log.info('Published: %s', tildify(post.path).magenta); + }); +}; \ No newline at end of file diff --git a/lib/plugins/console/render.js b/lib/plugins/console/render.js index d21cb0791c..2cf7337a6e 100644 --- a/lib/plugins/console/render.js +++ b/lib/plugins/console/render.js @@ -7,41 +7,38 @@ var fs = util.fs; require('colors'); -module.exports = function(ctx){ - var render = ctx.render; - var baseDir = hexo.base_dir; - - return function(args){ - var files = args._; - var output = args.o || args.output; - var engine = args.engine; - - return Promise.map(files, function(path){ - var src = pathFn.resolve(baseDir, path); - // var start = Date.now(); - var start = process.hrtime(); - - return render.render({ - path: src, - engine: engine - }).then(function(result){ - if (typeof result === 'object'){ - if (args.pretty){ - result = JSON.stringify(result, null, ' '); - } else { - result = JSON.stringify(result); - } +module.exports = function(args){ + var baseDir = this.base_dir; + var render = this.render; + var log = this.log; + var files = args._; + var output = args.o || args.output; + var engine = args.engine; + + return Promise.map(files, function(path){ + var src = pathFn.resolve(baseDir, path); + var start = process.hrtime(); + + return render.render({ + path: src, + engine: engine + }).then(function(result){ + if (typeof result === 'object'){ + if (args.pretty){ + result = JSON.stringify(result, null, ' '); + } else { + result = JSON.stringify(result); } + } - if (!output) return console.log(result); + if (!output) return console.log(result); - var extname = pathFn.extname(path); - var dest = pathFn.resolve(output, path.substring(0, path.length - extname.length + 1)) + render.getOutput(path); - var interval = prettyHrtime(process.hrtime(start)); + var extname = pathFn.extname(path); + var dest = pathFn.resolve(output, path.substring(0, path.length - extname.length + 1)) + render.getOutput(path); + var interval = prettyHrtime(process.hrtime(start)); - log.info('Rendered in %s: %s -> %s', interval.cyan, tildify(src).magenta, tildify(dest).magenta); - return fs.writeFile(dest, result); - }); + log.info('Rendered in %s: %s -> %s', interval.cyan, tildify(src).magenta, tildify(dest).magenta); + return fs.writeFile(dest, result); }); - }; + }); }; \ No newline at end of file diff --git a/lib/plugins/console/server.js b/lib/plugins/console/server.js new file mode 100644 index 0000000000..723548d0d1 --- /dev/null +++ b/lib/plugins/console/server.js @@ -0,0 +1,3 @@ +module.exports = function(args){ + // +}; \ No newline at end of file diff --git a/lib/plugins/console/version.js b/lib/plugins/console/version.js index 221d455741..39abb72d77 100644 --- a/lib/plugins/console/version.js +++ b/lib/plugins/console/version.js @@ -16,4 +16,19 @@ module.exports = function(ctx){ } } }; +}; + +module.exports = function(args){ + var versions = _.extend({ + hexo: this.version, + os: os.type() + ' ' + os.release() + ' ' + os.platform() + ' ' + os.arch() + }, process.versions); + + if (args.json){ + console.log(versions); + } else { + for (var i in versions){ + console.log(i + ': ' + versions[i]); + } + } }; \ No newline at end of file diff --git a/lib/plugins/filter/after_post_render/excerpt.js b/lib/plugins/filter/after_post_render/excerpt.js new file mode 100644 index 0000000000..0fa8e3208f --- /dev/null +++ b/lib/plugins/filter/after_post_render/excerpt.js @@ -0,0 +1,16 @@ +var rExcerpt = /<!--\s*more\s*-->/; + +module.exports = function(data){ + var content = data.content; + + if (rExcerpt.test(content)){ + data.content = content.replace(rExcerpt, function(match, index){ + data.excerpt = content.substring(0, index); + data.more = content.substring(index); + return '<a id="more"></a>'; + }); + } else { + data.excerpt = ''; + data.more = content; + } +}; \ No newline at end of file diff --git a/lib/plugins/filter/after_post_render/external_link.js b/lib/plugins/filter/after_post_render/external_link.js new file mode 100644 index 0000000000..55f31bd88a --- /dev/null +++ b/lib/plugins/filter/after_post_render/external_link.js @@ -0,0 +1,32 @@ +var cheerio = require('cheerio'); +var url = require('url'); + +module.exports = function(data){ + var config = this.config; + if (!config.external_link) return; + + var $ = cheerio.load(data.content, {decodeEntities: false}); + + $('a').each(function(){ + // Exit if the link has target attribute + if ($(this).attr('target')) return; + + // Exit if the href attribute doesn't exists + var href = $(this).attr('href'); + if (!href) return; + + var data = url.parse(href); + + // Exit if the link doesn't have protocol, which means it's a internal link + if (!data.protocol) return; + + // Exit if the url is started with site url + if (data.hostname === config.url) return; + + $(this) + .attr('target', '_blank') + .attr('rel', 'external'); + }); + + data.content = $.html(); +}; \ No newline at end of file diff --git a/lib/plugins/filter/after_post_render/index.js b/lib/plugins/filter/after_post_render/index.js new file mode 100644 index 0000000000..4b7b5df971 --- /dev/null +++ b/lib/plugins/filter/after_post_render/index.js @@ -0,0 +1,6 @@ +module.exports = function(ctx){ + var filter = ctx.extend.filter; + + filter.register('after_post_render', require('./excerpt')); + filter.register('external_link', require('./external_link')); +}; \ No newline at end of file diff --git a/lib/plugins/filter/before_post_render/backtick_code_block.js b/lib/plugins/filter/before_post_render/backtick_code_block.js new file mode 100644 index 0000000000..b38a11fdd0 --- /dev/null +++ b/lib/plugins/filter/before_post_render/backtick_code_block.js @@ -0,0 +1,41 @@ +var stripIndent = require('strip-indent'); +var util = require('../../../util'); +var highlight = util.highlight; + +var rBacktick = /\n*(`{3,}|~{3,}) *(.+)? *\n([\s\S]+?)\s*\1\n*/g; +var rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/; +var rLangCaption = /([^\s]+)\s*(.+)?/; + +module.exports = function(data){ + var config = this.config.highlight || {}; + if (!config.enable) return; + + data.content = data.content.replace(rBacktick, function(m, ticks, args, str){ + var options = { + gutter: config.line_number, + tab: config.tab_replace + }; + + if (args){ + var match; + + if (rAllOptions.test(args)){ + match = args.match(rAllOptions); + } else if (rLangCaption.test(args)){ + match = args.match(rLangCaption); + } + + options.lang = match[1]; + + if (match[2]){ + options.caption = '<span>' + match[2] + '</span>'; + + if (match[3]){ + options.caption += '<a href="' + match[3] + '">' + (match[4] ? match[4] : 'link') + '</a>'; + } + } + } + + return '\n\n<escape>' + highlight(stripIndent(str), options).replace(/&/g, '&') + '</escape>\n\n'; + }); +}; \ No newline at end of file diff --git a/lib/plugins/filter/before_post_render/index.js b/lib/plugins/filter/before_post_render/index.js new file mode 100644 index 0000000000..243c1ce3ec --- /dev/null +++ b/lib/plugins/filter/before_post_render/index.js @@ -0,0 +1,6 @@ +module.exports = function(ctx){ + var filter = ctx.extend.filter; + + filter.register('before_post_render', require('./backtick_code_block')); + filter.register('before_post_render', require('./titlecase')); +}; \ No newline at end of file diff --git a/lib/plugins/filter/before_post_render/titlecase.js b/lib/plugins/filter/before_post_render/titlecase.js new file mode 100644 index 0000000000..0f8366ae29 --- /dev/null +++ b/lib/plugins/filter/before_post_render/titlecase.js @@ -0,0 +1,8 @@ +var inflection = require('inflection'); +var titleize = inflection.titleize; + +module.exports = function(data){ + if (!this.config.titlecase || !data.title) return; + + data.title = titleize(data.title); +}; \ No newline at end of file diff --git a/lib/plugins/filter/index.js b/lib/plugins/filter/index.js index 98d1fc1cc0..097301616d 100644 --- a/lib/plugins/filter/index.js +++ b/lib/plugins/filter/index.js @@ -1,3 +1,10 @@ -module.exports = function(){ - // +module.exports = function(ctx){ + var filter = ctx.extend.filter; + + require('./after_post_render')(ctx); + require('./before_post_render')(ctx); + require('./server_middleware')(ctx); + + filter.register('new_post_path', require('./new_post_path')); + filter.register('post_permalink', require('./post_permalink')); }; \ No newline at end of file diff --git a/lib/plugins/filter/new_post_path.js b/lib/plugins/filter/new_post_path.js new file mode 100644 index 0000000000..260cf0d663 --- /dev/null +++ b/lib/plugins/filter/new_post_path.js @@ -0,0 +1,104 @@ +var pathFn = require('path'); +var moment = require('moment'); +var _ = require('lodash'); +var util = require('../../util'); +var fs = util.fs; +var escape = util.escape; +var Permalink = util.permalink; + +var reservedKeys = { + year: true, + month: true, + i_month: true, + day: true, + i_day: true, + title: true +}; + +module.exports = function(data, replace){ + var sourceDir = this.source_dir; + var draftDir = pathFn.join(sourceDir, '_drafts'); + var postDir = pathFn.join(sourceDir, '_posts'); + var config = this.config; + var newPostName = config.new_post_name; + var permalinkDefaults = config.permalink_defaults; + var permalink = new Permalink(newPostName); + var path = data.path; + var layout = data.layout; + var slug = data.slug; + var target = ''; + + if (path){ + switch (layout){ + case 'page': + target = pathFn.join(sourceDir, path); + break; + + case 'draft': + target = pathFn.join(draftDir, path); + break; + + default: + target = pathFn.join(postDir, path); + } + } else { + switch (layout){ + case 'page': + target = pathFn.join(sourceDir, slug, 'index'); + break; + + case 'draft': + target = pathFn.join(draftDir, slug); + break; + + default: + var date = moment(data.date || Date.now()); + var keys = Object.keys(data); + var key = ''; + + var filenameData = { + year: date.format('YYYY'), + month: date.format('MM'), + i_month: date.format('M'), + day: date.format('DD'), + i_day: date.format('D'), + title: slug + }; + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + if (!reservedKeys[key]) filenameData[key] = data[key]; + } + + target = pathFn.join(postDir, permalink.stringify( + _.defaults(filenameData, permalinkDefaults))); + } + } + + if (!pathFn.extname(target)){ + target += pathFn.extname(newPostName) || '.md'; + } + + if (replace) return target; + + return fs.exists(target).then(function(exist){ + if (!exist) return target; + + var extname = pathFn.extname(target); + var basename = pathFn.basename(target, extname); + var regex = new RegExp('^' + escape.regex(basename) + '-?(\\d+)?'); + + return fs.readdir(pathFn.dirname(target)).map(function(item){ + var match = pathFn.basename(item, pathFn.extname(item)).match(regex); + + if (match){ + return match[1] ? parseInt(match[1], 10) : 0; + } else { + return -1; + } + }).then(function(numbers){ + var max = Math.max.apply(null, numbers) + 1; + return target.substring(0, target.length - extname.length) + '-' + max + extname; + }); + }); +}; \ No newline at end of file diff --git a/lib/plugins/filter/post_permalink.js b/lib/plugins/filter/post_permalink.js new file mode 100644 index 0000000000..f8dda0811d --- /dev/null +++ b/lib/plugins/filter/post_permalink.js @@ -0,0 +1,49 @@ +var _ = require('lodash'); +var util = require('../../util'); +var Permalink = util.permalink; +var permalink; + +var ignoreKeys = { + path: true, + permalink: true +}; + +module.exports = function(data){ + var config = this.config; + // var Category = this.model('Category'); + var meta = { + id: data.id || data._id, + title: data.slug, + year: data.date.format('YYYY'), + month: data.date.format('MM'), + day: data.date.format('DD'), + i_month: data.date.format('M'), + i_day: data.date.format('D') + }; + + if (!permalink || permalink.rule !== config.permalink){ + permalink = new Permalink(config.permalink); + } +/* + var categories = data.categories; + + if (categories.length){ + var category = categories[categories.length - 1]; + meta.category = Category.get(category).slug; + } else { + meta.category = config.default_category; + }*/ + + var keys = Object.keys(data); + var key = ''; + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + + if (!ignoreKeys[key] && !meta.hasOwnProperty(key)){ + meta[key] = data[key]; + } + } + + return permalink.stringify(_.defaults(meta, config.permalink_defaults)); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/gzip.js b/lib/plugins/filter/server_middleware/gzip.js new file mode 100644 index 0000000000..37201edd22 --- /dev/null +++ b/lib/plugins/filter/server_middleware/gzip.js @@ -0,0 +1,5 @@ +var compress = require('compression'); + +module.exports = function(app){ + app.use(compress()); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/header.js b/lib/plugins/filter/server_middleware/header.js new file mode 100644 index 0000000000..dab3efde0a --- /dev/null +++ b/lib/plugins/filter/server_middleware/header.js @@ -0,0 +1,6 @@ +module.exports = function(app){ + app.use(function(req, res, next){ + res.setHeader('X-Powered-By', 'Hexo'); + next(); + }); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/index.js b/lib/plugins/filter/server_middleware/index.js new file mode 100644 index 0000000000..338ba44312 --- /dev/null +++ b/lib/plugins/filter/server_middleware/index.js @@ -0,0 +1,10 @@ +module.exports = function(ctx){ + var filter = ctx.extend.filter; + + filter.register('server_middleware', require('./logger')); + filter.register('server_middleware', require('./header')); + filter.register('server_middleware', require('./route')); + filter.register('server_middleware', require('./static')); + filter.register('server_middleware', require('./redirect')); + filter.register('server_middleware', require('./gzip')); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/logger.js b/lib/plugins/filter/server_middleware/logger.js new file mode 100644 index 0000000000..73074d45b7 --- /dev/null +++ b/lib/plugins/filter/server_middleware/logger.js @@ -0,0 +1,18 @@ +var morgan = require('morgan'); + +module.exports = function(app){ + var config = this.config; + var args = this.env.args || {}; + var log = this.log; + var format = args.l || args.log || config.logger_format; + + if (typeof format !== 'string') format = 'dev'; + + app.use(morgan(format, { + stream: { + write: function(data){ + log.debug(data); + } + } + })); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/redirect.js b/lib/plugins/filter/server_middleware/redirect.js new file mode 100644 index 0000000000..de21fb6ec7 --- /dev/null +++ b/lib/plugins/filter/server_middleware/redirect.js @@ -0,0 +1,13 @@ +var serverUtil = require('../../../util').server; + +module.exports = function(app){ + var root = this.config.root; + if (root === '/') return; + + // If root url is not `/`, redirect to the correct root url + app.use(function(req, res, next){ + if (req.method !== 'GET' || req.url !== '/') return next(); + + serverUtil.redirect(res, root); + }); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/route.js b/lib/plugins/filter/server_middleware/route.js new file mode 100644 index 0000000000..49d3e9edf6 --- /dev/null +++ b/lib/plugins/filter/server_middleware/route.js @@ -0,0 +1,42 @@ +var pathFn = require('path'); +var Readable = require('stream').Readable; +var serverUtil = require('../../../util').server; + +module.exports = function(app){ + var config = this.config; + var args = this.env.args; + var root = config.root; + var route = this.route; + + if (args.s || args.static) return; + + app.use(root, function(req, res, next){ + var method = req.method; + if (method !== 'GET' && method !== 'HEAD') return next(); + + var url = route.format(decodeURIComponent(req.url)); + var target = route.get(url); + + // When the URL is `foo/index.html` but users access `foo`, redirect to `foo/`. + if (!target){ + if (pathFn.extname(url)) return next(); + + return serverUtil.redirect(res, root + url + '/'); + } + + target(function(err, result){ + if (err) return next(err); + if (result == null) return next(); + + serverUtil.contentType(res, pathFn.extname(url)); + + if (method === 'HEAD') return res.end(); + + if (result instanceof Readable){ + result.pipe(res).on('error', next); + } else { + res.end(result); + } + }); + }); +}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/static.js b/lib/plugins/filter/server_middleware/static.js new file mode 100644 index 0000000000..bf7f17d6b8 --- /dev/null +++ b/lib/plugins/filter/server_middleware/static.js @@ -0,0 +1,5 @@ +var serveStatic = require('serve-static'); + +module.exports = function(app){ + app.use(this.config.root, serveStatic(this.public_dir)); +}; \ No newline at end of file diff --git a/lib/plugins/generator/index.js b/lib/plugins/generator/index.js index 98d1fc1cc0..3f882ea57b 100644 --- a/lib/plugins/generator/index.js +++ b/lib/plugins/generator/index.js @@ -1,3 +1,3 @@ -module.exports = function(){ +module.exports = function(ctx){ // }; \ No newline at end of file diff --git a/lib/plugins/helper/date.js b/lib/plugins/helper/date.js index 6a9aa467c6..3d68a4124e 100644 --- a/lib/plugins/helper/date.js +++ b/lib/plugins/helper/date.js @@ -1,5 +1,5 @@ -var moment = require('moment'), - isMoment = moment.isMoment; +var moment = require('moment'); +var isMoment = moment.isMoment; var result = function(date, format){ if (isMoment(date)){ @@ -22,7 +22,7 @@ var toISOString = function(date){ }; exports.date = function(date, format){ - var config = this.config || hexo.config; + var config = this.config; return result(date || new Date(), format || config.date_format); }; @@ -30,13 +30,13 @@ exports.date = function(date, format){ exports.date_xml = toISOString; exports.time = function(date, format){ - var config = this.config || hexo.config; + var config = this.config; return result(date || new Date(), format || config.time_format); }; exports.full_date = function(date, format){ - var config = this.config || hexo.config; + var config = this.config; return result(date || new Date(), format || config.date_format + ' ' + config.time_format); }; @@ -44,7 +44,7 @@ exports.full_date = function(date, format){ exports.time_tag = function(date, format){ date = date || new Date(); - var config = this.config || hexo.config; + var config = this.config; return '<time datetime="' + toISOString(date) + '">' + result(date, format || config.date_format) + '</time>'; }; diff --git a/lib/plugins/helper/form.js b/lib/plugins/helper/form.js index 0467d014fe..d3e0ae481a 100644 --- a/lib/plugins/helper/form.js +++ b/lib/plugins/helper/form.js @@ -7,7 +7,7 @@ exports.search_form = function(opts){ button: false }, opts); - var config = hexo.config; + var config = this.config; return '<form action="//google.com/search" method="get" accept-charset="UTF-8" class="' + options.class + '">' + '<input type="search" name="q" results="0" class="' + options.class + '-input"' + (options.text ? ' placeholder="' + options.text + '"' : '') + '>' + diff --git a/lib/plugins/helper/format.js b/lib/plugins/helper/format.js index 56a892ab14..c4bc04dbde 100644 --- a/lib/plugins/helper/format.js +++ b/lib/plugins/helper/format.js @@ -1,6 +1,5 @@ -var _ = require('lodash'), - util = require('../../util'), - format = util.format; +var util = require('../../util'); +var format = util.format; exports.strip_html = format.strip_html; @@ -8,14 +7,6 @@ exports.trim = format.trim; exports.titlecase = util.titlecase; -exports.markdown = function(text){ - return hexo.render.renderSync({text: text, engine: 'markdown'}); -}; - exports.word_wrap = format.word_wrap; -exports.truncate = format.truncate; - -exports.render = function(str, engine, locals){ - return hexo.render.renderSync(str, engine, locals); -}; \ No newline at end of file +exports.truncate = format.truncate; \ No newline at end of file diff --git a/lib/plugins/helper/gravatar.js b/lib/plugins/helper/gravatar.js index 4ea0b1df70..db800f3e0d 100644 --- a/lib/plugins/helper/gravatar.js +++ b/lib/plugins/helper/gravatar.js @@ -1,5 +1,5 @@ -var crypto = require('crypto'), - querystring = require('querystring'); +var crypto = require('crypto'); +var querystring = require('querystring'); var md5 = function(str){ return crypto.createHash('md5').update(str).digest('hex'); diff --git a/lib/plugins/helper/index.js b/lib/plugins/helper/index.js index 2ff1dce6fd..deeef206ba 100644 --- a/lib/plugins/helper/index.js +++ b/lib/plugins/helper/index.js @@ -19,10 +19,10 @@ module.exports = function(ctx){ helper.register('strip_html', format.strip_html); helper.register('trim', format.trim); helper.register('titlecase', format.titlecase); - helper.register('markdown', format.markdown); + // helper.register('markdown', format.markdown); helper.register('word_wrap', format.word_wrap); helper.register('truncate', format.truncate); - helper.register('render', format.render); + // helper.register('render', format.render); helper.register('fragment_cache', require('./fragment_cache')); @@ -56,7 +56,12 @@ module.exports = function(ctx){ helper.register('paginator', require('./paginator')); - helper.register('partial', require('./partial')); + helper.register('partial', require('./partial')(ctx)); + + var render = require('./render'); + + helper.register('markdown', render.markdown); + helper.register('render', render.render(ctx)); var tag = require('./tag'); diff --git a/lib/plugins/helper/is.js b/lib/plugins/helper/is.js index c74190cf02..354f9d7691 100644 --- a/lib/plugins/helper/is.js +++ b/lib/plugins/helper/is.js @@ -1,5 +1,5 @@ -var util = require('../../util'), - escape = util.escape; +var util = require('../../util'); +var escape = util.escape; exports.is_current = function(path, strict){ if (strict){ @@ -14,14 +14,14 @@ exports.is_current = function(path, strict){ }; exports.is_home = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.pagination_dir) + '\\/\\d+\\/'); return this.path === '' || r.test(this.path); }; exports.is_post = function(){ - var config = this.config || hexo.config; + var config = this.config; var rUrl = escape.regex(config.permalink) .replace(':id', '\\d+') @@ -36,35 +36,35 @@ exports.is_post = function(){ }; exports.is_archive = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.archive_dir) + '\\/'); return r.test(this.path); }; exports.is_year = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.archive_dir) + '\\/\\d{4}\\/'); return r.test(this.path); }; exports.is_month = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.archive_dir) + '\\/\\d{4}\\/\\d{2}\\/'); return r.test(this.path); }; exports.is_category = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.category_dir) + '\\/'); return r.test(this.path); }; exports.is_tag = function(){ - var config = this.config || hexo.config, + var config = this.config, r = new RegExp('^' + escape.regex(config.tag_dir) + '\\/'); return r.test(this.path); diff --git a/lib/plugins/helper/list.js b/lib/plugins/helper/list.js index a4712f5b0e..bab16da8ab 100644 --- a/lib/plugins/helper/list.js +++ b/lib/plugins/helper/list.js @@ -1,8 +1,10 @@ -var _ = require('lodash'), - moment = require('moment'), +var _ = require('lodash'); +var moment = require('moment'); - // default: do not transform the value - transform_default = function(value) { return value; }; +// default: do not transform the value +function transform_default(value){ + return value; +} exports.list_categories = function(categories, options){ if (!options){ @@ -23,16 +25,16 @@ exports.list_categories = function(categories, options){ class: 'category' }, options); - var style = options.style, - showCount = options.show_count, - className = options.class, - depth = parseInt(options.depth, 10), - orderby = options.orderby, - order = options.order, - result = '', - arr = [], - condition = {}, - self = this; + var style = options.style; + var showCount = options.show_count; + var className = options.class; + var depth = parseInt(options.depth, 10); + var orderby = options.orderby; + var order = options.order; + var result = ''; + var arr = []; + var condition = {}; + var self = this; if (style === 'list'){ result = '<ul class="' + className + '-list">'; @@ -122,12 +124,12 @@ exports.list_tags = function(tags, options){ class: 'tag' }, options); - var style = options.style, - showCount = options.show_count, - className = options.class, - result = '', - arr = [], - self = this; + var style = options.style; + var showCount = options.show_count; + var className = options.class; + var result = ''; + var arr = []; + var self = this; if (style === 'list'){ result = '<ul class="' + className + '-list">'; @@ -183,15 +185,15 @@ exports.list_archives = function(options){ } } - var style = options.style, - showCount = options.show_count, - className = options.class, - type = options.type, - format = options.format, - archiveDir = this.config.archive_dir, - result = '', - arr = [], - self = this; + var style = options.style; + var showCount = options.show_count; + var className = options.class; + var type = options.type; + var format = options.format; + var archiveDir = this.config.archive_dir; + var result = ''; + var arr = []; + var self = this; if (style === 'list'){ result = '<ul class="' + className + '-list">'; @@ -217,8 +219,8 @@ exports.list_archives = function(options){ } }; - var newest = posts.first().date, - oldest = posts.last().date; + var newest = posts.first().date; + var oldest = posts.last().date; for (var i = oldest.year(); i <= newest.year(); i++){ var yearly = posts.find({date: {$year: i}}); diff --git a/lib/plugins/helper/list_post.js b/lib/plugins/helper/list_post.js index 3558e1ca49..29b51c2587 100644 --- a/lib/plugins/helper/list_post.js +++ b/lib/plugins/helper/list_post.js @@ -1,6 +1,6 @@ var _ = require('lodash'); -var findData = function(data, source){ +function findData(data, source){ if (!source.length) return true; for (var i = 0, len = source.length; i < len; ++i){ @@ -8,9 +8,9 @@ var findData = function(data, source){ } return false; -}; +} -var arrayCaseFind = function(data, value){ +function arrayCaseFind(data, value){ value = value.toUpperCase(); for (var i = 0,len = data.length; i < len; i++){ @@ -18,13 +18,13 @@ var arrayCaseFind = function(data, value){ } return false; -}; +} -var makeWhereQuery = function(index){ +function makeWhereQuery(index){ return function(data){ return findData(data, index); }; -}; +} var getPosts = function(site, options){ options = _.extend({ @@ -38,13 +38,13 @@ var getPosts = function(site, options){ } }, options); - var posts = site.posts, - queryData = [], - conditions = options.query, - queryOperator = 'and', - query = {}; + var posts = site.posts; + var queryData = []; + var conditions = options.query; + var queryOperator = 'and'; + var query = {}; - var getIndex = function(type, query){ + function getIndex(type, query){ if (_.isObject(query._index)) return query._index; if (!_.isObject(query)) query = query.split(/\s*,\s*/); @@ -61,13 +61,13 @@ var getPosts = function(site, options){ } return false; - }; + } var keys = Object.keys(conditions); for (var i = 0, len = keys.length; i<len; ++i){ - var key = keys[i], - obj = {}; + var key = keys[i]; + var obj = {}; query = conditions[key]; @@ -127,12 +127,12 @@ exports.list_posts = function(options) { class: 'post' }, options); - var posts = getPosts(this.site, options), - style = options.style, - ul = '<ul class="' + (options.ulClass ? options.ulClass : options.class) + '">', - li = '<li class="' + (options.liClass ? options.liClass : options.class + '-list-item') + '">', - arr = [], - self = this; + var posts = getPosts(this.site, options); + var style = options.style; + var ul = '<ul class="' + (options.ulClass ? options.ulClass : options.class) + '">'; + var li = '<li class="' + (options.liClass ? options.liClass : options.class + '-list-item') + '">'; + var arr = []; + var self = this; posts.each(function(post){ arr.push( '<a href="' + self.url_for(post.path) + '">' + (post.title ? post.title : post.slug) + '</a>'); diff --git a/lib/plugins/helper/number.js b/lib/plugins/helper/number.js index 346ad8ea42..d112ff083b 100644 --- a/lib/plugins/helper/number.js +++ b/lib/plugins/helper/number.js @@ -7,18 +7,18 @@ exports.number_format = function(num, options){ separator: '.' }, options); - var split = num.toString().split('.'), - i; + var split = num.toString().split('.'); + var i, len; - var before = split.shift(), - after = split.length ? split[0] : '', - delimiter = options.delimiter, - precision = options.precision; + var before = split.shift(); + var after = split.length ? split[0] : ''; + var delimiter = options.delimiter; + var precision = options.precision; if (delimiter){ - var beforeArr = [], - beforeLength = before.length, - beforeFirst = beforeLength % 3; + var beforeArr = []; + var beforeLength = before.length; + var beforeFirst = beforeLength % 3; if (beforeFirst) beforeArr.push(before.substr(0, beforeFirst)); @@ -30,12 +30,12 @@ exports.number_format = function(num, options){ } if (precision){ - var afterLength = after.length, - afterResult = ''; + var afterLength = after.length; + var afterResult = ''; if (afterLength > precision){ - var afterLast = after[precision], - last = parseInt(after[precision - 1]); + var afterLast = after[precision]; + var last = parseInt(after[precision - 1]); afterResult = after.substr(0, precision - 1) + (afterLast < 5 ? last : last + 1); } else { diff --git a/lib/plugins/helper/open_graph.js b/lib/plugins/helper/open_graph.js index e1d3db9ecb..ed425cb1d7 100644 --- a/lib/plugins/helper/open_graph.js +++ b/lib/plugins/helper/open_graph.js @@ -1,8 +1,8 @@ -var _ = require('lodash'), - cheerio = require('cheerio'), - util = require('../../util'), - htmlTag = util.html_tag, - format = util.format; +var _ = require('lodash'); +var cheerio = require('cheerio'); +var util = require('../../util'); +var htmlTag = util.html_tag; +var format = util.format; var metaTag = function(name, content){ var data = {}; @@ -23,10 +23,10 @@ var metaTag = function(name, content){ }; module.exports = function(options){ - var page = this.page, - config = this.config || hexo.config, - content = page.content, - images = page.photos || []; + var page = this.page; + var config = this.config; + var content = page.content; + var images = page.photos || []; var description = page.description || ''; diff --git a/lib/plugins/helper/paginator.js b/lib/plugins/helper/paginator.js index 920ba17ecc..ad5d65a349 100644 --- a/lib/plugins/helper/paginator.js +++ b/lib/plugins/helper/paginator.js @@ -15,25 +15,25 @@ module.exports = function(options){ show_all: false }, options); - var current = options.current, - total = options.total, - endSize = options.end_size, - midSize = options.mid_size, - space = options.space, - base = options.base, - format = options.format, - self = this, - front = '', - back = '', - i; + var current = options.current; + var total = options.total; + var endSize = options.end_size; + var midSize = options.mid_size; + var space = options.space; + var base = options.base; + var format = options.format; + var self = this; + var front = ''; + var back = ''; + var i; - var link = function(i){ + function link(i){ return self.url_for(i == 1 ? base : base + format.replace('%d', i)); - }; + } - var pageNum = function(i){ + function pageNum(i){ return '<a class="page-number" href="' + link(i) + '">' + i + '</a>'; - }; + } if (options.prev_next){ if (current != 1) front = '<a class="extend prev" rel="prev" href="' + link(current - 1) + '">' + options.prev_text + '</a>'; diff --git a/lib/plugins/helper/partial.js b/lib/plugins/helper/partial.js index 1d1d47bd3c..9a4616501a 100644 --- a/lib/plugins/helper/partial.js +++ b/lib/plugins/helper/partial.js @@ -1,39 +1,35 @@ -var pathFn = require('path'), - _ = require('lodash'); +var pathFn = require('path'); +var _ = require('lodash'); -module.exports = function(name, locals, options){ - options = _.extend({ - cache: false, - only: false - }, options); +require('colors'); - var viewDir = this.view_dir || this.settings.views, - path = pathFn.join(pathFn.dirname(this.filename.substring(viewDir.length)), name), - view = hexo.theme.getView(path) || hexo.theme.getView(name), - self = this; +module.exports = function(ctx){ + return function(name, locals, options){ + options = options || {}; - if (!view){ - hexo.log.w('Partial %s does not exist', name); - return ''; - } - - var partial = function(){ + var cache = options.cache; + var only = options.only; + var viewDir = this.view_dir; + var path = pathFn.join(pathFn.dirname(this.filename.substring(viewDir.length)), name); + var view = ctx.theme.getView(path) || ctx.theme.getView(name); var viewLocals = {}; - if (options.only){ + if (!view){ + ctx.log.warn('Partial %s does not exist.', name.magenta); + return ''; + } + + if (only){ _.extend(viewLocals, locals); } else { - _.extend(viewLocals, _.omit(self, 'layout'), locals); + _.extend(viewLocals, _.omit(this, 'layout'), locals); } - return view.renderSync(viewLocals); + if (cache){ + var cacheId = typeof cache === 'string' ? cache : view.path; + return this.fragment_cache(cacheId, view.renderSync(viewLocals)); + } else { + return view.renderSync(viewLocals); + } }; - - if (options.cache){ - var cacheId = typeof options.cache === 'string' ? options.cache : view.path; - - return this.fragment_cache(cacheId, partial); - } else { - return partial(); - } }; \ No newline at end of file diff --git a/lib/plugins/helper/render.js b/lib/plugins/helper/render.js new file mode 100644 index 0000000000..38a99e18cf --- /dev/null +++ b/lib/plugins/helper/render.js @@ -0,0 +1,12 @@ +exports.markdown = function(text, options){ + return this.render(text, 'markdown', options); +}; + +exports.render = function(ctx){ + return function(text, engine, locals){ + return ctx.render.renderSync({ + text: text, + engine: engine + }, locals); + }; +}; \ No newline at end of file diff --git a/lib/plugins/helper/tag.js b/lib/plugins/helper/tag.js index e375fa4ca0..4aa6875c8f 100644 --- a/lib/plugins/helper/tag.js +++ b/lib/plugins/helper/tag.js @@ -1,11 +1,10 @@ -var _ = require('lodash'), - pathFn = require('path'), - url = require('url'), - qs = require('querystring'), - util = require('../../util'), - htmlTag = util.html_tag; - -var mergeAttrs = function(options, attrs){ +var _ = require('lodash'); +var pathFn = require('path'); +var qs = require('querystring'); +var util = require('../../util'); +var htmlTag = util.html_tag; + +function mergeAttrs(options, attrs){ if (options.class){ var classes = options.class; @@ -17,14 +16,12 @@ var mergeAttrs = function(options, attrs){ } if (options.id) attrs.id = options.id; -}; +} exports.css = function(){ - var args = _.flatten(_.toArray(arguments)), - config = this.config || hexo.config, - root = config.root, - str = '', - self = this; + var args = _.flatten(_.toArray(arguments)); + var self = this; + var str = ''; args.forEach(function(path){ if (pathFn.extname(path) !== '.css') path += '.css'; @@ -36,11 +33,9 @@ exports.css = function(){ }; exports.js = function(){ - var args = _.flatten(_.toArray(arguments)), - config = this.config || hexo.config, - root = config.root, - str = '', - self = this; + var args = _.flatten(_.toArray(arguments)); + var str = ''; + var self = this; args.forEach(function(path){ if (pathFn.extname(path) !== '.js') path += '.js'; @@ -143,7 +138,7 @@ exports.favicon_tag = function(path){ }; exports.feed_tag = function(path, options){ - var config = this.config || hexo.config; + var config = this.config; options = _.extend({ title: config.title, diff --git a/lib/plugins/helper/tagcloud.js b/lib/plugins/helper/tagcloud.js index 5b686b0be6..55bbec35bf 100644 --- a/lib/plugins/helper/tagcloud.js +++ b/lib/plugins/helper/tagcloud.js @@ -1,9 +1,9 @@ var _ = require('lodash'); // https://github.com/imathis/hsl-picker/blob/master/assets/javascripts/modules/color.coffee -var rHex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i, - rRgb = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,?\s*(0?\.?\d+)?\s*\)$/, - rHsl = /hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,?\s*(0?\.?\d+)?\s*\)$/; +var rHex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i; +var rRgb = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,?\s*(0?\.?\d+)?\s*\)$/; +var rHsl = /hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,?\s*(0?\.?\d+)?\s*\)$/; // http://www.w3.org/TR/css3-color/#svg-color var colorNames = { @@ -156,32 +156,27 @@ var colorNames = { yellowgreen: {r: 154, g: 205, b: 50, a: 1} }; -var parseColor = function(str){ +function parseColor(str){ str = str.toLowerCase(); - var color = {}, - valid = true, - r = '', - g = '', - b = '', - match; + var color = {}; + var valid = true; + var match; if (rHex.test(str)){ var txt = str.match(rHex)[1]; + var code = parseInt(txt, 16); - if (txt.length == 6){ - r = txt.substr(0, 2); - g = txt.substr(2, 2); - b = txt.substr(4, 2); + if (txt.length == 3){ + color.r = ((code & 0xF00) >> 8) * 17; + color.g = ((code & 0xF0) >> 4) * 17; + color.b = (code & 0xF) * 17; } else { - r = txt[0] + txt[0]; - g = txt[1] + txt[1]; - b = txt[2] + txt[2]; + color.r = (code & 0xFF0000) >> 16; + color.g = (code & 0xFF00) >> 8; + color.b = code & 0xFF; } - color.r = parseInt(r, 16); - color.g = parseInt(g, 16); - color.b = parseInt(b, 16); color.a = 1; } else if (rRgb.test(str)){ match = str.match(rRgb); @@ -198,12 +193,12 @@ var parseColor = function(str){ l = +match[3] / 100; if (s > 0){ - var q = l < 0.5 ? l * (1 + s) : l + s - (l * s), - p = 2 * l - q; + var q = l < 0.5 ? l * (1 + s) : l + s - (l * s); + var p = 2 * l - q; - var rt = h + 1 / 3, - gt = h, - bt = h - 1 / 3; + var rt = h + 1 / 3; + var gt = h; + var bt = h - 1 / 3; color.r = Math.round(hueToRgb(p, q, rt) * 255); color.g = Math.round(hueToRgb(p, q, gt) * 255); @@ -219,16 +214,20 @@ var parseColor = function(str){ return; } - ['r', 'g', 'b'].forEach(function(i){ - if (color[i] > 255 || color[i] < 0) valid = false; - }); + validateColor(color.r); + validateColor(color.g); + validateColor(color.b); if (color.a > 1 || color.a < 0) valid = false; if (valid) return color; -}; +} + +function validateColor(code){ + return code >= 0 && code <= 255; +} -var hueToRgb = function(p, q, h){ +function hueToRgb(p, q, h){ if (h < 0) h++; if (h > 1) h--; @@ -241,21 +240,21 @@ var hueToRgb = function(p, q, h){ } else { return p; } -}; +} -var rgbToHex = function(color){ - var hex = '#'; +function rgbToHex(color){ + return '#' + + rgbToHexCode(color.r) + + rgbToHexCode(color.g) + + rgbToHexCode(color.b); +} - ['r', 'g', 'b'].forEach(function(i){ - var code = color[i].toString(16); - - hex += code.length == 1 ? code + code : code; - }); +function rgbToHexCode(color){ + var code = color.toString(16); + return code.length === 1 ? code + code : code; +} - return hex; -}; - -var midColor = function(a, b, ratio){ +function midColor(a, b, ratio){ var color = {}; ['r', 'g', 'b'].forEach(function(i){ @@ -265,7 +264,7 @@ var midColor = function(a, b, ratio){ color.a = a.a + (b.a - a.a) * ratio; return color; -}; +} module.exports = function(tags, options){ if (!options){ @@ -287,18 +286,18 @@ module.exports = function(tags, options){ order: 1 }, options); - var min = options.min_font, - max = options.max_font, - orderby = options.orderby, - order = options.order, - unit = options.unit, - color = options.color, - tagColor = '', - self = this; + var min = options.min_font; + var max = options.max_font; + var orderby = options.orderby; + var order = options.order; + var unit = options.unit; + var color = options.color; + var tagColor = ''; + var self = this; if (color){ - var startColor = parseColor(options.start_color), - endColor = parseColor(options.end_color); + var startColor = parseColor(options.start_color); + var endColor = parseColor(options.end_color); if (!startColor || !endColor) color = false; } @@ -321,12 +320,12 @@ module.exports = function(tags, options){ sizes.push(length); }); - var length = sizes.length, - result = ''; + var length = sizes.length; + var result = ''; tags.each(function(tag){ - var index = sizes.indexOf(tag.length), - size = min + (max - min) / (length - 1) * index; + var index = sizes.indexOf(tag.length); + var size = min + (max - min) / (length - 1) * index; if (color){ var mid = midColor(startColor, endColor, index / (length - 1)); diff --git a/lib/plugins/helper/toc.js b/lib/plugins/helper/toc.js index e410dae1ce..c8b6376633 100644 --- a/lib/plugins/helper/toc.js +++ b/lib/plugins/helper/toc.js @@ -1,6 +1,5 @@ -var _ = require('lodash'), - cheerio = require('cheerio'), - util = require('../../util'); +var _ = require('lodash'); +var cheerio = require('cheerio'); module.exports = function(str, options){ options = _.extend({ @@ -13,13 +12,13 @@ module.exports = function(str, options){ if (!headings.length) return ''; - var className = options.class, - listNumber = options.list_number, - result = '<ol class="' + options.class + '">', - lastNumber = [0, 0, 0, 0, 0, 0], - firstLevel = 0, - lastLevel = 0, - i = 0; + var className = options.class; + var listNumber = options.list_number; + var result = '<ol class="' + options.class + '">'; + var lastNumber = [0, 0, 0, 0, 0, 0]; + var firstLevel = 0; + var lastLevel = 0; + var i = 0; headings.each(function(){ var level = +this.name[1], diff --git a/lib/plugins/helper/url.js b/lib/plugins/helper/url.js index cb64d5fa6c..4f56503ebf 100644 --- a/lib/plugins/helper/url.js +++ b/lib/plugins/helper/url.js @@ -1,9 +1,9 @@ var url = require('url'); -var trimArr = function(arr){ - var start = 0, - length = arr.length, - end = length - 1; +function trimArr(arr){ + var start = 0; + var length = arr.length; + var end = length - 1; for (; start < length; start++){ if (arr[start] !== '') break; @@ -14,17 +14,17 @@ var trimArr = function(arr){ } return arr.slice(start, end + 1); -}; +} exports.relative_url = function(from, to){ from = from || ''; to = to || ''; - var fromParts = trimArr(from.split('/')), - toParts = trimArr(to.split('/')); + var fromParts = trimArr(from.split('/')); + var toParts = trimArr(to.split('/')); - var length = Math.min(fromParts.length, toParts.length), - samePartsLength = 0; + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = 0; for (; samePartsLength < length; samePartsLength++){ if (fromParts[samePartsLength] !== toParts[samePartsLength]) break; @@ -42,9 +42,9 @@ exports.relative_url = function(from, to){ exports.url_for = function(path){ path = path || '/'; - var config = this.config || hexo.config, - root = config.root, - data = url.parse(path); + var config = this.config; + var root = config.root; + var data = url.parse(path); if (!data.protocol && path.substring(0, 2) !== '//'){ if (config.relative_link){ diff --git a/lib/plugins/tag/blockquote.js b/lib/plugins/tag/blockquote.js index 2882bd8889..d823b74172 100644 --- a/lib/plugins/tag/blockquote.js +++ b/lib/plugins/tag/blockquote.js @@ -1,12 +1,12 @@ // Based on: https://raw.github.com/imathis/octopress/master/plugins/blockquote.rb -var util = require('../../util'), - titlecase = util.titlecase; +var util = require('../../util'); +var titlecase = util.titlecase; -var rFullCiteWithTitle = /(\S.*)\s+(https?:\/\/)(\S+)\s+(.+)/i, - rFullCite = /(\S.*)\s+(https?:\/\/)(\S+)/i, - rAuthorTitle = /([^,]+),\s*([^,]+)/, - rAuthor = /(.+)/; +var rFullCiteWithTitle = /(\S.*)\s+(https?:\/\/)(\S+)\s+(.+)/i; +var rFullCite = /(\S.*)\s+(https?:\/\/)(\S+)/i; +var rAuthorTitle = /([^,]+),\s*([^,]+)/; +var rAuthor = /(.+)/; /** * Blockquote tag @@ -17,63 +17,65 @@ var rFullCiteWithTitle = /(\S.*)\s+(https?:\/\/)(\S+)\s+(.+)/i, * {% endblockquote %} */ -module.exports = function(args, content){ - var str = args.join(' '), - author = '', - source = '', - title = '', - footer = '', - match; +module.exports = function(ctx){ + return function(args, content){ + var str = args.join(' '); + var author = ''; + var source = ''; + var title = ''; + var footer = ''; + var match; - if (str){ - if (rFullCiteWithTitle.test(str)){ - match = str.match(rFullCiteWithTitle); - author = match[1]; - source = match[2] + match[3]; - title = hexo.config.titlecase ? titlecase(match[4]) : match[4]; - } else if (rFullCite.test(str)){ - match = str.match(rFullCite); - author = match[1]; - source = match[2] + match[3]; - } else if (rAuthorTitle.test(str)){ - match = str.match(rAuthorTitle); - author = match[1]; - title = hexo.config.titlecase ? titlecase(match[2]) : match[2]; - } else if (rAuthor.test(str)){ - match = str.match(rAuthor); - author = match[1]; - } + if (str){ + if (rFullCiteWithTitle.test(str)){ + match = str.match(rFullCiteWithTitle); + author = match[1]; + source = match[2] + match[3]; + title = ctx.config.titlecase ? titlecase(match[4]) : match[4]; + } else if (rFullCite.test(str)){ + match = str.match(rFullCite); + author = match[1]; + source = match[2] + match[3]; + } else if (rAuthorTitle.test(str)){ + match = str.match(rAuthorTitle); + author = match[1]; + title = ctx.config.titlecase ? titlecase(match[2]) : match[2]; + } else if (rAuthor.test(str)){ + match = str.match(rAuthor); + author = match[1]; + } - if (author) footer += '<strong>' + author + '</strong>'; + if (author) footer += '<strong>' + author + '</strong>'; - if (source){ - var url = source.match(/https?:\/\/(.+)/)[1], - parts = url.split('/'), - link = ''; + if (source){ + var url = source.match(/https?:\/\/(.+)/)[1], + parts = url.split('/'), + link = ''; - for (var i = 0, len = parts.length; i < len; i++){ - var nextLink = link + parts[i]; + for (var i = 0, len = parts.length; i < len; i++){ + var nextLink = link + parts[i]; - if (nextLink.length < 32){ - link = nextLink + '/'; - } else { - break; + if (nextLink.length < 32){ + link = nextLink + '/'; + } else { + break; + } } - } - if (url.replace(/\/?$/, '/') !== link) link += '…'; + if (url.replace(/\/?$/, '/') !== link) link += '…'; - footer += '<cite><a href="' + source + '">' + (title ? title : link) + '</a></cite>'; - } else if (title){ - footer += '<cite>' + title + '</cite>'; + footer += '<cite><a href="' + source + '">' + (title ? title : link) + '</a></cite>'; + } else if (title){ + footer += '<cite>' + title + '</cite>'; + } } - } - var out = ''; + var out = ''; - out += '<escape><blockquote></escape>\n\n'; - out += content + '\n\n'; - out += '<escape>' + (footer ? '<footer>' + footer + '</footer>' : '') + '</blockquote></escape>\n'; + out += '<escape><blockquote></escape>\n\n'; + out += content + '\n\n'; + out += '<escape>' + (footer ? '<footer>' + footer + '</footer>' : '') + '</blockquote></escape>\n'; - return out; + return out; + }; }; \ No newline at end of file diff --git a/lib/plugins/tag/code.js b/lib/plugins/tag/code.js index ed9a1d42ba..b817be16e0 100644 --- a/lib/plugins/tag/code.js +++ b/lib/plugins/tag/code.js @@ -1,7 +1,7 @@ // Based on: https://raw.github.com/imathis/octopress/master/plugins/code_block.rb -var util = require('../../util'), - highlight = util.highlight; +var util = require('../../util'); +var highlight = util.highlight; var rCaptionUrlTitle = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)\s+(.+)/i, rCaptionUrl = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)/i, @@ -17,33 +17,35 @@ var rCaptionUrlTitle = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)\s+(.+)/i, * {% endcodeblock %} */ -module.exports = function(args, content){ - var arg = args.join(' '), - config = hexo.config.highlight || {}, - caption = '', - lang = '', - match; +module.exports = function(ctx){ + return function(args, content){ + var arg = args.join(' '), + config = ctx.config.highlight || {}, + caption = '', + lang = '', + match; - if (rLang.test(arg)){ - lang = arg.match(rLang)[1]; - arg = arg.replace(/lang:\w+/i, ''); - } + if (rLang.test(arg)){ + lang = arg.match(rLang)[1]; + arg = arg.replace(/lang:\w+/i, ''); + } - if (rCaptionUrlTitle.test(arg)){ - match = arg.match(rCaptionUrlTitle); - caption = '<span>' + match[1] + '</span><a href="' + match[2] + match[3] + '">' + match[4] + '</a>'; - } else if (rCaptionUrl.test(arg)){ - match = arg.match(rCaptionUrl); - caption = '<span>' + match[1] + '</span><a href="' + match[2] + match[3] + '">link</a>'; - } else if (rCaption.test(arg)){ - match = arg.match(rCaption); - caption = '<span>' + match[1] + '</span>'; - } + if (rCaptionUrlTitle.test(arg)){ + match = arg.match(rCaptionUrlTitle); + caption = '<span>' + match[1] + '</span><a href="' + match[2] + match[3] + '">' + match[4] + '</a>'; + } else if (rCaptionUrl.test(arg)){ + match = arg.match(rCaptionUrl); + caption = '<span>' + match[1] + '</span><a href="' + match[2] + match[3] + '">link</a>'; + } else if (rCaption.test(arg)){ + match = arg.match(rCaption); + caption = '<span>' + match[1] + '</span>'; + } - return highlight(content.replace(/\n+$/, ''), { - lang: lang, - caption: caption, - gutter: config.line_number, - tab: config.tab_replace - }); + return highlight(content.replace(/\n+$/, ''), { + lang: lang, + caption: caption, + gutter: config.line_number, + tab: config.tab_replace + }); + }; }; \ No newline at end of file diff --git a/lib/plugins/tag/include_code.js b/lib/plugins/tag/include_code.js index 7900cda668..fe01ffca7e 100644 --- a/lib/plugins/tag/include_code.js +++ b/lib/plugins/tag/include_code.js @@ -14,51 +14,54 @@ var rCaptionTitleFile = /(.*)?(\s+|^)(\/*\S+)/i, * {% include_code [title] [lang:language] path/to/file %} */ -module.exports = function(args, callback){ - var codeDir = hexo.config.code_dir, - sourceDir = hexo.source_dir, - config = hexo.config.highlight || {}, - arg = args.join(' '), - path = '', - title = '', - lang = ''; +module.exports = function(ctx){ + return function(args){ + var codeDir = ctx.config.code_dir, + sourceDir = ctx.source_dir, + config = ctx.config.highlight || {}, + arg = args.join(' '), + path = '', + title = '', + lang = '', + caption = ''; - // Suffix code folder - if (codeDir[codeDir.length - 1] !== '/') codeDir += '/'; + // Suffix code folder + if (codeDir[codeDir.length - 1] !== '/') codeDir += '/'; - if (rLang.test(arg)){ - lang = arg.match(rLang)[1]; - arg = arg.replace(/lang:\w+/i, ''); - } + if (rLang.test(arg)){ + lang = arg.match(rLang)[1]; + arg = arg.replace(/lang:\w+/i, ''); + } - if (rCaptionTitleFile.test(arg)){ - var match = arg.match(rCaptionTitleFile); - title = match[1]; - path = match[3]; - } + if (rCaptionTitleFile.test(arg)){ + var match = arg.match(rCaptionTitleFile); + title = match[1]; + path = match[3]; + } - // Exit if path is not defined - if (!path) return; + // Exit if path is not defined + if (!path) return; - var local = pathFn.join(sourceDir, codeDir, path); + var local = pathFn.join(sourceDir, codeDir, path); - // Exit if the source file doesn't exist - if (!fs.existsSync(local)) return; + // Exit if the source file doesn't exist + if (!fs.existsSync(local)) return; - var code = file.readFileSync(local).replace(/\n$/, ''); + var code = file.readFileSync(local).replace(/\n$/, ''); - // If the title is not defined, use file name instead - title = title || pathFn.basename(path); + // If the title is not defined, use file name instead + title = title || pathFn.basename(path); - // If the language is not defined, use file extension instead - lang = lang || pathFn.extname(path).substring(1); + // If the language is not defined, use file extension instead + lang = lang || pathFn.extname(path).substring(1); - caption = '<span>' + title + '</span><a href="/' + codeDir + path + '">download</a>'; + caption = '<span>' + title + '</span><a href="/' + codeDir + path + '">download</a>'; - return highlight(code, { - lang: lang, - caption: caption, - gutter: config.line_number, - tab: config.tab_replace - }); + return highlight(code, { + lang: lang, + caption: caption, + gutter: config.line_number, + tab: config.tab_replace + }); + }; }; diff --git a/lib/plugins/tag/index.js b/lib/plugins/tag/index.js index f81fab784b..3018f5720a 100644 --- a/lib/plugins/tag/index.js +++ b/lib/plugins/tag/index.js @@ -1,13 +1,12 @@ -/* module.exports = function(ctx){ var tag = ctx.extend.tag; - var blockquote = require('./blockquote'); + var blockquote = require('./blockquote')(ctx); tag.register('quote', blockquote, {ends: true, escape: false}); tag.register('blockquote', blockquote, {ends: true, escape: false}); - var code = require('./code'); + var code = require('./code')(ctx); tag.register('code', code, true); tag.register('codeblock', code, true); @@ -21,7 +20,7 @@ module.exports = function(ctx){ tag.register('img', img); tag.register('image', img); - var include_code = require('./include_code'); + var include_code = require('./include_code')(ctx); tag.register('include_code', include_code); tag.register('include-code', include_code); @@ -41,8 +40,4 @@ module.exports = function(ctx){ tag.register('vimeo', require('./vimeo')); tag.register('youtube', require('./youtube')); -};*/ - -module.exports = function(ctx){ - // }; \ No newline at end of file diff --git a/lib/theme/index.js b/lib/theme/index.js new file mode 100644 index 0000000000..875ee16e6f --- /dev/null +++ b/lib/theme/index.js @@ -0,0 +1,140 @@ +var Promise = require('bluebird'); +var pathFn = require('path'); +var _ = require('lodash'); +var util = require('../util'); +var Box = require('../Box'); +var View = require('./view'); + +var i18n = util.i18n; + +require('colors'); + +function Theme(ctx){ + Box.call(this, ctx, ctx.config.theme_dir); + + this.config = {}; + + this.i18n = new i18n({ + code: ctx.config.language + }); + + this.views = {}; + + this.processors = [ + require('./processors/config'), + require('./processors/i18n'), + require('./processors/view'), + require('./processors/source') + ]; + + var _View = this.View = function(path){ + View.call(this, path); + }; + + util.inherits(_View, View); + + _View.prototype.theme = this; + _View.prototype.context = ctx; +} + +util.inherits(Theme, Box); + +Theme.prototype._generate = function(options){ + options = _.extend({ + watch: false + }, options); + + var ctx = this.context; + var config = ctx.config; + var siteLocals = ctx.locals; + var i18n = this.i18n; + + var generators = ctx.extend.generator.list(); + var generatorKeys = Object.keys(generators); + var excludeGenerator = config.exclude_generator || []; + + function Locals(path, locals){ + this.page = _.extend({ + path: path, + lang: ensureLanguage(this) // every page should have a 'lang' value + }, locals); + + this.path = path; + this.url = config.url + config.root + path; + } + + Locals.prototype.site = siteLocals; + Locals.prototype.config = config; + Locals.prototype.theme = _.extend({}, config, this.config, config.theme_config); + Locals.prototype._ = _; + Locals.prototype.__ = i18n.__(); + Locals.prototype._p = i18n._p(); + Locals.prototype.layout = 'layout'; + Locals.prototype.cache = !options.watch; + Locals.prototype.env = ctx.env; + Locals.prototype.view_dir = pathFn.join(ctx.theme_dir, 'layouts') + pathFn.sep; + + function generateRender(path, layouts, locals){ + if (!Array.isArray(layouts)) layouts = [layouts]; + layouts = _.uniq(layouts); + } + + return Promise.filter(generatorKeys, function(key){ + return !~excludeGenerator.indexOf(key); + }).map(function(key){ + var generator = generators[key]; + var start = Date.now(); + + return generator.call(ctx, siteLocals, generateRender).then(function(){ + ctx.log.debug('Generator: %s in ' + '%dms'.cyan, key.magenta, Date.now() - start); + }); + }); +}; + +function ensureLanguage(locals){ + var page = locals.page; + var lang = page.lang; + var config = locals.config; + + if (lang) return lang; + if (!config.language) return 'default'; + if (!Array.isArray(config.language)) return config.language; + + var path = locals.path; + if (path[0] !== '/') path = '/' + path; + + // TODO: Optimize language query +} + +Theme.prototype.generate = function(options, callback){ + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; + } + + var ctx = this.context; + + return Promise.all([ + ctx.database.save(), + this._generate() + ]).nodeify(callback); +}; + +Theme.prototype.getView = function(path){ + // Replace backslashes on Windows + path = path.replace(/\\/g, '/'); + + var extname = pathFn.extname(path); + var name = path.substring(0, path.length - extname.length); + var views = this.views[name]; + + if (!views) return; + + if (extname){ + return views[extname]; + } else { + return views[Object.keys(views)[0]]; + } +}; + +module.exports = Theme; \ No newline at end of file diff --git a/lib/theme/processors/config.js b/lib/theme/processors/config.js new file mode 100644 index 0000000000..17f348e798 --- /dev/null +++ b/lib/theme/processors/config.js @@ -0,0 +1,20 @@ +var Pattern = require('../../box/pattern'); + +exports.process = function(data){ + if (data.type === 'delete'){ + this.config = {}; + return; + } + + var ctx = this.context; + + return data.render().then(function(result){ + data.box.config = result; + ctx.log.debug('Theme config loaded.'); + }, function(err){ + ctx.log.error('Theme config load failed.'); + throw err; + }); +}; + +exports.pattern = new Pattern(/^_config\.\w+$/); \ No newline at end of file diff --git a/lib/theme/processors/i18n.js b/lib/theme/processors/i18n.js new file mode 100644 index 0000000000..5c8be782f4 --- /dev/null +++ b/lib/theme/processors/i18n.js @@ -0,0 +1,20 @@ +var pathFn = require('path'); +var Pattern = require('../../box/pattern'); + +exports.process = function(data){ + var path = data.params.path; + var extname = pathFn.extname(path); + var name = pathFn.substring(0, path.length - extname.length); + var i18n = this.i18n; + + if (data.type === 'delete'){ + i18n.remove(name); + return; + } + + return data.render().then(function(result){ + i18n.set(name, result); + }); +}; + +exports.pattern = new Pattern('languages/*path'); \ No newline at end of file diff --git a/lib/theme/processors/source.js b/lib/theme/processors/source.js new file mode 100644 index 0000000000..8d0a0323e0 --- /dev/null +++ b/lib/theme/processors/source.js @@ -0,0 +1,16 @@ +var Pattern = require('../../box/pattern'); + +var rHiddenFile = /^_|\/_/; + +exports.process = function(data){ + // +}; + +exports.pattern = new Pattern(function(path){ + if (path.substring(0, 6) !== 'source') return; + + path = path.substring(7); + if (rHiddenFile.test(path)) return; + + return {path: path}; +}); \ No newline at end of file diff --git a/lib/theme/processors/view.js b/lib/theme/processors/view.js new file mode 100644 index 0000000000..1b607b97c5 --- /dev/null +++ b/lib/theme/processors/view.js @@ -0,0 +1,7 @@ +var Pattern = require('../../box/pattern'); + +exports.process = function(data){ + // +}; + +exports.pattern = new Pattern('layout/*path'); \ No newline at end of file diff --git a/lib/theme/view.js b/lib/theme/view.js new file mode 100644 index 0000000000..fdb04a3cae --- /dev/null +++ b/lib/theme/view.js @@ -0,0 +1,129 @@ +var pathFn = require('path'); +var _ = require('lodash'); +var util = require('../util'); + +var yfm = util.yfm; + +function View(path, data){ + var ctx = this.context; + + this.path = path; + this.source = pathFn.join(this.theme.base, path); + this.extname = pathFn.extname(path); + this.render = ctx.render; + this.helper = ctx.extend.helper; + this.data = typeof data === 'string' ? yfm(data) : data; +} + +View.prototype.render = function(options, callback){ + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; + } + + options = options || {}; + + var data = this.data; + var layout = data.hasOwnProperty('layout') ? data.layout : options.layout; + var locals = this._buildLocals(options); + var self = this; + + return this.render.render({ + path: this.source, + text: data._content + }, this._bindHelpers(locals)).then(function(result){ + if (!layout) return result; + + var layoutView = self._resolveLayout(layout); + if (!layoutView) return result; + + var layoutLocals = _.clone(locals); + layoutLocals.body = result; + layoutLocals.layout = false; + + return layoutView.render(layoutLocals, callback); + }).nodeify(callback); +}; + +View.prototype.renderSync = function(options){ + options = options || {}; + + var data = this.data; + var layout = data.hasOwnProperty('layout') ? data.layout : options.layout; + var locals = this._buildLocals(options); + + var result = this.render.renderSync({ + path: this.source, + text: data._content + }, this._bindHelpers(locals)); + + if (result == null || !layout) return result; + + var layoutView = this._resolveLayout(layout); + if (!layoutView) return result; + + var layoutLocals = _.clone(locals); + layoutLocals.body = result; + layoutLocals.layout = false; + + return layoutView.renderSync(layoutLocals); +}; + +View.prototype._buildLocals = function(locals){ + var data = this.data; + var result = {}; + var keys = []; + var key = ''; + var i, len; + + // Assign locals + keys = Object.keys(locals); + + for (i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + result[key] = locals[key]; + } + + // Bind view data + keys = Object.keys(data); + + for (i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + if (key !== 'layout' && key !== '_content') result[key] = data[key]; + } + + // Bind view source + result.filename = this.source; + + return result; +}; + +View.prototype._bindHelpers = function(locals){ + var helpers = this.helper.list(); + var keys = Object.keys(helpers); + var result = {}; + var key = ''; + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + result[key] = helpers[key].bind(result); + } + + _.extend(result, locals); + + return result; +}; + +View.prototype._resolveLayout = function(name){ + // Relative path + var layoutPath = pathFn.join(pathFn.dirname(this.path), name); + var layoutView = this.theme.getView(layoutPath); + + if (layoutView && layoutView.source !== this.source) return layoutView; + + // Absolute path + layoutView = this.theme.getView(name); + if (layoutView && layoutView.source !== this.source) return layoutView; +}; + +module.exports = View; \ No newline at end of file diff --git a/lib/util/escape.js b/lib/util/escape.js index 7589a86169..a6428d6d80 100644 --- a/lib/util/escape.js +++ b/lib/util/escape.js @@ -19,9 +19,9 @@ * @static */ -var rFilenameEscape = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g, - rContinuesDash = /-{1,}/g, - rTailDash = /-+$/; +var rFilenameEscape = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g; +var rContinuesDash = /-{1,}/g; +var rTailDash = /-+$/; var EOL = require('os').EOL, rEOL = new RegExp(EOL, 'g'); @@ -190,6 +190,7 @@ var diacriticsMap = {}; for (var i = 0; i < defaultDiacriticsRemovalap.length; i++){ var letters = defaultDiacriticsRemovalap[i].letters.split(''); + for (var j = 0; j < letters.length ; j++){ diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base; } diff --git a/lib/util/file.js b/lib/util/file.js deleted file mode 100644 index 2a869cc40c..0000000000 --- a/lib/util/file.js +++ /dev/null @@ -1,282 +0,0 @@ -/** -* File utilities. -* -* @class file -* @namespace util -* @deprecated Use hexo.util.file2 or hexo.file instead. -* @module hexo -* @static -*/ - -var fs = require('graceful-fs'), - path = require('path'), - async = require('async'), - _ = require('lodash'), - EOL = require('os').EOL, - EOLre = new RegExp(EOL, 'g'); - -if (!fs.exists || !fs.existsSync){ - fs.exists = path.exists; - fs.existsSync = path.existsSync; -} - -/** -* Creates a directory. -* -* @method mkdir -* @param {String} destination -* @param {Function} callback -* @async -* @static -*/ - -var mkdir = exports.mkdir = function(destination, callback){ - var parent = path.dirname(destination); - - fs.exists(parent, function(exist){ - if (exist){ - fs.mkdir(destination, callback); - } else { - mkdir(parent, function(){ - fs.mkdir(destination, callback); - }); - } - }); -}; - -/** -* Writes a file. -* -* @method writeFile -* @param {String} destination -* @param {String} content -* @param {Function} callback -* @private -* @async -* @static -*/ - -var writeFile = function(destination, content, callback) { - fs.open(destination, 'w', function(err, fd) { - if (err) callback(err); - fs.write(fd, content, 0, 'utf8', function(err, written, buffer) { - callback(err); - }); - }); -}; - -/** -* Writes a file. -* -* @method write -* @param {String} destination -* @param {String} content -* @param {Function} callback -* @async -* @static -*/ - -var write = exports.write = function(destination, content, callback){ - var parent = path.dirname(destination); - - fs.exists(parent, function(exist){ - if (exist){ - fs.writeFile(destination, content, callback); - } else { - mkdir(parent, function(){ - fs.writeFile(destination, content, callback); - }); - } - }); -}; - -/** -* Copies a file from `source` to `destination`. -* -* @method copy -* @param {String} source -* @param {String} destination -* @param {Function} callback -* @async -* @static -*/ - -var copy = exports.copy = function(source, destination, callback){ - var parent = path.dirname(destination); - - async.series([ - function(next){ - fs.exists(parent, function(exist){ - if (exist) next(); - else mkdir(parent, next); - }); - } - ], function(){ - var rs = fs.createReadStream(source), - ws = fs.createWriteStream(destination); - - rs.pipe(ws) - .on('error', function(err){ - if (err) throw err; - }); - - ws.on('close', callback) - .on('error', function(err){ - if (err) throw err; - }); - }); -}; - -/** -* Returns a list of files in the directory. -* -* @method dir -* @param {String} source -* @param {Function} callback -* @param {String} [parent] -* @async -* @static -*/ - -var dir = exports.dir = function(source, callback, parent){ - fs.exists(source, function(exist){ - if (exist){ - fs.readdir(source, function(err, files){ - if (err) throw err; - - var result = []; - - async.each(files, function(item, next){ - fs.stat(source + '/' + item, function(err, stats){ - if (err) throw err; - - if (stats.isDirectory()){ - dir(source + '/' + item, function(children){ - result = result.concat(children); - next(); - }, (parent ? parent + '/' : '') + item); - } else { - result.push((parent ? parent + '/' : '') + item); - next(); - } - }); - }, function(){ - callback(result); - }); - }); - } else { - callback([]); - } - }); -}; - -var textProcess = function(str){ - // Transform EOL - if (EOL !== '\n') str = str.replace(EOLre, '\n'); - - // Remove UTF BOM - str = str.replace(/^\uFEFF/, ''); - - return str; -}; - -/** -* Reads a file. -* -* @method read -* @param {String} source -* @param {Function} callback -* @async -* @static -*/ - -var read = exports.read = function(source, callback){ - fs.exists(source, function(exist){ - if (exist){ - fs.readFile(source, 'utf8', function(err, result){ - if (err) return callback(err); - callback(null, textProcess(result)); - }); - } else { - callback(null); - } - }); -}; - -/** -* Reads a file synchronizedly. -* -* @method readSync -* @param {String} source -* @static -*/ - -var readSync = exports.readSync = function(source){ - var result = fs.readFileSync(source, 'utf8'); - if (result){ - return textProcess(result); - } -}; - -/** -* Cleans a directory. -* -* @method empty -* @param {String} target -* @param {Array} exception -* @param {Function} callback -* @async -* @static -*/ - -var empty = exports.empty = function(target, exception, callback){ - if (_.isFunction(exception)){ - callback = exception; - exception = []; - } - - if (!Array.isArray(exception)) exception = []; - if (target.substr(target.length - 1, 1) !== '/') target += '/'; - - var arr = [], - exclude = {}; - - exception.forEach(function(item){ - var split = item.split('/'), - front = split.shift(); - - arr.push(front); - if (!exclude[front]) exclude[front] = []; - if (split.length) exclude[front].push(split.join('/')); - }); - - fs.readdir(target, function(err, files){ - if (err) throw err; - - files = _.filter(files, function(item){ - return item.substr(0, 1) !== '.'; - }); - - async.each(files, function(item, next){ - var dest = target + item; - - fs.stat(dest, function(err, stats){ - if (err) throw err; - - if (stats.isDirectory()){ - empty(dest, exclude[item], function(){ - fs.readdir(dest, function(err, files){ - if (err) throw err; - - if (files.length === 0) fs.rmdir(dest, next); - else next(); - }); - }); - } else { - if (arr.indexOf(item) === -1) fs.unlink(dest, next); - else next(); - } - }); - }, callback); - }); -}; diff --git a/lib/util/file2.js b/lib/util/file2.js deleted file mode 100644 index 35144e8420..0000000000 --- a/lib/util/file2.js +++ /dev/null @@ -1,569 +0,0 @@ -/** -* File utilities. -* -* @class file2 -* @namespace util -* @module hexo -* @static -*/ - -var fs = require('graceful-fs'), - pathFn = require('path'), - async = require('async'), - _ = require('lodash'), - chokidar = require('chokidar'), - EOL = require('os').EOL, - EOLre = new RegExp(EOL, 'g'); - -if (!fs.exists || !fs.existsSync){ - fs.exists = pathFn.exists; - fs.existsSync = pathFn.existsSync; -} - -var join = function(parent, child){ - return parent ? pathFn.join(parent, child) : child; -}; - -/** -* Creates a directory recursively. -* -* @method mkdirs -* @param {String} path -* @param {Function} callback -* @async -* @static -*/ - -var mkdirs = exports.mkdirs = function(path, callback){ - var parent = pathFn.dirname(path); - - fs.exists(parent, function(exist){ - if (exist){ - fs.mkdir(path, callback); - } else { - mkdirs(parent, function(){ - fs.mkdir(path, callback); - }); - } - }); -}; - -var checkParent = function(path, callback){ - var parent = pathFn.dirname(path); - - fs.exists(parent, function(exist){ - if (exist) return callback(); - - mkdirs(parent, function(err){ - if (err && err.code === 'EEXIST') return callback(); - - callback(err); - }); - }); -}; - -/** -* Writes a file. -* -* @method writeFile -* @param {String} path -* @param {String|Buffer} data -* @param {Object} [options] -* @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. -* @param {Function} callback -* @async -* @static -*/ - -var writeFile = exports.writeFile = function(path, data, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - checkParent: true - }, options); - - async.series([ - // Check parent folder existance - function(next){ - if (!options.checkParent) return next(); - - checkParent(path, next); - } - ], function(err){ - if (err) return callback(err); - - fs.writeFile(path, data, options, callback); - }); -}; - -/** -* Appends a file. Creates the file if not yet exists. -* -* @method appendFile -* @param {String} path -* @param {String|Buffer} data -* @param {Object} [options] -* @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. -* @param {Function} callback -* @async -* @static -*/ - -var appendFile = exports.appendFile = function(path, data, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - checkParent: true - }, options); - - async.series([ - // Check parent folder existance - function(next){ - if (!options.checkParent) return next(); - - checkParent(path, next); - } - ], function(err){ - if (err) return callback(err); - - fs.appendFile(path, data, options, callback); - }); -}; - -/** -* Copies a file from `src` to `dest`. -* -* @method copyFile -* @param {String} src -* @param {String} dest -* @param {Object} [options] -* @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. -* @param {Function} callback -* @async -* @static -*/ - -var copyFile = exports.copyFile = function(src, dest, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - checkParent: true - }, options); - - var called = false; - - async.series([ - // Check parent folder existance - function(next){ - if (!options.checkParent) return next(); - - checkParent(dest, next); - } - ], function(err){ - if (err) return callback(err); - - var rs = fs.createReadStream(src), - ws = fs.createWriteStream(dest); - - rs.pipe(ws) - .on('error', function(err){ - if (err && !called){ - called = true; - callback(err); - } - }); - - ws.on('close', callback) - .on('error', function(err){ - if (err && !called){ - called = true; - callback(err); - } - }); - }); -}; - -/** -* Copies a directory from `src` to `dest`. -* -* @method copyDir -* @param {String} src -* @param {String} dest -* @param {Object} [options] -* @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. -* @param {RegExp} [options.ignorePattern] The file name pattern to ignore. -* @param {Function} callback -* @async -* @static -*/ - -var copyDir = exports.copyDir = function(src, dest, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - ignoreHidden: true, - ignorePattern: null - }, options); - - async.series([ - // Create parent folder - function(next){ - fs.exists(dest, function(exist){ - if (exist) return next(); - - mkdirs(dest, next); - }); - } - ], function(err){ - if (err) return callback(err); - - fs.readdir(src, function(err, files){ - if (err) return callback(err); - - async.each(files, function(item, next){ - if (options.ignoreHidden && item[0] === '.') return next(); - if (options.ignorePattern && options.ignorePattern.test(item)) return next(); - - var childSrc = join(src, item), - childDest = join(dest, item); - - fs.stat(childSrc, function(err, stats){ - if (err) return callback(err); - - if (stats.isDirectory()){ - copyDir(childSrc, childDest, options, next); - } else { - copyFile(childSrc, childDest, {checkParent: false}, next); - } - }); - }, callback); - }); - }); -}; - -/** -* Returns a list of files in the directory. -* -* @method list -* @param {String} path -* @param {Object} [options] -* @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. -* @param {RegExp} [options.ignorePattern] The file path pattern to ignore. -* @param {Function} callback -* @async -* @static -*/ - -var list = exports.list = function(path, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - ignoreHidden: true, - ignorePattern: null - }, options); - - var parent = options._parent; - - fs.readdir(path, function(err, files){ - if (err) return callback(err); - - var arr = []; - - async.each(files, function(item, next){ - if (options.ignoreHidden && item[0] === '.') return next(); - if (options.ignorePattern && options.ignorePattern.test(item)) return next(); - - var childPath = join(path, item); - - fs.stat(childPath, function(err, stats){ - if (err) return callback(err); - - if (stats.isDirectory()){ - var childOptions = _.extend({}, options, { - _parent: parent ? join(parent, item) : item - }); - - list(childPath, childOptions, function(err, files){ - if (err) return callback(err); - - arr = arr.concat(files); - - next(); - }); - } else { - if (parent){ - arr.push(join(parent, item)); - } else { - arr.push(item); - } - - next(); - } - }); - }, function(err){ - callback(err, arr); - }); - }); -}; - -var escape = function(str){ - // Transform EOL - if (EOL !== '\n') str = str.replace(EOLre, '\n'); - - // Remove UTF BOM - str = str.replace(/^\uFEFF/, ''); - - return str; -}; - -/** -* Reads a file. -* -* @method readFile -* @param {String} path -* @param {Object} [options] -* @param {Boolean} [options.escape=true] Transforms EOL & Removes UTF BOM in the file. -* @param {String} [options.encoding=utf8] File encoding. -* @param {Function} callback -* @async -* @static -*/ - -var readFile = exports.readFile = function(path, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - escape: true, - encoding: 'utf8' - }, options); - - fs.readFile(path, options.encoding, function(err, content){ - if (err) return callback(err); - - if (options.escape && options.encoding) content = escape(content); - - callback(null, content); - }); -}; - -/** -* Reads a file synchronizedly. -* -* @method readFileSync -* @param {String} path -* @param {Object} [options] -* @param {Boolean} [options.escape=true] Transforms EOL & Removes UTF BOM in the file. -* @param {String} [options.encoding=utf8] File encoding. -* @static -*/ - -var readFileSync = exports.readFileSync = function(path, options){ - if (!options) options = {}; - - options = _.extend({ - escape: true, - encoding: 'utf8' - }, options); - - var content = fs.readFileSync(path, options.encoding); - if (options.escape && options.encoding) content = escape(content); - - return content; -}; - -/** -* Cleans a directory. -* -* @method emptyDir -* @param {String} path -* @param {Object} [options] -* @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. -* @param {RegExp} [options.ignorePattern] The file name pattern to ignore. -* @param {Array} [options.exclude] The list of file path you don't want to delete. -* @param {Function} callback -* @async -* @static -*/ - -var emptyDir = exports.emptyDir = function(path, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - ignoreHidden: true, - ignorePattern: null, - exclude: [] - }, options); - - var parent = options._parent || '', - arr = []; - - fs.readdir(path, function(err, files){ - if (err) return callback(err); - - async.each(files, function(item, next){ - var itemPath = join(parent, item), - childPath = join(path, item); - - if (options.ignoreHidden && item[0] === '.') return next(); - if (options.ignorePattern && options.ignorePattern.test(itemPath)) return next(); - if (options.exclude && options.exclude.indexOf(itemPath) > -1) return next(); - - fs.stat(childPath, function(err, stats){ - if (err) return callback(err); - - if (stats.isDirectory()){ - emptyDir(childPath, _.extend({}, options, {_parent: itemPath}), function(err, removed){ - if (err) return callback(err); - - arr = arr.concat(removed); - - fs.readdir(childPath, function(err, files){ - if (err) return callback(err); - if (files.length) return next(); - - fs.rmdir(childPath, next); - }); - }); - } else { - arr.push(itemPath); - fs.unlink(childPath, next); - } - }); - }, function(err){ - callback(err, arr); - }); - }); -}; - -/** -* Removes a directory. -* -* @method rmdir -* @param {String} path -* @param {Function} callback -* @async -* @static -*/ - -var rmdir = exports.rmdir = function(path, callback){ - if (typeof callback !== 'function') callback = function(){}; - - fs.readdir(path, function(err, files){ - if (err) return callback(err); - - async.each(files, function(item, next){ - var childPath = join(path, item); - - fs.stat(childPath, function(err, stats){ - if (err) return callback(err); - - if (stats.isDirectory()){ - rmdir(childPath, next); - } else { - fs.unlink(childPath, next); - } - }); - }, function(){ - fs.rmdir(path, callback); - }); - }); -}; - -var _parseWatchType = function(type){ - switch (type){ - case 'add': - return 'create'; - - case 'change': - return 'update'; - - case 'unlink': - return 'delete'; - - default: - return type; - } -}; - -/** -* Watch a directory or a file. -* -* @method watch -* @param {String} path -* @param {Object} [options] See [chokidar](https://github.com/paulmillr/chokidar) -* @param {Function} callback -* @static -*/ - -var watch = exports.watch = function(path, options, callback){ - if (!callback){ - if (typeof options === 'function'){ - callback = options; - options = {}; - } else { - callback = function(){}; - } - } - - options = _.extend({ - persistent: true, - ignoreInitial: true - }, options); - - if (options.ignorePattern) options.ignored = options.ignorePattern; - - var watcher = chokidar.watch(path, options); - - watcher.on('all', function(type, src, stats){ - callback(_parseWatchType(type), src, stats); - }); -}; \ No newline at end of file diff --git a/lib/util/fs.js b/lib/util/fs.js index 50fd3bab03..577ac6f8ce 100644 --- a/lib/util/fs.js +++ b/lib/util/fs.js @@ -109,41 +109,35 @@ function trueFn(){ } function ignoreHiddenFiles(ignore){ - if (ignore){ - return function(item){ - return item[0] !== '.'; - }; - } else { - return trueFn; - } + if (!ignore) return trueFn; + + return function(item){ + return item[0] !== '.'; + }; } function ignoreFilesRegex(regex){ - if (regex){ - return function(item){ - return !regex.test(item); - }; - } else { - return trueFn; - } + if (!regex) return trueFn; + + return function(item){ + return !regex.test(item); + }; } function ignoreExcludeFiles(arr, parent){ - if (arr && arr.length){ - var len = arr.length; + if (!arr || !arr.length) return trueFn; - return function(item){ - var path = join(parent, item); + var len = arr.length; - for (var i = 0; i < len; i++){ - if (arr[i] === path) return false; - } + return function(item){ + var path = join(parent, item); - return true; - }; - } else { - return trueFn; - } + for (var i = 0; i < len; i++){ + if (arr[i] === path) return false; + } + + return true; + }; } function reduceFiles(result, item){ @@ -172,9 +166,8 @@ function copyDir(src, dest, options, parent){ if (stats.isDirectory()){ return copyDir(childSrc, childDest, options); } else { - return copyFile(childSrc, childDest, options).then(function(){ - return join(parent, item); - }); + return copyFile(childSrc, childDest, options) + .thenReturn(join(parent, item)); } }); }).reduce(reduceFiles, []); @@ -280,9 +273,7 @@ function emptyDir(path, options, parent){ }); }); } else { - return unlinkAsync(fullPath).then(function(){ - return item.path; - }); + return unlinkAsync(fullPath).thenReturn(item.path); } }).reduce(reduceFiles, []); } diff --git a/lib/util/index.js b/lib/util/index.js index f485bb0d9f..63612bf5d4 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -4,8 +4,6 @@ var util = require('util'), exports.inherits = util.inherits; exports.escape = require('./escape'); -exports.file = require('./file'); -exports.file2 = require('./file2'); exports.format = require('./format'); exports.fs = require('./fs'); exports.highlight = require('./highlight'); @@ -15,4 +13,5 @@ exports.server = require('./server'); exports.yfm = require('hexo-front-matter'); exports.inflector = inflection; exports.titlecase = inflection.titleize; -exports.Router = require('./router'); \ No newline at end of file +exports.Router = require('./router'); +exports.i18n = require('./i18n'); \ No newline at end of file diff --git a/lib/util/locals.js b/lib/util/locals.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/util/permalink.js b/lib/util/permalink.js index a6610bc851..5c4f6373de 100644 --- a/lib/util/permalink.js +++ b/lib/util/permalink.js @@ -1,5 +1,5 @@ -var _ = require('lodash'), - escape = require('./escape'); +var _ = require('lodash'); +var escape = require('./escape'); var rParam = /:(\w+)/g; @@ -14,13 +14,13 @@ var rParam = /:(\w+)/g; * @constructor * @module hexo */ -var Permalink = module.exports = function(rule, options){ +function Permalink(rule, options){ options = _.extend({ segments: {} }, options); - var segments = options.segments, - params = []; + var segments = options.segments; + var params = []; var regex = escape.regex(rule) .replace(rParam, function(match, name){ @@ -62,7 +62,7 @@ var Permalink = module.exports = function(rule, options){ * @type Array */ this.params = params; -}; +} /** * Tests if the string matches the permalink. @@ -115,4 +115,6 @@ Permalink.parse = function(rule, str){ Permalink.stringify = function(rule, data){ return new Permalink(rule).stringify(data); -}; \ No newline at end of file +}; + +module.exports = Permalink; \ No newline at end of file diff --git a/lib/util/router.js b/lib/util/router.js index e5b413e9e0..176644b7b9 100644 --- a/lib/util/router.js +++ b/lib/util/router.js @@ -1,4 +1,5 @@ var EventEmitter = require('events').EventEmitter; +var util = require('util'); /** * This module is used to manage routes. @@ -9,16 +10,18 @@ var EventEmitter = require('events').EventEmitter; * @module hexo */ -var Router = module.exports = function(){ +function Router(){ + EventEmitter.call(this); + /** * @property routes * @type Object */ this.routes = {}; -}; +} -Router.prototype.__proto__ = EventEmitter.prototype; +util.inherits(Router, EventEmitter); /** * Formats a URL. @@ -149,4 +152,6 @@ Router.prototype.remove = function(source){ this.emit('remove', source); return this; -}; \ No newline at end of file +}; + +module.exports = Router; \ No newline at end of file diff --git a/package.json b/package.json index d5feb19cc0..14cf7983da 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "hexo", "version": "2.8.3", "description": "A fast, simple & powerful blog framework, powered by Node.js.", - "main": "lib/hexo", + "main": "lib/index", "bin": { "hexo": "./bin/hexo" }, @@ -35,22 +35,22 @@ "license": "MIT", "dependencies": { "abbrev": "^1.0.5", - "bluebird": "^2.3.10", + "bluebird": "^2.3.11", "bunyan": "^1.2.1", "cheerio": "0.17.0", - "chokidar": "0.10.4", + "chokidar": "0.11.1", "colors": "1.0.3", "compression": "^1.0.3", "connect": "3.x", "graceful-fs": "^3.0.2", "hexo-front-matter": "0.0.4", - "highlight.js": "8.3.0", + "highlight.js": "8.4.0", "inflection": "^1.5.1", "js-yaml": "^3.1.0", "lodash": "^2.4.1", "mime": "^1.2.11", "minimist": "1.1.0", - "moment": "^2.8.1", + "moment": "^2.8.4", "morgan": "^1.1.1", "pretty-hrtime": "^0.2.2", "semver": "^4.1.0", @@ -63,7 +63,10 @@ }, "devDependencies": { "chai": "^1.9.1", + "coveralls": "^2.11.2", + "del": "^0.1.3", "gulp": "^3.8.9", + "gulp-istanbul": "^0.3.1", "gulp-jshint": "^1.8.6", "gulp-load-plugins": "^0.7.0", "gulp-mocha": "^1.1.1", diff --git a/test/index.js b/test/index.js index 124a19a9aa..ae039f53a3 100644 --- a/test/index.js +++ b/test/index.js @@ -1,5 +1,7 @@ describe('Hexo', function(){ require('./scripts/box'); + require('./scripts/extend'); require('./scripts/hexo'); + require('./scripts/models'); require('./scripts/util'); }); \ No newline at end of file diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 60a61e27ef..22087fb19c 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -1,13 +1,14 @@ var should = require('chai').should(); var pathFn = require('path'); var Hexo = require('../../../lib/hexo'); +var Box = require('../../../lib/box'); var util = Hexo.util; var fs = util.fs; describe('Box', function(){ var hexo = new Hexo(__dirname, {}); var base = pathFn.join(__dirname, 'tmp'); - var box = hexo._createBox(base); + var box = new Box(hexo, base); before(function(){ return fs.mkdir(base); diff --git a/test/scripts/extend/console.js b/test/scripts/extend/console.js new file mode 100644 index 0000000000..f7970989ea --- /dev/null +++ b/test/scripts/extend/console.js @@ -0,0 +1,104 @@ +var should = require('chai').should(); + +describe('Console', function(){ + var Console = require('../../../lib/extend/console'); + + it('register()', function(){ + var c = new Console(); + + // no name + try { + c.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'name is required'); + } + + // name, fn + c.register('test', function(){}); + c.get('test').should.exist; + + // name, not fn + try { + c.register('test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + + // name, desc, fn + c.register('test', 'this is a test', function(){}); + c.get('test').should.exist; + c.get('test').desc.should.eql('this is a test'); + + // name, desc, not fn + try { + c.register('test', 'this is a test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + + // name, desc, options, fn + c.register('test', 'this is a test', {init: true}, function(){}); + c.get('test').should.exist; + c.get('test').desc.should.eql('this is a test'); + c.get('test').options.init.should.be.true; + + // name, desc, options, not fn + try { + c.register('test', 'this is a test', {init: true}); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('register() - alias', function(){ + var c = new Console(); + + c.register('test', function(){}); + c.alias.should.eql({ + t: 'test', + te: 'test', + tes: 'test', + test: 'test' + }); + }); + + it('register() - promisify', function(){ + var c = new Console(); + + c.register('test', function(args, callback){ + args.should.eql({foo: 'bar'}); + callback(null, 'foo'); + }); + + c.get('test')({ + foo: 'bar' + }).then(function(result){ + result.should.eql('foo'); + }); + }); + + it('list()', function(){ + var c = new Console(); + + c.register('test', function(){}); + c.list().should.have.keys(['test']); + }); + + it('get()', function(){ + var c = new Console(); + + c.register('test', function(){}); + c.get('test').should.exist; + c.get('t').should.exist; + c.get('te').should.exist; + c.get('tes').should.exist; + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/deployer.js b/test/scripts/extend/deployer.js new file mode 100644 index 0000000000..932cb2a4ec --- /dev/null +++ b/test/scripts/extend/deployer.js @@ -0,0 +1,60 @@ +var should = require('chai').should(); + +describe('Deployer', function(){ + var Deployer = require('../../../lib/extend/deployer'); + + it('register()', function(){ + var d = new Deployer(); + + // name, fn + d.register('test', function(){}); + d.get('test').should.exist; + + // no name + try { + d.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'name is required'); + } + + // no fn + try { + d.register('test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('register() - promisify', function(){ + var d = new Deployer(); + + d.register('test', function(args, callback){ + args.should.eql({foo: 'bar'}); + callback(null, 'foo'); + }); + + d.get('test')({ + foo: 'bar' + }).then(function(result){ + result.should.eql('foo'); + }); + }); + + it('list()', function(){ + var d = new Deployer(); + + d.register('test', function(){}); + d.list().should.have.keys(['test']); + }); + + it('get()', function(){ + var d = new Deployer(); + + d.register('test', function(){}); + d.get('test').should.exist; + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/filter.js b/test/scripts/extend/filter.js new file mode 100644 index 0000000000..3c0e6323c1 --- /dev/null +++ b/test/scripts/extend/filter.js @@ -0,0 +1,202 @@ +var should = require('chai').should(); + +describe('Filter', function(){ + var Filter = require('../../../lib/extend/filter'); + + it('register()', function(){ + var f = new Filter(); + + // type, fn + f.register('test', function(){}); + f.list('test')[0].should.exist; + f.list('test')[0].priority.should.eql(10); + + // type, fn, priority + f.register('test2', function(){}, 50); + f.list('test2')[0].priority.should.eql(50); + + // fn + f.register(function(){}); + f.list('after_post_render')[0].should.exist; + f.list('after_post_render')[0].priority.should.eql(10); + + // fn, priority + f.register(function(){}, 50); + f.list('after_post_render')[1].priority.should.eql(50); + + // no fn + try { + f.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('register() - type alias', function(){ + var f = new Filter(); + + // pre + f.register('pre', function(){}); + f.list('before_post_render')[0].should.exist; + + // post + f.register('post', function(){}); + f.list('after_post_render')[0].should.exist; + }); + + it('register() - priority', function(){ + var f = new Filter(); + + f.register('test', function(){}); + f.register('test', function(){}, 5); + f.register('test', function(){}, 15); + + f.list('test').map(function(item){ + return item.priority; + }).should.eql([5, 10, 15]); + }); + + it('list()', function(){ + var f = new Filter(); + + f.register('test', function(){}); + f.list('test')[0].should.exist; + f.list('foo').length.should.eql(0); + }); + + it('exec()', function(){ + var f = new Filter(); + + f.register('test', function(data){ + data.should.eql(''); + return data + 'foo'; + }); + + f.register('test', function(data){ + data.should.eql('foo'); + return data + 'bar'; + }); + + f.exec('test', '').then(function(data){ + data.should.eql('foobar'); + }); + }); + + it('exec() - pointer', function(){ + var f = new Filter(); + + f.register('test', function(data){ + data.should.eql({}); + data.foo = 1; + }); + + f.register('test', function(data){ + data.should.eql({foo: 1}); + data.bar = 2; + }); + + f.exec('test', {}).then(function(data){ + data.should.eql({foo: 1, bar: 2}); + }); + }); + + it('exec() - args', function(){ + var f = new Filter(); + + f.register('test', function(data, arg1, arg2){ + arg1.should.eql(1); + arg2.should.eql(2); + }); + + f.register('test', function(data, arg1, arg2){ + arg1.should.eql(1); + arg2.should.eql(2); + }); + + f.exec('test', {}, { + args: [1, 2] + }); + }); + + it('exec() - context', function(){ + var f = new Filter(); + var ctx = {foo: 1, bar: 2}; + + f.register('test', function(data){ + this.should.eql(ctx); + }); + + f.register('test', function(data){ + this.should.eql(ctx); + }); + + f.exec('test', {}, {context: ctx}); + }); + + it('execSync()', function(){ + var f = new Filter(); + + f.register('test', function(data){ + data.should.eql(''); + return data + 'foo'; + }); + + f.register('test', function(data){ + data.should.eql('foo'); + return data + 'bar'; + }); + + f.execSync('test', '').should.eql('foobar'); + }); + + it('execSync() - pointer', function(){ + var f = new Filter(); + + f.register('test', function(data){ + data.should.eql({}); + data.foo = 1; + }); + + f.register('test', function(data){ + data.should.eql({foo: 1}); + data.bar = 2; + }); + + f.execSync('test', {}).should.eql({foo: 1, bar: 2}); + }); + + it('execSync() - args', function(){ + var f = new Filter(); + + f.register('test', function(data, arg1, arg2){ + arg1.should.eql(1); + arg2.should.eql(2); + }); + + f.register('test', function(data, arg1, arg2){ + arg1.should.eql(1); + arg2.should.eql(2); + }); + + f.execSync('test', {}, { + args: [1, 2] + }); + }); + + it('execSync() - context', function(){ + var f = new Filter(); + var ctx = {foo: 1, bar: 2}; + + f.register('test', function(data){ + this.should.eql(ctx); + }); + + f.register('test', function(data){ + this.should.eql(ctx); + }); + + f.execSync('test', {}, {context: ctx}); + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/generator.js b/test/scripts/extend/generator.js new file mode 100644 index 0000000000..03e99be073 --- /dev/null +++ b/test/scripts/extend/generator.js @@ -0,0 +1,52 @@ +var should = require('chai').should(); + +describe('Generator', function(){ + var Generator = require('../../../lib/extend/generator'); + + it('register()', function(){ + var g = new Generator(); + + // name, fn + g.register('test', function(){}); + g.get('test').should.exist; + + // fn + g.register(function(){}); + g.get('generator-0').should.exist; + + // no fn + try { + g.register('test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('register() - promisify', function(){ + var g = new Generator(); + + g.register('test', function(locals, render, callback){ + callback(null, 'foo'); + }); + + g.get('test')({}, {}).then(function(result){ + result.should.eql('foo'); + }); + }); + + it('get()', function(){ + var g = new Generator(); + + g.register('test', function(){}); + g.get('test').should.exist; + }); + + it('list()', function(){ + var g = new Generator(); + + g.register('test', function(){}); + g.list().should.have.keys(['test']); + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/helper.js b/test/scripts/extend/helper.js new file mode 100644 index 0000000000..4f4c42139a --- /dev/null +++ b/test/scripts/extend/helper.js @@ -0,0 +1,45 @@ +var should = require('chai').should(); + +describe('Helper', function(){ + var Helper = require('../../../lib/extend/helper'); + + it('register()', function(){ + var h = new Helper(); + + // name, fn + h.register('test', function(){}); + h.get('test').should.exist; + + // no fn + try { + h.register('test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + + // no name + try { + h.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'name is required'); + } + }); + + it('list()', function(){ + var h = new Helper(); + + h.register('test', function(){}); + h.list().should.have.keys(['test']); + }); + + it('get()', function(){ + var h = new Helper(); + + h.register('test', function(){}); + h.get('test').should.exist; + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/index.js b/test/scripts/extend/index.js new file mode 100644 index 0000000000..cd237a31a9 --- /dev/null +++ b/test/scripts/extend/index.js @@ -0,0 +1,11 @@ +describe('Extend', function(){ + require('./console'); + require('./deployer'); + require('./filter'); + require('./generator'); + require('./helper'); + require('./migrator'); + require('./processor'); + require('./renderer'); + require('./tag'); +}); \ No newline at end of file diff --git a/test/scripts/extend/migrator.js b/test/scripts/extend/migrator.js new file mode 100644 index 0000000000..5e4414d6ca --- /dev/null +++ b/test/scripts/extend/migrator.js @@ -0,0 +1,60 @@ +var should = require('chai').should(); + +describe('Migrator', function(){ + var Migrator = require('../../../lib/extend/migrator'); + + it('register()', function(){ + var d = new Migrator(); + + // name, fn + d.register('test', function(){}); + d.get('test').should.exist; + + // no name + try { + d.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'name is required'); + } + + // no fn + try { + d.register('test'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('register() - promisify', function(){ + var d = new Migrator(); + + d.register('test', function(args, callback){ + args.should.eql({foo: 'bar'}); + callback(null, 'foo'); + }); + + d.get('test')({ + foo: 'bar' + }).then(function(result){ + result.should.eql('foo'); + }); + }); + + it('list()', function(){ + var d = new Migrator(); + + d.register('test', function(){}); + d.list().should.have.keys(['test']); + }); + + it('get()', function(){ + var d = new Migrator(); + + d.register('test', function(){}); + d.get('test').should.exist; + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/processor.js b/test/scripts/extend/processor.js new file mode 100644 index 0000000000..9838bc0038 --- /dev/null +++ b/test/scripts/extend/processor.js @@ -0,0 +1,34 @@ +var should = require('chai').should(); + +describe('Processor', function(){ + var Processor = require('../../../lib/extend/processor'); + var Pattern = require('../../../lib/box/pattern'); + + it('register()', function(){ + var p = new Processor(); + + // pattern, fn + p.register('test', function(){}); + p.list()[0].should.exist; + + // fn + p.register(function(){}); + p.list()[1].should.exist; + + // no fn + try { + p.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + }); + + it('list()', function(){ + var p = new Processor(); + + p.register('test', function(){}); + p.list().length.should.eql(1); + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/renderer.js b/test/scripts/extend/renderer.js new file mode 100644 index 0000000000..cdf7637a00 --- /dev/null +++ b/test/scripts/extend/renderer.js @@ -0,0 +1,128 @@ +var should = require('chai').should(); + +describe('Renderer', function(){ + var Renderer = require('../../../lib/extend/renderer'); + + it('register()', function(){ + var r = new Renderer(); + + // name, output, fn + r.register('yaml', 'json', function(){}); + r.get('yaml').should.exist; + r.get('yaml').output.should.eql('json'); + + // name, output, fn, sync + r.register('yaml', 'json', function(){}, true); + r.get('yaml').should.exist; + r.get('yaml').output.should.eql('json'); + r.get('yaml', true).should.exist; + r.get('yaml', true).output.should.eql('json'); + + // no fn + try { + r.register('yaml', 'json'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'fn must be a function'); + } + + // no output + try { + r.register('yaml'); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'output is required'); + } + + // no name + try { + r.register(); + } catch (err){ + err.should.be + .instanceOf(TypeError) + .property('message', 'name is required'); + } + }); + + it('register() - promisify', function(){ + var r = new Renderer(); + + // async + r.register('yaml', 'json', function(data, options, callback){ + callback(null, 'foo'); + }); + + r.get('yaml')({}, {}).then(function(result){ + result.should.eql('foo'); + }); + + // sync + r.register('swig', 'html', function(data, options){ + return 'foo'; + }, true); + + r.get('swig')({}, {}).then(function(result){ + result.should.eql('foo'); + }); + }); + + it('getOutput()', function(){ + var r = new Renderer(); + + r.register('yaml', 'json', function(){}); + r.getOutput('yaml').should.eql('json'); + r.getOutput('.yaml').should.eql('json'); + r.getOutput('config.yaml').should.eql('json'); + r.getOutput('foo.xml').should.not.ok; + }); + + it('isRenderable()', function(){ + var r = new Renderer(); + + r.register('yaml', 'json', function(){}); + r.isRenderable('yaml').should.be.true; + r.isRenderable('.yaml').should.be.true; + r.isRenderable('config.yaml').should.be.true; + r.isRenderable('foo.xml').should.be.false; + }); + + it('isRenderableSync()', function(){ + var r = new Renderer(); + + r.register('yaml', 'json', function(){}); + r.isRenderableSync('yaml').should.be.false; + + r.register('swig', 'html', function(){}, true); + r.isRenderableSync('swig').should.be.true; + r.isRenderableSync('.swig').should.be.true; + r.isRenderableSync('layout.swig').should.be.true; + r.isRenderableSync('foo.html').should.be.false; + }); + + it('get()', function(){ + var r = new Renderer(); + + r.register('yaml', 'json', function(){}); + r.get('yaml').should.exist; + r.get('.yaml').should.exist; + r.get('config.yaml').should.exist; + should.not.exist(r.get('foo.xml')); + should.not.exist(r.get('yaml', true)); + + r.register('swig', 'html', function(){}, true); + r.get('swig').should.exist; + r.get('swig', true).should.exist; + }); + + it('list()', function(){ + var r = new Renderer(); + + r.register('yaml', 'json', function(){}); + r.register('swig', 'html', function(){}, true); + + r.list().should.have.keys(['yaml', 'swig']); + r.list(true).should.have.keys(['swig']); + }); +}); \ No newline at end of file diff --git a/test/scripts/extend/tag.js b/test/scripts/extend/tag.js new file mode 100644 index 0000000000..e484c470d7 --- /dev/null +++ b/test/scripts/extend/tag.js @@ -0,0 +1,5 @@ +var should = require('chai').should(); + +describe.skip('Tag', function(){ + var Tag = require('../../../lib/extend/tag'); +}); \ No newline at end of file diff --git a/test/scripts/models/assets.js b/test/scripts/models/assets.js new file mode 100644 index 0000000000..c1bbde17a9 --- /dev/null +++ b/test/scripts/models/assets.js @@ -0,0 +1,33 @@ +var should = require('chai').should(); + +describe('Assets', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Asset = hexo.model('Asset'); + + it('default values', function(){ + return Asset.insert({ + _id: 'foo', + path: 'bar' + }).then(function(data){ + data.modified.should.be.true; + return Asset.removeById(data._id); + }); + }); + + it('_id - required', function(){ + return Asset.insert({}).catch(function(err){ + err.should.have.property('message', 'ID is not defined'); + }); + }); + + it('path - required', function(){ + return Asset.insert({ + _id: 'foo' + }).catch(function(err){ + err.should.have.property('message', '`path` is required!'); + }); + }); + + it.skip('post - reference'); +}); \ No newline at end of file diff --git a/test/scripts/models/cache.js b/test/scripts/models/cache.js new file mode 100644 index 0000000000..9dbe624457 --- /dev/null +++ b/test/scripts/models/cache.js @@ -0,0 +1,24 @@ +var should = require('chai').should(); + +describe('Cache', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Cache = hexo.model('Cache'); + + it('default values', function(){ + var now = Date.now(); + + return Cache.insert({ + _id: 'foo' + }).then(function(data){ + data.mtime.should.gte(now); + return Cache.removeById(data._id); + }); + }); + + it('_id - required', function(){ + return Cache.insert({}).catch(function(err){ + err.should.have.property('message', 'ID is not defined'); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/category.js b/test/scripts/models/category.js new file mode 100644 index 0000000000..00fc2913c9 --- /dev/null +++ b/test/scripts/models/category.js @@ -0,0 +1,191 @@ +var should = require('chai').should(); +var Promise = require('bluebird'); + +describe('Category', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Category = hexo.model('Category'); + var Post = hexo.model('Post'); + var PostCategory = hexo.model('PostCategory'); + + before(function(){ + return hexo.init(); + }); + + it('name - required', function(){ + return Category.insert({}).catch(function(err){ + err.should.have.property('message', '`name` is required!'); + }); + }); + + it.skip('parent - reference'); + + it('slug - virtual', function(){ + return Category.insert({ + name: 'foo' + }).then(function(data){ + data.slug.should.eql('foo'); + return Category.removeById(data._id); + }); + }); + + it('slug - category_map', function(){ + hexo.config.category_map = { + test: 'wat' + }; + + return Category.insert({ + name: 'test' + }).then(function(data){ + data.slug.should.eql('wat'); + hexo.config.category_map = {}; + return Category.removeById(data._id); + }); + }); + + it('slug - filename_case: 0', function(){ + return Category.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('WahAHa'); + return Category.removeById(data._id); + }); + }); + + it('slug - filename_case: 1', function(){ + hexo.config.filename_case = 1; + + return Category.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('wahaha'); + hexo.config.filename_case = 0; + return Category.removeById(data._id); + }); + }); + + it('slug - filename_case: 2', function(){ + hexo.config.filename_case = 2; + + return Category.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('WAHAHA'); + hexo.config.filename_case = 0; + return Category.removeById(data._id); + }); + }); + + it('slug - parent', function(){ + return Category.insert({ + name: 'parent' + }).then(function(cat){ + return Category.insert({ + name: 'child', + parent: cat._id + }); + }).then(function(cat){ + cat.slug.should.eql('parent/child'); + + return Promise.all([ + Category.removeById(cat._id), + Category.removeById(cat.parent) + ]); + }); + }); + + it('path - virtual', function(){ + return Category.insert({ + name: 'foo' + }).then(function(data){ + data.path.should.eql(hexo.config.category_dir + '/' + data.slug + '/'); + return Category.removeById(data._id); + }); + }); + + it('permalink - virtual', function(){ + return Category.insert({ + name: 'foo' + }).then(function(data){ + data.permalink.should.eql(hexo.config.url + '/' + data.path); + return Category.removeById(data._id); + }); + }); + + it('posts - virtual', function(){ + return Post.insert([ + {source: 'foo.md', slug: 'foo'}, + {source: 'bar.md', slug: 'bar'}, + {source: 'baz.md', slug: 'baz'} + ]).then(function(posts){ + return Promise.map(posts, function(post){ + return post.setCategories(['foo']).thenReturn(post); + }, {concurrency: 1}); // One item a time + }).then(function(posts){ + var cat = Category.findOne({name: 'foo'}); + + function mapper(post){ + return post._id; + } + + cat.posts.map(mapper).should.eql(posts.map(mapper)); + cat.length.should.eql(posts.length); + + return Category.removeById(cat._id).thenReturn(posts); + }).map(function(post){ + return Post.removeById(post._id); + }); + }); + + it('check whether a category exists', function(){ + return Category.insert({ + name: 'foo' + }).then(function(data){ + Category.insert({ + name: 'foo' + }).catch(function(err){ + err.should.have.property('message', 'Category `foo` has already existed!'); + }); + + return Category.removeById(data._id); + }); + }); + + it('check whether a category exists (with parent)', function(){ + return Category.insert({ + name: 'foo' + }).then(function(data){ + return Category.insert({ + name: 'foo', + parent: data._id + }); + }).then(function(data){ + return Promise.all([ + Category.removeById(data._id), + Category.removeById(data.parent) + ]); + }); + }); + + it('remove PostCategory references when a category is removed', function(){ + var cat; + + return Post.insert([ + {source: 'foo.md', slug: 'foo'}, + {source: 'bar.md', slug: 'bar'}, + {source: 'baz.md', slug: 'baz'} + ]).then(function(posts){ + return Promise.map(posts, function(post){ + return post.setCategories(['foo']).thenReturn(post); + }, {concurrency: 1}); // One item a time + }).then(function(posts){ + cat = Category.findOne({name: 'foo'}); + return Category.removeById(cat._id).thenReturn(posts); + }).then(function(posts){ + PostCategory.find({category_id: cat._id}).length.should.eql(0); + return posts; + }).map(function(post){ + return Post.removeById(post._id); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/index.js b/test/scripts/models/index.js new file mode 100644 index 0000000000..67511a7b70 --- /dev/null +++ b/test/scripts/models/index.js @@ -0,0 +1,9 @@ +describe('Models', function(){ + require('./assets'); + require('./cache'); + require('./category'); + require('./moment'); + require('./page'); + require('./post'); + require('./tag'); +}); \ No newline at end of file diff --git a/test/scripts/models/moment.js b/test/scripts/models/moment.js new file mode 100644 index 0000000000..0c0e0956c6 --- /dev/null +++ b/test/scripts/models/moment.js @@ -0,0 +1,90 @@ +var should = require('chai').should(); +var moment = require('moment'); + +describe('SchemaTypeMoment', function(){ + var SchemaTypeMoment = require('../../../lib/models/types/moment'); + var type = new SchemaTypeMoment('test'); + + it('cast()', function(){ + type.cast(1e8).should.eql(moment(1e8)); + type.cast(new Date(2014, 1, 1)).should.eql(moment(new Date(2014, 1, 1))); + type.cast('2014-11-03T07:45:41.237Z').should.eql(moment('2014-11-03T07:45:41.237Z')); + type.cast(moment(1e8)).should.eql(moment(1e8)); + }); + + it('cast() - default', function(){ + var type = new SchemaTypeMoment('test', {default: moment}); + moment.isMoment(type.cast()).should.be.true; + }); + + function shouldThrowError(value){ + type.validate(value).should.have.property('message', '`' + value + '` is not a valid date!'); + } + + it('validate()', function(){ + type.validate(moment(1e8)).valueOf().should.eql(1e8); + // shouldThrowError('foo'); + // shouldThrowError([]); + // shouldThrowError(true); + // shouldThrowError({}); + shouldThrowError(moment.invalid()); + }); + + it('validate() - required', function(){ + var type = new SchemaTypeMoment('test', {required: true}); + type.validate().should.have.property('message', '`test` is required!'); + }); + + it('match()', function(){ + type.match(moment(1e8), moment(1e8)).should.be.true; + type.match(moment(1e8), moment(1e8 + 1)).should.be.false; + type.match(undefined, moment()).should.be.false; + }); + + it('compare()', function(){ + type.compare(moment([2014, 1, 3]), moment([2014, 1, 2])).should.gt(0); + type.compare(moment([2014, 1, 1]), moment([2014, 1, 2])).should.lt(0); + type.compare(moment([2014, 1, 2]), moment([2014, 1, 2])).should.eql(0); + type.compare(moment()).should.eql(1); + type.compare(undefined, moment()).should.eql(-1); + type.compare().should.eql(0); + }); + + it('parse()', function(){ + type.parse('2014-11-03T07:45:41.237Z').should.eql(moment('2014-11-03T07:45:41.237Z')); + should.not.exist(type.parse()); + }); + + it('value()', function(){ + type.value(moment('2014-11-03T07:45:41.237Z')).should.eql('2014-11-03T07:45:41.237Z'); + should.not.exist(type.value()); + }); + + it('q$day()', function(){ + type.q$day(moment([2014, 1, 1]), 1).should.be.true; + type.q$day(moment([2014, 1, 1]), 5).should.be.false; + type.q$day(undefined, 1).should.be.false; + }); + + it('q$month()', function(){ + type.q$month(moment([2014, 1, 1]), 1).should.be.true; + type.q$month(moment([2014, 1, 1]), 5).should.be.false; + type.q$month(undefined, 1).should.be.false; + }); + + it('q$year()', function(){ + type.q$year(moment([2014, 1, 1]), 2014).should.be.true; + type.q$year(moment([2014, 1, 1]), 1999).should.be.false; + type.q$year(undefined, 1).should.be.false; + }); + + it('u$inc()', function(){ + type.u$inc(moment(1e8), 1).valueOf().should.eql(1e8 + 1); + should.not.exist(undefined, 1); + }); + + it('u$dec()', function(){ + type.u$dec(moment(1e8), 1).valueOf().should.eql(1e8 - 1); + should.not.exist(undefined, 1); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/page.js b/test/scripts/models/page.js new file mode 100644 index 0000000000..94d5054080 --- /dev/null +++ b/test/scripts/models/page.js @@ -0,0 +1,62 @@ +var should = require('chai').should(); +var pathFn = require('path'); + +describe('Page', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Page = hexo.model('Page'); + + it('default values', function(){ + var now = Date.now(); + + return Page.insert({ + source: 'foo', + path: 'bar' + }).then(function(data){ + data.title.should.eql(''); + data.date.valueOf().should.gte(now); + data.updated.valueOf().should.gte(now); + data.comments.should.be.true; + data.layout.should.eql('page'); + data.content.should.eql(''); + data.excerpt.should.eql(''); + data.raw.should.eql(''); + + return Page.removeById(data._id); + }); + }); + + it('source - required', function(){ + return Page.insert({}).catch(function(err){ + err.should.have.property('message', '`source` is required!'); + }); + }); + + it('path - required', function(){ + return Page.insert({ + source: 'foo' + }).catch(function(err){ + err.should.have.property('message', '`path` is required!'); + }); + }); + + it('permalink - virtual', function(){ + return Page.insert({ + source: 'foo', + path: 'bar' + }).then(function(data){ + data.permalink.should.eql(hexo.config.url + '/' + data.path); + return Page.removeById(data._id); + }); + }); + + it('full_source - virtual', function(){ + return Page.insert({ + source: 'foo', + path: 'bar' + }).then(function(data){ + data.full_source.should.eql(pathFn.join(hexo.source_dir, data.source)); + return Page.removeById(data._id); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/post.js b/test/scripts/models/post.js new file mode 100644 index 0000000000..7f7bbf4ac7 --- /dev/null +++ b/test/scripts/models/post.js @@ -0,0 +1,189 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var Promise = require('bluebird'); + +describe('Post', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Post = hexo.model('Post'); + var Tag = hexo.model('Tag'); + var Category = hexo.model('Category'); + var PostTag = hexo.model('PostTag'); + var PostCategory = hexo.model('PostCategory'); + var Asset = hexo.model('Asset'); + + before(function(){ + hexo.config.permalink = ':title'; + return hexo.init(); + }); + + it('default values', function(){ + var now = Date.now(); + + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(data){ + data.title.should.eql(''); + data.date.valueOf().should.gte(now); + data.updated.valueOf().should.gte(now); + data.comments.should.be.true; + data.layout.should.eql('post'); + data.content.should.eql(''); + data.excerpt.should.eql(''); + data.more.should.eql(''); + data.link.should.eql(''); + data.raw.should.eql(''); + data.published.should.be.true; + + return Post.removeById(data._id); + }); + }); + + it('source - required', function(){ + return Post.insert({}).catch(function(err){ + err.should.have.property('message', '`source` is required!'); + }); + }); + + it('slug - required', function(){ + return Post.insert({ + source: 'foo.md' + }).catch(function(err){ + err.should.have.property('message', '`slug` is required!'); + }); + }); + + it('path - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(data){ + data.path.should.eql(data.slug); + return Post.removeById(data._id); + }); + }); + + it('permalink - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(data){ + data.permalink.should.eql(hexo.config.url + '/' + data.path); + return Post.removeById(data._id); + }); + }); + + it('full_source - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(data){ + data.full_source.should.eql(pathFn.join(hexo.source_dir, data.source)); + return Post.removeById(data._id); + }); + }); + + it('asset_dir - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(data){ + data.asset_dir.should.eql(pathFn.join(hexo.source_dir, 'foo') + pathFn.sep); + return Post.removeById(data._id); + }); + }); + + it('tags - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(post){ + return post.setTags(['foo', 'bar', 'baz']) + .thenReturn(Post.findById(post._id)); + }).then(function(post){ + post.tags.map(function(tag){ + return tag.name; + }).should.eql(['foo', 'bar', 'baz']); + + return Post.removeById(post._id); + }); + }); + + it('categories - virtual', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(post){ + return post.setCategories(['foo', 'bar', 'baz']) + .thenReturn(Post.findById(post._id)); + }).then(function(post){ + var cats = post.categories; + + // Make sure the order of categories is correct + cats.map(function(cat, i){ + // Make sure the parent reference is correct + if (i){ + cat.parent.should.eql(cats.eq(i - 1)._id); + } else { + should.not.exist(cat.parent); + } + + return cat.name; + }).should.eql(['foo', 'bar', 'baz']); + + return Post.removeById(post._id); + }); + }); + + it('remove PostTag references when a post is removed', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(post){ + return post.setTags(['foo', 'bar', 'baz']) + .thenReturn(Post.findById(post._id)); + }).then(function(post){ + return Post.removeById(post._id); + }).then(function(post){ + PostTag.find({post_id: post._id}).length.should.eql(0); + Tag.findOne({name: 'foo'}).posts.length.should.eql(0); + Tag.findOne({name: 'bar'}).posts.length.should.eql(0); + Tag.findOne({name: 'baz'}).posts.length.should.eql(0); + }); + }); + + it('remove PostCategory references when a post is removed', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(post){ + return post.setCategories(['foo', 'bar', 'baz']) + .thenReturn(Post.findById(post._id)); + }).then(function(post){ + return Post.removeById(post._id); + }).then(function(post){ + PostCategory.find({post_id: post._id}).length.should.eql(0); + Category.findOne({name: 'foo'}).posts.length.should.eql(0); + Category.findOne({name: 'bar'}).posts.length.should.eql(0); + Category.findOne({name: 'baz'}).posts.length.should.eql(0); + }); + }); + + it('remove related assets when a post is removed', function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }).then(function(post){ + return Promise.all([ + Asset.insert({_id: 'foo', path: 'foo'}), + Asset.insert({_id: 'bar', path: 'bar'}), + Asset.insert({_id: 'baz', path: 'bar'}) + ]).thenReturn(post); + }).then(function(post){ + return Post.removeById(post._id); + }).then(function(post){ + Asset.find({post: post._id}).length.should.eql(0); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/tag.js b/test/scripts/models/tag.js new file mode 100644 index 0000000000..6f98304b5f --- /dev/null +++ b/test/scripts/models/tag.js @@ -0,0 +1,157 @@ +var should = require('chai').should(); +var Promise = require('bluebird'); +var _ = require('lodash'); + +describe('Tag', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Tag = hexo.model('Tag'); + var Post = hexo.model('Post'); + var PostTag = hexo.model('PostTag'); + + before(function(){ + return hexo.init(); + }); + + it('name - required', function(){ + return Tag.insert({}).catch(function(err){ + err.should.have.property('message', '`name` is required!'); + }); + }); + + it('slug - virtual', function(){ + return Tag.insert({ + name: 'foo' + }).then(function(data){ + data.slug.should.eql('foo'); + return Tag.removeById(data._id); + }); + }); + + it('slug - tag_map', function(){ + hexo.config.tag_map = { + test: 'wat' + }; + + return Tag.insert({ + name: 'test' + }).then(function(data){ + data.slug.should.eql('wat'); + hexo.config.tag_map = {}; + + return Tag.removeById(data._id); + }); + }); + + it('slug - filename_case: 0', function(){ + return Tag.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('WahAHa'); + return Tag.removeById(data._id); + }); + }); + + it('slug - filename_case: 1', function(){ + hexo.config.filename_case = 1; + + return Tag.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('wahaha'); + hexo.config.filename_case = 0; + return Tag.removeById(data._id); + }); + }); + + it('slug - filename_case: 2', function(){ + hexo.config.filename_case = 2; + + return Tag.insert({ + name: 'WahAHa' + }).then(function(data){ + data.slug.should.eql('WAHAHA'); + hexo.config.filename_case = 0; + return Tag.removeById(data._id); + }); + }); + + it('path - virtual', function(){ + return Tag.insert({ + name: 'foo' + }).then(function(data){ + data.path.should.eql(hexo.config.tag_dir + '/' + data.slug + '/'); + return Tag.removeById(data._id); + }) + }); + + it('permalink - virtual', function(){ + return Tag.insert({ + name: 'foo' + }).then(function(data){ + data.permalink.should.eql(hexo.config.url + '/' + data.path); + return Tag.removeById(data._id); + }); + }); + + it('posts - virtual', function(){ + return Post.insert([ + {source: 'foo.md', slug: 'foo'}, + {source: 'bar.md', slug: 'bar'}, + {source: 'baz.md', slug: 'baz'} + ]).then(function(posts){ + return Promise.map(posts, function(post){ + return post.setTags(['foo']).thenReturn(post); + }, {concurrency: 1}); // One item a time + }).then(function(posts){ + var tag = Tag.findOne({name: 'foo'}); + + function mapper(post){ + return post._id; + } + + tag.posts.map(mapper).should.eql(posts.map(mapper)); + tag.length.should.eql(posts.length); + + return Tag.removeById(tag._id).thenReturn(posts); + }).map(function(post){ + return Post.removeById(post._id); + }); + }); + + it('check whether a tag exists', function(){ + return Tag.insert({ + name: 'foo' + }).then(function(data){ + Tag.insert({ + name: 'foo' + }).catch(function(err){ + err.should.have.property('message', 'Tag `foo` has already existed!'); + }); + + return Tag.removeById(data._id); + }); + }); + + it('remove PostTag references when a tag is removed', function(){ + var tag; + + return Post.insert([ + {source: 'foo.md', slug: 'foo'}, + {source: 'bar.md', slug: 'bar'}, + {source: 'baz.md', slug: 'baz'} + ]).then(function(posts){ + return Promise.map(posts, function(post){ + return post.setTags(['foo']).thenReturn(post); + }, {concurrency: 1}); // One item a time + }).then(function(posts){ + tag = Tag.findOne({name: 'foo'}); + return Tag.removeById(tag._id).thenReturn(posts); + }).then(function(posts){ + PostTag.find({tag_id: tag._id}).length.should.eql(0); + return posts; + }).map(function(post){ + return Post.removeById(post._id); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/util/file2.js b/test/scripts/util/file2.js deleted file mode 100644 index 2d41180481..0000000000 --- a/test/scripts/util/file2.js +++ /dev/null @@ -1,470 +0,0 @@ -var path = require('path'), - fs = require('graceful-fs'), - async = require('async'); - -describe('file2', function(){ - var file = require('../../../lib/util/file2'); - - var uid = function(length){ - var txt = '0123456789abcdefghijklmnopqrstuvwxyz', - total = txt.length, - result = ''; - - for (var i = 0; i < length; i++){ - result += txt[parseInt(Math.random() * total)]; - } - - return result; - }; - - it('mkdirs', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - async.series([ - function(next){ - file.mkdirs(dest, next); - }, - function(next){ - fs.exists(dest, function(exist){ - exist.should.be.true; - next(); - }); - }, - function(next){ - fs.rmdir(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - describe('writeFile', function(){ - it('normal', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)), - content = uid(48); - - async.series([ - function(next){ - file.writeFile(dest, content, next); - }, - function(next){ - fs.readFile(dest, 'utf8', function(err, result){ - if (err) throw err; - - result.should.eql(content); - next(); - }); - }, - function(next){ - fs.unlink(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - it('checkParent disabled', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)), - content = uid(48); - - file.writeFile(dest, content, {checkParent: false}, function(err){ - err.code.should.eql('ENOENT'); - done(); - }); - }); - }); - - describe('copyFile', function(){ - var src = path.join(__dirname, uid(12)), - content = uid(48); - - before(function(done){ - fs.writeFile(src, content, done); - }); - - it('normal', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - async.series([ - function(next){ - file.copyFile(src, dest, next); - }, - function(next){ - fs.readFile(dest, 'utf8', function(err, result){ - if (err) throw err; - - result.should.eql(content); - next(); - }); - }, - function(next){ - fs.unlink(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - it('checkParent disabled', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - file.copyFile(src, dest, {checkParent: false}, function(err){ - err.code.should.eql('ENOENT'); - done(); - }); - }); - - after(function(done){ - fs.unlink(src, done); - }); - }); - - describe('copyDir', function(){ - var src = path.join(__dirname, uid(12)); - - var srcFiles = [ - '.' + uid(12), - uid(12) + '.txt', - uid(12) + '.js' - ]; - - before(function(done){ - fs.mkdir(src, function(err){ - if (err) throw err; - - async.forEach(srcFiles, function(item, next){ - fs.writeFile(path.join(src, item), '', next); - }, done); - }); - }); - - it('normal', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - async.series([ - function(next){ - file.copyDir(src, dest, next); - }, - function(next){ - async.forEach(srcFiles.slice(1), function(item, next){ - var filePath = path.join(dest, item); - - fs.exists(filePath, function(exist){ - exist.should.be.true; - - fs.unlink(filePath, next); - }); - }, next); - }, - function(next){ - fs.rmdir(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - it('ignoreHidden disabled', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - async.series([ - function(next){ - file.copyDir(src, dest, {ignoreHidden: false}, next); - }, - function(next){ - async.forEach(srcFiles, function(item, next){ - var filePath = path.join(dest, item); - - fs.exists(filePath, function(exist){ - exist.should.be.true; - - fs.unlink(filePath, next); - }); - }, next); - }, - function(next){ - fs.rmdir(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - it('ignorePattern', function(done){ - var dest = path.join(__dirname, uid(12), uid(12)); - - async.series([ - function(next){ - file.copyDir(src, dest, {ignorePattern: /\.js$/}, next); - }, - function(next){ - async.forEach(srcFiles.slice(1, 2), function(item, next){ - var filePath = path.join(dest, item); - - fs.exists(filePath, function(exist){ - exist.should.be.true; - - fs.unlink(filePath, next); - }); - }, next); - }, - function(next){ - fs.rmdir(dest, next); - }, - function(next){ - fs.rmdir(path.dirname(dest), next); - } - ], done); - }); - - after(function(done){ - async.forEach(srcFiles, function(item, next){ - fs.unlink(path.join(src, item), next); - }, function(err){ - if (err) throw err; - - fs.rmdir(src, done); - }); - }); - }); - - describe('list', function(){ - var src = path.join(__dirname, uid(12)); - - var srcFiles = [ - uid(12) + '.txt', - path.join('.hiddendir', uid(12) + '.txt'), - path.join('.hiddendir', uid(12) + '.js'), - path.join('.hiddendir', 'hiddenchild', uid(12)), - path.join('dir', uid(12) + '.txt'), - path.join('dir', uid(12) + '.js'), - path.join('dir', 'childdir', uid(12) + '.txt'), - path.join('dir', 'childdir', uid(12) + '.js'), - '.hiddenfile' - ]; - - before(function(done){ - async.series([ - function(next){ - fs.mkdir(src, next); - }, - function(next){ - async.forEach(srcFiles, function(item, next){ - file.writeFile(path.join(src, item), '', next); - }, next); - } - ], done); - }); - - var defer = function(arr, callback){ - return function(err, files){ - srcFiles.forEach(function(item, i){ - if (arr.indexOf(i) > -1){ - files.should.include(item); - } else { - files.should.not.include(item); - } - }); - - callback(); - } - }; - - it('normal', function(done){ - file.list(src, defer([0, 4, 5, 6, 7], done)); - }); - - it('ignoreHidden disabled', function(done){ - file.list(src, {ignoreHidden: false}, defer([0, 1, 2, 3, 4, 5, 6, 7, 8], done)); - }); - - it('ignorePattern', function(done){ - file.list(src, {ignorePattern: /\.js$/}, defer([0, 4, 6], done)); - }); - - after(function(done){ - file.rmdir(src, done); - }); - }); - - describe('readFile', function(){ - var src = path.join(__dirname, uid(12)), - content = uid(48); - - before(function(done){ - fs.writeFile(src, content, done); - }); - - it('normal', function(done){ - file.readFile(src, function(err, result){ - if (err) throw err; - - result.should.eql(content); - done(); - }); - }); - - it('buffer', function(done){ - file.readFile(src, {encoding: null}, function(err, result){ - if (err) throw err; - - result.should.be.instanceof(Buffer); - result.toString('utf8').should.eql(content); - done(); - }); - }); - - after(function(done){ - fs.unlink(src, done); - }); - }); - - describe('readFileSync', function(){ - var src = path.join(__dirname, uid(12)), - content = uid(48); - - before(function(done){ - fs.writeFile(src, content, done); - }); - - it('normal', function(done){ - var result = file.readFileSync(src); - - result.should.eql(content); - done(); - }); - - it('buffer', function(done){ - var result = file.readFileSync(src, {encoding: null}); - - result.should.be.instanceof(Buffer); - result.toString('utf8').should.eql(content); - - done(); - }); - - after(function(done){ - fs.unlink(src, done); - }); - }); - - describe('emptyDir', function(){ - var src = path.join(__dirname, uid(12)); - - var srcFiles = [ - uid(12) + '.txt', - path.join('.hiddendir', uid(12) + '.txt'), - path.join('.hiddendir', uid(12) + '.js'), - path.join('.hiddendir', 'hiddenchild', uid(12)), - path.join('dir', uid(12) + '.txt'), - path.join('dir', uid(12) + '.js'), - path.join('dir', 'childdir', uid(12) + '.txt'), - path.join('dir', 'childdir', uid(12) + '.js'), - '.hiddenfile' - ]; - - before(function(done){ - fs.mkdir(src, done); - }); - - beforeEach(function(done){ - async.forEach(srcFiles, function(item, next){ - file.writeFile(path.join(src, item), '', next); - }, done); - }); - - var defer = function(arr, callback){ - return function(err, removed){ - if (err) throw err; - - srcFiles.forEach(function(item, i){ - if (arr.indexOf(i) > -1){ - removed.should.include(item); - } else { - removed.should.not.include(item); - } - }); - - var i = 0; - - async.forEachSeries(srcFiles, function(item, next){ - fs.exists(path.join(src, item), function(exist){ - if (arr.indexOf(i) > -1){ - exist.should.be.false; - } else { - exist.should.be.true; - } - - i++; - next(); - }); - }, callback); - } - }; - - it('normal', function(done){ - file.emptyDir(src, defer([0, 4, 5, 6, 7], done)); - }); - - it('ignoreHidden disabled', function(done){ - file.emptyDir(src, {ignoreHidden: false}, defer([0, 1, 2, 3, 4, 5, 6, 7, 8], done)); - }); - - it('ignorePattern', function(done){ - file.emptyDir(src, {ignorePattern: /\.js$/}, defer([0, 4, 6], done)); - }); - - it('exclude', function(done){ - var options = { - exclude: [srcFiles[4], srcFiles[6]] - }; - - file.emptyDir(src, options, defer([0, 5, 7], done)); - }); - - after(function(done){ - file.rmdir(src, done); - }); - }); - - describe('rmdir', function(){ - var src = path.join(__dirname, uid(12)); - - var srcFiles = [ - uid(12), - uid(12), - path.join('dir', uid(12)), - path.join('dir', uid(12)), - path.join('dir', 'chlid', uid(12)) - ]; - - beforeEach(function(done){ - async.series([ - function(next){ - fs.mkdir(src, next); - }, - function(next){ - async.forEach(srcFiles, function(item, next){ - file.writeFile(path.join(src, item), '', next); - }, next); - } - ], done); - }); - - it('normal', function(done){ - file.rmdir(src, function(err){ - if (err) throw err; - - fs.exists(src, function(exist){ - exist.should.be.false; - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/scripts/util/index.js b/test/scripts/util/index.js index 8fc9b37b0f..ecfaac1a68 100644 --- a/test/scripts/util/index.js +++ b/test/scripts/util/index.js @@ -1,6 +1,6 @@ describe('Utilities', function(){ - require('./file2'); require('./fs'); require('./html_tag'); require('./permalink'); + require('./router'); }); \ No newline at end of file diff --git a/test/scripts/util/permalink.js b/test/scripts/util/permalink.js index 6ae510d248..ec1d117fc5 100644 --- a/test/scripts/util/permalink.js +++ b/test/scripts/util/permalink.js @@ -1,8 +1,8 @@ var should = require('chai').should(); -describe('permalink', function(){ - var Permalink = require('../../../lib/util/permalink'), - permalink; +describe('Permalink', function(){ + var Permalink = require('../../../lib/util/permalink'); + var permalink; it('constructor', function(){ permalink = new Permalink(':year/:month/:day/:title'); @@ -24,12 +24,12 @@ describe('permalink', function(){ permalink.params.should.eql(['year', 'month', 'day', 'title']); }); - it('test', function(){ + it('test()', function(){ permalink.test('2014/01/31/test').should.be.true; permalink.test('foweirojwoier').should.be.false; }); - it('parse', function(){ + it('parse()', function(){ permalink.parse('2014/01/31/test').should.eql({ year: '2014', month: '01', @@ -38,7 +38,7 @@ describe('permalink', function(){ }); }); - it('stringify', function(){ + it('stringify()', function(){ permalink.stringify({ year: '2014', month: '01', diff --git a/test/scripts/util/router.js b/test/scripts/util/router.js new file mode 100644 index 0000000000..2ad1b44fc1 --- /dev/null +++ b/test/scripts/util/router.js @@ -0,0 +1,69 @@ +var should = require('chai').should(); + +describe('Router', function(){ + var Router = require('../../../lib/util/router'); + var router = new Router(); + + it('format()', function(){ + router.format('foo').should.eql('foo'); + + // Remove prefixed slashes + router.format('/foo').should.eql('foo'); + router.format('///foo').should.eql('foo'); + + // Append `index.html` to the URL with trailing slash + router.format('foo/').should.eql('foo/index.html'); + + // '' => `index.html + router.format('').should.eql('index.html'); + router.format().should.eql('index.html'); + + // Remove backslashes + router.format('foo\\bar').should.eql('foo/bar'); + router.format('foo\\bar\\').should.eql('foo/bar/index.html'); + + // Remove query string + router.format('foo?a=1&b=2').should.eql('foo'); + }); + + it('set() - string', function(){ + router.once('update', function(source, route){ + source.should.eql('foo'); + + route(function(err, content){ + should.not.exist(err); + content.should.eql('bar'); + }); + }); + + router.set('foo', 'bar'); + router.get('foo', function(err, content){ + should.not.exist(err); + content.should.eql('bar'); + }); + }); + + it('set() - function', function(){ + router.once('update', function(source, route){ + source.should.eql('hello'); + + route(function(err, content){ + should.not.exist(err); + content.should.eql('world'); + }); + }); + + router.set('hello', function(fn){ + fn(null, 'world'); + }); + + router.get('hello', function(err, content){ + should.not.exist(err); + content.should.eql('world'); + }); + }); + + it('remove()', function(){ + // + }); +}); \ No newline at end of file From eb5af65403bc12da2e0832acddcc3ed35ce6f1c0 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Wed, 3 Dec 2014 16:58:20 +0800 Subject: [PATCH 03/15] Update travis.yml --- .travis.yml | 2 ++ lib/theme/index.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 944cc6d1d0..fc06a90a27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,6 @@ node_js: - "0.11" before_script: - npm install -g gulp +script: + - gulp test - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js \ No newline at end of file diff --git a/lib/theme/index.js b/lib/theme/index.js index 875ee16e6f..1b50999ce7 100644 --- a/lib/theme/index.js +++ b/lib/theme/index.js @@ -2,7 +2,7 @@ var Promise = require('bluebird'); var pathFn = require('path'); var _ = require('lodash'); var util = require('../util'); -var Box = require('../Box'); +var Box = require('../box'); var View = require('./view'); var i18n = util.i18n; From 45deed847f7392ec9e0c78355c39ad5ef78b01ec Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Sat, 6 Dec 2014 16:10:55 +0800 Subject: [PATCH 04/15] Fix SchemaTypeMoment. Add filter/helper/tag tests. --- lib/models/page.js | 1 + lib/models/types/moment.js | 8 +- .../filter/after_post_render/excerpt.js | 7 +- lib/plugins/filter/new_post_path.js | 15 +- lib/plugins/filter/post_permalink.js | 8 +- .../filter/server_middleware/logger.js | 9 +- lib/plugins/helper/date.js | 36 +- lib/plugins/helper/form.js | 19 +- lib/plugins/helper/gravatar.js | 8 +- lib/plugins/helper/number.js | 16 +- lib/plugins/helper/open_graph.js | 142 +++--- lib/plugins/helper/paginator.js | 41 +- lib/plugins/tag/gist.js | 6 +- lib/util/format.js | 22 +- test/index.js | 3 + test/scripts/filters/backtick_code_block.js | 62 +++ test/scripts/filters/excerpt.js | 89 ++++ test/scripts/filters/external_link.js | 57 +++ test/scripts/filters/index.js | 8 + test/scripts/filters/new_post_path.js | 169 +++++++ test/scripts/filters/post_permalink.js | 71 +++ test/scripts/filters/titlecase.js | 25 + test/scripts/helpers/date.js | 101 ++++ test/scripts/helpers/form.js | 48 ++ test/scripts/helpers/gravatar.js | 29 ++ test/scripts/helpers/index.js | 12 + test/scripts/helpers/is.js | 52 ++ test/scripts/helpers/number.js | 28 ++ test/scripts/helpers/open_graph.js | 470 ++++++++++++++++++ test/scripts/helpers/paginator.js | 3 + test/scripts/helpers/tag.js | 372 ++++++++++++++ test/scripts/helpers/toc.js | 110 ++++ test/scripts/helpers/url.js | 60 +++ test/scripts/hexo/render.js | 3 +- test/scripts/models/moment.js | 6 +- test/scripts/models/page.js | 1 + test/scripts/tags/blockquote.js | 44 ++ test/scripts/tags/code.js | 50 ++ test/scripts/tags/gist.js | 16 + test/scripts/tags/iframe.js | 36 ++ test/scripts/tags/img.js | 81 +++ test/scripts/tags/include_code.js | 50 ++ test/scripts/tags/index.js | 14 + test/scripts/tags/jsfiddle.js | 52 ++ test/scripts/tags/link.js | 51 ++ test/scripts/tags/pullquote.js | 24 + test/scripts/tags/raw.js | 10 + test/scripts/tags/vimeo.js | 15 + test/scripts/tags/youtube.js | 15 + test/scripts/util/format.js | 28 ++ test/scripts/util/fs.js | 8 +- test/scripts/util/index.js | 1 + test/scripts/util/router.js | 8 +- 53 files changed, 2416 insertions(+), 204 deletions(-) create mode 100644 test/scripts/filters/backtick_code_block.js create mode 100644 test/scripts/filters/excerpt.js create mode 100644 test/scripts/filters/external_link.js create mode 100644 test/scripts/filters/index.js create mode 100644 test/scripts/filters/new_post_path.js create mode 100644 test/scripts/filters/post_permalink.js create mode 100644 test/scripts/filters/titlecase.js create mode 100644 test/scripts/helpers/date.js create mode 100644 test/scripts/helpers/form.js create mode 100644 test/scripts/helpers/gravatar.js create mode 100644 test/scripts/helpers/index.js create mode 100644 test/scripts/helpers/is.js create mode 100644 test/scripts/helpers/number.js create mode 100644 test/scripts/helpers/open_graph.js create mode 100644 test/scripts/helpers/paginator.js create mode 100644 test/scripts/helpers/tag.js create mode 100644 test/scripts/helpers/toc.js create mode 100644 test/scripts/helpers/url.js create mode 100644 test/scripts/tags/blockquote.js create mode 100644 test/scripts/tags/code.js create mode 100644 test/scripts/tags/gist.js create mode 100644 test/scripts/tags/iframe.js create mode 100644 test/scripts/tags/img.js create mode 100644 test/scripts/tags/include_code.js create mode 100644 test/scripts/tags/index.js create mode 100644 test/scripts/tags/jsfiddle.js create mode 100644 test/scripts/tags/link.js create mode 100644 test/scripts/tags/pullquote.js create mode 100644 test/scripts/tags/raw.js create mode 100644 test/scripts/tags/vimeo.js create mode 100644 test/scripts/tags/youtube.js create mode 100644 test/scripts/util/format.js diff --git a/lib/models/page.js b/lib/models/page.js index ec8e76b680..6134650f9d 100644 --- a/lib/models/page.js +++ b/lib/models/page.js @@ -12,6 +12,7 @@ module.exports = function(ctx){ layout: {type: String, default: 'page'}, content: {type: String, default: ''}, excerpt: {type: String, default: ''}, + more: {type: String, default: ''}, source: {type: String, required: true}, path: {type: String, required: true}, raw: {type: String, default: ''} diff --git a/lib/models/types/moment.js b/lib/models/types/moment.js index cf361e3e53..0b66dbeb86 100644 --- a/lib/models/types/moment.js +++ b/lib/models/types/moment.js @@ -11,7 +11,8 @@ util.inherits(SchemaTypeMoment, SchemaType); SchemaTypeMoment.prototype.cast = function(value, data){ value = SchemaType.prototype.cast.call(this, value, data); - if (value == null || moment.isMoment(value)) return value; + if (value == null) return value; + return moment(value); }; @@ -19,14 +20,15 @@ SchemaTypeMoment.prototype.validate = function(value, data){ value = SchemaType.prototype.validate.call(this, value, data); if (value instanceof Error) return value; - // NEED FIX: Why value sometimes don't have "isValid" method? value = moment(value); if (value != null && (!moment.isMoment(value) || !value.isValid())){ return new Error('`' + value + '` is not a valid date!'); } - return value; + // It seems that something's wrong when warehouse is trying to clone a moment + // object. So we cast the instance to a string and cast it back later. + return value.toISOString(); }; SchemaTypeMoment.prototype.match = function(value, query, data){ diff --git a/lib/plugins/filter/after_post_render/excerpt.js b/lib/plugins/filter/after_post_render/excerpt.js index 0fa8e3208f..a4f722771e 100644 --- a/lib/plugins/filter/after_post_render/excerpt.js +++ b/lib/plugins/filter/after_post_render/excerpt.js @@ -4,9 +4,10 @@ module.exports = function(data){ var content = data.content; if (rExcerpt.test(content)){ - data.content = content.replace(rExcerpt, function(match, index){ - data.excerpt = content.substring(0, index); - data.more = content.substring(index); + data.content = content.replace(rExcerpt, function(match, index){ + data.excerpt = content.substring(0, index).trim(); + data.more = content.substring(index + match.length).trim(); + return '<a id="more"></a>'; }); } else { diff --git a/lib/plugins/filter/new_post_path.js b/lib/plugins/filter/new_post_path.js index 260cf0d663..52eba05869 100644 --- a/lib/plugins/filter/new_post_path.js +++ b/lib/plugins/filter/new_post_path.js @@ -1,10 +1,12 @@ var pathFn = require('path'); var moment = require('moment'); var _ = require('lodash'); +var Promise = require('bluebird'); var util = require('../../util'); var fs = util.fs; var escape = util.escape; var Permalink = util.permalink; +var permalink; var reservedKeys = { year: true, @@ -16,18 +18,23 @@ var reservedKeys = { }; module.exports = function(data, replace){ + data = data || {}; + var sourceDir = this.source_dir; var draftDir = pathFn.join(sourceDir, '_drafts'); var postDir = pathFn.join(sourceDir, '_posts'); var config = this.config; var newPostName = config.new_post_name; var permalinkDefaults = config.permalink_defaults; - var permalink = new Permalink(newPostName); var path = data.path; var layout = data.layout; var slug = data.slug; var target = ''; + if (!permalink || permalink.rule !== newPostName){ + permalink = new Permalink(newPostName); + } + if (path){ switch (layout){ case 'page': @@ -41,7 +48,7 @@ module.exports = function(data, replace){ default: target = pathFn.join(postDir, path); } - } else { + } else if (slug){ switch (layout){ case 'page': target = pathFn.join(sourceDir, slug, 'index'); @@ -73,13 +80,15 @@ module.exports = function(data, replace){ target = pathFn.join(postDir, permalink.stringify( _.defaults(filenameData, permalinkDefaults))); } + } else { + return Promise.reject(new TypeError('Either data.path or data.slug is required!')); } if (!pathFn.extname(target)){ target += pathFn.extname(newPostName) || '.md'; } - if (replace) return target; + if (replace) return Promise.resolve(target); return fs.exists(target).then(function(exist){ if (!exist) return target; diff --git a/lib/plugins/filter/post_permalink.js b/lib/plugins/filter/post_permalink.js index f8dda0811d..234a736a51 100644 --- a/lib/plugins/filter/post_permalink.js +++ b/lib/plugins/filter/post_permalink.js @@ -10,7 +10,6 @@ var ignoreKeys = { module.exports = function(data){ var config = this.config; - // var Category = this.model('Category'); var meta = { id: data.id || data._id, title: data.slug, @@ -24,15 +23,14 @@ module.exports = function(data){ if (!permalink || permalink.rule !== config.permalink){ permalink = new Permalink(config.permalink); } -/* + var categories = data.categories; if (categories.length){ - var category = categories[categories.length - 1]; - meta.category = Category.get(category).slug; + meta.category = categories.last().slug; } else { meta.category = config.default_category; - }*/ + } var keys = Object.keys(data); var key = ''; diff --git a/lib/plugins/filter/server_middleware/logger.js b/lib/plugins/filter/server_middleware/logger.js index 73074d45b7..88b3438275 100644 --- a/lib/plugins/filter/server_middleware/logger.js +++ b/lib/plugins/filter/server_middleware/logger.js @@ -3,16 +3,9 @@ var morgan = require('morgan'); module.exports = function(app){ var config = this.config; var args = this.env.args || {}; - var log = this.log; var format = args.l || args.log || config.logger_format; if (typeof format !== 'string') format = 'dev'; - app.use(morgan(format, { - stream: { - write: function(data){ - log.debug(data); - } - } - })); + app.use(morgan(format)); }; \ No newline at end of file diff --git a/lib/plugins/helper/date.js b/lib/plugins/helper/date.js index 3d68a4124e..1b30354469 100644 --- a/lib/plugins/helper/date.js +++ b/lib/plugins/helper/date.js @@ -1,52 +1,42 @@ var moment = require('moment'); var isMoment = moment.isMoment; -var result = function(date, format){ +function output(date, format){ if (isMoment(date)){ return date.format(format); } else { return moment(date).format(format); } -}; - -var toISOString = function(date){ - if (!date) date = new Date(); +} - if (date instanceof Date){ +function toISOString(date){ + if (date instanceof Date || isMoment(date)){ return date.toISOString(); - } else if (isMoment(date)){ - return date.toDate().toISOString(); } else { return new Date(date).toISOString(); } -}; +} exports.date = function(date, format){ - var config = this.config; - - return result(date || new Date(), format || config.date_format); + return output(date, format || this.config.date_format); }; exports.date_xml = toISOString; exports.time = function(date, format){ - var config = this.config; - - return result(date || new Date(), format || config.time_format); + return output(date, format || this.config.time_format); }; exports.full_date = function(date, format){ - var config = this.config; - - return result(date || new Date(), format || config.date_format + ' ' + config.time_format); + if (format){ + return output(date, format); + } else { + return this.date(date) + ' ' + this.time(date); + } }; exports.time_tag = function(date, format){ - date = date || new Date(); - - var config = this.config; - - return '<time datetime="' + toISOString(date) + '">' + result(date, format || config.date_format) + '</time>'; + return '<time datetime="' + toISOString(date) + '">' + this.date(date, format) + '</time>'; }; exports.moment = moment; \ No newline at end of file diff --git a/lib/plugins/helper/form.js b/lib/plugins/helper/form.js index d3e0ae481a..a6f7982da0 100644 --- a/lib/plugins/helper/form.js +++ b/lib/plugins/helper/form.js @@ -1,17 +1,14 @@ -var _ = require('lodash'); - -exports.search_form = function(opts){ - var options = _.extend({ - class: 'search-form', - text: 'Search', - button: false - }, opts); +exports.search_form = function(options){ + options = options || {}; var config = this.config; + var className = options.class || 'search-form'; + var text = options.hasOwnProperty('text') ? options.text : 'Search'; + var button = options.button; - return '<form action="//google.com/search" method="get" accept-charset="UTF-8" class="' + options.class + '">' + - '<input type="search" name="q" results="0" class="' + options.class + '-input"' + (options.text ? ' placeholder="' + options.text + '"' : '') + '>' + - (options.button ? '<input type="submit" value="' + (typeof options.button === 'string' ? options.button : options.text) + '" class="' + options.class + '-submit">' : '') + + return '<form action="//google.com/search" method="get" accept-charset="UTF-8" class="' + className + '">' + + '<input type="search" name="q" results="0" class="' + className + '-input"' + (text ? ' placeholder="' + text + '"' : '') + '>' + + (button ? '<button type="submit" class="' + className + '-submit">' + (typeof button === 'string' ? button : text) + '</button>' : '') + '<input type="hidden" name="q" value="site:' + config.url + '">' + '</form>'; }; \ No newline at end of file diff --git a/lib/plugins/helper/gravatar.js b/lib/plugins/helper/gravatar.js index db800f3e0d..d51a6aa15c 100644 --- a/lib/plugins/helper/gravatar.js +++ b/lib/plugins/helper/gravatar.js @@ -1,17 +1,17 @@ var crypto = require('crypto'); var querystring = require('querystring'); -var md5 = function(str){ +function md5(str){ return crypto.createHash('md5').update(str).digest('hex'); -}; +} module.exports = function(email, options){ if (typeof options === 'number'){ options = {s: options}; } - var str = 'http://www.gravatar.com/avatar/' + md5(email.toLowerCase()), - qs = querystring.stringify(options); + var str = 'http://www.gravatar.com/avatar/' + md5(email.toLowerCase()); + var qs = querystring.stringify(options); if (qs) str += '?' + qs; diff --git a/lib/plugins/helper/number.js b/lib/plugins/helper/number.js index d112ff083b..19fe94842c 100644 --- a/lib/plugins/helper/number.js +++ b/lib/plugins/helper/number.js @@ -1,19 +1,13 @@ -var _ = require('lodash'); - exports.number_format = function(num, options){ - options = _.extend({ - precision: false, - delimiter: ',', - separator: '.' - }, options); + options = options || {}; var split = num.toString().split('.'); - var i, len; - var before = split.shift(); var after = split.length ? split[0] : ''; - var delimiter = options.delimiter; + var delimiter = options.delimiter || ','; + var separator = options.separator || '.'; var precision = options.precision; + var i, len; if (delimiter){ var beforeArr = []; @@ -50,5 +44,5 @@ exports.number_format = function(num, options){ after = ''; } - return before + (after ? options.separator + after : ''); + return before + (after ? separator + after : ''); }; \ No newline at end of file diff --git a/lib/plugins/helper/open_graph.js b/lib/plugins/helper/open_graph.js index ed425cb1d7..58168a76b7 100644 --- a/lib/plugins/helper/open_graph.js +++ b/lib/plugins/helper/open_graph.js @@ -1,49 +1,50 @@ -var _ = require('lodash'); var cheerio = require('cheerio'); var util = require('../../util'); var htmlTag = util.html_tag; var format = util.format; - -var metaTag = function(name, content){ - var data = {}; - - switch (name.split(':')[0]){ - case 'og': - case 'fb': - data.property = name; - break; - - default: - data.name = name; - } - - data.content = content; - - return htmlTag('meta', data) + '\n'; -}; +var stripHtml = format.stripHtml; + +function meta(name, content){ + return htmlTag('meta', { + name: name, + content: content + }) + '\n'; +} + +function og(name, content){ + return htmlTag('meta', { + property: name, + content: content + }) + '\n'; +} module.exports = function(options){ + options = options || {}; + var page = this.page; var config = this.config; var content = page.content; - var images = page.photos || []; - - var description = page.description || ''; - - if (!description){ - if (page.excerpt){ - description = format.stripHtml(page.excerpt); - } else if (page.content){ - description = format.stripHtml(content); - } else if (config.description){ - description = config.description; - } + var images = options.images || page.photos || []; + var description = options.description || page.description || page.excerpt || content || config.description; + var title = options.title || page.title || config.title; + var type = options.type || (this.is_post() ? 'article' : 'website'); + var url = options.url || this.url; + var siteName = options.site_name || config.title; + var twitterCard = options.twitter_card || 'summary'; + var result = ''; + + if (!Array.isArray(images)) images = [images]; + + if (description){ + description = stripHtml(description).substring(0, 200) + .replace(/^\s+|\s+$/g, '') // Remove prefixing/trailing spaces + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, '''); } - description = description.substring(0, 200).replace(/^\s+|\s+$/g, '') - .replace(/\"/g, '\'') - .replace(/\>/g, '&'); - if (!images.length && content){ var $ = cheerio.load(content); @@ -53,60 +54,43 @@ module.exports = function(options){ }); } - var data = _.extend({ - title: page.title || config.title, - type: this.is_post() ? 'article' : 'website', - url: this.url, - image: images, - site_name: config.title, - description: description, - twitter_card: 'summary', - twitter_id: '', - twitter_site: '', - google_plus: '', - fb_admins: '', - fb_app_id: '' - }, options); - - var str = ''; - - str += metaTag('description', data.description); - str += metaTag('og:type', data.type); - str += metaTag('og:title', data.title); - str += metaTag('og:url', data.url); - str += metaTag('og:site_name', data.site_name); - str += metaTag('og:description', data.description); - - images.forEach(function(image){ - str += metaTag('og:image', image); - }); - - str += metaTag('twitter:card', data.twitter_card); - str += metaTag('twitter:title', data.title); - str += metaTag('twitter:description', data.description); - - if (data.twitter_id){ - var twitterId = data.twitter_id; + result += meta('description', description); + result += og('og:type', type); + result += og('og:title', title); + result += og('og:url', url); + result += og('og:site_name', siteName); + result += og('og:description', description); + + for (var i = 0, len = images.length; i < len; i++){ + result += og('og:image', images[i]); + } + + result += meta('twitter:card', twitterCard); + result += meta('twitter:title', title); + result += meta('twitter:description', description); + + if (options.twitter_id){ + var twitterId = options.twitter_id; if (twitterId[0] !== '@') twitterId = '@' + twitterId; - str += metaTag('twitter:creator', twitterId); + result += meta('twitter:creator', twitterId); } - if (data.twitter_site){ - str += metaTag('twitter:site', data.twitter_site); + if (options.twitter_site){ + result += meta('twitter:site', options.twitter_site); } - if (data.google_plus){ - str += htmlTag('link', {rel: 'publisher', href: data.google_plus}) + '\n'; + if (options.google_plus){ + result += htmlTag('link', {rel: 'publisher', href: options.google_plus}) + '\n'; } - if (data.fb_admins){ - str += metaTag('fb:admins', data.fb_admins); + if (options.fb_admins){ + result += og('fb:admins', options.fb_admins); } - if (data.fb_app_id){ - str += metaTag('fb:app_id', data.fb_app_id); + if (options.fb_app_id){ + result += og('fb:app_id', options.fb_app_id); } - return str; + return result; }; \ No newline at end of file diff --git a/lib/plugins/helper/paginator.js b/lib/plugins/helper/paginator.js index ad5d65a349..63b7bc9ffc 100644 --- a/lib/plugins/helper/paginator.js +++ b/lib/plugins/helper/paginator.js @@ -1,43 +1,32 @@ -var _ = require('lodash'); - module.exports = function(options){ - options = _.extend({ - base: this.page.base, - format: this.config.pagination_dir + '/%d/', - total: this.page.total || 1, - current: this.page.current || 0, - prev_text: 'Prev', - next_text: 'Next', - space: '…', - prev_next: true, - end_size: 1, - mid_size: 2, - show_all: false - }, options); + options = options || {}; - var current = options.current; - var total = options.total; - var endSize = options.end_size; - var midSize = options.mid_size; - var space = options.space; - var base = options.base; - var format = options.format; + var current = options.current || 0; + var total = options.total || 1; + var endSize = options.end_size || 1; + var midSize = options.mid_size || 2; + var space = options.space || '…'; + var base = options.base || this.page.base; + var format = options.format || this.config.pagination_dir + '/%d/'; + var prevText = options.prev_text || 'Prev'; + var nextText = options.next_text || 'Next'; + var prevNext = options.hasOwnProperty('prev_next') ? options.prev_next : true; var self = this; var front = ''; var back = ''; var i; function link(i){ - return self.url_for(i == 1 ? base : base + format.replace('%d', i)); + return self.url_for(i === 1 ? base : base + format.replace('%d', i)); } function pageNum(i){ return '<a class="page-number" href="' + link(i) + '">' + i + '</a>'; } - if (options.prev_next){ - if (current != 1) front = '<a class="extend prev" rel="prev" href="' + link(current - 1) + '">' + options.prev_text + '</a>'; - if (current != total) back = '<a class="extend next" rel="next" href="' + link(current + 1) + '">' + options.next_text + '</a>'; + if (prevNext){ + if (current !== 1) front = '<a class="extend prev" rel="prev" href="' + link(current - 1) + '">' + prevText + '</a>'; + if (current !== total) back = '<a class="extend next" rel="next" href="' + link(current + 1) + '">' + nextText + '</a>'; } if (options.show_all){ diff --git a/lib/plugins/tag/gist.js b/lib/plugins/tag/gist.js index 6c4e2c9afd..5ace1647bf 100644 --- a/lib/plugins/tag/gist.js +++ b/lib/plugins/tag/gist.js @@ -6,8 +6,8 @@ */ module.exports = function(args, content){ - var id = args.shift(), - file = args.length ? '?file=' + args[0] : ''; + var id = args.shift(); + var file = args.length ? '?file=' + args[0] : ''; - return '<script src="https://gist.github.com/' + id + '.js' + file + '"></script>'; + return '<script src="//gist.github.com/' + id + '.js' + file + '"></script>'; }; \ No newline at end of file diff --git a/lib/util/format.js b/lib/util/format.js index f750ac5d2d..fd900d5f4a 100644 --- a/lib/util/format.js +++ b/lib/util/format.js @@ -7,8 +7,6 @@ * @module hexo */ -var _ = require('lodash'); - /** * Removes all HTML tags. * @@ -18,10 +16,12 @@ var _ = require('lodash'); * @static */ -exports.strip_html = exports.stripHtml = function(content){ +exports.stripHtml = function(content){ return content.toString().replace(/<[^>]*>/g, ''); }; +exports.strip_html = exports.stripHtml; + /** * Removes whitespace from both ends of the string. * @@ -56,7 +56,7 @@ exports.titlecase = require('inflection').titleize; * @static */ -exports.word_wrap = exports.wordWrap = function(text, width){ +exports.wordWrap = function(text, width){ width = width || 80; var arr = []; @@ -68,6 +68,8 @@ exports.word_wrap = exports.wordWrap = function(text, width){ return arr.join('\n'); }; +exports.word_wrap = exports.wordWrap; + /** * Truncates the given `text`. * @@ -82,15 +84,9 @@ exports.word_wrap = exports.wordWrap = function(text, width){ */ exports.truncate = function(text, options){ - options = _.extend({ - length: 30, - omission: '...', - seperator: '' - }, options); - - var length = options.length, - omission = options.omission, - separator = options.separator; + var length = options.length || 30; + var omission = options.omission || '...'; + var separator = options.separator; if (text.length <= length) return text; diff --git a/test/index.js b/test/index.js index ae039f53a3..da5e5d930d 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,10 @@ describe('Hexo', function(){ require('./scripts/box'); require('./scripts/extend'); + require('./scripts/filters'); + require('./scripts/helpers'); require('./scripts/hexo'); require('./scripts/models'); + require('./scripts/tags'); require('./scripts/util'); }); \ No newline at end of file diff --git a/test/scripts/filters/backtick_code_block.js b/test/scripts/filters/backtick_code_block.js new file mode 100644 index 0000000000..b19c72f801 --- /dev/null +++ b/test/scripts/filters/backtick_code_block.js @@ -0,0 +1,62 @@ +var should = require('chai').should(); +var util = require('../../../lib/util'); +var highlight = util.highlight; + +describe.skip('Backtick code block', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var codeBlock = require('../../../lib/plugins/filter/before_post_render/backtick_code_block').bind(hexo); + + hexo.config.highlight = { + enable: true + }; + + it('disabled', function(){ + hexo.config.highlight.enable = false; + + var content = [ + '```', + 'alert("Hello")', + '```' + ].join('\n'); + + var data = {content: content}; + + codeBlock(data); + data.content.should.eql(content); + + hexo.config.highlight.enable = true; + }); + + it('normal', function(){ + // + }); + + it('specified language', function(){ + // + }); + + it('title', function(){ + // + }); + + it('url', function(){ + // + }); + + it('link text', function(){ + // + }); + + it('indention', function(){ + // + }); + + it('line_number', function(){ + // + }); + + it('tab_replace', function(){ + // + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/excerpt.js b/test/scripts/filters/excerpt.js new file mode 100644 index 0000000000..148a73aed1 --- /dev/null +++ b/test/scripts/filters/excerpt.js @@ -0,0 +1,89 @@ +var should = require('chai').should(); + +describe('Excerpt', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var excerpt = require('../../../lib/plugins/filter/after_post_render/excerpt').bind(hexo); + + it('without <!-- more -->', function(){ + var content = [ + 'foo', + 'bar', + 'baz' + ].join('\n'); + + var data = { + content: content + }; + + excerpt(data); + data.content.should.eql(content); + data.excerpt.should.eql(''); + data.more.should.eql(content); + }); + + it('with <!-- more -->', function(){ + var content = [ + 'foo', + 'bar', + '<!-- more -->', + 'baz' + ].join('\n'); + + var data = { + content: content + }; + + excerpt(data); + + data.content.should.eql([ + 'foo', + 'bar', + '<a id="more"></a>', + 'baz' + ].join('\n')); + + data.excerpt.should.eql([ + 'foo', + 'bar', + ].join('\n')); + + data.more.should.eql([ + 'baz' + ].join('\n')); + }); + + it('multiple <!-- more -->', function(){ + var content = [ + 'foo', + '<!-- more -->', + 'bar', + '<!-- more -->', + 'baz' + ].join('\n'); + + var data = { + content: content + }; + + excerpt(data); + + data.content.should.eql([ + 'foo', + '<a id="more"></a>', + 'bar', + '<!-- more -->', + 'baz' + ].join('\n')); + + data.excerpt.should.eql([ + 'foo' + ].join('\n')); + + data.more.should.eql([ + 'bar', + '<!-- more -->', + 'baz' + ].join('\n')); + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/external_link.js b/test/scripts/filters/external_link.js new file mode 100644 index 0000000000..c1c95bc531 --- /dev/null +++ b/test/scripts/filters/external_link.js @@ -0,0 +1,57 @@ +var should = require('chai').should(); + +describe('External link', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var externalLink = require('../../../lib/plugins/filter/after_post_render/external_link').bind(hexo); + + hexo.config.external_link = true; + hexo.config.url = 'maji.moe'; + + it('disabled', function(){ + var content = 'foo' + + '<a href="http://hexo.io/">Hexo</a>' + + 'bar'; + + var data = {content: content}; + hexo.config.external_link = false; + + externalLink(data); + data.content.should.eql(content); + hexo.config.external_link = true; + }); + + it('enabled', function(){ + var content = [ + '# External link test', + '1. External link', + '<a href="http://hexo.io/">Hexo</a>', + '2. Internal link', + '<a href="/archives/foo.html">Link</a>', + '3. Ignore links have "target" attribute', + '<a href="http://hexo.io/" target="_blank">Hexo</a>', + '4. Ignore links don\'t have "href" attribute', + '<a>Anchor</a>', + '5. Ignore links whose hostname is same as config', + '<a href="http://maji.moe">moe</a>' + ].join('\n'); + + var data = {content: content}; + + externalLink(data); + + data.content.should.eql([ + '# External link test', + '1. External link', + '<a href="http://hexo.io/" target="_blank" rel="external">Hexo</a>', + '2. Internal link', + '<a href="/archives/foo.html">Link</a>', + '3. Ignore links have "target" attribute', + '<a href="http://hexo.io/" target="_blank">Hexo</a>', + '4. Ignore links don\'t have "href" attribute', + '<a>Anchor</a>', + '5. Ignore links whose hostname is same as config', + '<a href="http://maji.moe">moe</a>' + ].join('\n')); + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/index.js b/test/scripts/filters/index.js new file mode 100644 index 0000000000..250c2e25c6 --- /dev/null +++ b/test/scripts/filters/index.js @@ -0,0 +1,8 @@ +describe('Filters', function(){ + require('./backtick_code_block'); + require('./excerpt'); + require('./external_link'); + require('./new_post_path'); + require('./post_permalink'); + require('./titlecase'); +}); \ No newline at end of file diff --git a/test/scripts/filters/new_post_path.js b/test/scripts/filters/new_post_path.js new file mode 100644 index 0000000000..90dcc9b505 --- /dev/null +++ b/test/scripts/filters/new_post_path.js @@ -0,0 +1,169 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var moment = require('moment'); +var Promise = require('bluebird'); +var util = require('../../../lib/util'); +var fs = util.fs; + +var NEW_POST_NAME = ':title.md'; + +describe('new_post_path', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname); + var newPostPath = require('../../../lib/plugins/filter/new_post_path').bind(hexo); + var sourceDir = hexo.source_dir; + var draftDir = pathFn.join(sourceDir, '_drafts'); + var postDir = pathFn.join(sourceDir, '_posts'); + + before(function(){ + hexo.config.new_post_name = NEW_POST_NAME; + return hexo.init(); + }); + + it('page layout + path', function(){ + return newPostPath({ + path: 'foo', + layout: 'page' + }).then(function(target){ + target.should.eql(pathFn.join(sourceDir, 'foo.md')); + }); + }); + + it('draft layout + path', function(){ + return newPostPath({ + path: 'foo', + layout: 'draft' + }).then(function(target){ + target.should.eql(pathFn.join(draftDir, 'foo.md')); + }); + }); + + it('default layout + path', function(){ + return newPostPath({ + path: 'foo' + }).then(function(target){ + target.should.eql(pathFn.join(postDir, 'foo.md')); + }); + }); + + it('page layout + slug', function(){ + return newPostPath({ + slug: 'foo', + layout: 'page' + }).then(function(target){ + target.should.eql(pathFn.join(sourceDir, 'foo', 'index.md')); + }); + }); + + it('draft layout + slug', function(){ + return newPostPath({ + slug: 'foo', + layout: 'draft' + }).then(function(target){ + target.should.eql(pathFn.join(draftDir, 'foo.md')); + }) + }); + + it('default layout + slug', function(){ + var now = moment(); + hexo.config.new_post_name = ':year-:month-:day-:title.md'; + + return newPostPath({ + slug: 'foo' + }).then(function(target){ + target.should.eql(pathFn.join(postDir, now.format('YYYY-MM-DD') + '-foo.md')); + hexo.config.new_post_name = NEW_POST_NAME; + }); + }); + + it('date', function(){ + var date = moment([2014, 0, 1]); + hexo.config.new_post_name = ':year-:i_month-:i_day-:title.md'; + + return newPostPath({ + slug: 'foo', + date: date.toDate() + }).then(function(target){ + target.should.eql(pathFn.join(postDir, date.format('YYYY-M-D') + '-foo.md')); + hexo.config.new_post_name = NEW_POST_NAME; + }) + }); + + it('extra data', function(){ + hexo.config.new_post_name = ':foo-:bar-:title.md'; + + return newPostPath({ + slug: 'foo', + foo: 'oh', + bar: 'ya' + }).then(function(target){ + target.should.eql(pathFn.join(postDir, 'oh-ya-foo.md')); + hexo.config.new_post_name = NEW_POST_NAME; + }); + }); + + it('append extension name if not existed', function(){ + hexo.config.new_post_name = ':title'; + + return newPostPath({ + slug: 'foo' + }).then(function(target){ + target.should.eql(pathFn.join(postDir, 'foo.md')); + hexo.config.new_post_name = NEW_POST_NAME; + }); + }); + + it('don\'t append extension name if existed', function(){ + return newPostPath({ + path: 'foo.markdown' + }).then(function(target){ + target.should.eql(pathFn.join(postDir, 'foo.markdown')); + }); + }); + + it('replace existed files', function(){ + var filename = 'test.md'; + var path = pathFn.join(postDir, filename); + + return fs.writeFile(path, '').then(function(){ + return newPostPath({ + path: filename + }, true); + }).then(function(target){ + target.should.eql(path); + return fs.unlink(path); + }); + }); + + it('rename if target existed', function(){ + var filename = [ + 'test.md', + 'test-1.md', + 'test-2.md', + 'test-foo.md' + ]; + + var path = filename.map(function(item){ + return pathFn.join(postDir, item); + }); + + return Promise.map(path, function(item){ + return fs.writeFile(item, ''); + }).then(function(){ + return newPostPath({ + path: filename[0] + }); + }).then(function(target){ + target.should.eql(pathFn.join(postDir, 'test-3.md')); + return path; + }).map(function(item){ + return fs.unlink(item); + }); + }); + + it('data is required', function(){ + return newPostPath().catch(function(err){ + err.should.have.property('message', 'Either data.path or data.slug is required!'); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/post_permalink.js b/test/scripts/filters/post_permalink.js new file mode 100644 index 0000000000..793aefba25 --- /dev/null +++ b/test/scripts/filters/post_permalink.js @@ -0,0 +1,71 @@ +var should = require('chai').should(); +var moment = require('moment'); + +var PERMALINK = ':year/:month/:day/:title/'; + +describe('post_permalink', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var postPermalink = require('../../../lib/plugins/filter/post_permalink').bind(hexo); + var Post = hexo.model('Post'); + var post; + + hexo.config.permalink = PERMALINK; + + before(function(){ + var id; + + return hexo.init().then(function(){ + return Post.insert({ + source: 'foo.md', + slug: 'foo', + date: moment('2014-01-02') + }); + }).then(function(post){ + id = post._id; + return post.setCategories(['foo', 'bar']); + }).then(function(){ + post = Post.findById(id); + }); + }); + + it('default', function(){ + postPermalink(post).should.eql('2014/01/02/foo/'); + }); + + it('categories', function(){ + hexo.config.permalink = ':category/:title/'; + postPermalink(post).should.eql('foo/bar/foo/'); + hexo.config.permalink = PERMALINK; + }); + + it('uncategorized', function(){ + hexo.config.permalink = ':category/:title/'; + + return Post.insert({ + source: 'bar.md', + slug: 'bar' + }).then(function(post){ + postPermalink(post).should.eql(hexo.config.default_category + '/bar/'); + hexo.config.permalink = PERMALINK; + return Post.removeById(post._id); + }); + }); + + it('extra data', function(){ + hexo.config.permalink = ':layout/:title/'; + postPermalink(post).should.eql(post.layout + '/foo/'); + hexo.config.permalink = PERMALINK; + }); + + it('id', function(){ + hexo.config.permalink = ':id'; + + postPermalink(post).should.eql(post._id); + + post.id = 1; + postPermalink(post).should.eql('1'); + + hexo.config.permalink = PERMALINK; + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/titlecase.js b/test/scripts/filters/titlecase.js new file mode 100644 index 0000000000..6b86d1b635 --- /dev/null +++ b/test/scripts/filters/titlecase.js @@ -0,0 +1,25 @@ +var should = require('chai').should(); + +describe('Titlecase', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var titlecase = require('../../../lib/plugins/filter/before_post_render/titlecase').bind(hexo); + + it('disabled', function(){ + var title = 'toDAy IS A gOOd DaY'; + var data = {title: title}; + hexo.config.titlecase = false; + + titlecase(data); + data.title.should.eql(title); + }); + + it('enabled', function(){ + var title = 'toDAy IS A gOOd DaY'; + var data = {title: title}; + hexo.config.titlecase = true; + + titlecase(data); + data.title.should.eql('Today Is a Good Day'); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/date.js b/test/scripts/helpers/date.js new file mode 100644 index 0000000000..134604da3e --- /dev/null +++ b/test/scripts/helpers/date.js @@ -0,0 +1,101 @@ +var moment = require('moment'); +var should = require('chai').should(); + +describe('date', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var date = require('../../../lib/plugins/helper/date'); + + it('date', function(){ + // moment + var now = moment(); + date.date.call(hexo, now).should.eql(now.format(hexo.config.date_format)); + date.date(now, 'MMM-D-YYYY').should.eql(now.format('MMM-D-YYYY')); + + // date + now = new Date(); + date.date.call(hexo, now).should.eql(moment(now).format(hexo.config.date_format)); + date.date(now, 'MMM-D-YYYY').should.eql(moment(now).format('MMM-D-YYYY')); + + // number + now = Date.now(); + date.date.call(hexo, now).should.eql(moment(now).format(hexo.config.date_format)); + date.date(now, 'MMM-D-YYYY').should.eql(moment(now).format('MMM-D-YYYY')); + }); + + it('date_xml', function(){ + // moment + var now = moment(); + date.date_xml(now).should.eql(now.toISOString()); + + // date + now = new Date(); + date.date_xml(now).should.eql(now.toISOString()); + + // number + now = Date.now(); + date.date_xml(now).should.eql(new Date(now).toISOString()); + }); + + it('time', function(){ + // moment + var now = moment(); + date.time.call(hexo, now).should.eql(now.format(hexo.config.time_format)); + date.time(now, 'H:mm').should.eql(now.format('H:mm')); + + // date + now = new Date(); + date.time.call(hexo, now).should.eql(moment(now).format(hexo.config.time_format)); + date.time(now, 'H:mm').should.eql(moment(now).format('H:mm')); + + // number + now = Date.now(); + date.time.call(hexo, now).should.eql(moment(now).format(hexo.config.time_format)); + date.time(now, 'H:mm').should.eql(moment(now).format('H:mm')); + }); + + it('full_date', function(){ + var fullDate = date.full_date.bind({ + config: hexo.config, + date: date.date, + time: date.time + }); + + // moment + var now = moment(); + fullDate(now).should.eql(now.format(hexo.config.date_format + ' ' + hexo.config.time_format)); + fullDate(now, 'MMM-D-YYYY').should.eql(now.format('MMM-D-YYYY')); + + // date + now = new Date(); + fullDate(now).should.eql(moment(now).format(hexo.config.date_format + ' ' + hexo.config.time_format)); + fullDate(now, 'MMM-D-YYYY').should.eql(moment(now).format('MMM-D-YYYY')); + + // number + now = Date.now(); + fullDate(now).should.eql(moment(now).format(hexo.config.date_format + ' ' + hexo.config.time_format)); + fullDate(now, 'MMM-D-YYYY').should.eql(moment(now).format('MMM-D-YYYY')); + }); + + it('time_tag', function(){ + var timeTag = date.time_tag.bind({ + config: hexo.config, + date: date.date + }); + + // moment + var now = moment(); + timeTag(now).should.eql('<time datetime="' + now.toISOString() + '">' + now.format(hexo.config.date_format) + '</time>'); + timeTag(now, 'MMM-D-YYYY').should.eql('<time datetime="' + now.toISOString() + '">' + now.format('MMM-D-YYYY') + '</time>'); + + // date + now = new Date(); + timeTag(now).should.eql('<time datetime="' + moment(now).toISOString() + '">' + moment(now).format(hexo.config.date_format) + '</time>'); + timeTag(now, 'MMM-D-YYYY').should.eql('<time datetime="' + moment(now).toISOString() + '">' + moment(now).format('MMM-D-YYYY') + '</time>'); + + // number + now = Date.now(); + timeTag(now).should.eql('<time datetime="' + moment(now).toISOString() + '">' + moment(now).format(hexo.config.date_format) + '</time>'); + timeTag(now, 'MMM-D-YYYY').should.eql('<time datetime="' + moment(now).toISOString() + '">' + moment(now).format('MMM-D-YYYY') + '</time>'); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/form.js b/test/scripts/helpers/form.js new file mode 100644 index 0000000000..c91d6196be --- /dev/null +++ b/test/scripts/helpers/form.js @@ -0,0 +1,48 @@ +var should = require('chai').should(); + +describe('form', function(){ + var form = require('../../../lib/plugins/helper/form'); + + describe('search_form', function(){ + var searchForm = form.search_form.bind({ + config: {url: 'http://hexo.io'} + }); + + it('default', function(){ + searchForm().should.eql('<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form">' + + '<input type="search" name="q" results="0" class="search-form-input" placeholder="Search">' + + '<input type="hidden" name="q" value="site:http://hexo.io">' + + '</form>'); + }); + + it('class', function(){ + searchForm({class: 'foo'}).should.eql('<form action="//google.com/search" method="get" accept-charset="UTF-8" class="foo">' + + '<input type="search" name="q" results="0" class="foo-input" placeholder="Search">' + + '<input type="hidden" name="q" value="site:http://hexo.io">' + + '</form>'); + }); + + it('text', function(){ + searchForm({text: 'Find'}).should.eql('<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form">' + + '<input type="search" name="q" results="0" class="search-form-input" placeholder="Find">' + + '<input type="hidden" name="q" value="site:http://hexo.io">' + + '</form>'); + }); + + it('button enabled', function(){ + searchForm({button: true, text: 'Find'}).should.eql('<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form">' + + '<input type="search" name="q" results="0" class="search-form-input" placeholder="Find">' + + '<button type="submit" class="search-form-submit">Find</button>' + + '<input type="hidden" name="q" value="site:http://hexo.io">' + + '</form>'); + }); + + it('button text', function(){ + searchForm({button: 'Go', text: 'Find'}).should.eql('<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form">' + + '<input type="search" name="q" results="0" class="search-form-input" placeholder="Find">' + + '<button type="submit" class="search-form-submit">Go</button>' + + '<input type="hidden" name="q" value="site:http://hexo.io">' + + '</form>'); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/gravatar.js b/test/scripts/helpers/gravatar.js new file mode 100644 index 0000000000..9b0c2bfeab --- /dev/null +++ b/test/scripts/helpers/gravatar.js @@ -0,0 +1,29 @@ +var crypto = require('crypto'), + should = require('chai').should(); + +describe('gravatar', function(){ + var gravatar = require('../../../lib/plugins/helper/gravatar'); + + function md5(str){ + return crypto.createHash('md5').update(str).digest('hex'); + } + + var email = 'abc@abc.com'; + var hash = md5(email); + + it('default', function(){ + gravatar(email).should.eql('http://www.gravatar.com/avatar/' + hash); + }); + + it('size', function(){ + gravatar(email, 100).should.eql('http://www.gravatar.com/avatar/' + hash + '?s=100'); + }); + + it('options', function(){ + gravatar(email, { + s: 200, + r: 'pg', + d: 'mm' + }).should.eql('http://www.gravatar.com/avatar/' + hash + '?s=200&r=pg&d=mm'); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/index.js b/test/scripts/helpers/index.js new file mode 100644 index 0000000000..b95363c64c --- /dev/null +++ b/test/scripts/helpers/index.js @@ -0,0 +1,12 @@ +describe('Helpers', function(){ + require('./date'); + require('./form'); + require('./gravatar'); + require('./is'); + require('./number'); + require('./open_graph'); + require('./paginator'); + require('./tag'); + require('./toc'); + require('./url'); +}); \ No newline at end of file diff --git a/test/scripts/helpers/is.js b/test/scripts/helpers/is.js new file mode 100644 index 0000000000..7a408b9296 --- /dev/null +++ b/test/scripts/helpers/is.js @@ -0,0 +1,52 @@ +var should = require('chai').should(); + +describe('is', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var is = require('../../../lib/plugins/helper/is'); + + it('is_current', function(){ + is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo').should.be.true; + is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo/bar').should.be.true; + is.is_current.call({path: 'foo/bar', config: hexo.config}, 'foo/baz').should.be.false; + }); + + it('is_home', function(){ + var paginationDir = hexo.config.pagination_dir; + + is.is_home.call({path: '', config: hexo.config}).should.be.true; + is.is_home.call({path: paginationDir + '/2/', config: hexo.config}).should.be.true; + }); + + it('is_post', function(){ + var config = { + permalink: ':id/:category/:year/:month/:day/:title' + }; + + is.is_post.call({path: '123/foo/bar/2013/08/12/foo-bar', config: config}).should.be.true; + }); + + it('is_archive', function(){ + var archiveDir = hexo.config.archive_dir; + + is.is_archive.call({path: archiveDir + '/', config: hexo.config}).should.be.true; + is.is_archive.call({path: archiveDir + '/2013', config: hexo.config}).should.be.true; + is.is_archive.call({path: archiveDir + '/2013/08', config: hexo.config}).should.be.true; + }); + + it('is_year', function(){ + is.is_archive.call({path: hexo.config.archive_dir + '/2013', config: hexo.config}).should.be.true; + }); + + it('is_month', function(){ + is.is_archive.call({path: hexo.config.archive_dir + '/2013/08', config: hexo.config}).should.be.true; + }); + + it('is_category', function(){ + is.is_category.call({path: hexo.config.category_dir + '/foo', config: hexo.config}).should.be.true; + }); + + it('is_tag', function(){ + is.is_tag.call({path: hexo.config.tag_dir + '/foo', config: hexo.config}).should.be.true; + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/number.js b/test/scripts/helpers/number.js new file mode 100644 index 0000000000..6237c0176e --- /dev/null +++ b/test/scripts/helpers/number.js @@ -0,0 +1,28 @@ +var should = require('chai').should(); + +describe('number', function(){ + var number = require('../../../lib/plugins/helper/number'); + + describe('number_format', function(){ + it('default', function(){ + number.number_format(1234.567).should.eql('1,234.567'); + }); + + it('precision', function(){ + number.number_format(1234.567, {precision: false}).should.eql('1,234.567'); + number.number_format(1234.567, {precision: 0}).should.eql('1,234'); + number.number_format(1234.567, {precision: 1}).should.eql('1,234.6'); + number.number_format(1234.567, {precision: 2}).should.eql('1,234.57'); + number.number_format(1234.567, {precision: 3}).should.eql('1,234.567'); + number.number_format(1234.567, {precision: 4}).should.eql('1,234.5670'); + }); + + it('delimiter', function(){ + number.number_format(1234.567, {delimiter: ' '}).should.eql('1 234.567'); + }); + + it('separator', function(){ + number.number_format(1234.567, {separator: '*'}).should.eql('1,234*567'); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/open_graph.js b/test/scripts/helpers/open_graph.js new file mode 100644 index 0000000000..d9b567abb7 --- /dev/null +++ b/test/scripts/helpers/open_graph.js @@ -0,0 +1,470 @@ +var should = require('chai').should(); + +describe('open_graph', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var openGraph = require('../../../lib/plugins/helper/open_graph'); + var isPost = require('../../../lib/plugins/helper/is').is_post; + var tag = require('../../../lib/util/html_tag'); + + function meta(options){ + return tag('meta', options); + } + + it('default', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('title - page', function(){ + var ctx = { + page: {title: 'Hello world'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: ctx.page.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: ctx.page.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('title - options', function(){ + var result = openGraph.call({ + page: {title: 'Hello world'}, + config: hexo.config, + is_post: isPost + }, {title: 'test'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: 'test'}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: 'test'}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('type - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {type: 'photo'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'photo'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('type - is_post', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: function(){ return true; } + }); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'article'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('url - context', function(){ + var ctx = { + page: {}, + config: hexo.config, + is_post: isPost, + url: 'http://hexo.io/foo' + }; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url', content: ctx.url}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('url - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost, + url: 'http://hexo.io/foo' + }, {url: 'http://hexo.io/bar'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url', content: 'http://hexo.io/bar'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it.skip('images - page', function(){ + // + }); + + it.skip('images - options', function(){ + // + }); + + it('site_name - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {site_name: 'foo'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: 'foo'}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('description - page', function(){ + var ctx = { + page: {description: 'test'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description', content: ctx.page.description}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: ctx.page.description}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: ctx.page.description}) + ].join('\n') + '\n'); + }); + + it('description - options', function(){ + var ctx = { + page: {description: 'test'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx, {description: 'foo'}); + + result.should.eql([ + meta({name: 'description', content: 'foo'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: 'foo'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: 'foo'}) + ].join('\n') + '\n'); + }); + + it('description - excerpt', function(){ + var ctx = { + page: {excerpt: 'test'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description', content: ctx.page.excerpt}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: ctx.page.excerpt}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: ctx.page.excerpt}) + ].join('\n') + '\n'); + }); + + it('description - content', function(){ + var ctx = { + page: {content: 'test'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description', content: ctx.page.content}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: ctx.page.content}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: ctx.page.content}) + ].join('\n') + '\n'); + }); + + it('description - config', function(){ + var ctx = { + page: {}, + config: hexo.config, + is_post: isPost + }; + + hexo.config.description = 'test'; + + var result = openGraph.call(ctx); + + result.should.eql([ + meta({name: 'description', content: hexo.config.description}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: hexo.config.description}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: hexo.config.description}) + ].join('\n') + '\n'); + + hexo.config.description = ''; + }); + + it('description - escape', function(){ + var ctx = { + page: {description: '<b>Important!</b> Today is "not" \'Xmas\'!'}, + config: hexo.config, + is_post: isPost + }; + + var result = openGraph.call(ctx); + var escaped = 'Important! Today is "not" 'Xmas'!'; + + result.should.eql([ + meta({name: 'description', content: escaped}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description', content: escaped}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description', content: escaped}) + ].join('\n') + '\n'); + }); + + it('twitter_card - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {twitter_card: 'photo'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'photo'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}) + ].join('\n') + '\n'); + }); + + it('twitter_id - options (without prefixing @)', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {twitter_id: 'hexojs'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + meta({name: 'twitter:creator', content: '@hexojs'}) + ].join('\n') + '\n'); + }); + + it('twitter_id - options (with prefixing @)', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {twitter_id: '@hexojs'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + meta({name: 'twitter:creator', content: '@hexojs'}) + ].join('\n') + '\n'); + }); + + it('twitter_site - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {twitter_site: 'Hello'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + meta({name: 'twitter:site', content: 'Hello'}) + ].join('\n') + '\n'); + }); + + it('google_plus - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {google_plus: '+123456789'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + tag('link', {rel: 'publisher', href: '+123456789'}) + ].join('\n') + '\n'); + }); + + it('fb_admins - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {fb_admins: '123456789'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + meta({property: 'fb:admins', content: '123456789'}) + ].join('\n') + '\n'); + }); + + it('fb_app_id - options', function(){ + var result = openGraph.call({ + page: {}, + config: hexo.config, + is_post: isPost + }, {fb_app_id: '123456789'}); + + result.should.eql([ + meta({name: 'description'}), + meta({property: 'og:type', content: 'website'}), + meta({property: 'og:title', content: hexo.config.title}), + meta({property: 'og:url'}), + meta({property: 'og:site_name', content: hexo.config.title}), + meta({property: 'og:description'}), + meta({name: 'twitter:card', content: 'summary'}), + meta({name: 'twitter:title', content: hexo.config.title}), + meta({name: 'twitter:description'}), + meta({property: 'fb:app_id', content: '123456789'}) + ].join('\n') + '\n'); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/paginator.js b/test/scripts/helpers/paginator.js new file mode 100644 index 0000000000..292e7f23d8 --- /dev/null +++ b/test/scripts/helpers/paginator.js @@ -0,0 +1,3 @@ +describe.skip('paginator', function(){ + // +}); \ No newline at end of file diff --git a/test/scripts/helpers/tag.js b/test/scripts/helpers/tag.js new file mode 100644 index 0000000000..07e4e78656 --- /dev/null +++ b/test/scripts/helpers/tag.js @@ -0,0 +1,372 @@ +var should = require('chai').should(); +var qs = require('querystring'); +var htmlTag = require('../../../lib/util/html_tag'); + +describe.skip('tag', function(){ + var tag = require('../../../lib/plugins/helper/tag'); + var context = require('../../../lib/plugins/helper/url'); + + describe('css', function(){ + var css = tag.css.bind(context); + + var genResult = function(arr){ + var result = ''; + + arr.forEach(function(item){ + result += htmlTag('link', {rel: 'stylesheet', href: item + '.css', type: 'text/css'}) + '\n'; + }); + + return result; + }; + + it('a string', function(){ + var result = genResult(['/style']); + + css('style').should.eql(result); + css('style.css').should.eql(result); + + css('http://zespia.tw/style.css').should.eql(genResult(['http://zespia.tw/style'])); + css('//zespia.tw/style.css').should.eql(genResult(['//zespia.tw/style'])); + }); + + it('an array', function(){ + var result = genResult(['/foo', '/bar', '/baz']); + + css(['foo', 'bar', 'baz']).should.eql(result); + }); + + it('multiple strings', function(){ + var result = genResult(['/foo', '/bar', '/baz']); + + css('foo', 'bar', 'baz').should.eql(result); + }); + + it('multiple arrays', function(){ + var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); + + css(['s1', 's2', 's3'], ['s4', 's5'], ['s6']).should.eql(result); + }); + + it('mixed', function(){ + var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); + + css(['s1', 's2'], 's3', 's4', ['s5'], 's6').should.eql(result); + }); + }); + + describe('js', function(){ + var js = tag.js.bind(context); + + var genResult = function(arr){ + var result = ''; + + arr.forEach(function(item){ + result += htmlTag('script', {src: item + '.js', type: 'text/javascript'}, '') + '\n'; + }); + + return result; + }; + + it('a string', function(){ + var result = genResult(['/script']); + + js('script').should.eql(result); + js('script.js').should.eql(result); + + js('http://code.jquery.com/jquery-2.0.3.min.js').should.eql(genResult(['http://code.jquery.com/jquery-2.0.3.min'])); + js('//code.jquery.com/jquery-2.0.3.min.js').should.eql(genResult(['//code.jquery.com/jquery-2.0.3.min'])); + }); + + it('an array', function(){ + var result = genResult(['/foo', '/bar', '/baz']); + + js(['foo', 'bar', 'baz']).should.eql(result); + }); + + it('multiple strings', function(){ + var result = genResult(['/foo', '/bar', '/baz']); + + js('foo', 'bar', 'baz').should.eql(result); + }); + + it('multiple arrays', function(){ + var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); + + js(['s1', 's2', 's3'], ['s4', 's5'], ['s6']).should.eql(result); + }); + + it('mixed', function(){ + var result = genResult(['/s1', '/s2', '/s3', '/s4', '/s5', '/s6']); + + js(['s1', 's2'], 's3', 's4', ['s5'], 's6').should.eql(result); + }); + }); + + describe('link_to', function(){ + var link_to = tag.link_to.bind(context), + url = 'http://zespia.tw/', + text = 'Zespia'; + + it('path', function(){ + var text = url.replace(/^https?:\/\//, ''); + + link_to(url).should.eql(htmlTag('a', { + href: url, + title: text + }, text)); + }); + + it('title', function(){ + link_to(url, text).should.eql(htmlTag('a', { + href: url, + title: text + }, text)); + }); + + it('external (boolean)', function(){ + link_to(url, text, true).should.eql(htmlTag('a', { + href: url, + title: text, + target: '_blank', + rel: 'external' + }, text)); + }); + + it('external (options)', function(){ + link_to(url, text, {external: true}).should.eql(htmlTag('a', { + href: url, + title: text, + target: '_blank', + rel: 'external' + }, text)); + }); + + it('class (string)', function(){ + link_to(url, text, {class: 'foo bar'}).should.eql(htmlTag('a', { + href: url, + title: text, + class: 'foo bar' + }, text)); + }); + + it('class (array)', function(){ + link_to(url, text, {class: ['foo', 'bar']}).should.eql(htmlTag('a', { + href: url, + title: text, + class: 'foo bar' + }, text)); + }); + + it('id', function(){ + link_to(url, text, {id: 'foo'}).should.eql(htmlTag('a', { + href: url, + title: text, + id: 'foo' + }, text)); + }); + }); + + describe('mail_to', function(){ + var mail_to = tag.mail_to.bind(context), + url = 'abc@abc.com', + text = 'Email'; + + it('path', function(){ + mail_to(url).should.eql(htmlTag('a', { + href: 'mailto:' + url, + title: url + }, url)); + }); + + it('text', function(){ + mail_to(url, text).should.eql(htmlTag('a', { + href: 'mailto:' + url, + title: text + }, text)); + }); + + it('class (string)', function(){ + mail_to(url, text, {class: 'foo bar'}).should.eql(htmlTag('a', { + href: 'mailto:' + url, + title: text, + class: 'foo bar' + }, text)); + }); + + it('class (array)', function(){ + mail_to(url, text, {class: ['foo', 'bar']}).should.eql(htmlTag('a', { + href: 'mailto:' + url, + title: text, + class: 'foo bar' + }, text)); + }); + + it('id', function(){ + mail_to(url, text, {id: 'foo'}).should.eql(htmlTag('a', { + href: 'mailto:' + url, + title: text, + id: 'foo' + }, text)); + }); + + it('subject', function(){ + var data = {subject: 'Hello World'}, + querystring = qs.stringify(data); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + + it('cc (string)', function(){ + var data = {cc: 'abc@abc.com'}, + querystring = qs.stringify(data); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + + it('cc (array)', function(){ + var data = {cc: 'abc@abc.com,bcd@bcd.com'}, + querystring = qs.stringify(data); + + data.cc = data.cc.split(','); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + + it('bcc (string)', function(){ + var data = {bcc: 'abc@abc.com'}, + querystring = qs.stringify(data); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + + it('bcc (array)', function(){ + var data = {bcc: 'abc@abc.com,bcd@bcd.com'}, + querystring = qs.stringify(data); + + data.bcc = data.bcc.split(','); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + + it('body', function(){ + var data = {body: 'Lorem Ipsum'}, + querystring = qs.stringify(data); + + mail_to(url, text, data).should.eql(htmlTag('a', { + href: 'mailto:' + url + '?' + querystring, + title: text + }, text)); + }); + }); + + describe('image_tag', function(){ + var image_tag = tag.image_tag.bind(context), + url = 'http://haha.com/some_img.jpg', + text = 'An image'; + + it('path', function(){ + image_tag(url).should.eql(htmlTag('img', { + src: url + })); + }); + + it('alt', function(){ + image_tag(url, {alt: text}).should.eql(htmlTag('img', { + src: url, + alt: text + })); + }); + + it('class (string)', function(){ + image_tag(url, {class: 'foo bar'}).should.eql(htmlTag('img', { + src: url, + class: 'foo bar' + })); + }); + + it('class (array)', function(){ + image_tag(url, {class: ['foo', 'bar']}).should.eql(htmlTag('img', { + src: url, + class: 'foo bar' + })); + }); + + it('id', function(){ + image_tag(url, {id: 'foo'}).should.eql(htmlTag('img', { + src: url, + id: 'foo' + })); + }); + + it('width', function(){ + image_tag(url, {width: 100}).should.eql(htmlTag('img', { + src: url, + width: 100 + })); + }); + + it('height', function(){ + image_tag(url, {height: 100}).should.eql(htmlTag('img', { + src: url, + height: 100 + })); + }); + }); + + describe('favicon_tag', function(){ + var favicon_tag = tag.favicon_tag.bind(context), + path = '/favicon.ico'; + + it('path', function(){ + favicon_tag(path).should.eql(htmlTag('link', { + rel: 'shortcut icon', + href: path + })); + }); + }); + + describe('feed_tag', function(){ + var feed_tag = tag.feed_tag.bind(context), + url = '/atom.xml', + text = 'Feed Title'; + + var attrs = { + rel: 'alternative', + href: url, + title: '', + type: 'application/atom+xml' + }; + + it('path', function(){ + attrs.title = 'Hello world'; + feed_tag(url).should.eql(htmlTag('link', attrs)); + }); + + it('title', function(){ + attrs.title = 'Hello world'; + feed_tag(url, {title: text}).should.eql(htmlTag('link', attrs)); + }); + + it('type', function(){ + attrs.title = 'Hello world'; + attrs.type = 'application/rss+xml'; + + feed_tag(url, {type: 'rss'}).should.eql(htmlTag('link', attrs)); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/toc.js b/test/scripts/helpers/toc.js new file mode 100644 index 0000000000..541bdebc10 --- /dev/null +++ b/test/scripts/helpers/toc.js @@ -0,0 +1,110 @@ +var should = require('chai').should(); +var _ = require('lodash'); + +describe('toc', function(){ + var toc = require('../../../lib/plugins/helper/toc'); + + var html = [ + '<h1 id="title_1">Title 1</h1>', + '<h2 id="title_1_1">Title 1.1</h2>', + '<h3 id="title_1_1_1">Title 1.1.1</h3>', + '<h2 id="title_1_2">Title 1.2</h2>', + '<h2 id="title_1_3">Title 1.3</h2>', + '<h3 id="title_1_3_1">Title 1.3.1</h3>', + '<h1 id="title_2">Title 2</h1>', + '<h2 id="title_2_1">Title 2.1</h2>' + ].join(''); + + var genResult = function(options){ + options = _.extend({ + class: 'toc', + list_number: true + }, options); + + var className = options.class, + listNumber = options.list_number; + + var result = [ + '<ol class="' + className + '">', + '<li class="' + className + '-item ' + className + '-level-1">', + '<a class="' + className + '-link" href="#title_1">', + (listNumber ? '<span class="' + className + '-number">1.</span> ' : ''), + '<span class="' + className + '-text">Title 1</span>', + '</a>', + '<ol class="' + className + '-child">', + '<li class="' + className + '-item ' + className + '-level-2">', + '<a class="' + className + '-link" href="#title_1_1">', + (listNumber ? '<span class="' + className + '-number">1.1.</span> ' : ''), + '<span class="' + className + '-text">Title 1.1</span>', + '</a>', + '<ol class="' + className + '-child">', + '<li class="' + className + '-item ' + className + '-level-3">', + '<a class="' + className + '-link" href="#title_1_1_1">', + (listNumber ? '<span class="' + className + '-number">1.1.1.</span> ' : ''), + '<span class="' + className + '-text">Title 1.1.1</span>', + '</a>', + '</li>', + '</ol>', + '</li>', + '<li class="' + className + '-item ' + className + '-level-2">', + '<a class="' + className + '-link" href="#title_1_2">', + (listNumber ? '<span class="' + className + '-number">1.2.</span> ' : ''), + '<span class="' + className + '-text">Title 1.2</span>', + '</a>', + '</li>', + '<li class="' + className + '-item ' + className + '-level-2">', + '<a class="' + className + '-link" href="#title_1_3">', + (listNumber ? '<span class="' + className + '-number">1.3.</span> ' : ''), + '<span class="' + className + '-text">Title 1.3</span>', + '</a>', + '<ol class="' + className + '-child">', + '<li class="' + className + '-item ' + className + '-level-3">', + '<a class="' + className + '-link" href="#title_1_3_1">', + (listNumber ? '<span class="' + className + '-number">1.3.1.</span> ' : ''), + '<span class="' + className + '-text">Title 1.3.1</span>', + '</a>', + '</li>', + '</ol>', + '</li>', + '</ol>', + '</li>', + '<li class="' + className + '-item ' + className + '-level-1">', + '<a class="' + className + '-link" href="#title_2">', + (listNumber ? '<span class="' + className + '-number">2.</span> ' : ''), + '<span class="' + className + '-text">Title 2</span>', + '</a>', + '<ol class="' + className + '-child">', + '<li class="' + className + '-item ' + className + '-level-2">', + '<a class="' + className + '-link" href="#title_2_1">', + (listNumber ? '<span class="' + className + '-number">2.1.</span> ' : ''), + '<span class="' + className + '-text">Title 2.1</span>', + '</a>', + '</li>', + '</ol>', + '</li>', + '</ol>' + ].join(''); + + return result; + }; + + it('default', function(){ + genResult().should.eql(toc(html)); + }); + + it('class', function(){ + var options = { + class: 'foo' + }; + + genResult(options).should.eql(toc(html, options)); + }); + + it('list_number', function(){ + var options = { + list_number: false + }; + + genResult(options).should.eql(toc(html, options)); + }); +}); \ No newline at end of file diff --git a/test/scripts/helpers/url.js b/test/scripts/helpers/url.js new file mode 100644 index 0000000000..71af0e37df --- /dev/null +++ b/test/scripts/helpers/url.js @@ -0,0 +1,60 @@ +var should = require('chai').should(); +var urlHelper = require('../../../lib/plugins/helper/url'); +var relative_url = urlHelper.relative_url; + +describe('relative_url', function(){ + it('from root', function(){ + relative_url('', 'foo/').should.eql('foo'); + relative_url('/', 'bar/').should.eql('bar'); + }); + + it('from same root', function(){ + relative_url('foo/', 'foo/bar/').should.eql('bar'); + }); + + it('from different root', function(){ + relative_url('foo/', 'bar/baz/').should.eql('../bar/baz'); + }); +}); + +describe('url_for', function(){ + var url_for = require('../../../lib/plugins/helper/url').url_for; + + it('internal url (relative off)', function(){ + url_for.call({ + config: {root: '/'}, + relative_url: relative_url + }, 'index.html').should.eql('/index.html'); + + url_for.call({ + config: {root: '/blog/'}, + relative_url: relative_url + }, 'index.html').should.eql('/blog/index.html'); + }); + + it('internal url (relative on)', function(){ + url_for.call({ + config: {root: '/', relative_link: true}, + path: '', + relative_url: relative_url + }, 'index.html').should.eql('index.html'); + + url_for.call({ + config: {root: '/', relative_link: true}, + path: 'foo/bar/', + relative_url: relative_url + }, 'index.html').should.eql('../../index.html'); + }); + + it('external url', function(){ + [ + 'http://zespia.tw/', + '//google.com/' + ].forEach(function(url){ + url_for.call({ + config: {}, + relative_url: relative_url + }, url).should.eql(url); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/hexo/render.js b/test/scripts/hexo/render.js index a0f8279929..d630bd832f 100644 --- a/test/scripts/hexo/render.js +++ b/test/scripts/hexo/render.js @@ -5,10 +5,9 @@ var pathFn = require('path'); var fixtureDir = pathFn.join(__dirname, '../../fixtures'); describe('Render', function(){ - var hexo; + var hexo = new Hexo(__dirname); before(function(){ - hexo = new Hexo(__dirname); return hexo.init(); }); diff --git a/test/scripts/models/moment.js b/test/scripts/models/moment.js index 0c0e0956c6..8270dc311e 100644 --- a/test/scripts/models/moment.js +++ b/test/scripts/models/moment.js @@ -22,11 +22,7 @@ describe('SchemaTypeMoment', function(){ } it('validate()', function(){ - type.validate(moment(1e8)).valueOf().should.eql(1e8); - // shouldThrowError('foo'); - // shouldThrowError([]); - // shouldThrowError(true); - // shouldThrowError({}); + type.validate(moment('2014-11-03T07:45:41.237Z')).should.eql('2014-11-03T07:45:41.237Z'); shouldThrowError(moment.invalid()); }); diff --git a/test/scripts/models/page.js b/test/scripts/models/page.js index 94d5054080..5be767a535 100644 --- a/test/scripts/models/page.js +++ b/test/scripts/models/page.js @@ -20,6 +20,7 @@ describe('Page', function(){ data.layout.should.eql('page'); data.content.should.eql(''); data.excerpt.should.eql(''); + data.more.should.eql(''); data.raw.should.eql(''); return Page.removeById(data._id); diff --git a/test/scripts/tags/blockquote.js b/test/scripts/tags/blockquote.js new file mode 100644 index 0000000000..aa1cbe69f8 --- /dev/null +++ b/test/scripts/tags/blockquote.js @@ -0,0 +1,44 @@ +var cheerio = require('cheerio'), + should = require('chai').should(); + +describe.skip('blockquote', function(){ + var blockquote = require('../../../lib/plugins/tag/blockquote'); + + var bq = function(args){ + var result = blockquote(args.split(' '), '123456 **bold** and *italic*'); + + return result.replace(/<escape>(.*?)<\/escape>/g, '$1'); + }; + + it('author', function(){ + var $ = cheerio.load(bq('John Doe')); + + $('blockquote footer strong').html().should.eql('John Doe'); + }); + + it('author + source', function(){ + var $ = cheerio.load(bq('John Doe, A book')); + + $('blockquote footer strong').html().should.eql('John Doe'); + $('blockquote footer cite').html().should.eql('A book'); + }); + + it('author + link', function(){ + var $ = cheerio.load(bq('John Doe http://zespia.tw')); + + $('blockquote footer strong').html().should.eql('John Doe'); + $('blockquote footer cite').html().should.eql('<a href="http://zespia.tw">zespia.tw/</a>'); + + $ = cheerio.load(bq('John Doe http://zespia.tw/this/is/a/fucking/long/url')); + + $('blockquote footer strong').html().should.eql('John Doe'); + $('blockquote footer cite').html().should.eql('<a href="http://zespia.tw/this/is/a/fucking/long/url">zespia.tw/this/is/a/fucking/…</a>'); + }); + + it('author + link + title', function(){ + var $ = cheerio.load(bq('John Doe http://zespia.tw My Blog')); + + $('blockquote footer strong').html().should.eql('John Doe'); + $('blockquote footer cite').html().should.eql('<a href="http://zespia.tw">My Blog</a>'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/code.js b/test/scripts/tags/code.js new file mode 100644 index 0000000000..fc88461487 --- /dev/null +++ b/test/scripts/tags/code.js @@ -0,0 +1,50 @@ +var cheerio = require('cheerio'), + should = require('chai').should(), + highlight = require('../../../lib/util/highlight'); + +describe.skip('code', function(){ + var code = require('../../../lib/plugins/tag/code'); + + var dummy = [ + 'var dummy = function(){', + ' alert("dummy");', + '});' + ].join('\n'); + + var content = cheerio.load(highlight(dummy))('table').html(); + + it('content', function(){ + var $ = cheerio.load(code([], dummy)); + + $('figure').attr('class').should.eql('highlight'); + $('figure table').html().should.eql(content); + }); + + it('lang', function(){ + var $ = cheerio.load(code('lang:js'.split(' '), '')); + + $('figure').attr('class').should.eql('highlight js'); + }); + + it('title', function(){ + var $ = cheerio.load(code('Code block test'.split(' '), '')); + + $('figcaption span').html().should.eql('Code block test'); + }); + + it('title + url', function(){ + var $ = cheerio.load(code('Code block test http://zespia.tw'.split(' '), '')); + + $('figcaption span').html().should.eql('Code block test'); + $('figcaption a').attr('href').should.eql('http://zespia.tw'); + $('figcaption a').html().should.eql('link'); + }); + + it('title + url + link', function(){ + var $ = cheerio.load(code('Code block test http://zespia.tw My blog'.split(' '), '')); + + $('figcaption span').html().should.eql('Code block test'); + $('figcaption a').attr('href').should.eql('http://zespia.tw'); + $('figcaption a').html().should.eql('My blog'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/gist.js b/test/scripts/tags/gist.js new file mode 100644 index 0000000000..afb52f4f70 --- /dev/null +++ b/test/scripts/tags/gist.js @@ -0,0 +1,16 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('gist', function(){ + var gist = require('../../../lib/plugins/tag/gist'); + + it('id', function(){ + var $ = cheerio.load(gist(['foo'])); + $('script').attr('src').should.eql('//gist.github.com/foo.js'); + }); + + it('file', function(){ + var $ = cheerio.load(gist(['foo', 'bar'])); + $('script').attr('src').should.eql('//gist.github.com/foo.js?file=bar'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/iframe.js b/test/scripts/tags/iframe.js new file mode 100644 index 0000000000..5eb87d806b --- /dev/null +++ b/test/scripts/tags/iframe.js @@ -0,0 +1,36 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('iframe', function(){ + var iframe = require('../../../lib/plugins/tag/iframe'); + + it('url', function(){ + var $ = cheerio.load(iframe(['http://zespia.tw'])); + + $('iframe').attr('src').should.eql('http://zespia.tw'); + $('iframe').attr('width').should.eql('100%'); + $('iframe').attr('height').should.eql('300'); + $('iframe').attr('frameborder').should.eql('0'); + $('iframe').attr('allowfullscreen').should.eql(''); + }); + + it('width', function(){ + var $ = cheerio.load(iframe(['http://zespia.tw', '500'])); + + $('iframe').attr('src').should.eql('http://zespia.tw'); + $('iframe').attr('width').should.eql('500'); + $('iframe').attr('height').should.eql('300'); + $('iframe').attr('frameborder').should.eql('0'); + $('iframe').attr('allowfullscreen').should.eql(''); + }); + + it('height', function(){ + var $ = cheerio.load(iframe(['http://zespia.tw', '500', '600'])); + + $('iframe').attr('src').should.eql('http://zespia.tw'); + $('iframe').attr('width').should.eql('500'); + $('iframe').attr('height').should.eql('600'); + $('iframe').attr('frameborder').should.eql('0'); + $('iframe').attr('allowfullscreen').should.eql(''); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/img.js b/test/scripts/tags/img.js new file mode 100644 index 0000000000..2902924244 --- /dev/null +++ b/test/scripts/tags/img.js @@ -0,0 +1,81 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('img', function(){ + var img = require('../../../lib/plugins/tag/img'); + + it('src', function(){ + var $ = cheerio.load(img(['http://placekitten.com/200/300'])); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + }); + + it('class + src', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + }); + + it('multiple classes + src', function(){ + var $ = cheerio.load(img('left top http://placekitten.com/200/300'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left top'); + }); + + it('class + src + width', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 200'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('width').should.eql('200'); + }); + + it('class + src + width + height', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('width').should.eql('200'); + $('img').attr('height').should.eql('300'); + }); + + it('class + src + title', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 Place Kitten'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('title').should.eql('Place Kitten'); + }); + + it('class + src + width + title', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 200 Place Kitten'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('width').should.eql('200'); + $('img').attr('title').should.eql('Place Kitten'); + }); + + it('class + src + width + height + title', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300 Place Kitten'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('width').should.eql('200'); + $('img').attr('height').should.eql('300'); + $('img').attr('title').should.eql('Place Kitten'); + }); + + it('class + src + width + height + title + alt', function(){ + var $ = cheerio.load(img('left http://placekitten.com/200/300 200 300 "Place Kitten" "A cute kitten"'.split(' '))); + + $('img').attr('src').should.eql('http://placekitten.com/200/300'); + $('img').attr('class').should.eql('left'); + $('img').attr('width').should.eql('200'); + $('img').attr('height').should.eql('300'); + $('img').attr('title').should.eql('Place Kitten'); + $('img').attr('alt').should.eql('A cute kitten'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/include_code.js b/test/scripts/tags/include_code.js new file mode 100644 index 0000000000..d48c1d75e6 --- /dev/null +++ b/test/scripts/tags/include_code.js @@ -0,0 +1,50 @@ +var cheerio = require('cheerio'); +var path = require('path'); +var should = require('chai').should(); +var fs = require('../../../lib/util/fs'); +var highlight = require('../../../lib/util/highlight'); + +// Fixture +function unique(arr){ + var a = [], + l = arr.length; + + for (var i = 0; i < l; i++){ + for (var j = i + 1; j < l; j++){ + if (arr[i] === arr[j]) j = ++i; + } + + a.push(arr[i]); + } + + return a; +} + +describe.skip('include_code', function(){ + var include_code = require('../../../lib/plugins/tag/include_code'), + raw = unique.toString(), + content = cheerio.load(highlight(raw))('table').html(); + + before(function(done){ + file.writeFile(path.join(hexo.source_dir, 'downloads', 'code', 'test.js'), raw, done); + }); + + it('file', function(){ + var $ = cheerio.load(include_code('test.js'.split(' '))); + + $('figure').attr('class').should.eql('highlight js'); + $('figure table').html().should.eql(content); + }); + + it('title', function(){ + var $ = cheerio.load(include_code('Code block title test.js'.split(' '))); + + $('figcaption span').html().should.eql('Code block title'); + }); + + it('lang', function(){ + var $ = cheerio.load(include_code('lang:javascript test.js'.split(' '))); + + $('figure').attr('class').should.eql('highlight javascript'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/index.js b/test/scripts/tags/index.js new file mode 100644 index 0000000000..49d5678875 --- /dev/null +++ b/test/scripts/tags/index.js @@ -0,0 +1,14 @@ +describe('Tags', function(){ + require('./blockquote'); + require('./code'); + require('./gist'); + require('./iframe'); + require('./img'); + require('./include_code'); + require('./jsfiddle'); + require('./link'); + require('./pullquote'); + require('./raw'); + require('./vimeo'); + require('./youtube'); +}); \ No newline at end of file diff --git a/test/scripts/tags/jsfiddle.js b/test/scripts/tags/jsfiddle.js new file mode 100644 index 0000000000..c820f8b129 --- /dev/null +++ b/test/scripts/tags/jsfiddle.js @@ -0,0 +1,52 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('jsfiddle', function(){ + var jsfiddle = require('../../../lib/plugins/tag/jsfiddle'); + + it('id', function(){ + var $ = cheerio.load(jsfiddle(['foo'])); + + $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); + }); + + it('tabs', function(){ + var $ = cheerio.load(jsfiddle(['foo', 'default'])); + + $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); + + $ = cheerio.load(jsfiddle(['foo', 'html,css'])); + + $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/html,css/light'); + }); + + it('skin', function(){ + var $ = cheerio.load(jsfiddle(['foo', 'default', 'default'])); + + $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/light'); + + $ = cheerio.load(jsfiddle(['foo', 'default', 'dark'])); + + $('iframe').attr('src').should.eql('http://jsfiddle.net/foo/embedded/js,resources,html,css,result/dark'); + }); + + it('width', function(){ + var $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default'])); + + $('iframe').attr('width').should.eql('100%'); + + $ = cheerio.load(jsfiddle(['foo', 'default', 'default', '500'])); + + $('iframe').attr('width').should.eql('500'); + }); + + it('height', function(){ + var $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default', 'default'])); + + $('iframe').attr('height').should.eql('300'); + + $ = cheerio.load(jsfiddle(['foo', 'default', 'default', 'default', '500'])); + + $('iframe').attr('height').should.eql('500'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/link.js b/test/scripts/tags/link.js new file mode 100644 index 0000000000..e27b51cac6 --- /dev/null +++ b/test/scripts/tags/link.js @@ -0,0 +1,51 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('link', function(){ + var link = require('../../../lib/plugins/tag/link'); + + it('text + url', function(){ + var $ = cheerio.load(link('Click here to Google http://google.com'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + }); + + it('text + url + external', function(){ + var $ = cheerio.load(link('Click here to Google http://google.com true'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + $('a').attr('target').should.eql('_blank'); + + $ = cheerio.load(link('Click here to Google http://google.com false'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + should.not.exist($('a').attr('target')); + }); + + it('text + url + title', function(){ + var $ = cheerio.load(link('Click here to Google http://google.com Google link'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + $('a').attr('title').should.eql('Google link'); + }); + + it('text + url + external + title', function(){ + var $ = cheerio.load(link('Click here to Google http://google.com true Google link'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + $('a').attr('target').should.eql('_blank'); + $('a').attr('title').should.eql('Google link'); + + $ = cheerio.load(link('Click here to Google http://google.com false Google link'.split(' '))); + + $('a').attr('href').should.eql('http://google.com'); + $('a').html().should.eql('Click here to Google'); + should.not.exist($('a').attr('target')); + $('a').attr('title').should.eql('Google link'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/pullquote.js b/test/scripts/tags/pullquote.js new file mode 100644 index 0000000000..c9f9d53296 --- /dev/null +++ b/test/scripts/tags/pullquote.js @@ -0,0 +1,24 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('pullquote', function(){ + var pullquote = require('../../../lib/plugins/tag/pullquote'); + + var raw = '123456 **bold** and *italic*'; + + it('content', function(){ + var $ = cheerio.load(pullquote([], raw)); + + $('blockquote').attr('class').should.eql('pullquote'); + }); + + it('class', function(){ + var $ = cheerio.load(pullquote(['foo'], raw)); + + $('blockquote').attr('class').should.eql('pullquote foo'); + + $ = cheerio.load(pullquote(['foo', 'bar'], raw)); + + $('blockquote').attr('class').should.eql('pullquote foo bar'); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/raw.js b/test/scripts/tags/raw.js new file mode 100644 index 0000000000..d235453be8 --- /dev/null +++ b/test/scripts/tags/raw.js @@ -0,0 +1,10 @@ +var should = require('chai').should(); + +describe('raw', function(){ + var raw = require('../../../lib/plugins/tag/raw'); + + it('content', function(){ + var content = '123456789<b>strong</b>987654321'; + raw([], content).should.eql(content); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/vimeo.js b/test/scripts/tags/vimeo.js new file mode 100644 index 0000000000..df0f1ed295 --- /dev/null +++ b/test/scripts/tags/vimeo.js @@ -0,0 +1,15 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('vimeo', function(){ + var vimeo = require('../../../lib/plugins/tag/vimeo'); + + it('id', function(){ + var $ = cheerio.load(vimeo(['foo'])); + + $('.video-container').html().should.be.ok; + $('iframe').attr('src').should.eql('//player.vimeo.com/video/foo'); + $('iframe').attr('frameborder').should.eql('0'); + $('iframe').attr('allowfullscreen').should.eql(''); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/youtube.js b/test/scripts/tags/youtube.js new file mode 100644 index 0000000000..56bfe62156 --- /dev/null +++ b/test/scripts/tags/youtube.js @@ -0,0 +1,15 @@ +var cheerio = require('cheerio'); +var should = require('chai').should(); + +describe('youtube', function(){ + var youtube = require('../../../lib/plugins/tag/youtube'); + + it('id', function(){ + var $ = cheerio.load(youtube(['foo'])); + + $('.video-container').html().should.be.ok; + $('iframe').attr('src').should.eql('//www.youtube.com/embed/foo'); + $('iframe').attr('frameborder').should.eql('0'); + $('iframe').attr('allowfullscreen').should.eql(''); + }); +}); \ No newline at end of file diff --git a/test/scripts/util/format.js b/test/scripts/util/format.js new file mode 100644 index 0000000000..7ba8121491 --- /dev/null +++ b/test/scripts/util/format.js @@ -0,0 +1,28 @@ +var should = require('chai').should(); + +describe('format', function(){ + var format = require('../../../lib/util/format'); + + it('stripHtml()', function(){ + format.stripHtml('Strip <i>these</i> tags!').should.eql('Strip these tags!'); + format.stripHtml('<b>Bold</b> no more! <a href="more.html">See more here</a>...') + .should.eql('Bold no more! See more here...'); + format.stripHtml('<div id="top-bar">Welcome to my website!</div>') + .should.eql('Welcome to my website!'); + }); + + it('trim()', function(){ + format.trim(' foo bar baz ').should.eql('foo bar baz'); + format.trim(' foo bar baz').should.eql('foo bar baz'); + format.trim('foo bar baz ').should.eql('foo bar baz'); + format.trim('foo bar baz').should.eql('foo bar baz'); + }); + + it.skip('wordWrap()', function(){ + // + }); + + it.skip('truncate()', function(){ + // + }); +}); \ No newline at end of file diff --git a/test/scripts/util/fs.js b/test/scripts/util/fs.js index aac01bfd83..3fc4f4ea36 100644 --- a/test/scripts/util/fs.js +++ b/test/scripts/util/fs.js @@ -1,7 +1,7 @@ -var should = require('chai').should(), - pathFn = require('path'), - Promise = require('bluebird'), - fs = require('../../../lib/util').fs; +var should = require('chai').should(); +var pathFn = require('path'); +var Promise = require('bluebird'); +var fs = require('../../../lib/util').fs; function createDummyFolder(path){ // TODO nested dummy files diff --git a/test/scripts/util/index.js b/test/scripts/util/index.js index ecfaac1a68..fdefac19b0 100644 --- a/test/scripts/util/index.js +++ b/test/scripts/util/index.js @@ -1,4 +1,5 @@ describe('Utilities', function(){ + require('./format'); require('./fs'); require('./html_tag'); require('./permalink'); diff --git a/test/scripts/util/router.js b/test/scripts/util/router.js index 2ad1b44fc1..ee9c8d7e20 100644 --- a/test/scripts/util/router.js +++ b/test/scripts/util/router.js @@ -64,6 +64,12 @@ describe('Router', function(){ }); it('remove()', function(){ - // + router.once('remove', function(source){ + source.should.eql('foo'); + }); + + router.set('foo', 'bar'); + router.remove('foo'); + should.not.exist(router.get('foo')); }); }); \ No newline at end of file From 8091bbf3aec3553e81f64749a0bb9f7872d54af3 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Sat, 6 Dec 2014 16:22:25 +0800 Subject: [PATCH 05/15] Create appveyor.yml --- .npmignore | 3 ++- appveyor.yml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 appveyor.yml diff --git a/.npmignore b/.npmignore index 3a43090e28..7c092c2113 100644 --- a/.npmignore +++ b/.npmignore @@ -5,4 +5,5 @@ coverage/ .jshintrc .travis.yml gulpfile.js -.idea/ \ No newline at end of file +.idea/ +appveyor.yml \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..b56277f5a2 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,37 @@ +# Reference: https://github.com/gruntjs/grunt/blob/master/appveyor.yml +# http://www.appveyor.com/docs/appveyor-yml + +# Fix line endings in Windows. (runs before repo cloning) +init: + - git config --global core.autocrlf input + + +# Test against these versions of Node.js. +environment: + matrix: + - nodejs_version: "0.10" + - nodejs_version: "0.11" + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node 0.STABLE.latest + - ps: Install-Product node $env:nodejs_version + # Typical npm stuff. + - npm install + - npm install gulp -g + +# Post-install test scripts. +test_script: + # Output useful info for debugging. + - node --version + - npm --version + # We test multiple Windows shells because of prior stdout buffering issues + # filed against Grunt. https://github.com/joyent/node/issues/3584 + - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging + - cmd: npm test + +# Don't actually build. +build: off + +# Set build version format here instead of in the admin panel. +version: "{build}" \ No newline at end of file From 6e75830cbaacef111c7b33d058d5fe3202a5bcca Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Mon, 8 Dec 2014 00:28:43 +0800 Subject: [PATCH 06/15] Add post, scaffold tests. Split out fs module. --- .travis.yml | 15 +- README.md | 2 +- appveyor.yml | 4 +- gulpfile.js | 5 +- lib/hexo/default_config.js | 61 ++++ lib/hexo/index.js | 66 +--- lib/hexo/post.js | 57 ++-- lib/hexo/scaffold.js | 11 +- lib/plugins/renderer/swig.js | 4 +- lib/plugins/renderer/yaml.js | 4 +- lib/util/escape.js | 14 +- lib/util/fs.js | 394 ---------------------- lib/util/index.js | 2 +- package.json | 10 +- test/scripts/filters/new_post_path.js | 2 +- test/scripts/hexo/index.js | 4 + test/scripts/hexo/load_config.js | 5 + test/scripts/hexo/post.js | 344 ++++++++++++++++++++ test/scripts/hexo/scaffold.js | 98 ++++++ test/scripts/hexo/update_package.js | 68 ++++ test/scripts/tags/include_code.js | 2 +- test/scripts/util/fs.js | 449 -------------------------- test/scripts/util/index.js | 1 - 23 files changed, 662 insertions(+), 960 deletions(-) create mode 100644 lib/hexo/default_config.js delete mode 100644 lib/util/fs.js create mode 100644 test/scripts/hexo/load_config.js create mode 100644 test/scripts/hexo/post.js create mode 100644 test/scripts/hexo/scaffold.js create mode 100644 test/scripts/hexo/update_package.js delete mode 100644 test/scripts/util/fs.js diff --git a/.travis.yml b/.travis.yml index fc06a90a27..f76a79adf8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,16 @@ language: node_js + node_js: - "0.10" - "0.11" -before_script: - - npm install -g gulp + script: - - gulp test - - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js \ No newline at end of file + - npm test + +after_script: + - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js + - cat ./coverage/lcov.info | ./node_modules/codeclimate-test-reporter/bin/codeclimate.js + +addons: + code_climate: + repo_token: 161a304c89a989fd09844b22ad0b3af84a930cea8915350b82ecd8fa12c92985 \ No newline at end of file diff --git a/README.md b/README.md index 344847838b..9388e3da50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Hexo -[![Build Status](https://travis-ci.org/hexojs/hexo.svg?branch=master)](https://travis-ci.org/hexojs/hexo) [![NPM version](https://badge.fury.io/js/hexo.svg)](http://badge.fury.io/js/hexo) +[![Build Status](https://travis-ci.org/hexojs/hexo.svg?branch=master)](https://travis-ci.org/hexojs/hexo) [![NPM version](https://badge.fury.io/js/hexo.svg)](http://badge.fury.io/js/hexo) [![Coverage Status](https://img.shields.io/coveralls/hexojs/hexo.svg)](https://coveralls.io/r/hexojs/hexo?branch=master) [![Code Climate](https://codeclimate.com/github/hexojs/hexo/badges/gpa.svg)](https://codeclimate.com/github/hexojs/hexo) [![Build status](https://ci.appveyor.com/api/projects/status/hpx3lduqjj2t6uqq/branch/master?svg=true)](https://ci.appveyor.com/project/tommy351/hexo/branch/master) A fast, simple & powerful blog framework, powered by [Node.js](http://nodejs.org). diff --git a/appveyor.yml b/appveyor.yml index b56277f5a2..7f6ade8648 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,10 @@ -# Reference: https://github.com/gruntjs/grunt/blob/master/appveyor.yml +# Based on https://github.com/gruntjs/grunt/blob/master/appveyor.yml # http://www.appveyor.com/docs/appveyor-yml # Fix line endings in Windows. (runs before repo cloning) init: - git config --global core.autocrlf input - # Test against these versions of Node.js. environment: matrix: @@ -18,7 +17,6 @@ install: - ps: Install-Product node $env:nodejs_version # Typical npm stuff. - npm install - - npm install gulp -g # Post-install test scripts. test_script: diff --git a/gulpfile.js b/gulpfile.js index c32b3a8d18..b0273d6734 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,8 @@ var test = 'test/scripts/**/*.js'; gulp.task('coverage', function(){ return gulp.src(lib) - .pipe($.istanbul()); + .pipe($.istanbul()) + .pipe($.istanbul.hookRequire()); }); gulp.task('coverage:clean', function(callback){ @@ -30,7 +31,7 @@ gulp.task('jshint', function(){ }); gulp.task('watch', function(){ - gulp.watch(lib, e['mocha', 'jshint']); + gulp.watch(lib, ['mocha', 'jshint']); gulp.watch(['test/index.js', test], ['mocha']); }); diff --git a/lib/hexo/default_config.js b/lib/hexo/default_config.js new file mode 100644 index 0000000000..09fa5ebe06 --- /dev/null +++ b/lib/hexo/default_config.js @@ -0,0 +1,61 @@ +module.exports = { + // Site + title: 'Hexo', + subtitle: '', + description: '', + author: 'John Doe', + email: '', + language: '', + // URL + url: 'http://yoursite.com', + root: '/', + permalink: ':year/:month/:day/:title/', + tag_dir: 'tags', + archive_dir: 'archives', + category_dir: 'categories', + code_dir: 'downloads/code', + permalink_defaults: {}, + // Directory + source_dir: 'source', + public_dir: 'public', + // Writing + new_post_name: ':title.md', + default_layout: 'post', + titlecase: false, + external_link: true, + filename_case: 0, + render_drafts: false, + post_asset_folder: false, + relative_link: false, + highlight: { + enable: true, + line_number: true, + tab_replace: '', + }, + // Category & Tag + default_category: 'uncategorized', + category_map: {}, + tag_map: {}, + // Archives + archive: 2, + category: 2, + tag: 2, + // Server + port: 4000, + server_ip: 'localhost', + logger: false, + logger_format: 'dev', + // Date / Time format + date_format: 'MMM D YYYY', + time_format: 'H:mm:ss', + // Pagination + per_page: 10, + pagination_dir: 'page', + // Disqus + disqus_shortname: '', + // Extensions + theme: 'landscape', + exclude_generator: [], + // Deployment + deploy: {} +}; \ No newline at end of file diff --git a/lib/hexo/index.js b/lib/hexo/index.js index b82b3b0895..b02c8ef791 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -3,6 +3,7 @@ var Promise = require('bluebird'); var pathFn = require('path'); var tildify = require('tildify'); var Database = require('warehouse'); +var _ = require('lodash'); var EventEmitter = require('events').EventEmitter; var pkg = require('../../package.json'); var createLogger = require('./create_logger'); @@ -12,6 +13,7 @@ var registerModels = require('./register_models'); var Post = require('./post'); var Scaffold = require('./scaffold'); var Router = util.Router; +var defaultConfig = require('./default_config'); var libDir = pathFn.dirname(__dirname); var dbVersion = 1; @@ -56,67 +58,7 @@ function Hexo(base, args){ tag: new extend.Tag() }; - this.config = { - // Site - title: 'Hexo', - subtitle: '', - description: '', - author: 'John Doe', - email: '', - language: '', - // URL - url: 'http://yoursite.com', - root: '/', - permalink: ':year/:month/:day/:title/', - tag_dir: 'tags', - archive_dir: 'archives', - category_dir: 'categories', - code_dir: 'downloads/code', - permalink_defaults: {}, - // Directory - source_dir: 'source', - public_dir: 'public', - // Writing - new_post_name: ':title.md', - default_layout: 'post', - titlecase: false, - external_link: true, - filename_case: 0, - render_drafts: false, - post_asset_folder: false, - relative_link: false, - highlight: { - enable: true, - line_number: true, - tab_replace: '', - }, - // Category & Tag - default_category: 'uncategorized', - category_map: {}, - tag_map: {}, - // Archives - archive: 2, - category: 2, - tag: 2, - // Server - port: 4000, - server_ip: 'localhost', - logger: false, - logger_format: 'dev', - // Date / Time format - date_format: 'MMM D YYYY', - time_format: 'H:mm:ss', - // Pagination - per_page: 10, - pagination_dir: 'page', - // Disqus - disqus_shortname: '', - // Extensions - theme: 'landscape', - exclude_generator: [], - // Deployment - deploy: {} - }; + this.config = _.clone(defaultConfig); this.log = createLogger(this.env); @@ -137,7 +79,7 @@ function Hexo(base, args){ this.locals = { get posts(){ - return db.model('Post').populate('categories tags'); + return db.model('Post'); }, get pages(){ return db.model('Page'); diff --git a/lib/hexo/post.js b/lib/hexo/post.js index f1edfce731..bf537dbc64 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -3,6 +3,7 @@ var swig = require('swig'); var Promise = require('bluebird'); var pathFn = require('path'); var _ = require('lodash'); +var yaml = require('js-yaml'); var util = require('../util'); var escape = util.escape; @@ -59,25 +60,36 @@ Post.prototype.create = function(data, replace, callback){ var split = yfm.split(scaffold); // Compile front-matter with data - var frontMatter = swig.compile(split.data)(data); + var content = swig.compile(split.data)(data) + '\n'; - // Concat compiled front-matter and the content part of the raw scaffold - var compiled = yfm.parse(frontMatter + '\n---\n' + split.content); + // Parse front-matter + var compiled = yaml.load(content); + // Add data which are not in the front-matter var keys = Object.keys(data); - var key; + var key = ''; + var obj = {}; - // Add data which are not in the raw scaffold for (var i = 0, len = keys.length; i < len; i++){ key = keys[i]; - if (!preservedKeys[key]) compiled[key] = data[key]; + + if (!preservedKeys[key] && !compiled.hasOwnProperty(key)){ + obj[key] = data[key]; + } + } + + if (Object.keys(obj).length){ + content += yaml.dump(obj); } - // Append content - if (data.content) compiled._content += '\n' + data.content; + content += '---\n'; + + // Concat content + content += split.content; - // Stringify front-matter - var content = yfm.stringify(compiled); + if (data.content){ + content += '\n' + data.content; + } var result = { path: path, @@ -157,17 +169,21 @@ Post.prototype.publish = function(data, replace, callback){ var config = ctx.config; var draftDir = pathFn.join(ctx.source_dir, '_drafts'); var slug = data.slug = escape.filename(data.slug, config.filename_case); - var regex = new RegExp('^' + escape.regex(slug)); + var regex = new RegExp('^' + escape.regex(slug) + '(?:[^\\/\\\\]+)'); var self = this; var src = ''; - var dest = ''; - var content = ''; + var result = {}; data.layout = (data.layout || config.default_layout).toLowerCase(); // Find the draft - return fs.listDir(draftDir).any(function(item){ - return regex.test(item); + return fs.listDir(draftDir).then(function(list){ + var item = ''; + + for (var i = 0, len = list.length; i < len; i++){ + item = list[i]; + if (regex.test(item)) return item; + } }).then(function(item){ if (!item) throw new Error('Draft "' + slug + '" does not exist.'); @@ -181,8 +197,8 @@ Post.prototype.publish = function(data, replace, callback){ delete data._content; return self.create(data, replace).then(function(post){ - dest = post.path; - content = post.content; + result.path = post.path; + result.content = post.content; }); }).then(function(){ // Remove the original draft file @@ -192,7 +208,7 @@ Post.prototype.publish = function(data, replace, callback){ // Copy assets var assetSrc = removeExtname(src); - var assetDest = removeExtname(dest); + var assetDest = removeExtname(result.path); return fs.exists(assetSrc).then(function(exist){ if (!exist) return; @@ -201,10 +217,7 @@ Post.prototype.publish = function(data, replace, callback){ return fs.rmdir(assetSrc); }); }); - }).thenReturn({ - path: dest, - content: content - }).nodeify(callback); + }).thenReturn(result).nodeify(callback); }; Post.prototype.render = function(source, data, callback){ diff --git a/lib/hexo/scaffold.js b/lib/hexo/scaffold.js index f9fd5b1db5..773397f248 100644 --- a/lib/hexo/scaffold.js +++ b/lib/hexo/scaffold.js @@ -34,8 +34,13 @@ Scaffold.prototype._listDir = function(){ }; Scaffold.prototype._getScaffold = function(name){ - return this._listDir().any(function(item){ - return item.name === name; + return this._listDir().then(function(list){ + var item; + + for (var i = 0, len = list.length; i < len; i++){ + item = list[i]; + if (item.name === name) return item; + } }); }; @@ -71,7 +76,7 @@ Scaffold.prototype.set = function(name, content, callback){ var scaffoldDir = this.scaffoldDir; return this._getScaffold(name).then(function(item){ - var path = item.path || pathFn.join(scaffoldDir, name); + var path = item ? item.path : pathFn.join(scaffoldDir, name); if (!pathFn.extname(path)) path += '.md'; return fs.writeFile(path, content); diff --git a/lib/plugins/renderer/swig.js b/lib/plugins/renderer/swig.js index a99790fe85..3cdecfb340 100644 --- a/lib/plugins/renderer/swig.js +++ b/lib/plugins/renderer/swig.js @@ -1,5 +1,5 @@ -var swig = require('swig'), - forTag = require('swig/lib/tags/for'); +var swig = require('swig'); +var forTag = require('swig/lib/tags/for'); swig.setDefaults({ cache: false diff --git a/lib/plugins/renderer/yaml.js b/lib/plugins/renderer/yaml.js index c5621c19bc..f9af9d7172 100644 --- a/lib/plugins/renderer/yaml.js +++ b/lib/plugins/renderer/yaml.js @@ -1,5 +1,5 @@ -var yaml = require('js-yaml'), - escape = require('../../util').escape.yaml; +var yaml = require('js-yaml'); +var escape = require('../../util').escape.yaml; module.exports = function(data){ return yaml.load(escape(data.text)); diff --git a/lib/util/escape.js b/lib/util/escape.js index a6428d6d80..fbf0a2aaf7 100644 --- a/lib/util/escape.js +++ b/lib/util/escape.js @@ -19,12 +19,14 @@ * @static */ +var fs = require('hexo-fs'); + var rFilenameEscape = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g; var rContinuesDash = /-{1,}/g; var rTailDash = /-+$/; -var EOL = require('os').EOL, - rEOL = new RegExp(EOL, 'g'); +var EOL = require('os').EOL; +var rEOL = new RegExp(EOL, 'g'); exports.filename = function(str, transform){ var result = exports.diacritic(str.toString()) @@ -212,10 +214,6 @@ exports.diacritic = function(str){ }); }; -exports.eol = function(str){ - return EOL === '\n' ? str : str.replace(rEOL, '\n'); -}; +exports.eol = fs.escapeEOL; -exports.bom = function(str){ - return str.replace(/^\uFEFF/, ''); -}; \ No newline at end of file +exports.bom = fs.escapeBOM; \ No newline at end of file diff --git a/lib/util/fs.js b/lib/util/fs.js deleted file mode 100644 index 577ac6f8ce..0000000000 --- a/lib/util/fs.js +++ /dev/null @@ -1,394 +0,0 @@ -var Promise = require('bluebird'); -var fs = require('graceful-fs'); -var pathFn = require('path'); -var escape = require('./escape'); - -var dirname = pathFn.dirname; -var join = pathFn.join; -var escapeEOL = escape.eol; -var escapeBOM = escape.bom; - -var statAsync = Promise.promisify(fs.stat); -var readdirAsync = Promise.promisify(fs.readdir); -var unlinkAsync = Promise.promisify(fs.unlink); -var mkdirAsync = Promise.promisify(fs.mkdir); -var renameAsync = Promise.promisify(fs.rename); -var writeFileAsync = Promise.promisify(fs.writeFile); -var appendFileAsync = Promise.promisify(fs.appendFile); -var rmdirAsync = Promise.promisify(fs.rmdir); -var readFileAsync = Promise.promisify(fs.readFile); -var createReadStream = fs.createReadStream; -var createWriteStream = fs.createWriteStream; - -function exists(path){ - return new Promise(function(resolve, reject){ - fs.exists(path, resolve); - }); -} - -function mkdirs(path){ - var parent = dirname(path); - - return exists(parent).then(function(exist){ - if (!exist) return mkdirs(parent); - }).then(function(){ - return mkdirAsync(path); - }); -} - -function mkdirsSync(path){ - var parent = dirname(path); - var exist = fs.existsSync(parent); - - if (!exist) mkdirsSync(parent); - fs.mkdirSync(path); -} - -function checkParent(path){ - var parent = dirname(path); - - return exists(parent).then(function(exist){ - if (!exist) return mkdirs(parent); - }).catch(function(err){ - if (err.cause.code !== 'EEXIST') throw err; - }); -} - -function checkParentSync(path){ - var parent = dirname(path); - var exist = fs.existsSync(parent); - - if (exist) return; - - try { - mkdirsSync(parent); - } catch (err){ - if (err.code !== 'EEXIST') throw err; - } -} - -function writeFile(path, data, options){ - return checkParent(path).then(function(){ - return writeFileAsync(path, data, options); - }); -} - -function writeFileSync(path, data, options){ - checkParentSync(path); - fs.writeFileSync(path, data, options); -} - -function appendFile(path, data, options){ - return checkParent(path).then(function(){ - return appendFileAsync(path, data, options); - }); -} - -function appendFileSync(path, data, options){ - checkParentSync(path); - fs.appendFileSync(path, data, options); -} - -function copyFile(src, dest){ - return checkParent(dest).then(function(){ - return new Promise(function(resolve, reject){ - var rs = createReadStream(src); - var ws = createWriteStream(dest); - - rs.pipe(ws) - .on('error', reject); - - ws.on('close', resolve) - .on('error', reject); - }); - }); -} - -function trueFn(){ - return true; -} - -function ignoreHiddenFiles(ignore){ - if (!ignore) return trueFn; - - return function(item){ - return item[0] !== '.'; - }; -} - -function ignoreFilesRegex(regex){ - if (!regex) return trueFn; - - return function(item){ - return !regex.test(item); - }; -} - -function ignoreExcludeFiles(arr, parent){ - if (!arr || !arr.length) return trueFn; - - var len = arr.length; - - return function(item){ - var path = join(parent, item); - - for (var i = 0; i < len; i++){ - if (arr[i] === path) return false; - } - - return true; - }; -} - -function reduceFiles(result, item){ - if (Array.isArray(item)){ - return result.concat(item); - } else { - result.push(item); - return result; - } -} - -function copyDir(src, dest, options, parent){ - options = options || {}; - parent = parent || ''; - - return checkParent(dest).then(function(){ - return readdirAsync(src); - }) - .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) - .filter(ignoreFilesRegex(options.ignorePattern)) - .map(function(item){ - var childSrc = join(src, item); - var childDest = join(dest, item); - - return statAsync(childSrc).then(function(stats){ - if (stats.isDirectory()){ - return copyDir(childSrc, childDest, options); - } else { - return copyFile(childSrc, childDest, options) - .thenReturn(join(parent, item)); - } - }); - }).reduce(reduceFiles, []); -} - -function listDir(path, options, parent){ - options = options || {}; - parent = parent || ''; - - return readdirAsync(path) - .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) - .filter(ignoreFilesRegex(options.ignorePattern)) - .map(function(item){ - var childPath = join(path, item); - - return statAsync(childPath).then(function(stats){ - if (stats.isDirectory()){ - return listDir(childPath, options, join(parent, item)); - } else { - return join(parent, item); - } - }); - }).reduce(reduceFiles, []); -} - -function listDirSync(path, options, parent){ - options = options || {}; - parent = parent || ''; - - return fs.readdirSync(path) - .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) - .filter(ignoreFilesRegex(options.ignorePattern)) - .map(function(item){ - var childPath = join(path, item); - var stats = fs.statSync(childPath); - - if (stats.isDirectory()){ - return listDirSync(childPath, options, join(parent, item)); - } else { - return join(parent, item); - } - }).reduce(reduceFiles, []); -} - -function escapeFileContent(content){ - return escapeBOM(escapeEOL(content)); -} - -function readFile(path, options){ - options = options || {}; - if (options.encoding == null) options.encoding = 'utf8'; - - return readFileAsync(path, options).then(function(content){ - if (options.escape == null || options.escape){ - return escapeFileContent(content); - } else { - return content; - } - }); -} - -function readFileSync(path, options){ - options = options || {}; - if (options.encoding == null) options.encoding = 'utf8'; - - var content = fs.readFileSync(path, options); - - if (options.escape == null || options.escape){ - return escapeFileContent(content); - } else { - return content; - } -} - -function emptyDir(path, options, parent){ - options = options || {}; - parent = parent || ''; - - return readdirAsync(path) - .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) - .filter(ignoreFilesRegex(options.ignorePattern)) - .filter(ignoreExcludeFiles(options.exclude, parent)) - .map(function(item){ - var childPath = join(path, item); - - return statAsync(childPath).then(function(stats){ - return { - isDirectory: stats.isDirectory(), - path: join(parent, item), - fullPath: childPath - }; - }); - }).map(function(item){ - var fullPath = item.fullPath; - - if (item.isDirectory){ - return emptyDir(fullPath, options, item.path).then(function(removed){ - return rmdirAsync(fullPath).then(function(){ - return removed; - }, function(err){ - if (err.code !== 'ENOTEMPTY') throw err; - return removed; - }); - }); - } else { - return unlinkAsync(fullPath).thenReturn(item.path); - } - }).reduce(reduceFiles, []); -} - -function emptyDirSync(path, options, parent){ - options = options || {}; - parent = parent || ''; - - return fs.readdirSync(path) - .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden)) - .filter(ignoreFilesRegex(options.ignorePattern)) - .filter(ignoreExcludeFiles(options.exclude, parent)) - .map(function(item){ - var childPath = join(path, item); - var stats = fs.statSync(childPath); - - if (stats.isDirectory()){ - var removed = emptyDirSync(childPath, options, join(parent, item)); - - try { - rmdirSync(childPath); - } catch (err){ - if (err.code !== 'ENOTEMPTY') throw err; - } - - return removed; - } else { - fs.unlinkSync(childPath); - return join(parent, item); - } - }).reduce(reduceFiles, []); -} - -function rmdir(path){ - return readdirAsync(path).map(function(item){ - var childPath = join(path, item); - - return statAsync(childPath).then(function(stats){ - if (stats.isDirectory()){ - return rmdir(childPath); - } else { - return unlinkAsync(childPath); - } - }); - }).then(function(){ - return rmdirAsync(path); - }); -} - -function rmdirSync(path){ - var files = fs.readdirSync(path); - var childPath; - var stats; - - for (var i = 0, len = files.length; i < len; i++){ - childPath = join(path, files[i]); - stats = fs.statSync(childPath); - - if (stats.isDirectory()){ - rmdirSync(childPath); - } else { - fs.unlinkSync(childPath); - } - } - - fs.rmdirSync(path); -} - -exports.appendFile = appendFile; -exports.appendFileSync = appendFileSync; - -exports.chmod = Promise.promisify(fs.chmod); -exports.chmodSync = fs.chmodSync; - -exports.chown = Promise.promisify(fs.chown); -exports.chownSync = fs.chownSync; - -exports.copyDir = copyDir; -exports.copyFile = copyFile; - -exports.createReadStream = createReadStream; -exports.createWriteStream = createWriteStream; - -exports.emptyDir = emptyDir; -exports.emptyDirSync = emptyDirSync; - -exports.exists = exists; -exports.existsSync = fs.existsSync; - -exports.listDir = listDir; -exports.listDirSync = listDirSync; - -exports.mkdir = mkdirAsync; -exports.mkdirSync = fs.mkdirSync; - -exports.mkdirs = mkdirs; -exports.mkdirsSync = mkdirsSync; - -exports.readdir = readdirAsync; -exports.readdirSync = fs.readdirSync; - -exports.readFile = readFile; -exports.readFileSync = readFileSync; - -exports.rename = renameAsync; -exports.renameSync = fs.renameSync; - -exports.rmdir = rmdir; -exports.rmdirSync = rmdirSync; - -exports.stat = statAsync; -exports.statSync = fs.statSync; - -exports.unlink = unlinkAsync; -exports.unlinkSync = fs.unlinkSync; - -exports.writeFile = writeFile; -exports.writeFileSync = writeFileSync; \ No newline at end of file diff --git a/lib/util/index.js b/lib/util/index.js index 63612bf5d4..b64851f156 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -5,7 +5,7 @@ exports.inherits = util.inherits; exports.escape = require('./escape'); exports.format = require('./format'); -exports.fs = require('./fs'); +exports.fs = require('hexo-fs'); exports.highlight = require('./highlight'); exports.htmlTag = exports.html_tag = require('./html_tag'); exports.permalink = require('./permalink'); diff --git a/package.json b/package.json index 14cf7983da..5a78aec534 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,12 @@ "name": "hexo", "version": "2.8.3", "description": "A fast, simple & powerful blog framework, powered by Node.js.", - "main": "lib/index", + "main": "lib/hexo", "bin": { "hexo": "./bin/hexo" }, "scripts": { - "test": "gulp test" + "test": "./node_modules/.bin/gulp test" }, "directories": { "lib": "./lib", @@ -44,6 +44,7 @@ "connect": "3.x", "graceful-fs": "^3.0.2", "hexo-front-matter": "0.0.4", + "hexo-fs": "0.0.4", "highlight.js": "8.4.0", "inflection": "^1.5.1", "js-yaml": "^3.1.0", @@ -59,14 +60,15 @@ "strip-indent": "^0.1.3", "swig": "1.4.2", "tildify": "^1.0.0", - "warehouse": "1.0.0-rc.2" + "warehouse": "1.0.0-rc.3" }, "devDependencies": { "chai": "^1.9.1", + "codeclimate-test-reporter": "0.0.4", "coveralls": "^2.11.2", "del": "^0.1.3", "gulp": "^3.8.9", - "gulp-istanbul": "^0.3.1", + "gulp-istanbul": "^0.5.0", "gulp-jshint": "^1.8.6", "gulp-load-plugins": "^0.7.0", "gulp-mocha": "^1.1.1", diff --git a/test/scripts/filters/new_post_path.js b/test/scripts/filters/new_post_path.js index 90dcc9b505..9c0f6650c5 100644 --- a/test/scripts/filters/new_post_path.js +++ b/test/scripts/filters/new_post_path.js @@ -121,7 +121,7 @@ describe('new_post_path', function(){ }); }); - it('replace existed files', function(){ + it('replace existing files', function(){ var filename = 'test.md'; var path = pathFn.join(postDir, filename); diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js index 4a90369804..8909e8492d 100644 --- a/test/scripts/hexo/index.js +++ b/test/scripts/hexo/index.js @@ -1,4 +1,8 @@ describe('Hexo', function(){ require('./hexo'); + require('./load_config'); + require('./post'); require('./render'); + require('./scaffold'); + require('./update_package') }); \ No newline at end of file diff --git a/test/scripts/hexo/load_config.js b/test/scripts/hexo/load_config.js new file mode 100644 index 0000000000..e4bf5bb612 --- /dev/null +++ b/test/scripts/hexo/load_config.js @@ -0,0 +1,5 @@ +var should = require('chai').should(); + +describe('Load config', function(){ + // +}); \ No newline at end of file diff --git a/test/scripts/hexo/post.js b/test/scripts/hexo/post.js new file mode 100644 index 0000000000..3bc03950e5 --- /dev/null +++ b/test/scripts/hexo/post.js @@ -0,0 +1,344 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var moment = require('moment'); +var Promise = require('bluebird'); +var util = require('../../../lib/util'); +var fs = util.fs; + +describe('Post', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname); + var post = hexo.post; + + before(function(){ + return hexo.init(); + }); + + it('create()', function(){ + var emitted = false; + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + hexo.once('new', function(){ + emitted = true; + }); + + return post.create({ + title: 'Hello World' + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + emitted.should.be.true; + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }); + }); + + it('create() - slug', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'foo.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World', + slug: 'foo' + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }); + }); + + it('create() - filename_case', function(){ + hexo.config.filename_case = 1; + + var path = pathFn.join(hexo.source_dir, '_posts', 'hello-world.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World' + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + hexo.config.filename_case = 0; + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }); + }); + + it('create() - layout', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'layout: photo', + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World', + layout: 'photo' + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }); + }); + + it('create() - extra data', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + 'foo: bar', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World', + foo: 'bar' + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }); + }); + + it('create() - rename if target existed', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return post.create({ + title: 'Hello World' + }); + }).then(function(post){ + post.path.should.eql(path); + return fs.exists(path); + }).then(function(exist){ + exist.should.be.true; + + return Promise.all([ + fs.unlink(path), + fs.unlink(pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md')) + ]); + }); + }); + + it('create() - replace existing files', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return post.create({ + title: 'Hello World' + }, true); + }).then(function(post){ + post.path.should.eql(path); + return fs.unlink(path); + }); + }); + + it('create() - asset folder', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World'); + + hexo.config.post_asset_folder = true; + + return post.create({ + title: 'Hello World' + }).then(function(post){ + hexo.config.post_asset_folder = false; + return fs.stat(path); + }).then(function(stats){ + stats.isDirectory().should.be.true; + return fs.unlink(path + '.md'); + }); + }); + + it.skip('load()'); + + it('publish()', function(){ + var draftPath = ''; + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World', + layout: 'draft' + }).then(function(data){ + draftPath = data.path; + + return post.publish({ + slug: 'Hello-World' + }); + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + + return Promise.all([ + fs.exists(draftPath), + fs.readFile(path) + ]); + }).spread(function(exist, data){ + exist.should.be.false; + data.should.eql(content); + + return fs.unlink(path); + }); + }); + + it('publish() - layout', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'layout: photo', + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return post.create({ + title: 'Hello World', + layout: 'draft' + }).then(function(data){ + return post.publish({ + slug: 'Hello-World', + layout: 'photo' + }); + }).then(function(post){ + post.path.should.eql(path); + post.content.should.eql(content); + + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + + return fs.unlink(path); + }); + }); + + it('publish() - rename if target existed', function(){ + var paths = [pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md')]; + + return Promise.all([ + post.create({title: 'Hello World', layout: 'draft'}), + post.create({title: 'Hello World'}) + ]).then(function(data){ + paths.push(data[1].path); + + return post.publish({ + slug: 'Hello-World' + }); + }).then(function(data){ + data.path.should.eql(paths[0]); + return paths; + }).map(function(item){ + return fs.unlink(item); + }); + }); + + it('publish() - replace existing files', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + + return Promise.all([ + post.create({title: 'Hello World', layout: 'draft'}), + post.create({title: 'Hello World'}) + ]).then(function(data){ + return post.publish({ + slug: 'Hello-World' + }, true); + }).then(function(data){ + data.path.should.eql(path); + return fs.unlink(path); + }); + }); + + it('publish() - asset folder', function(){ + var assetDir = pathFn.join(hexo.source_dir, '_drafts', 'Hello-World'); + var newAssetDir = pathFn.join(hexo.source_dir, '_posts', 'Hello-World'); + hexo.config.post_asset_folder = true; + + return post.create({ + title: 'Hello World', + layout: 'draft' + }).then(function(data){ + // Put some files into the asset folder + return Promise.all([ + fs.writeFile(pathFn.join(assetDir, 'a.txt'), 'a'), + fs.writeFile(pathFn.join(assetDir, 'b.txt'), 'b') + ]); + }).then(function(){ + return post.publish({ + slug: 'Hello-World' + }); + }).then(function(post){ + return Promise.all([ + fs.exists(assetDir), + fs.listDir(newAssetDir), + fs.unlink(post.path) + ]); + }).spread(function(exist, files){ + hexo.config.post_asset_folder = false; + exist.should.be.false; + files.should.have.members(['a.txt', 'b.txt']); + return fs.rmdir(newAssetDir); + }); + }); + + it.skip('render()'); +}); \ No newline at end of file diff --git a/test/scripts/hexo/scaffold.js b/test/scripts/hexo/scaffold.js new file mode 100644 index 0000000000..93ade9598e --- /dev/null +++ b/test/scripts/hexo/scaffold.js @@ -0,0 +1,98 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var Promise = require('bluebird'); +var util = require('../../../lib/util'); +var fs = util.fs; + +describe('Scaffold', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname); + var scaffold = hexo.scaffold; + var scaffoldDir = hexo.scaffold_dir; + + var testContent = [ + 'title: {{ title }}', + '---', + 'test scaffold' + ].join('\n'); + + var testPath = pathFn.join(scaffoldDir, 'test.md'); + + before(function(){ + return hexo.init().then(function(){ + return fs.writeFile(testPath, testContent); + }); + }); + + after(function(){ + return fs.unlink(testPath); + }); + + it('get() - file exists', function(){ + return scaffold.get('test').then(function(data){ + data.should.eql(testContent); + }); + }); + + it('get() - file does not exists, use default scaffold instead', function(){ + var assetPath = pathFn.join(hexo.core_dir, 'assets', 'scaffolds', 'post.md'); + + return Promise.all([ + fs.readFile(assetPath), + scaffold.get('post') + ]).spread(function(asset, data){ + asset.should.eql(data); + }); + }); + + it('get() - normal scaffold', function(){ + return scaffold.get('normal').then(function(data){ + data.should.eql(scaffold.defaults.normal); + }); + }); + + it('set() - file exists', function(){ + return scaffold.set('test', 'foo').then(function(){ + return Promise.all([ + fs.readFile(testPath), + scaffold.get('test') + ]); + }).spread(function(file, data){ + file.should.eql('foo'); + data.should.eql('foo'); + return fs.writeFile(testPath, testContent); + }); + }); + + it('set() - file does not exist', function(){ + var testPath = pathFn.join(scaffoldDir, 'foo.md'); + + return scaffold.set('foo', 'bar').then(function(){ + return Promise.all([ + fs.readFile(testPath), + scaffold.get('foo') + ]); + }).spread(function(file, data){ + file.should.eql('bar'); + data.should.eql('bar'); + return fs.unlink(testPath); + }); + }); + + it('remove() - file exist', function(){ + return scaffold.remove('test').then(function(){ + return Promise.all([ + fs.exists(testPath), + scaffold.get('test') + ]); + }).spread(function(exist, data){ + exist.should.be.false; + should.not.exist(data); + return fs.writeFile(testPath, testContent); + }); + }); + + it('remove() - file does not exist', function(){ + return scaffold.remove('foo'); + }); +}); \ No newline at end of file diff --git a/test/scripts/hexo/update_package.js b/test/scripts/hexo/update_package.js new file mode 100644 index 0000000000..4c9a0d8d56 --- /dev/null +++ b/test/scripts/hexo/update_package.js @@ -0,0 +1,68 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var util = require('../../../lib/util'); +var fs = util.fs; + +describe('Update package.json', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname, {silent: true}); + var updatePkg = require('../../../lib/hexo/update_package'); + var packagePath = pathFn.join(hexo.base_dir, 'package.json'); + + hexo.env.init = true; + + before(function(){ + return hexo.init(); + }); + + afterEach(function(){ + return fs.exists(packagePath).then(function(exist){ + if (exist) return fs.unlink(packagePath); + }); + }); + + it('package.json does not exist', function(){ + var pkg = require('../../../assets/package.json'); + + return updatePkg(hexo).then(function(){ + return fs.readFile(packagePath); + }).then(function(raw){ + var content = JSON.parse(raw); + pkg.version = hexo.version; + content.should.eql(pkg); + }); + }); + + it('package.json exists, but the version does not match', function(){ + var pkg = { + name: 'hexo-site', + version: '0.0.1' + }; + + return fs.writeFile(packagePath, JSON.stringify(pkg)).then(function(){ + return updatePkg(hexo); + }).then(function(){ + return fs.readFile(packagePath); + }).then(function(raw){ + var content = JSON.parse(raw); + pkg.version = hexo.version; + content.should.eql(pkg); + }); + }); + + it('package.json exists and everything is ok', function(){ + var pkg = { + name: 'hexo-site', + version: hexo.version + }; + + return fs.writeFile(packagePath, JSON.stringify(pkg)).then(function(){ + return updatePkg(hexo); + }).then(function(){ + return fs.readFile(packagePath); + }).then(function(raw){ + var content = JSON.parse(raw); + content.should.eql(pkg); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/include_code.js b/test/scripts/tags/include_code.js index d48c1d75e6..648f4dd250 100644 --- a/test/scripts/tags/include_code.js +++ b/test/scripts/tags/include_code.js @@ -1,7 +1,7 @@ var cheerio = require('cheerio'); var path = require('path'); var should = require('chai').should(); -var fs = require('../../../lib/util/fs'); +var fs = require('../../../lib/util').fs; var highlight = require('../../../lib/util/highlight'); // Fixture diff --git a/test/scripts/util/fs.js b/test/scripts/util/fs.js deleted file mode 100644 index 3fc4f4ea36..0000000000 --- a/test/scripts/util/fs.js +++ /dev/null @@ -1,449 +0,0 @@ -var should = require('chai').should(); -var pathFn = require('path'); -var Promise = require('bluebird'); -var fs = require('../../../lib/util').fs; - -function createDummyFolder(path){ - // TODO nested dummy files - return Promise.all([ - fs.writeFile(pathFn.join(path, 'a.txt'), 'a'), - fs.writeFile(pathFn.join(path, 'b.js'), 'b'), - fs.writeFile(pathFn.join(path, '.c'), 'c') - ]);} - -describe('fs', function(){ - var tmpDir = pathFn.join(__dirname, 'fs_tmp'); - - before(function(){ - return fs.mkdir(tmpDir); - }); - - it('exists()', function(){ - return fs.exists(tmpDir).then(function(exist){ - exist.should.be.true; - }); - }); - - it('mkdirs()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'c'); - - return fs.mkdirs(target).then(function(){ - return fs.exists(target); - }).then(function(exist){ - exist.should.be.true; - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('mkdirsSync()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'c'); - - fs.mkdirsSync(target); - - return fs.exists(target).then(function(exist){ - exist.should.be.true; - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('writeFile()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); - var body = 'foo'; - - return fs.writeFile(target, body).then(function(){ - return fs.readFile(target); - }).then(function(content){ - content.should.eql(body); - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('writeFileSync()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); - var body = 'foo'; - - fs.writeFileSync(target, body); - - return fs.readFile(target).then(function(content){ - content.should.eql(body); - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('appendFile()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); - var body = 'foo'; - var body2 = 'bar'; - - return fs.writeFile(target, body).then(function(){ - return fs.appendFile(target, body2); - }).then(function(){ - return fs.readFile(target); - }).then(function(content){ - content.should.eql(body + body2); - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('appendFileSync()', function(){ - var target = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); - var body = 'foo'; - var body2 = 'bar'; - - return fs.writeFile(target, body).then(function(){ - fs.appendFileSync(target, body2); - return fs.readFile(target); - }).then(function(content){ - content.should.eql(body + body2); - return fs.rmdir(pathFn.join(tmpDir, 'a')); - }); - }); - - it('copyFile()', function(){ - var src = pathFn.join(tmpDir, 'test.txt'); - var dest = pathFn.join(tmpDir, 'a', 'b', 'test.txt'); - var body = 'foo'; - - return fs.writeFile(src, body).then(function(){ - return fs.copyFile(src, dest); - }).then(function(){ - return fs.readFile(dest); - }).then(function(content){ - content.should.eql(body); - - return Promise.all([ - fs.unlink(src), - fs.rmdir(pathFn.join(tmpDir, 'a')) - ]); - }); - }); - - it('copyDir()', function(){ - var src = pathFn.join(tmpDir, 'a'); - var dest = pathFn.join(tmpDir, 'b'); - - return createDummyFolder(src).then(function(){ - return fs.copyDir(src, dest); - }).then(function(files){ - files.should.eql(['a.txt', 'b.js']); - - return Promise.all([ - fs.readFile(pathFn.join(dest, 'a.txt')), - fs.readFile(pathFn.join(dest, 'b.js')) - ]); - }).then(function(result){ - result.should.eql(['a', 'b']); - }).then(function(){ - return Promise.all([ - fs.rmdir(src), - fs.rmdir(dest) - ]); - }); - }); - - it('copyDir() - ignoreHidden off', function(){ - var src = pathFn.join(tmpDir, 'a'); - var dest = pathFn.join(tmpDir, 'b'); - - return createDummyFolder(src).then(function(){ - return fs.copyDir(src, dest, {ignoreHidden: false}); - }).then(function(files){ - files.should.have.members(['a.txt', 'b.js', '.c']); - - return Promise.all([ - fs.readFile(pathFn.join(dest, 'a.txt')), - fs.readFile(pathFn.join(dest, 'b.js')), - fs.readFile(pathFn.join(dest, '.c')) - ]); - }).then(function(result){ - result.should.eql(['a', 'b', 'c']); - }).then(function(){ - return Promise.all([ - fs.rmdir(src), - fs.rmdir(dest) - ]); - }); - }); - - it('copyDir() - ignorePattern', function(){ - var src = pathFn.join(tmpDir, 'a'); - var dest = pathFn.join(tmpDir, 'b'); - - return createDummyFolder(src).then(function(){ - return fs.copyDir(src, dest, {ignorePattern: /\.js/}); - }).then(function(files){ - files.should.have.members(['a.txt']); - - return Promise.all([ - fs.readFile(pathFn.join(dest, 'a.txt')) - ]); - }).then(function(result){ - result.should.eql(['a']); - }).then(function(){ - return Promise.all([ - fs.rmdir(src), - fs.rmdir(dest) - ]); - }); - }); - - it('listDir()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.listDir(target); - }).then(function(files){ - files.should.have.members(['a.txt', 'b.js']); - return fs.rmdir(target); - }); - }); - - it('listDir() - ignoreHidden off', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.listDir(target, {ignoreHidden: false}); - }).then(function(files){ - files.should.have.members(['a.txt', 'b.js', '.c']); - return fs.rmdir(target); - }); - }); - - it('listDir() - ignorePattern', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.listDir(target, {ignorePattern: /\.js/}); - }).then(function(files){ - files.should.have.members(['a.txt']); - return fs.rmdir(target); - }); - }); - - it('listDirSync()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.listDirSync(target); - files.should.have.members(['a.txt', 'b.js']); - return fs.rmdir(target); - }); - }); - - it('listDirSync() - ignoreHidden off', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.listDirSync(target, {ignoreHidden: false}); - files.should.have.members(['a.txt', 'b.js', '.c']); - return fs.rmdir(target); - }); - }); - - it('listDirSync() - ignorePattern', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.listDirSync(target, {ignorePattern: /\.js/}); - files.should.have.members(['a.txt']); - return fs.rmdir(target); - }); - }); - - it('readFile()', function(){ - var target = pathFn.join(tmpDir, 'test.txt'); - var body = 'test'; - - return fs.writeFile(target, body).then(function(){ - return fs.readFile(target); - }).then(function(content){ - content.should.eql(body); - return fs.unlink(target); - }); - }); - - it('readFileSync()', function(){ - var target = pathFn.join(tmpDir, 'test.txt'); - var body = 'test'; - - return fs.writeFile(target, body).then(function(){ - fs.readFileSync(target).should.eql(body); - return fs.unlink(target); - }); - }); - - it('emptyDir()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.emptyDir(target); - }).then(function(files){ - files.should.have.members(['a.txt', 'b.js']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, false, true]); - return fs.rmdir(target); - }); - }); - - it('emptyDir() - ignoreHidden off', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.emptyDir(target, {ignoreHidden: false}); - }).then(function(files){ - files.should.have.members(['a.txt', 'b.js', '.c']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, false, false]); - return fs.rmdir(target); - }); - }); - - it('emptyDir() - ignorePattern', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.emptyDir(target, {ignorePattern: /\.js/}); - }).then(function(files){ - files.should.have.members(['a.txt']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, true, true]); - return fs.rmdir(target); - }); - }); - - it('emptyDir() - exclude', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.emptyDir(target, {exclude: ['a.txt']}); - }).then(function(files){ - files.should.have.members(['b.js']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([true, false, true]); - return fs.rmdir(target); - }); - }); - - it('emptyDirSync()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.emptyDirSync(target); - files.should.have.members(['a.txt', 'b.js']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, false, true]); - return fs.rmdir(target); - }); - }); - - it('emptyDirSync() - ignoreHidden off', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.emptyDirSync(target, {ignoreHidden: false}); - files.should.have.members(['a.txt', 'b.js', '.c']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, false, false]); - return fs.rmdir(target); - }); - }); - - it('emptyDirSync() - ignorePattern', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.emptyDirSync(target, {ignorePattern: /\.js/}); - files.should.have.members(['a.txt']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([false, true, true]); - return fs.rmdir(target); - }); - }); - - it('emptyDirSync() - exclude', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - var files = fs.emptyDirSync(target, {exclude: ['a.txt']}); - files.should.have.members(['b.js']); - - return Promise.all([ - fs.exists(pathFn.join(target, 'a.txt')), - fs.exists(pathFn.join(target, 'b.js')), - fs.exists(pathFn.join(target, '.c')) - ]); - }).then(function(result){ - result.should.eql([true, false, true]); - return fs.rmdir(target); - }); - }); - - it('rmdir()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - return fs.rmdir(target); - }).then(function(){ - return fs.exists(target); - }).then(function(exist){ - exist.should.be.false; - }) - }); - - it('rmdirSync()', function(){ - var target = pathFn.join(tmpDir, 'test'); - - return createDummyFolder(target).then(function(){ - fs.rmdirSync(target); - return fs.exists(target); - }).then(function(exist){ - exist.should.be.false; - }); - }); - - after(function(){ - return fs.rmdir(tmpDir); - }); -}); \ No newline at end of file diff --git a/test/scripts/util/index.js b/test/scripts/util/index.js index fdefac19b0..d4411c5fa0 100644 --- a/test/scripts/util/index.js +++ b/test/scripts/util/index.js @@ -1,6 +1,5 @@ describe('Utilities', function(){ require('./format'); - require('./fs'); require('./html_tag'); require('./permalink'); require('./router'); From bd17852ba8a018936d08400dd7dc3668221326c4 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Mon, 8 Dec 2014 13:35:25 +0800 Subject: [PATCH 07/15] Add config loader test --- lib/box/file.js | 3 +- lib/box/index.js | 2 +- lib/box/pattern.js | 4 +- lib/cli/init.js | 4 +- lib/hexo/index.js | 3 + lib/hexo/load_config.js | 45 +++++----- lib/hexo/load_database.js | 3 +- lib/hexo/load_plugins.js | 3 +- lib/hexo/load_scripts.js | 3 +- lib/hexo/post.js | 4 +- lib/hexo/render.js | 3 +- lib/hexo/scaffold.js | 4 +- lib/hexo/source.js | 4 +- lib/hexo/update_package.js | 3 +- lib/plugins/console/clean.js | 3 +- lib/plugins/console/deploy.js | 4 +- lib/plugins/console/init.js | 3 +- lib/plugins/console/render.js | 3 +- lib/plugins/console/version.js | 17 ---- lib/plugins/renderer/index.js | 2 + lib/plugins/renderer/json.js | 3 + lib/plugins/renderer/yaml.js | 2 +- lib/theme/view.js | 4 +- lib/util/escape.js | 11 +-- lib/util/index.js | 4 +- test/scripts/hexo/index.js | 2 +- test/scripts/hexo/load_config.js | 145 ++++++++++++++++++++++++++++++- 27 files changed, 202 insertions(+), 89 deletions(-) create mode 100644 lib/plugins/renderer/json.js diff --git a/lib/box/file.js b/lib/box/file.js index 0abc72e68f..a041600979 100644 --- a/lib/box/file.js +++ b/lib/box/file.js @@ -1,5 +1,4 @@ -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); function File(data){ this.source = data.source; diff --git a/lib/box/index.js b/lib/box/index.js index f45e7792f1..c8db8f5fb6 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -5,7 +5,7 @@ var chokidar = require('chokidar'); var File = require('./file'); var Pattern = require('./pattern'); var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/box/pattern.js b/lib/box/pattern.js index a8db4af8e2..8309ce5606 100644 --- a/lib/box/pattern.js +++ b/lib/box/pattern.js @@ -1,5 +1,5 @@ -var util = require('../util'), - escape = util.escape; +var util = require('../util'); +var escape = util.escape; var rParam = /([:\*])([\w\?]*)?/g; diff --git a/lib/cli/init.js b/lib/cli/init.js index 6b09840f6d..6eb45c7b67 100644 --- a/lib/cli/init.js +++ b/lib/cli/init.js @@ -1,7 +1,6 @@ var Hexo = require('../hexo'); -var util = require('../util'); var pathFn = require('path'); -var fs = util.fs; +var fs = require('hexo-fs'); var cwd = process.cwd(); var lastCwd = cwd; @@ -10,6 +9,7 @@ require('colors'); // Find Hexo folder recursively function findConfigFile(){ + // TODO: support for _config.yaml, _config.json or other extension name return fs.exists(pathFn.join(cwd, '_config.yml')).then(function(exist){ if (exist) return; diff --git a/lib/hexo/index.js b/lib/hexo/index.js index b02c8ef791..cf510943a2 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -12,6 +12,7 @@ var Render = require('./render'); var registerModels = require('./register_models'); var Post = require('./post'); var Scaffold = require('./scaffold'); +var Source = require('./source'); var Router = util.Router; var defaultConfig = require('./default_config'); @@ -70,6 +71,8 @@ function Hexo(base, args){ this.scaffold = new Scaffold(this); + this.source = new Source(this); + var db = this.database = new Database({ version: dbVersion, path: pathFn.join(base, 'db.json') diff --git a/lib/hexo/load_config.js b/lib/hexo/load_config.js index ba43f06b7e..41491fb585 100644 --- a/lib/hexo/load_config.js +++ b/lib/hexo/load_config.js @@ -1,10 +1,9 @@ var _ = require('lodash'); var pathFn = require('path'); var tildify = require('tildify'); -var util = require('../util'); var Theme = require('../theme'); var Source = require('./source'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); @@ -15,14 +14,17 @@ module.exports = function(ctx){ var dirname = pathFn.dirname(configPath); var basename = pathFn.basename(configPath, extname); - return fs.readdir(dirname).filter(function(item){ - return item.substring(0, basename.length) === basename; - }).then(function(result){ - if (!result.length) return; + return fs.readdir(dirname).then(function(files){ + var item = ''; - configPath = pathFn.join(pathFn.dirname(configPath), result[0]); + for (var i = 0, len = files.length; i < len; i++){ + item = files[i]; - return ctx.render.render({path: configPath}); + if (item.substring(0, basename.length) === basename){ + configPath = pathFn.join(dirname, item); + return ctx.render.render({path: configPath}); + } + } }).then(function(config){ if (!config) return; @@ -32,26 +34,25 @@ module.exports = function(ctx){ ctx.config_path = configPath; ctx.env.init = true; - if (_.last(config.root) !== '/'){ - config.root += '/'; + if (!config.theme){ + throw new Error('Theme has not been set. Please set the theme in _config.yml.'); } - if (_.last(config.url) === '/'){ - config.url = config.url.substring(0, config.url.length - 1); - } + config.root = config.root.replace(/\/*$/, '/'); + config.url = config.url.replace(/\/+$/, ''); - ctx.public_dir = pathFn.join(baseDir, config.public_dir) + pathFn.sep; - ctx.source_dir = pathFn.join(baseDir, config.source_dir) + pathFn.sep; + ctx.public_dir = pathFn.resolve(baseDir, config.public_dir) + pathFn.sep; + ctx.source_dir = pathFn.resolve(baseDir, config.source_dir) + pathFn.sep; ctx.source = new Source(ctx); - if (config.theme){ - ctx.theme_dir = pathFn.join(baseDir, 'themes', config.theme) + pathFn.sep; - ctx.theme_script_dir = pathFn.join(ctx.theme_dir, 'scripts') + pathFn.sep; + ctx.theme_dir = pathFn.join(baseDir, 'themes', config.theme) + pathFn.sep; + ctx.theme_script_dir = pathFn.join(ctx.theme_dir, 'scripts') + pathFn.sep; - ctx.theme = new Theme(ctx); - } else { - throw new Error('Theme has not been set. Please set the theme in _config.yml.'); - } + ctx.theme = new Theme(ctx); + + return fs.exists(ctx.theme_dir).then(function(exist){ + if (!exist) throw new Error('Theme "' + config.theme + '" does not exist.'); + }); }); }; \ No newline at end of file diff --git a/lib/hexo/load_database.js b/lib/hexo/load_database.js index 1b289c008b..7a92fa4c16 100644 --- a/lib/hexo/load_database.js +++ b/lib/hexo/load_database.js @@ -1,6 +1,5 @@ var semver = require('semver'); -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); module.exports = function(ctx){ var db = ctx.database; diff --git a/lib/hexo/load_plugins.js b/lib/hexo/load_plugins.js index 6c85590c7d..da91a95f9e 100644 --- a/lib/hexo/load_plugins.js +++ b/lib/hexo/load_plugins.js @@ -1,6 +1,5 @@ var pathFn = require('path'); -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/hexo/load_scripts.js b/lib/hexo/load_scripts.js index 2352083a82..f49a6a9496 100644 --- a/lib/hexo/load_scripts.js +++ b/lib/hexo/load_scripts.js @@ -1,8 +1,7 @@ var Promise = require('bluebird'); var pathFn = require('path'); var tildify = require('tildify'); -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/hexo/post.js b/lib/hexo/post.js index bf537dbc64..9fad5c7c93 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -5,10 +5,10 @@ var pathFn = require('path'); var _ = require('lodash'); var yaml = require('js-yaml'); var util = require('../util'); +var fs = require('hexo-fs'); +var yfm = require('hexo-front-matter'); var escape = util.escape; -var yfm = util.yfm; -var fs = util.fs; var rEscapeContent = /<escape(?:[^>]*)>([\s\S]+?)<\/escape>/g; var rUnescape = /<hexoescape>(\d+)<\/hexoescape>/g; diff --git a/lib/hexo/render.js b/lib/hexo/render.js index 2d45d36c2e..ebb6e7cf9d 100644 --- a/lib/hexo/render.js +++ b/lib/hexo/render.js @@ -1,7 +1,6 @@ var pathFn = require('path'); var Promise = require('bluebird'); -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); function getExtname(str){ var extname = pathFn.extname(str); diff --git a/lib/hexo/scaffold.js b/lib/hexo/scaffold.js index 773397f248..7c5508e472 100644 --- a/lib/hexo/scaffold.js +++ b/lib/hexo/scaffold.js @@ -1,8 +1,6 @@ var pathFn = require('path'); var Promise = require('bluebird'); -var util = require('../util'); - -var fs = util.fs; +var fs = require('hexo-fs'); function Scaffold(context){ this.context = context; diff --git a/lib/hexo/source.js b/lib/hexo/source.js index a9f4019347..ad07089106 100644 --- a/lib/hexo/source.js +++ b/lib/hexo/source.js @@ -7,4 +7,6 @@ function Source(ctx){ this.processors = ctx.extend.processor.list(); } -util.inherits(Source, Box); \ No newline at end of file +util.inherits(Source, Box); + +module.exports = Source; \ No newline at end of file diff --git a/lib/hexo/update_package.js b/lib/hexo/update_package.js index c050591a90..07bc089a27 100644 --- a/lib/hexo/update_package.js +++ b/lib/hexo/update_package.js @@ -1,6 +1,5 @@ var pathFn = require('path'); -var util = require('../util'); -var fs = util.fs; +var fs = require('hexo-fs'); module.exports = function(ctx){ if (!ctx.env.init) return; diff --git a/lib/plugins/console/clean.js b/lib/plugins/console/clean.js index 14afbcc3ae..0c3bac310f 100644 --- a/lib/plugins/console/clean.js +++ b/lib/plugins/console/clean.js @@ -1,6 +1,5 @@ var Promise = require('bluebird'); -var util = require('../../util'); -var fs = util.fs; +var fs = require('hexo-fs'); module.exports = function(args){ return Promise.all([ diff --git a/lib/plugins/console/deploy.js b/lib/plugins/console/deploy.js index 2ceb2ea204..74c0c97ce5 100644 --- a/lib/plugins/console/deploy.js +++ b/lib/plugins/console/deploy.js @@ -1,7 +1,5 @@ var _ = require('lodash'); -var util = require('../../util'); - -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/plugins/console/init.js b/lib/plugins/console/init.js index 412455ec85..a6960b09a2 100644 --- a/lib/plugins/console/init.js +++ b/lib/plugins/console/init.js @@ -1,7 +1,6 @@ var pathFn = require('path'); var tildify = require('tildify'); -var util = require('../../util'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/plugins/console/render.js b/lib/plugins/console/render.js index 2cf7337a6e..49891d72b7 100644 --- a/lib/plugins/console/render.js +++ b/lib/plugins/console/render.js @@ -2,8 +2,7 @@ var Promise = require('bluebird'); var pathFn = require('path'); var tildify = require('tildify'); var prettyHrtime = require('pretty-hrtime'); -var util = require('../../util'); -var fs = util.fs; +var fs = require('hexo-fs'); require('colors'); diff --git a/lib/plugins/console/version.js b/lib/plugins/console/version.js index 39abb72d77..f36f59f337 100644 --- a/lib/plugins/console/version.js +++ b/lib/plugins/console/version.js @@ -1,23 +1,6 @@ var _ = require('lodash'); var os = require('os'); -module.exports = function(ctx){ - return function(args){ - var versions = _.extend({ - hexo: ctx.version, - os: os.type() + ' ' + os.release() + ' ' + os.platform() + ' ' + os.arch() - }, process.versions); - - if (args.json){ - console.log(versions); - } else { - for (var i in versions){ - console.log(i + ': ' + versions[i]); - } - } - }; -}; - module.exports = function(args){ var versions = _.extend({ hexo: this.version, diff --git a/lib/plugins/renderer/index.js b/lib/plugins/renderer/index.js index 9f26879d0b..cc6d448904 100644 --- a/lib/plugins/renderer/index.js +++ b/lib/plugins/renderer/index.js @@ -6,6 +6,8 @@ module.exports = function(ctx){ renderer.register('htm', 'html', html, true); renderer.register('html', 'html', html, true); + renderer.register('json', 'json', require('./json'), true); + renderer.register('swig', 'html', require('./swig'), true); var yml = require('./yaml'); diff --git a/lib/plugins/renderer/json.js b/lib/plugins/renderer/json.js new file mode 100644 index 0000000000..76cf141f49 --- /dev/null +++ b/lib/plugins/renderer/json.js @@ -0,0 +1,3 @@ +module.exports = function(data){ + return JSON.parse(data.text); +}; \ No newline at end of file diff --git a/lib/plugins/renderer/yaml.js b/lib/plugins/renderer/yaml.js index f9af9d7172..58541b5850 100644 --- a/lib/plugins/renderer/yaml.js +++ b/lib/plugins/renderer/yaml.js @@ -1,5 +1,5 @@ var yaml = require('js-yaml'); -var escape = require('../../util').escape.yaml; +var escape = require('hexo-front-matter').escape; module.exports = function(data){ return yaml.load(escape(data.text)); diff --git a/lib/theme/view.js b/lib/theme/view.js index fdb04a3cae..86500a3e4d 100644 --- a/lib/theme/view.js +++ b/lib/theme/view.js @@ -1,8 +1,6 @@ var pathFn = require('path'); var _ = require('lodash'); -var util = require('../util'); - -var yfm = util.yfm; +var yfm = require('hexo-front-matter'); function View(path, data){ var ctx = this.context; diff --git a/lib/util/escape.js b/lib/util/escape.js index fbf0a2aaf7..bd67a98446 100644 --- a/lib/util/escape.js +++ b/lib/util/escape.js @@ -19,15 +19,10 @@ * @static */ -var fs = require('hexo-fs'); - var rFilenameEscape = /[\s~`!@#\$%\^&\*\(\)\-_\+=\[\]\{\}\|\\;:"'<>,\.\?\/]/g; var rContinuesDash = /-{1,}/g; var rTailDash = /-+$/; -var EOL = require('os').EOL; -var rEOL = new RegExp(EOL, 'g'); - exports.filename = function(str, transform){ var result = exports.diacritic(str.toString()) .replace(rFilenameEscape, '-') @@ -212,8 +207,4 @@ exports.diacritic = function(str){ return str.replace(/[^\u0000-\u007E]/g, function(a){ return diacriticsMap[a] || a; }); -}; - -exports.eol = fs.escapeEOL; - -exports.bom = fs.escapeBOM; \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/util/index.js b/lib/util/index.js index b64851f156..df3eedaf48 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -1,5 +1,5 @@ -var util = require('util'), - inflection = require('inflection'); +var util = require('util'); +var inflection = require('inflection'); exports.inherits = util.inherits; diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js index 8909e8492d..72108731ed 100644 --- a/test/scripts/hexo/index.js +++ b/test/scripts/hexo/index.js @@ -1,4 +1,4 @@ -describe('Hexo', function(){ +describe('Core', function(){ require('./hexo'); require('./load_config'); require('./post'); diff --git a/test/scripts/hexo/load_config.js b/test/scripts/hexo/load_config.js index e4bf5bb612..d13a3c5354 100644 --- a/test/scripts/hexo/load_config.js +++ b/test/scripts/hexo/load_config.js @@ -1,5 +1,148 @@ var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var _ = require('lodash'); describe('Load config', function(){ - // + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'config_test'), {silent: true}); + var loadConfig = require('../../../lib/hexo/load_config'); + var defaultConfig = require('../../../lib/hexo/default_config'); + + function reset(){ + hexo.env.init = false; + hexo.config = _.clone(defaultConfig); + } + + before(function(){ + return fs.mkdirs(pathFn.join(hexo.base_dir, 'themes', 'landscape')).then(function(){ + return hexo.init(); + }); + }); + + it('config file does not exist', function(){ + return loadConfig(hexo).then(function(){ + hexo.env.init.should.be.false; + }); + }); + + it('_config.yml exists', function(){ + var configPath = pathFn.join(hexo.base_dir, '_config.yml'); + + return fs.writeFile(configPath, 'foo: 1').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.env.init.should.be.true; + hexo.config.foo.should.eql(1); + + reset(); + return fs.unlink(configPath); + }); + }); + + it('_config.yaml exists', function(){ + var configPath = pathFn.join(hexo.base_dir, '_config.yaml'); + + return fs.writeFile(configPath, 'bar: 2').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.env.init.should.be.true; + hexo.config.bar.should.eql(2); + + reset(); + return fs.unlink(configPath); + }); + }); + + it('_config.json exists', function(){ + var configPath = pathFn.join(hexo.base_dir, '_config.json'); + + return fs.writeFile(configPath, '{"baz": 3}').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.env.init.should.be.true; + hexo.config.baz.should.eql(3); + + reset(); + return fs.unlink(configPath); + }); + }); + + it('custom config path', function(){ + var configPath = hexo.config_path = pathFn.join(__dirname, 'werwerwer.yml'); + + return fs.writeFile(configPath, 'foo: 1').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.env.init.should.be.true; + hexo.config.foo.should.eql(1); + + reset(); + hexo.config_path = pathFn.join(hexo.base_dir, '_config.yml'); + return fs.unlink(configPath); + }); + }); + + it('handle trailing "/" of url', function(){ + var content = [ + 'root: foo', + 'url: http://hexo.io/' + ].join('\n'); + + return fs.writeFile(hexo.config_path, content).then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.config.root.should.eql('foo/'); + hexo.config.url.should.eql('http://hexo.io'); + + reset(); + return fs.unlink(hexo.config_path); + }); + }); + + it('custom public_dir', function(){ + return fs.writeFile(hexo.config_path, 'public_dir: foo').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.config.public_dir = pathFn.resolve(hexo.base_dir, 'foo') + pathFn.sep; + + reset(); + return fs.unlink(hexo.config_path); + }); + }); + + it('custom source_dir', function(){ + return fs.writeFile(hexo.config_path, 'source_dir: bar').then(function(){ + return loadConfig(hexo); + }).then(function(){ + hexo.config.source_dir = pathFn.resolve(hexo.base_dir, 'bar') + pathFn.sep; + + reset(); + return fs.unlink(hexo.config_path); + }); + }); + + it('theme has not been set', function(){ + return fs.writeFile(hexo.config_path, 'theme: ""').then(function(){ + return loadConfig(hexo); + }).catch(function(err){ + err.should.have.property('message', 'Theme has not been set. Please set the theme in _config.yml.'); + reset(); + return fs.unlink(hexo.config_path); + }); + }); + + it('theme does not exist', function(){ + return fs.writeFile(hexo.config_path, 'theme: yooo').then(function(){ + return loadConfig(hexo); + }).catch(function(err){ + err.should.have.property('message', 'Theme "yooo" does not exist.'); + reset(); + return fs.unlink(hexo.config_path); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); }); \ No newline at end of file From 9ad2447b6909c69101bf9d26bc9fc8e4392c0c32 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Mon, 8 Dec 2014 23:02:46 +0800 Subject: [PATCH 08/15] Add some console/renderer plugin tests. Fix scaffold load failed when scaffold_dir does not exist. --- lib/hexo/load_database.js | 12 +- lib/hexo/scaffold.js | 8 +- lib/plugins/console/clean.js | 2 +- lib/plugins/console/new.js | 1 + package.json | 2 - test/index.js | 2 + test/scripts/box/box.js | 3 +- test/scripts/box/file.js | 24 +++- test/scripts/console/clean.js | 32 +++++ test/scripts/console/index.js | 7 ++ test/scripts/console/init.js | 57 +++++++++ test/scripts/console/new.js | 162 ++++++++++++++++++++++++++ test/scripts/console/publish.js | 116 ++++++++++++++++++ test/scripts/console/render.js | 35 ++++++ test/scripts/filters/new_post_path.js | 3 +- test/scripts/hexo/index.js | 1 + test/scripts/hexo/load_database.js | 56 +++++++++ test/scripts/hexo/post.js | 7 +- test/scripts/hexo/render.js | 3 +- test/scripts/hexo/scaffold.js | 5 +- test/scripts/hexo/update_package.js | 3 +- test/scripts/renderers/html.js | 9 ++ test/scripts/renderers/index.js | 6 + test/scripts/renderers/json.js | 16 +++ test/scripts/renderers/swig.js | 34 ++++++ test/scripts/renderers/yaml.js | 24 ++++ test/scripts/tags/include_code.js | 2 +- 27 files changed, 598 insertions(+), 34 deletions(-) create mode 100644 test/scripts/console/clean.js create mode 100644 test/scripts/console/index.js create mode 100644 test/scripts/console/init.js create mode 100644 test/scripts/console/new.js create mode 100644 test/scripts/console/publish.js create mode 100644 test/scripts/console/render.js create mode 100644 test/scripts/hexo/load_database.js create mode 100644 test/scripts/renderers/html.js create mode 100644 test/scripts/renderers/index.js create mode 100644 test/scripts/renderers/json.js create mode 100644 test/scripts/renderers/swig.js create mode 100644 test/scripts/renderers/yaml.js diff --git a/lib/hexo/load_database.js b/lib/hexo/load_database.js index 7a92fa4c16..5659e95bf7 100644 --- a/lib/hexo/load_database.js +++ b/lib/hexo/load_database.js @@ -1,4 +1,3 @@ -var semver = require('semver'); var fs = require('hexo-fs'); module.exports = function(ctx){ @@ -9,15 +8,10 @@ module.exports = function(ctx){ return fs.exists(path).then(function(exist){ if (!exist) return; - if (semver.lt(ctx.version, '3.0.0')){ - log.debug('Deleting old database.'); - return fs.unlink(path); - } else { - log.debug('Loading database.'); - return db.load(); - } + log.debug('Loading database.'); + return db.load(); }).catch(function(){ - log.error('Database log failed. Deleting database.'); + log.error('Database load failed. Deleting database.'); return fs.unlink(path); }); }; \ No newline at end of file diff --git a/lib/hexo/scaffold.js b/lib/hexo/scaffold.js index 7c5508e472..d2ac5696ff 100644 --- a/lib/hexo/scaffold.js +++ b/lib/hexo/scaffold.js @@ -21,8 +21,12 @@ Scaffold.prototype.defaults = { Scaffold.prototype._listDir = function(){ var scaffoldDir = this.scaffoldDir; - return fs.listDir(scaffoldDir, { - ignoreFilesRegex: /^_|\/_/ + return fs.exists(scaffoldDir).then(function(exist){ + if (!exist) return []; + + return fs.listDir(scaffoldDir, { + ignoreFilesRegex: /^_|\/_/ + }); }).map(function(item){ return { name: item.substring(0, item.length - pathFn.extname(item).length), diff --git a/lib/plugins/console/clean.js b/lib/plugins/console/clean.js index 0c3bac310f..084f9e55b2 100644 --- a/lib/plugins/console/clean.js +++ b/lib/plugins/console/clean.js @@ -27,7 +27,7 @@ function deletePublicDir(ctx){ if (!exist) return; return fs.rmdir(publicDir).then(function(){ - ctx.log.info('Deleted public directory.'); + ctx.log.info('Deleted public folder.'); }); }); } \ No newline at end of file diff --git a/lib/plugins/console/new.js b/lib/plugins/console/new.js index e4f929eaa9..47e74fb778 100644 --- a/lib/plugins/console/new.js +++ b/lib/plugins/console/new.js @@ -8,6 +8,7 @@ var reservedKeys = { layout: true, slug: true, path: true, + replace: true, // Global options config: true, debug: true, diff --git a/package.json b/package.json index 5a78aec534..8e1e37c3bb 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "colors": "1.0.3", "compression": "^1.0.3", "connect": "3.x", - "graceful-fs": "^3.0.2", "hexo-front-matter": "0.0.4", "hexo-fs": "0.0.4", "highlight.js": "8.4.0", @@ -54,7 +53,6 @@ "moment": "^2.8.4", "morgan": "^1.1.1", "pretty-hrtime": "^0.2.2", - "semver": "^4.1.0", "serve-static": "^1.2.0", "sprintf-js": "0.0.7", "strip-indent": "^0.1.3", diff --git a/test/index.js b/test/index.js index da5e5d930d..7f05fba1e9 100644 --- a/test/index.js +++ b/test/index.js @@ -1,10 +1,12 @@ describe('Hexo', function(){ require('./scripts/box'); + require('./scripts/console'); require('./scripts/extend'); require('./scripts/filters'); require('./scripts/helpers'); require('./scripts/hexo'); require('./scripts/models'); + require('./scripts/renderers'); require('./scripts/tags'); require('./scripts/util'); }); \ No newline at end of file diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 22087fb19c..9a97b4d599 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -2,8 +2,7 @@ var should = require('chai').should(); var pathFn = require('path'); var Hexo = require('../../../lib/hexo'); var Box = require('../../../lib/box'); -var util = Hexo.util; -var fs = util.fs; +var fs = require('hexo-fs'); describe('Box', function(){ var hexo = new Hexo(__dirname, {}); diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index b5af8c1f49..0b8da8ded3 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -1,15 +1,19 @@ var should = require('chai').should(); var pathFn = require('path'); var Promise = require('bluebird'); -var File = require('../../../lib/box/file'); -var util = require('../../../lib/util'); -var fs = util.fs; +var fs = require('hexo-fs'); +var yaml = require('js-yaml'); describe('File', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Box = require('../../../lib/box'); + var box = new Box(hexo, __dirname); var target = pathFn.join(__dirname, '../../fixtures/test.yml'); var body; + var obj; - var file = new File({ + var file = new box.File({ source: target, path: target, type: 'create', @@ -19,6 +23,8 @@ describe('File', function(){ before(function(){ return fs.readFile(target).then(function(result){ body = result; + obj = yaml.load(result); + return hexo.init(); }); }); @@ -47,7 +53,13 @@ describe('File', function(){ }); }); - it.skip('render()'); + it('render()', function(){ + return file.render().then(function(data){ + data.should.eql(obj); + }); + }); - it.skip('renderSync()'); + it('renderSync()', function(){ + file.renderSync().should.eql(obj); + }); }); \ No newline at end of file diff --git a/test/scripts/console/clean.js b/test/scripts/console/clean.js new file mode 100644 index 0000000000..8492de307a --- /dev/null +++ b/test/scripts/console/clean.js @@ -0,0 +1,32 @@ +var should = require('chai').should(); +var fs = require('hexo-fs'); + +describe('clean', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname, {silent: true}); + var clean = require('../../../lib/plugins/console/clean').bind(hexo); + + it('delete database', function(){ + var dbPath = hexo.database.options.path; + + return fs.writeFile(dbPath, '').then(function(){ + return clean(); + }).then(function(){ + return fs.exists(dbPath); + }).then(function(exist){ + exist.should.be.false; + }); + }); + + it('delete public folder', function(){ + var publicDir = hexo.public_dir; + + return fs.mkdirs(publicDir).then(function(){ + return clean(); + }).then(function(){ + return fs.exists(publicDir); + }).then(function(exist){ + exist.should.be.false; + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/console/index.js b/test/scripts/console/index.js new file mode 100644 index 0000000000..01e7121e4a --- /dev/null +++ b/test/scripts/console/index.js @@ -0,0 +1,7 @@ +describe('Console', function(){ + require('./clean'); + require('./init'); + require('./new'); + require('./publish'); + require('./render'); +}); \ No newline at end of file diff --git a/test/scripts/console/init.js b/test/scripts/console/init.js new file mode 100644 index 0000000000..86e5aa9e4a --- /dev/null +++ b/test/scripts/console/init.js @@ -0,0 +1,57 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +describe('init', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'init_test'), {silent: true}); + var init = require('../../../lib/plugins/console/init').bind(hexo); + var assetDir = pathFn.join(hexo.core_dir, 'assets'); + var assets = []; + + before(function(){ + return fs.listDir(assetDir).then(function(files){ + assets = files.map(function(item){ + return item === 'gitignore' ? '.gitignore' : item; + }); + }); + }); + + function check(baseDir){ + return Promise.map(assets, function(item){ + return Promise.all([ + fs.readFile(pathFn.join(assetDir, item === '.gitignore' ? 'gitignore' : item)), + fs.readFile(pathFn.join(baseDir, item)) + ]).spread(function(asset, target){ + target.should.eql(asset); + }); + }).then(function(){ + return fs.rmdir(baseDir); + }) + } + + it('current path', function(){ + return init({ + _: [] + }).then(function(){ + return check(hexo.base_dir); + }); + }); + + it('relative path', function(){ + return init({ + _: ['foo'] + }).then(function(){ + return check(pathFn.join(hexo.base_dir, 'foo')); + }); + }); + + it('absolute path', function(){ + return init({ + _: [hexo.base_dir] + }).then(function(){ + return check(hexo.base_dir); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/console/new.js b/test/scripts/console/new.js new file mode 100644 index 0000000000..025e37505d --- /dev/null +++ b/test/scripts/console/new.js @@ -0,0 +1,162 @@ +var should = require('chai').should(); +var fs = require('hexo-fs'); +var moment = require('moment'); +var pathFn = require('path'); +var Promise = require('bluebird'); + +describe('new', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname, {silent: true}); + var n = require('../../../lib/plugins/console/new').bind(hexo); + var post = hexo.post; + + before(function(){ + return hexo.init(); + }); + + after(function(){ + return fs.rmdir(hexo.source_dir); + }); + + it('title', function(){ + var date = moment(); + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var body = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return n({ + _: ['Hello World'] + }).then(function(){ + return fs.readFile(path); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(path); + }); + }); + + it('layout', function(){ + var path = pathFn.join(hexo.source_dir, '_drafts', 'Hello-World.md'); + var body = [ + 'title: "Hello World"', + 'tags:', + '---', + ].join('\n') + '\n'; + + return n({ + _: ['draft', 'Hello World'] + }).then(function(){ + return fs.readFile(path); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(path); + }); + }); + + it('slug', function(){ + var date = moment(); + var path = pathFn.join(hexo.source_dir, '_posts', 'foo.md'); + var body = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return n({ + _: ['Hello World'], + slug: 'foo' + }).then(function(){ + return fs.readFile(path); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(path); + }); + }); + + it('path', function(){ + var date = moment(); + var path = pathFn.join(hexo.source_dir, '_posts', 'bar.md'); + var body = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return n({ + _: ['Hello World'], + slug: 'foo', + path: 'bar' + }).then(function(){ + return fs.readFile(path); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(path); + }); + }); + + it('rename if target existed', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return n({ + _: ['Hello World'] + }); + }).then(function(){ + return fs.exists(path); + }).then(function(exist){ + exist.should.be.true; + + return Promise.all([ + fs.unlink(path), + fs.unlink(pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md')) + ]); + }); + }); + + it('replace existing files', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return n({ + _: ['Hello World'], + replace: true + }); + }).then(function(){ + return fs.exists(pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md')); + }).then(function(exist){ + exist.should.be.false; + return fs.unlink(path); + }); + }); + + it('extra data', function(){ + var date = moment(); + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var body = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + 'foo: bar', + '---' + ].join('\n') + '\n'; + + return n({ + _: ['Hello World'], + foo: 'bar' + }).then(function(){ + return fs.readFile(path); + }).then(function(content){ + content.should.eql(body); + return fs.unlink(path); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/console/publish.js b/test/scripts/console/publish.js new file mode 100644 index 0000000000..4e142b1702 --- /dev/null +++ b/test/scripts/console/publish.js @@ -0,0 +1,116 @@ +var should = require('chai').should(); +var fs = require('hexo-fs'); +var moment = require('moment'); +var pathFn = require('path'); +var Promise = require('bluebird'); + +describe('publish', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname, {silent: true}); + var publish = require('../../../lib/plugins/console/publish').bind(hexo); + var post = hexo.post; + + before(function(){ + return hexo.init(); + }); + + after(function(){ + return fs.rmdir(hexo.source_dir); + }); + + beforeEach(function(){ + return post.create({ + title: 'Hello World', + layout: 'draft' + }); + }); + + it('slug', function(){ + var draftPath = pathFn.join(hexo.source_dir, '_drafts', 'Hello-World.md'); + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return publish({ + _: ['Hello-World'] + }).then(function(){ + return Promise.all([ + fs.exists(draftPath), + fs.readFile(path) + ]); + }).spread(function(exist, data){ + exist.should.be.false; + data.should.eql(content); + + return fs.unlink(path); + }); + }); + + it('layout', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + var date = moment(); + + var content = [ + 'layout: photo', + 'title: "Hello World"', + 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), + 'tags:', + '---' + ].join('\n') + '\n'; + + return publish({ + _: ['photo', 'Hello-World'] + }).then(function(){ + return fs.readFile(path); + }).then(function(data){ + data.should.eql(content); + return fs.unlink(path); + }) + }); + + it('rename if target existed', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return publish({ + _: ['Hello-World'] + }); + }).then(function(){ + return fs.exists(path); + }).then(function(exist){ + exist.should.be.true; + + return Promise.all([ + fs.unlink(path), + fs.unlink(pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md')) + ]); + }); + }); + + it('replace existing target', function(){ + var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); + + return post.create({ + title: 'Hello World' + }).then(function(){ + return publish({ + _: ['Hello-World'], + replace: true + }); + }).then(function(){ + return fs.exists(pathFn.join(hexo.source_dir, '_posts', 'Hello-World-1.md')); + }).then(function(exist){ + exist.should.be.false; + + return fs.unlink(path); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/console/render.js b/test/scripts/console/render.js new file mode 100644 index 0000000000..4732d8f4c9 --- /dev/null +++ b/test/scripts/console/render.js @@ -0,0 +1,35 @@ +var should = require('chai').should(); + +describe.skip('render', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(__dirname, {silent: true}); + var render = require('../../../lib/plugins/console/render').bind(hexo); + + before(function(){ + return hexo.init(); + }); + + it('relative path', function(){ + // + }); + + it('absolute path', function(){ + // + }); + + it('multiple files', function(){ + // + }); + + it('output', function(){ + // + }); + + it('engine', function(){ + // + }); + + it('pretty', function(){ + // + }); +}); \ No newline at end of file diff --git a/test/scripts/filters/new_post_path.js b/test/scripts/filters/new_post_path.js index 9c0f6650c5..ad9c5ead37 100644 --- a/test/scripts/filters/new_post_path.js +++ b/test/scripts/filters/new_post_path.js @@ -2,8 +2,7 @@ var should = require('chai').should(); var pathFn = require('path'); var moment = require('moment'); var Promise = require('bluebird'); -var util = require('../../../lib/util'); -var fs = util.fs; +var fs = require('hexo-fs'); var NEW_POST_NAME = ':title.md'; diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js index 72108731ed..307ed7fefe 100644 --- a/test/scripts/hexo/index.js +++ b/test/scripts/hexo/index.js @@ -1,6 +1,7 @@ describe('Core', function(){ require('./hexo'); require('./load_config'); + require('./load_database'); require('./post'); require('./render'); require('./scaffold'); diff --git a/test/scripts/hexo/load_database.js b/test/scripts/hexo/load_database.js new file mode 100644 index 0000000000..53f1a6a097 --- /dev/null +++ b/test/scripts/hexo/load_database.js @@ -0,0 +1,56 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); + +describe('Load database', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'db_test'), {silent: true}); + var loadDatabase = require('../../../lib/hexo/load_database'); + var dbPath = hexo.database.options.path; + + var fixture = { + meta: { + version: 1, + warehouse: require('warehouse').version + }, + models: { + Test: [ + {_id: "A"}, + {_id: "B"}, + {_id: "C"} + ] + } + }; + + before(function(){ + return fs.mkdir(hexo.base_dir); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('database does not exist', function(){ + return loadDatabase(hexo); + }); + + it('database load success', function(){ + return fs.writeFile(dbPath, JSON.stringify(fixture)).then(function(){ + return loadDatabase(hexo); + }).then(function(){ + hexo.model('Test').toArray({lean: true}).should.eql(fixture.models.Test); + hexo.model('Test').destroy(); + return fs.unlink(dbPath); + }); + }); + + it('database load failed', function(){ + return fs.writeFile(dbPath, '{1423432: 324').then(function(){ + return loadDatabase(hexo); + }).then(function(){ + return fs.exists(dbPath); + }).then(function(exist){ + exist.should.be.false; + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/hexo/post.js b/test/scripts/hexo/post.js index 3bc03950e5..e2da876d30 100644 --- a/test/scripts/hexo/post.js +++ b/test/scripts/hexo/post.js @@ -2,8 +2,7 @@ var should = require('chai').should(); var pathFn = require('path'); var moment = require('moment'); var Promise = require('bluebird'); -var util = require('../../../lib/util'); -var fs = util.fs; +var fs = require('hexo-fs'); describe('Post', function(){ var Hexo = require('../../../lib/hexo'); @@ -14,6 +13,10 @@ describe('Post', function(){ return hexo.init(); }); + after(function(){ + return fs.rmdir(hexo.source_dir); + }); + it('create()', function(){ var emitted = false; var path = pathFn.join(hexo.source_dir, '_posts', 'Hello-World.md'); diff --git a/test/scripts/hexo/render.js b/test/scripts/hexo/render.js index d630bd832f..b80d6cdf63 100644 --- a/test/scripts/hexo/render.js +++ b/test/scripts/hexo/render.js @@ -1,6 +1,5 @@ var Hexo = require('../../../lib/hexo'); -var util = Hexo.util; -var fs = util.fs; +var fs = require('hexo-fs'); var pathFn = require('path'); var fixtureDir = pathFn.join(__dirname, '../../fixtures'); diff --git a/test/scripts/hexo/scaffold.js b/test/scripts/hexo/scaffold.js index 93ade9598e..2efcebe171 100644 --- a/test/scripts/hexo/scaffold.js +++ b/test/scripts/hexo/scaffold.js @@ -1,8 +1,7 @@ var should = require('chai').should(); var pathFn = require('path'); var Promise = require('bluebird'); -var util = require('../../../lib/util'); -var fs = util.fs; +var fs = require('hexo-fs'); describe('Scaffold', function(){ var Hexo = require('../../../lib/hexo'); @@ -25,7 +24,7 @@ describe('Scaffold', function(){ }); after(function(){ - return fs.unlink(testPath); + return fs.rmdir(scaffoldDir); }); it('get() - file exists', function(){ diff --git a/test/scripts/hexo/update_package.js b/test/scripts/hexo/update_package.js index 4c9a0d8d56..e1df9457bf 100644 --- a/test/scripts/hexo/update_package.js +++ b/test/scripts/hexo/update_package.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var pathFn = require('path'); -var util = require('../../../lib/util'); -var fs = util.fs; +var fs = require('hexo-fs'); describe('Update package.json', function(){ var Hexo = require('../../../lib/hexo'); diff --git a/test/scripts/renderers/html.js b/test/scripts/renderers/html.js new file mode 100644 index 0000000000..570434d87f --- /dev/null +++ b/test/scripts/renderers/html.js @@ -0,0 +1,9 @@ +var should = require('chai').should(); + +describe('html', function(){ + var r = require('../../../lib/plugins/renderer/html'); + + it('normal', function(){ + r({text: '123'}).should.eql('123'); + }); +}); \ No newline at end of file diff --git a/test/scripts/renderers/index.js b/test/scripts/renderers/index.js new file mode 100644 index 0000000000..6bce99435e --- /dev/null +++ b/test/scripts/renderers/index.js @@ -0,0 +1,6 @@ +describe('Renderers', function(){ + require('./html'); + require('./json'); + require('./swig'); + require('./yaml'); +}); \ No newline at end of file diff --git a/test/scripts/renderers/json.js b/test/scripts/renderers/json.js new file mode 100644 index 0000000000..7c251992a6 --- /dev/null +++ b/test/scripts/renderers/json.js @@ -0,0 +1,16 @@ +var should = require('chai').should(); + +describe('json', function(){ + var r = require('../../../lib/plugins/renderer/json'); + + it('normal', function(){ + var data = { + foo: 1, + bar: { + baz: 2 + } + }; + + r({text: JSON.stringify(data)}).should.eql(data); + }); +}); \ No newline at end of file diff --git a/test/scripts/renderers/swig.js b/test/scripts/renderers/swig.js new file mode 100644 index 0000000000..1abfeb9f99 --- /dev/null +++ b/test/scripts/renderers/swig.js @@ -0,0 +1,34 @@ +var should = require('chai').should(); +var fs = require('hexo-fs'); + +describe('swig', function(){ + var r = require('../../../lib/plugins/renderer/swig'); + + it('normal', function(){ + var body = [ + 'Hello {{ name }}!' + ].join('\n'); + + r({text: body}, { + name: 'world' + }).should.eql('Hello world!'); + }); + + it('override "for" tag', function(){ + var body = [ + '{% for x in arr %}', + '{{ x }}', + '{% endfor %}' + ].join(''); + + var data = { + arr: { + toArray: function(){ + return [1, 2, 3]; + } + } + }; + + r({text: body}, data).should.eql('123'); + }); +}); \ No newline at end of file diff --git a/test/scripts/renderers/yaml.js b/test/scripts/renderers/yaml.js new file mode 100644 index 0000000000..7095b910aa --- /dev/null +++ b/test/scripts/renderers/yaml.js @@ -0,0 +1,24 @@ +var should = require('chai').should(); + +describe('yaml', function(){ + var r = require('../../../lib/plugins/renderer/yaml'); + + it('normal', function(){ + r({text: 'foo: 1'}).should.eql({foo: 1}); + }); + + it('escape', function(){ + var body = [ + 'foo: 1', + 'bar:', + '\tbaz: 3' + ].join('\n'); + + r({text: body}).should.eql({ + foo: 1, + bar: { + baz: 3 + } + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/tags/include_code.js b/test/scripts/tags/include_code.js index 648f4dd250..b26f59610b 100644 --- a/test/scripts/tags/include_code.js +++ b/test/scripts/tags/include_code.js @@ -1,7 +1,7 @@ var cheerio = require('cheerio'); var path = require('path'); var should = require('chai').should(); -var fs = require('../../../lib/util').fs; +var fs = require('hexo-fs'); var highlight = require('../../../lib/util/highlight'); // Fixture From d3196a21f7264512cf034248b9378e852ee44b89 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Tue, 9 Dec 2014 17:28:01 +0800 Subject: [PATCH 09/15] Run external plugins in sandbox --- lib/hexo/index.js | 1 - lib/hexo/load_plugins.js | 74 +++++++++++++++++++++++++++++---- lib/hexo/load_scripts.js | 25 ----------- lib/plugins/tag/include_code.js | 2 +- test/scripts/helpers/number.js | 34 +++++++-------- 5 files changed, 84 insertions(+), 52 deletions(-) delete mode 100644 lib/hexo/load_scripts.js diff --git a/lib/hexo/index.js b/lib/hexo/index.js index cf510943a2..e97422caaf 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -119,7 +119,6 @@ Hexo.prototype.init = function(){ // Load external plugins & scripts return Promise.all([ require('./load_plugins')(self), - require('./load_scripts')(self), require('./update_package')(self) ]); }).then(function(){ diff --git a/lib/hexo/load_plugins.js b/lib/hexo/load_plugins.js index da91a95f9e..3eab51353d 100644 --- a/lib/hexo/load_plugins.js +++ b/lib/hexo/load_plugins.js @@ -1,23 +1,83 @@ var pathFn = require('path'); var fs = require('hexo-fs'); +var tildify = require('tildify'); +var Promise = require('bluebird'); +var vm = require('vm'); +var Module = require('module'); + +var pre = '(function(exports, require, module, __filename, __dirname, hexo){'; +var post = '});'; require('colors'); module.exports = function(ctx){ if (!ctx.env.init || ctx.env.safe) return; + return Promise.all([ + loadModules(ctx), + loadScripts(ctx) + ]); +}; + +function runInContext(ctx, path){ + return fs.readFile(path).then(function(script){ + // Based on: https://github.com/joyent/node/blob/v0.10.33/src/node.js#L516 + var module = new Module(path); + module.filename = path; + module.paths = Module._nodeModulePaths(path); + + function require(path){ + return module.require(path); + } + + require.resolve = function(request){ + return Module._resolveFilename(request, module); + }; + + require.main = process.mainModule; + require.extensions = Module._extensions; + require.cache = Module._cache; + + var fn = vm.runInThisContext(pre + script + post, path); + + return fn(module.exports, require, module, path, pathFn.dirname(path), ctx); + }); +} + +function loadModules(ctx){ var pluginDir = ctx.plugin_dir; - var currentName = ''; return fs.exists(pluginDir).then(function(exist){ return exist ? fs.readdir(pluginDir) : []; }).filter(function(name){ return name.slice(0, 5) === 'hexo-'; }).map(function(name){ - currentName = name; - require(pathFn.join(pluginDir, name)); - ctx.log.debug('Plugin loaded: %s', name.magenta); - }).catch(function(err){ - ctx.log.error({err: err}, 'Plugin load failed: %s', currentName.magenta); + var path = require.resolve(pathFn.join(pluginDir, name)); + + return runInContext(ctx, path).then(function(){ + ctx.log.debug('Plugin loaded: %s', name.magenta); + }, function(err){ + ctx.log.error({err: err}, 'Plugin load failed: %s', name.magenta); + }); + }); +} + +function loadScripts(ctx){ + return Promise.filter([ + ctx.script_dir, + ctx.theme_script_dir + ], function(scriptDir){ + return scriptDir ? fs.exists(scriptDir) : false; + }).map(function(scriptDir){ + var path = ''; + + return fs.listDir(scriptDir).map(function(name){ + path = pathFn.join(scriptDir, name); + return runInContext(ctx, path); + }).then(function(){ + ctx.log.debug('Script loaded: %s', tildify(path).magenta); + }, function(err){ + ctx.log.error({err: err}, 'Script load failed: %s', tildify(path).magenta); + }); }); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/lib/hexo/load_scripts.js b/lib/hexo/load_scripts.js deleted file mode 100644 index f49a6a9496..0000000000 --- a/lib/hexo/load_scripts.js +++ /dev/null @@ -1,25 +0,0 @@ -var Promise = require('bluebird'); -var pathFn = require('path'); -var tildify = require('tildify'); -var fs = require('hexo-fs'); - -require('colors'); - -module.exports = function(ctx){ - return Promise.filter([ - ctx.script_dir, - ctx.theme_script_dir - ], function(scriptDir){ - return scriptDir ? fs.exists(scriptDir) : false; - }).map(function(scriptDir){ - var scriptPath = ''; - - return fs.listDir(scriptDir).map(function(name){ - scriptPath = pathFn.join(scriptDir, name); - require(scriptPath); - ctx.log.debug('Script loaded: %s', tildify(scriptPath).magenta); - }).catch(function(err){ - ctx.log.error({err: err}, 'Script load failed: %s', tildify(scriptPath).magenta); - }); - }); -}; \ No newline at end of file diff --git a/lib/plugins/tag/include_code.js b/lib/plugins/tag/include_code.js index fe01ffca7e..dcd4456c79 100644 --- a/lib/plugins/tag/include_code.js +++ b/lib/plugins/tag/include_code.js @@ -1,4 +1,4 @@ -var fs = require('graceful-fs'), +var fs = require('hexo-fs'), pathFn = require('path'), util = require('../../util'), file = util.file2, diff --git a/test/scripts/helpers/number.js b/test/scripts/helpers/number.js index 6237c0176e..8a107e9595 100644 --- a/test/scripts/helpers/number.js +++ b/test/scripts/helpers/number.js @@ -3,26 +3,24 @@ var should = require('chai').should(); describe('number', function(){ var number = require('../../../lib/plugins/helper/number'); - describe('number_format', function(){ - it('default', function(){ - number.number_format(1234.567).should.eql('1,234.567'); - }); + it('default', function(){ + number.number_format(1234.567).should.eql('1,234.567'); + }); - it('precision', function(){ - number.number_format(1234.567, {precision: false}).should.eql('1,234.567'); - number.number_format(1234.567, {precision: 0}).should.eql('1,234'); - number.number_format(1234.567, {precision: 1}).should.eql('1,234.6'); - number.number_format(1234.567, {precision: 2}).should.eql('1,234.57'); - number.number_format(1234.567, {precision: 3}).should.eql('1,234.567'); - number.number_format(1234.567, {precision: 4}).should.eql('1,234.5670'); - }); + it('precision', function(){ + number.number_format(1234.567, {precision: false}).should.eql('1,234.567'); + number.number_format(1234.567, {precision: 0}).should.eql('1,234'); + number.number_format(1234.567, {precision: 1}).should.eql('1,234.6'); + number.number_format(1234.567, {precision: 2}).should.eql('1,234.57'); + number.number_format(1234.567, {precision: 3}).should.eql('1,234.567'); + number.number_format(1234.567, {precision: 4}).should.eql('1,234.5670'); + }); - it('delimiter', function(){ - number.number_format(1234.567, {delimiter: ' '}).should.eql('1 234.567'); - }); + it('delimiter', function(){ + number.number_format(1234.567, {delimiter: ' '}).should.eql('1 234.567'); + }); - it('separator', function(){ - number.number_format(1234.567, {separator: '*'}).should.eql('1,234*567'); - }); + it('separator', function(){ + number.number_format(1234.567, {separator: '*'}).should.eql('1,234*567'); }); }); \ No newline at end of file From e8dad06bd8008dbc6fd25b8887f878d704707136 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Wed, 10 Dec 2014 15:55:58 +0800 Subject: [PATCH 10/15] Split server module --- lib/plugins/console/index.js | 11 ----- lib/plugins/console/server.js | 3 -- lib/plugins/filter/index.js | 1 - lib/plugins/filter/server_middleware/gzip.js | 5 --- .../filter/server_middleware/header.js | 6 --- lib/plugins/filter/server_middleware/index.js | 10 ----- .../filter/server_middleware/logger.js | 11 ----- .../filter/server_middleware/redirect.js | 13 ------ lib/plugins/filter/server_middleware/route.js | 42 ------------------- .../filter/server_middleware/static.js | 5 --- lib/util/index.js | 1 - lib/util/server.js | 40 ------------------ package.json | 9 +--- 13 files changed, 2 insertions(+), 155 deletions(-) delete mode 100644 lib/plugins/console/server.js delete mode 100644 lib/plugins/filter/server_middleware/gzip.js delete mode 100644 lib/plugins/filter/server_middleware/header.js delete mode 100644 lib/plugins/filter/server_middleware/index.js delete mode 100644 lib/plugins/filter/server_middleware/logger.js delete mode 100644 lib/plugins/filter/server_middleware/redirect.js delete mode 100644 lib/plugins/filter/server_middleware/route.js delete mode 100644 lib/plugins/filter/server_middleware/static.js delete mode 100644 lib/util/server.js diff --git a/lib/plugins/console/index.js b/lib/plugins/console/index.js index 4af250aca6..be28ebb594 100644 --- a/lib/plugins/console/index.js +++ b/lib/plugins/console/index.js @@ -69,17 +69,6 @@ module.exports = function(ctx){ ] }, require('./render')); - console.register('server', 'Start the server.', { - desc: 'Start the server and watch for file changes.', - options: [ - {name: '-i, --ip', desc: 'Override the default server IP. Bind to all IP address by default.'}, - {name: '-p, --port', desc: 'Override the default port.'}, - {name: '-s, --static', desc: 'Only serve static files.'}, - {name: '-l, --log [format]', desc: 'Enable logger. Override the logger format.'}, - {name: '-d, --drafts', desc: 'Serve draft posts.'} - ] - }, require('./server')); - console.register('version', 'Display version information.', { init: true }, require('./version')); diff --git a/lib/plugins/console/server.js b/lib/plugins/console/server.js deleted file mode 100644 index 723548d0d1..0000000000 --- a/lib/plugins/console/server.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function(args){ - // -}; \ No newline at end of file diff --git a/lib/plugins/filter/index.js b/lib/plugins/filter/index.js index 097301616d..c20035222d 100644 --- a/lib/plugins/filter/index.js +++ b/lib/plugins/filter/index.js @@ -3,7 +3,6 @@ module.exports = function(ctx){ require('./after_post_render')(ctx); require('./before_post_render')(ctx); - require('./server_middleware')(ctx); filter.register('new_post_path', require('./new_post_path')); filter.register('post_permalink', require('./post_permalink')); diff --git a/lib/plugins/filter/server_middleware/gzip.js b/lib/plugins/filter/server_middleware/gzip.js deleted file mode 100644 index 37201edd22..0000000000 --- a/lib/plugins/filter/server_middleware/gzip.js +++ /dev/null @@ -1,5 +0,0 @@ -var compress = require('compression'); - -module.exports = function(app){ - app.use(compress()); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/header.js b/lib/plugins/filter/server_middleware/header.js deleted file mode 100644 index dab3efde0a..0000000000 --- a/lib/plugins/filter/server_middleware/header.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function(app){ - app.use(function(req, res, next){ - res.setHeader('X-Powered-By', 'Hexo'); - next(); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/index.js b/lib/plugins/filter/server_middleware/index.js deleted file mode 100644 index 338ba44312..0000000000 --- a/lib/plugins/filter/server_middleware/index.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function(ctx){ - var filter = ctx.extend.filter; - - filter.register('server_middleware', require('./logger')); - filter.register('server_middleware', require('./header')); - filter.register('server_middleware', require('./route')); - filter.register('server_middleware', require('./static')); - filter.register('server_middleware', require('./redirect')); - filter.register('server_middleware', require('./gzip')); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/logger.js b/lib/plugins/filter/server_middleware/logger.js deleted file mode 100644 index 88b3438275..0000000000 --- a/lib/plugins/filter/server_middleware/logger.js +++ /dev/null @@ -1,11 +0,0 @@ -var morgan = require('morgan'); - -module.exports = function(app){ - var config = this.config; - var args = this.env.args || {}; - var format = args.l || args.log || config.logger_format; - - if (typeof format !== 'string') format = 'dev'; - - app.use(morgan(format)); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/redirect.js b/lib/plugins/filter/server_middleware/redirect.js deleted file mode 100644 index de21fb6ec7..0000000000 --- a/lib/plugins/filter/server_middleware/redirect.js +++ /dev/null @@ -1,13 +0,0 @@ -var serverUtil = require('../../../util').server; - -module.exports = function(app){ - var root = this.config.root; - if (root === '/') return; - - // If root url is not `/`, redirect to the correct root url - app.use(function(req, res, next){ - if (req.method !== 'GET' || req.url !== '/') return next(); - - serverUtil.redirect(res, root); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/route.js b/lib/plugins/filter/server_middleware/route.js deleted file mode 100644 index 49d3e9edf6..0000000000 --- a/lib/plugins/filter/server_middleware/route.js +++ /dev/null @@ -1,42 +0,0 @@ -var pathFn = require('path'); -var Readable = require('stream').Readable; -var serverUtil = require('../../../util').server; - -module.exports = function(app){ - var config = this.config; - var args = this.env.args; - var root = config.root; - var route = this.route; - - if (args.s || args.static) return; - - app.use(root, function(req, res, next){ - var method = req.method; - if (method !== 'GET' && method !== 'HEAD') return next(); - - var url = route.format(decodeURIComponent(req.url)); - var target = route.get(url); - - // When the URL is `foo/index.html` but users access `foo`, redirect to `foo/`. - if (!target){ - if (pathFn.extname(url)) return next(); - - return serverUtil.redirect(res, root + url + '/'); - } - - target(function(err, result){ - if (err) return next(err); - if (result == null) return next(); - - serverUtil.contentType(res, pathFn.extname(url)); - - if (method === 'HEAD') return res.end(); - - if (result instanceof Readable){ - result.pipe(res).on('error', next); - } else { - res.end(result); - } - }); - }); -}; \ No newline at end of file diff --git a/lib/plugins/filter/server_middleware/static.js b/lib/plugins/filter/server_middleware/static.js deleted file mode 100644 index bf7f17d6b8..0000000000 --- a/lib/plugins/filter/server_middleware/static.js +++ /dev/null @@ -1,5 +0,0 @@ -var serveStatic = require('serve-static'); - -module.exports = function(app){ - app.use(this.config.root, serveStatic(this.public_dir)); -}; \ No newline at end of file diff --git a/lib/util/index.js b/lib/util/index.js index df3eedaf48..8e0c84602d 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -9,7 +9,6 @@ exports.fs = require('hexo-fs'); exports.highlight = require('./highlight'); exports.htmlTag = exports.html_tag = require('./html_tag'); exports.permalink = require('./permalink'); -exports.server = require('./server'); exports.yfm = require('hexo-front-matter'); exports.inflector = inflection; exports.titlecase = inflection.titleize; diff --git a/lib/util/server.js b/lib/util/server.js deleted file mode 100644 index 5a8f8a4426..0000000000 --- a/lib/util/server.js +++ /dev/null @@ -1,40 +0,0 @@ -/** -* Server utilities. -* -* @class server -* @namespace util -* @since 2.7.0 -* @module hexo -*/ - -var mime = require('mime'); - -/** -* Redirects to a URL. -* -* @method redirect -* @param {Response} res -* @param {String} path -*/ -exports.redirect = function(res, path){ - if (res == null) throw new Error('res is required.'); - if (typeof path !== 'string') throw new TypeError('path must be a string.'); - - res.statusCode = 302; - res.setHeader('Location', path); - res.end('Redirecting to ' + path); -}; - -/** -* Sets Content-Type header. -* -* @method contentType -* @param {Response} res -* @param {String} type -*/ -exports.contentType = function(res, type){ - if (res == null) throw new Error('res is required.'); - type = type || 'application/octet-stream'; - - res.setHeader('Content-Type', ~type.indexOf('/') ? type : mime.lookup(type)); -}; diff --git a/package.json b/package.json index 8e1e37c3bb..59d6cc8e49 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "hexo": "./bin/hexo" }, "scripts": { - "test": "./node_modules/.bin/gulp test" + "test": "gulp test" }, "directories": { "lib": "./lib", @@ -38,22 +38,17 @@ "bluebird": "^2.3.11", "bunyan": "^1.2.1", "cheerio": "0.17.0", - "chokidar": "0.11.1", + "chokidar": "0.12.0", "colors": "1.0.3", - "compression": "^1.0.3", - "connect": "3.x", "hexo-front-matter": "0.0.4", "hexo-fs": "0.0.4", "highlight.js": "8.4.0", "inflection": "^1.5.1", "js-yaml": "^3.1.0", "lodash": "^2.4.1", - "mime": "^1.2.11", "minimist": "1.1.0", "moment": "^2.8.4", - "morgan": "^1.1.1", "pretty-hrtime": "^0.2.2", - "serve-static": "^1.2.0", "sprintf-js": "0.0.7", "strip-indent": "^0.1.3", "swig": "1.4.2", From 1b340783345d154868e7c8632d834ceb6216f6e9 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Thu, 11 Dec 2014 23:01:38 +0800 Subject: [PATCH 11/15] Rewrite Box and add tests for it. --- lib/box/file.js | 56 +++-- lib/box/index.js | 316 +++++++++++++++++++------ lib/box/pattern.js | 10 +- lib/cli/init.js | 2 - lib/hexo/index.js | 5 +- lib/models/cache.js | 7 +- lib/plugins/deployer/index.js | 3 - lib/theme/index.js | 2 +- package.json | 3 +- test/scripts/box/box.js | 383 ++++++++++++++++++++++++++++++- test/scripts/box/file.js | 86 ++++++- test/scripts/hexo/load_config.js | 8 +- test/scripts/models/cache.js | 11 - 13 files changed, 750 insertions(+), 142 deletions(-) delete mode 100644 lib/plugins/deployer/index.js diff --git a/lib/box/file.js b/lib/box/file.js index a041600979..089e06f310 100644 --- a/lib/box/file.js +++ b/lib/box/file.js @@ -1,14 +1,20 @@ var fs = require('hexo-fs'); +var Promise = require('bluebird'); function File(data){ this.source = data.source; this.path = data.path; this.type = data.type; this.params = data.params; + this.content = data.content; +} - if (this._context){ - this._render = this._context.render; - } +function wrapReadOptions(options){ + options = options || {}; + if (typeof options === 'string') options = {encoding: options}; + if (!options.hasOwnProperty('encoding')) options.encoding = 'utf8'; + + return options; } File.prototype.read = function(options, callback){ @@ -17,11 +23,38 @@ File.prototype.read = function(options, callback){ options = {}; } - return fs.readFile(this.source, options).nodeify(callback); + var self = this; + var content = this.content; + + options = wrapReadOptions(options); + + return new Promise(function(resolve, reject){ + if (content){ + var encoding = options.encoding; + + if (encoding){ + resolve(content.toString(encoding)); + } else { + resolve(content); + } + } else { + reject(new Error('File "' + self.source + '" was deleted.')); + } + }).nodeify(callback); }; File.prototype.readSync = function(options){ - return fs.readFileSync(this.source, options); + var content = this.content; + if (!content) throw new Error('File "' + this.source + '" was deleted.'); + + options = wrapReadOptions(options); + var encoding = options.encoding; + + if (encoding){ + return content.toString(encoding); + } else { + return content; + } }; File.prototype.stat = function(callback){ @@ -32,17 +65,4 @@ File.prototype.statSync = function(){ return fs.statSync(this.source); }; -File.prototype.render = function(options, callback){ - if (!callback && typeof options === 'function'){ - callback = options; - options = {}; - } - - return this._render.render({path: this.source}, options).nodeify(callback); -}; - -File.prototype.renderSync = function(options){ - return this._render.renderSync({path: this.source}, options); -}; - module.exports = File; \ No newline at end of file diff --git a/lib/box/index.js b/lib/box/index.js index c8db8f5fb6..82768a13f8 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -1,27 +1,37 @@ var pathFn = require('path'); var Promise = require('bluebird'); var _ = require('lodash'); -var chokidar = require('chokidar'); var File = require('./file'); var Pattern = require('./pattern'); var util = require('../util'); var fs = require('hexo-fs'); +var prettyHrtime = require('pretty-hrtime'); +var crypto = require('crypto'); + +var escape = util.escape; +var join = pathFn.join; require('colors'); -function Box(context, base, options){ - this.context = context; - this.base = base; - this.processors = []; - this.processingFiles = {}; - this.watcher = null; - this.isProcessing = false; +function Box(ctx, base, options){ this.options = _.extend({ presistent: true, ignored: /[\/\\]\./, ignoreInitial: true }, options); + if (base.substring(base.length - 1) !== pathFn.sep){ + base += pathFn.sep; + } + + this.context = ctx; + this.base = base; + this.processors = []; + this.processingFiles = {}; + this.watcher = null; + this.Cache = ctx.model('Cache'); + this.bufferStore = {}; + var _File = this.File = function(data){ File.call(this, data); }; @@ -29,13 +39,29 @@ function Box(context, base, options){ util.inherits(_File, File); _File.prototype.box = this; - _File.prototype._context = context; + + _File.prototype.render = function(options, callback){ + if (!callback && typeof options === 'function'){ + callback = options; + options = {}; + } + + return ctx.render.render({path: this.source}, options).nodeify(callback); + }; + + _File.prototype.renderSync = function(options){ + return ctx.render.renderSync({path: this.source}, options); + }; +} + +function patternNoob(){ + return {}; } Box.prototype.addProcessor = function(pattern, fn){ if (!fn && typeof pattern === 'function'){ fn = pattern; - pattern = new Pattern(/(.*)/); + pattern = new Pattern(patternNoob); } if (typeof fn !== 'function') throw new TypeError('fn must be a function'); @@ -43,113 +69,267 @@ Box.prototype.addProcessor = function(pattern, fn){ this.processors.push({ pattern: pattern, - process: fn + process: Promise.method(fn) }); }; -Box.prototype.process = function(files, callback){ +Box.prototype.process = function(callback){ var self = this; + + return this._loadFiles().then(function(files){ + return self._process(files); + }).nodeify(callback); +}; + +function listDir(path){ + return fs.listDir(path).catch(function(err){ + // Return an empty array if path does not exist + if (err.cause.code !== 'ENOENT') throw err; + return []; + }).map(function(path){ + // Replace backslashes on Windows + return path.replace(/\\/g, '/'); + }); +} + +Box.prototype._loadFiles = function(){ var base = this.base; + var Cache = this.Cache; + var self = this; var ctx = this.context; + var relativeBase = base.substring(ctx.base_dir.length); + var regex = new RegExp('^' + escape.regex(relativeBase)); + var baseLength = relativeBase.length; - return new Promise(function(resolve, reject){ - if (self.isProcessing) return reject(new Error('Box is processing!')); + // Find existing cache data + var existed = Cache.find({_id: regex}).map(function(item){ + return item._id.substring(baseLength); + }); - self.isProcessing = true; - ctx.emit('processBefore', base); + return listDir(base).then(function(files){ + var result = []; - if (files){ - return Array.isArray(files) ? files : [files]; - } else { - return self._loadFileList(); + // created = files - existed + var created = _.difference(files, existed); + var i, len, item; + + for (i = 0, len = created.length; i < len; i++){ + item = created[i]; + + result.push({ + path: item, + type: 'create' + }); + } + + for (i = 0, len = existed.length; i < len; i++){ + item = existed[i]; + + if (~files.indexOf(item)){ + result.push({ + path: item, + type: 'update' + }); + } else { + result.push({ + path: item, + type: 'delete' + }); + } } + + return result; }).map(function(item){ - if (typeof item === 'object'){ - return self._dispatch(item); + switch (item.type){ + case 'create': + return self._handleCreatedFile(item.path); + + case 'update': + return self._handleUpdatedFile(item.path); + + case 'delete': + return self._handleDeletedFile(item.path); + } + }); +}; + +function getChecksum(path){ + return new Promise(function(resolve, reject){ + var hash = crypto.createHash('sha1'); + var buffers = []; + var length = 0; + var stream = fs.createReadStream(path); + + stream.on('readable', function(){ + var chunk; + + while ((chunk = stream.read()) != null){ + length += chunk.length; + buffers.push(chunk); + hash.update(chunk); + } + }).on('end', function(){ + resolve({ + content: Buffer.concat(buffers, length), + checksum: hash.digest('hex') + }); + }).on('error', reject); + }); +} + +Box.prototype._handleCreatedFile = function(path, stats){ + var Cache = this.Cache; + var ctx = this.context; + var fullPath = join(this.base, path); + var self = this; + + return getChecksum(fullPath).then(function(data){ + self.bufferStore[path] = data.content; + + return Cache.insert({ + _id: fullPath.substring(ctx.base_dir.length), + checksum: data.checksum + }).thenReturn({ + type: 'create', + path: path + }); + }); +}; + +Box.prototype._handleUpdatedFile = function(path, stats){ + var Cache = this.Cache; + var ctx = this.context; + var fullPath = join(this.base, path); + + return getChecksum(fullPath).then(function(data){ + var id = fullPath.substring(ctx.base_dir.length); + var cache = Cache.findById(id); + var checksum = data.checksum; + + if (cache.checksum === checksum){ + return { + type: 'skip', + path: path + }; + } else if (cache){ + cache.checksum = checksum; + return cache.save().thenReturn({ + type: 'update', + path: path + }); } else { - return self._dispatch({ - path: item, - type: 'update' + return Cache.insert({ + _id: id, + checksum: checksum + }).thenReturn({ + type: 'create', + path: path }); } - }).finally(function(){ - self.isProcessing = false; - }).nodeify(callback); + }); +}; + +Box.prototype._handleDeletedFile = function(path){ + var fullPath = join(this.base, path); + var ctx = this.context; + + this.bufferStore[path] = null; + + return this.Cache.removeById(fullPath.substring(ctx.base_dir.length)).thenReturn({ + type: 'delete', + path: path + }); +}; + +Box.prototype._process = function(files){ + var self = this; + + return Promise.map(files, function(item){ + return self._dispatch(item); + }); }; Box.prototype.load = Box.prototype.process; Box.prototype._dispatch = function(item){ - var path = item.path.replace(/\\/g, '/'); - var start = Date.now(); - var base = this.base; + var path = item.path; + var File = this.File; var self = this; var ctx = this.context; - var log = ctx.log; - var executed = false; + var base = this.base; + var start = process.hrtime(); + // Skip processing files if (this.processingFiles[path]) return; + // Lock the file this.processingFiles[path] = true; - return Promise.map(this.processors, function(processor){ - var params = processor.pattern.match(item); - if (!params) return; + // Use Promise.reduce to calculate the number of processors executed + return Promise.reduce(this.processors, function(count, processor){ + // Check whether the path match with the pattern of the processor + var params = processor.pattern.match(path); + if (!params) return count; - var file = new self.File({ - src: pathFn.join(base, item), + var file = new File({ + source: join(base, path), path: path, + type: item.type, params: params, - type: item.type + content: self.bufferStore[path] }); - executed = true; - - return processor.process.call(self, file); - }).then(function(){ - if (!executed) return; - - log.debug('Processed: %s in ' + '%dms'.cyan, path.magenta, Date.now() - start); + return processor.process(file).thenReturn(count + 1); + }, 0).then(function(count){ + // Display debug message if there're any processors executed + if (count){ + var interval = prettyHrtime(process.hrtime(start)); + ctx.log.debug('Processed in %s: %s', interval.cyan, path.magenta); + } }, function(err){ - log.error('Process failed: %s', path.magenta); + ctx.log.error({err: err}, 'Process failed: %s', path.magenta); throw err; }).finally(function(){ - self.processingFiles = false; + // Remember to unlock the file + self.processingFiles[path] = false; }); }; -Box.prototype._loadFileList = function(){ - return fs.listDir(this.base); -}; - -var chokidarEventMap = { - add: 'create', - change: 'update', - unlink: 'delete' -}; - -Box.prototype.watch = function(){ - if (this.watcher) throw new Error('Watcher has already started.'); - +Box.prototype.watch = function(callback){ var base = this.base; var baseLength = base.length; var self = this; - this.watcher = chokidar.watch(this.base, this.options) - .on('all', function(event, src){ - var type = chokidarEventMap[event]; - var path = src.substring(baseLength); + function getPath(path){ + return path.substring(baseLength); + } - if (!type) return; + function dispatch(data){ + self._dispatch(data); + } - self.process({type: type, path: path}); + return new Promise(function(resolve, reject){ + if (self.watcher) return reject(new Error('Watcher has already started.')); + fs.watch(base, self.options).then(resolve, reject); + }).then(function(watcher){ + self.watcher = watcher; + + watcher.on('add', function(path){ + self._handleCreatedFile(getPath(path)).then(dispatch); + }).on('change', function(path){ + self._handleUpdatedFile(getPath(path)).then(dispatch); + }).on('unlink', function(path){ + self._handleDeletedFile(getPath(path)).then(dispatch); }); + + return watcher; + }).nodeify(callback); }; Box.prototype.unwatch = function(){ if (!this.watcher) throw new Error('Watcher hasn\'t started yet.'); - this.watcher.stop(); + this.watcher.close(); this.watcher = null; }; diff --git a/lib/box/pattern.js b/lib/box/pattern.js index 8309ce5606..be057ee307 100644 --- a/lib/box/pattern.js +++ b/lib/box/pattern.js @@ -17,11 +17,11 @@ var rParam = /([:\*])([\w\?]*)?/g; */ function Pattern(rule){ if (typeof rule === 'function'){ - this.filter = rule; + this.match = rule; } else if (rule instanceof RegExp){ - this.filter = regexFilter(rule); + this.match = regexFilter(rule); } else { - this.filter = stringFilter(rule); + this.match = stringFilter(rule); } } @@ -44,9 +44,7 @@ function Pattern(rule){ * @param {String} str * @return {Object} */ -Pattern.prototype.match = function(str){ - return this.filter(str); -}; +Pattern.prototype.match; function regexFilter(rule){ return function(str){ diff --git a/lib/cli/init.js b/lib/cli/init.js index 6eb45c7b67..00bd9f2438 100644 --- a/lib/cli/init.js +++ b/lib/cli/init.js @@ -34,8 +34,6 @@ module.exports = function(args){ hexo = new Hexo(cwd, args); } - global.hexo = hexo; - return hexo.init(); }).then(function(){ var command = args._.shift(); diff --git a/lib/hexo/index.js b/lib/hexo/index.js index e97422caaf..845c4a029a 100644 --- a/lib/hexo/index.js +++ b/lib/hexo/index.js @@ -71,8 +71,6 @@ function Hexo(base, args){ this.scaffold = new Scaffold(this); - this.source = new Source(this); - var db = this.database = new Database({ version: dbVersion, path: pathFn.join(base, 'db.json') @@ -94,6 +92,8 @@ function Hexo(base, args){ return db.model('Tag'); } }; + + this.source = new Source(this); } util.inherits(Hexo, EventEmitter); @@ -106,7 +106,6 @@ Hexo.prototype.init = function(){ // Load internal plugins require('../plugins/console')(this); - require('../plugins/deployer')(this); require('../plugins/filter')(this); require('../plugins/generator')(this); require('../plugins/helper')(this); diff --git a/lib/models/cache.js b/lib/models/cache.js index 616e67845f..e7a703fe3f 100644 --- a/lib/models/cache.js +++ b/lib/models/cache.js @@ -1,8 +1,11 @@ var Schema = require('warehouse').Schema; module.exports = function(ctx){ - return new Schema({ + var Cache = new Schema({ _id: {type: String, required: true}, - mtime: {type: Number, default: Date.now} + // mtime: {type: Number, default: Date.now}, + checksum: {type: String} }); + + return Cache; }; \ No newline at end of file diff --git a/lib/plugins/deployer/index.js b/lib/plugins/deployer/index.js deleted file mode 100644 index 98d1fc1cc0..0000000000 --- a/lib/plugins/deployer/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function(){ - // -}; \ No newline at end of file diff --git a/lib/theme/index.js b/lib/theme/index.js index 1b50999ce7..aeb45305a8 100644 --- a/lib/theme/index.js +++ b/lib/theme/index.js @@ -10,7 +10,7 @@ var i18n = util.i18n; require('colors'); function Theme(ctx){ - Box.call(this, ctx, ctx.config.theme_dir); + Box.call(this, ctx, ctx.theme_dir); this.config = {}; diff --git a/package.json b/package.json index 59d6cc8e49..606ec26d92 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,9 @@ "bluebird": "^2.3.11", "bunyan": "^1.2.1", "cheerio": "0.17.0", - "chokidar": "0.12.0", "colors": "1.0.3", "hexo-front-matter": "0.0.4", - "hexo-fs": "0.0.4", + "hexo-fs": "0.0.7", "highlight.js": "8.4.0", "inflection": "^1.5.1", "js-yaml": "^3.1.0", diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 9a97b4d599..5832968a06 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -1,27 +1,386 @@ var should = require('chai').should(); var pathFn = require('path'); -var Hexo = require('../../../lib/hexo'); -var Box = require('../../../lib/box'); var fs = require('hexo-fs'); +var Promise = require('bluebird'); +var crypto = require('crypto'); + +function wait(ms){ + return new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve(); + }, ms); + }); +} + +function checksum(content){ + var hash = crypto.createHash('sha1'); + hash.update(content); + return hash.digest('hex'); +} describe('Box', function(){ - var hexo = new Hexo(__dirname, {}); - var base = pathFn.join(__dirname, 'tmp'); - var box = new Box(hexo, base); + var Hexo = require('../../../lib/hexo'); + var baseDir = pathFn.join(__dirname, 'box_tmp'); + var Box = require('../../../lib/box'); + var Pattern = Box.Pattern; + + function newBox(path){ + var hexo = new Hexo(baseDir, {silent: true}); + var base = path ? pathFn.join(baseDir, path) : baseDir; + return new Box(hexo, base); + } before(function(){ - return fs.mkdir(base); + return fs.mkdir(baseDir); + }); + + after(function(){ + return fs.rmdir(baseDir); }); - it.skip('addProcessor()'); + it('constructor - add trailing "/" to the base path', function(){ + var box = newBox('foo'); + box.base.should.eql(pathFn.join(baseDir, 'foo') + pathFn.sep); + }); - it.skip('process()'); + it('addProcessor() - no pattern', function(){ + var box = newBox(); - it.skip('watch()'); + box.addProcessor(function(){ + return 'test'; + }); - it.skip('unwatch()'); + var p = box.processors[0]; - after(function(){ - return fs.rmdir(base); + p.pattern.match('').should.eql({}); + p.process().then(function(data){ + data.should.eql('test'); + }); + }); + + it('addProcessor() - with regex', function(){ + var box = newBox(); + + box.addProcessor(/^foo/, function(){ + return 'test'; + }); + + var p = box.processors[0]; + + p.pattern.match('foobar').should.be.ok; + p.pattern.should.be.an.instanceof(Pattern); + p.process().then(function(data){ + data.should.eql('test'); + }); + }); + + it('addProcessor() - with pattern', function(){ + var box = newBox(); + + box.addProcessor(new Pattern(/^foo/), function(){ + return 'test'; + }); + + var p = box.processors[0]; + + p.pattern.match('foobar').should.be.ok; + p.pattern.should.be.an.instanceof(Pattern); + p.process().then(function(data){ + data.should.eql('test'); + }); + }); + + it('addProcessor() - no fn', function(){ + var box = newBox(); + + try { + box.addProcessor('test'); + } catch (err){ + err.should.have.property('message', 'fn must be a function'); + } + }); + + it('_loadFiles() - create', function(){ + var box = newBox('test'); + var path = pathFn.join(box.base, 'a.txt'); + + return fs.writeFile(path, 'a').then(function(){ + return box._loadFiles(); + }).then(function(files){ + var cacheId = pathFn.join('test', 'a.txt'); + + files.should.eql([ + {path: 'a.txt', type: 'create'} + ]); + + box.Cache.toArray({lean: true}).should.eql([ + {_id: cacheId, checksum: checksum('a')} + ]); + + return fs.rmdir(box.base); + }); + }); + + it('_loadFiles() - update', function(){ + var box = newBox('test'); + var path = pathFn.join(box.base, 'a.txt'); + var cacheId = pathFn.join('test', 'a.txt'); + var Cache = box.Cache; + + return Promise.all([ + fs.writeFile(path, 'a'), + Cache.insert({_id: cacheId, checksum: 'a'}) + ]).then(function(){ + return box._loadFiles(); + }).then(function(files){ + files.should.eql([ + {path: 'a.txt', type: 'update'} + ]); + + Cache.toArray({lean: true}).should.eql([ + {_id: cacheId, checksum: checksum('a')} + ]); + + return fs.rmdir(box.base); + }); + }); + + it('_loadFiles() - skip', function(){ + var box = newBox('test'); + var path = pathFn.join(box.base, 'a.txt'); + var cacheId = pathFn.join('test', 'a.txt'); + var hash = checksum('a'); + var Cache = box.Cache; + + return Promise.all([ + fs.writeFile(path, 'a'), + Cache.insert({_id: cacheId, checksum: hash}) + ]).then(function(){ + return box._loadFiles(); + }).then(function(files){ + files.should.eql([ + {type: 'skip', path: 'a.txt'} + ]); + + Cache.toArray({lean: true}).should.eql([ + {_id: cacheId, checksum: hash} + ]); + + return fs.rmdir(box.base); + }); + }); + + it('_loadFiles() - delete', function(){ + var box = newBox('test'); + var cacheId = pathFn.join('test', 'a.txt'); + var Cache = box.Cache; + + return Cache.insert({ + _id: cacheId, + checksum: 'a' + }).then(function(){ + return box._loadFiles(); + }).then(function(files){ + files.should.eql([ + {type: 'delete', path: 'a.txt'} + ]); + + should.not.exist(Cache.findById(cacheId)); + }); + }); + + it.skip('_loadFiles() - escape backslash', function(){ + + }); + + it('_dispatch()', function(){ + var box = newBox(); + var path = 'a.txt'; + var data; + + box.addProcessor(function(file){ + box.processingFiles[path].should.be.true; + data = file; + }); + + return box._dispatch({ + path: path, + type: 'create' + }).then(function(){ + box.processingFiles[path].should.be.false; + data.source.should.eql(pathFn.join(box.base, path)); + data.path.should.eql(path); + data.type.should.eql('create'); + data.params.should.eql({}); + }); + }); + + it('_dispatch() - params', function(){ + var box = newBox(); + var data = new Array(2); + + box.addProcessor(/(.*).js/, function(file){ + data[0] = file; + }); + + box.addProcessor(function(file){ + data[1] = file; + }); + + return box._dispatch({ + path: 'server.js', + type: 'create' + }).then(function(){ + data[0].params[1].should.eql('server'); + data[1].params.should.eql({}); + }); + }); + + it('process()', function(){ + var box = newBox('test'); + var data = {}; + + box.addProcessor(function(file){ + data[file.path] = file; + }); + + return Promise.all([ + fs.writeFile(pathFn.join(box.base, 'a.txt'), 'a'), + fs.writeFile(pathFn.join(box.base, 'b', 'c.js'), 'c') + ]).then(function(){ + return box.process(); + }).then(function(){ + var keys = Object.keys(data); + var key, item; + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + item = data[key]; + + item.path.should.eql(key); + item.source.should.eql(pathFn.join(box.base, key)); + item.type.should.eql('create'); + item.params.should.eql({}); + } + + return fs.rmdir(box.base); + }); + }); + + it('watch() - create', function(callback){ + var box = newBox('test'); + var path = 'a.txt'; + var src = pathFn.join(box.base, path); + + box.watch().then(function(){ + box.watcher.should.exist; + + box.addProcessor(function(file){ + file.source.should.eql(src); + file.path.should.eql(path); + file.type.should.eql('create'); + file.params.should.eql({}); + file.content.toString().should.eql('a'); + + box.unwatch(); + + fs.rmdir(box.base, callback); + }); + + fs.writeFile(src, 'a'); + }); + }); + + it('watch() - update', function(callback){ + var box = newBox('test'); + var path = 'a.txt'; + var src = pathFn.join(box.base, path); + + box.watch().then(function(){ + return fs.writeFile(src, 'a'); + }).then(function(){ + // Wait for 200ms because chokidar uses polling + return wait(200); + }).then(function(){ + box.addProcessor(function(file){ + file.source.should.eql(src); + file.path.should.eql(path); + file.type.should.eql('update'); + file.params.should.eql({}); + file.content.toString().should.eql('a'); + + box.unwatch(); + fs.rmdir(box.base, callback); + }); + + fs.appendFile(src, 'b'); + }); + }); + + it('watch() - delete', function(callback){ + var box = newBox('test'); + var path = 'a.txt'; + var src = pathFn.join(box.base, path); + + fs.writeFile(src, 'a').then(function(){ + return box.watch(); + }).then(function(){ + // Wait for 200ms because chokidar uses polling + return wait(200); + }).then(function(){ + box.addProcessor(function(file){ + file.source.should.eql(src); + file.path.should.eql(path); + file.type.should.eql('delete'); + file.params.should.eql({}); + should.not.exist(file.content); + + box.unwatch(); + fs.rmdir(box.base, callback); + }); + + fs.unlink(src); + }); + }); + + it('watch() - watcher has started', function(callback){ + var box = newBox(); + + box.watch().then(function(){ + box.watch().catch(function(err){ + err.should.have.property('message', 'Watcher has already started.'); + box.unwatch(); + callback(); + }); + }); + }); + + it.skip('unwatch()', function(callback){ + var box = newBox('test'); + + box.watch().then(function(){ + var emitted = false; + + box.addProcessor(function(file){ + emitted = true; + }); + + box.unwatch(); + + fs.writeFile(pathFn.join(box.base, 'a.txt'), 'a').then(function(){ + emitted.should.be.false; + fs.rmdir(box.base, callback); + }); + }); + }); + + it('unwatch() - watcher not started', function(){ + var box = newBox(); + + try { + box.unwatch(); + } catch (err){ + err.should.have.property('message', 'Watcher hasn\'t started yet.'); + } }); }); \ No newline at end of file diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index 0b8da8ded3..36e37f3315 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -6,24 +6,25 @@ var yaml = require('js-yaml'); describe('File', function(){ var Hexo = require('../../../lib/hexo'); - var hexo = new Hexo(); + var hexo = new Hexo(__dirname); var Box = require('../../../lib/box'); var box = new Box(hexo, __dirname); var target = pathFn.join(__dirname, '../../fixtures/test.yml'); - var body; - var obj; - - var file = new box.File({ - source: target, - path: target, - type: 'create', - params: {foo: 'bar'} - }); + var body, obj, file; before(function(){ return fs.readFile(target).then(function(result){ body = result; obj = yaml.load(result); + + file = new box.File({ + source: target, + path: target, + type: 'create', + params: {foo: 'bar'}, + content: new Buffer(body) + }); + return hexo.init(); }); }); @@ -34,10 +35,56 @@ describe('File', function(){ }); }); + it('read() - callback', function(callback){ + file.read(function(err, content){ + should.not.exist(err); + content.should.eql(body); + callback(); + }); + }); + + it('read() - raw buffer', function(){ + file.read({encoding: null}).then(function(content){ + content.should.eql(new Buffer(body)); + }); + }); + + it('read() - string encoding', function(){ + file.read('hex').then(function(content){ + content.should.eql(new Buffer(body).toString('hex')); + }); + }); + + it('read() - file was deleted', function(){ + var file = new box.File({source: 'foo'}); + + file.read().catch(function(err){ + err.should.have.property('message', 'File "foo" was deleted.'); + }); + }); + it('readSync()', function(){ file.readSync().should.eql(body); }); + it('readSync() - raw buffer', function(){ + file.readSync({encoding: null}).should.eql(new Buffer(body)); + }); + + it('readSync() - string encoding', function(){ + file.readSync('hex').should.eql(new Buffer(body).toString('hex')); + }); + + it('readSync() - file was deleted', function(){ + var file = new box.File({source: 'foo'}); + + try { + file.readSync(); + } catch (err){ + err.should.have.property('message', 'File "foo" was deleted.'); + } + }); + it('stat()', function(){ return Promise.all([ fs.stat(target), @@ -47,6 +94,17 @@ describe('File', function(){ }); }); + it('stat() - callback', function(callback){ + file.stat(function(err, stats){ + should.not.exist(err); + + fs.stat(target, function(err, realStats){ + stats.should.eql(realStats); + callback(); + }); + }); + }); + it('statSync()', function(){ return fs.stat(target).then(function(stats){ file.statSync().should.eql(stats); @@ -59,6 +117,14 @@ describe('File', function(){ }); }); + it('render() - callback', function(callback){ + file.render(function(err, data){ + should.not.exist(err); + data.should.eql(obj); + callback(); + }); + }); + it('renderSync()', function(){ file.renderSync().should.eql(obj); }); diff --git a/test/scripts/hexo/load_config.js b/test/scripts/hexo/load_config.js index d13a3c5354..602e53a784 100644 --- a/test/scripts/hexo/load_config.js +++ b/test/scripts/hexo/load_config.js @@ -34,6 +34,8 @@ describe('Load config', function(){ }).then(function(){ hexo.env.init.should.be.true; hexo.config.foo.should.eql(1); + hexo.theme_dir.should.eql(pathFn.resolve(hexo.base_dir, 'themes', 'landscape') + pathFn.sep); + hexo.theme_script_dir.should.eql(pathFn.resolve(hexo.theme_dir, 'scripts') + pathFn.sep); reset(); return fs.unlink(configPath); @@ -104,8 +106,7 @@ describe('Load config', function(){ return fs.writeFile(hexo.config_path, 'public_dir: foo').then(function(){ return loadConfig(hexo); }).then(function(){ - hexo.config.public_dir = pathFn.resolve(hexo.base_dir, 'foo') + pathFn.sep; - + hexo.public_dir.should.eql(pathFn.resolve(hexo.base_dir, 'foo') + pathFn.sep); reset(); return fs.unlink(hexo.config_path); }); @@ -115,8 +116,7 @@ describe('Load config', function(){ return fs.writeFile(hexo.config_path, 'source_dir: bar').then(function(){ return loadConfig(hexo); }).then(function(){ - hexo.config.source_dir = pathFn.resolve(hexo.base_dir, 'bar') + pathFn.sep; - + hexo.source_dir.should.eql(pathFn.resolve(hexo.base_dir, 'bar') + pathFn.sep); reset(); return fs.unlink(hexo.config_path); }); diff --git a/test/scripts/models/cache.js b/test/scripts/models/cache.js index 9dbe624457..902ce9211f 100644 --- a/test/scripts/models/cache.js +++ b/test/scripts/models/cache.js @@ -5,17 +5,6 @@ describe('Cache', function(){ var hexo = new Hexo(); var Cache = hexo.model('Cache'); - it('default values', function(){ - var now = Date.now(); - - return Cache.insert({ - _id: 'foo' - }).then(function(data){ - data.mtime.should.gte(now); - return Cache.removeById(data._id); - }); - }); - it('_id - required', function(){ return Cache.insert({}).catch(function(err){ err.should.have.property('message', 'ID is not defined'); From bc1a34aa982ef71ae923cbb6f74922af40698fce Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Fri, 12 Dec 2014 01:58:52 +0800 Subject: [PATCH 12/15] Fix Box test --- lib/box/index.js | 90 ++++++++++++++++++++++------------------- test/scripts/box/box.js | 38 +++++++++-------- 2 files changed, 70 insertions(+), 58 deletions(-) diff --git a/lib/box/index.js b/lib/box/index.js index 82768a13f8..d207a04ad3 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -7,9 +7,12 @@ var util = require('../util'); var fs = require('hexo-fs'); var prettyHrtime = require('pretty-hrtime'); var crypto = require('crypto'); +var tildify = require('tildify'); var escape = util.escape; var join = pathFn.join; +var sep = pathFn.sep; +var rSep = new RegExp(escape.regex(sep), 'g'); require('colors'); @@ -20,8 +23,8 @@ function Box(ctx, base, options){ ignoreInitial: true }, options); - if (base.substring(base.length - 1) !== pathFn.sep){ - base += pathFn.sep; + if (base.substring(base.length - 1) !== sep){ + base += sep; } this.context = ctx; @@ -58,6 +61,19 @@ function patternNoob(){ return {}; } +var escapeBackslash; + +if (sep === '/'){ + escapeBackslash = function(path){ + return path; + }; +} else { + escapeBackslash = function(path){ + // Replace backslashes on Windows + return path.replace(rSep, '/'); + }; +} + Box.prototype.addProcessor = function(pattern, fn){ if (!fn && typeof pattern === 'function'){ fn = pattern; @@ -86,10 +102,7 @@ function listDir(path){ // Return an empty array if path does not exist if (err.cause.code !== 'ENOENT') throw err; return []; - }).map(function(path){ - // Replace backslashes on Windows - return path.replace(/\\/g, '/'); - }); + }).map(escapeBackslash); } Box.prototype._loadFiles = function(){ @@ -97,7 +110,7 @@ Box.prototype._loadFiles = function(){ var Cache = this.Cache; var self = this; var ctx = this.context; - var relativeBase = base.substring(ctx.base_dir.length); + var relativeBase = escapeBackslash(base.substring(ctx.base_dir.length)); var regex = new RegExp('^' + escape.regex(relativeBase)); var baseLength = relativeBase.length; @@ -142,8 +155,6 @@ Box.prototype._loadFiles = function(){ }).map(function(item){ switch (item.type){ case 'create': - return self._handleCreatedFile(item.path); - case 'update': return self._handleUpdatedFile(item.path); @@ -177,54 +188,38 @@ function getChecksum(path){ }); } -Box.prototype._handleCreatedFile = function(path, stats){ +Box.prototype._handleUpdatedFile = function(path){ var Cache = this.Cache; var ctx = this.context; var fullPath = join(this.base, path); var self = this; return getChecksum(fullPath).then(function(data){ - self.bufferStore[path] = data.content; - - return Cache.insert({ - _id: fullPath.substring(ctx.base_dir.length), - checksum: data.checksum - }).thenReturn({ - type: 'create', - path: path - }); - }); -}; - -Box.prototype._handleUpdatedFile = function(path, stats){ - var Cache = this.Cache; - var ctx = this.context; - var fullPath = join(this.base, path); - - return getChecksum(fullPath).then(function(data){ - var id = fullPath.substring(ctx.base_dir.length); + var id = escapeBackslash(fullPath.substring(ctx.base_dir.length)); var cache = Cache.findById(id); var checksum = data.checksum; - if (cache.checksum === checksum){ + self.bufferStore[path] = data.content; + + if (!cache){ + return Cache.insert({ + _id: id, + checksum: checksum + }).thenReturn({ + type: 'create', + path: path + }); + } else if (cache.checksum === checksum){ return { type: 'skip', path: path }; - } else if (cache){ + } else { cache.checksum = checksum; return cache.save().thenReturn({ type: 'update', path: path }); - } else { - return Cache.insert({ - _id: id, - checksum: checksum - }).thenReturn({ - type: 'create', - path: path - }); } }); }; @@ -232,10 +227,17 @@ Box.prototype._handleUpdatedFile = function(path, stats){ Box.prototype._handleDeletedFile = function(path){ var fullPath = join(this.base, path); var ctx = this.context; + var Cache = this.Cache; this.bufferStore[path] = null; - return this.Cache.removeById(fullPath.substring(ctx.base_dir.length)).thenReturn({ + return new Promise(function(resolve, reject){ + var id = escapeBackslash(fullPath.substring(ctx.base_dir.length)); + var cache = Cache.findById(id); + if (!cache) return resolve(); + + return cache.remove().then(resolve, reject); + }).thenReturn({ type: 'delete', path: path }); @@ -299,9 +301,10 @@ Box.prototype.watch = function(callback){ var base = this.base; var baseLength = base.length; var self = this; + var log = this.context.log; function getPath(path){ - return path.substring(baseLength); + return escapeBackslash(path.substring(baseLength)); } function dispatch(data){ @@ -315,10 +318,13 @@ Box.prototype.watch = function(callback){ self.watcher = watcher; watcher.on('add', function(path){ - self._handleCreatedFile(getPath(path)).then(dispatch); + log.info('Added: %s', tildify(path).magenta); + self._handleUpdatedFile(getPath(path)).then(dispatch); }).on('change', function(path){ + log.info('Updated: %s', tildify(path).magenta); self._handleUpdatedFile(getPath(path)).then(dispatch); }).on('unlink', function(path){ + log.info('Deleted: %s', tildify(path).magenta); self._handleDeletedFile(getPath(path)).then(dispatch); }); diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 5832968a06..aa7ced04e1 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -107,7 +107,7 @@ describe('Box', function(){ return fs.writeFile(path, 'a').then(function(){ return box._loadFiles(); }).then(function(files){ - var cacheId = pathFn.join('test', 'a.txt'); + var cacheId = 'test/a.txt'; files.should.eql([ {path: 'a.txt', type: 'create'} @@ -124,7 +124,7 @@ describe('Box', function(){ it('_loadFiles() - update', function(){ var box = newBox('test'); var path = pathFn.join(box.base, 'a.txt'); - var cacheId = pathFn.join('test', 'a.txt'); + var cacheId = 'test/a.txt'; var Cache = box.Cache; return Promise.all([ @@ -148,7 +148,7 @@ describe('Box', function(){ it('_loadFiles() - skip', function(){ var box = newBox('test'); var path = pathFn.join(box.base, 'a.txt'); - var cacheId = pathFn.join('test', 'a.txt'); + var cacheId = 'test/a.txt'; var hash = checksum('a'); var Cache = box.Cache; @@ -172,7 +172,7 @@ describe('Box', function(){ it('_loadFiles() - delete', function(){ var box = newBox('test'); - var cacheId = pathFn.join('test', 'a.txt'); + var cacheId = 'test/a.txt'; var Cache = box.Cache; return Cache.insert({ @@ -189,10 +189,6 @@ describe('Box', function(){ }); }); - it.skip('_loadFiles() - escape backslash', function(){ - - }); - it('_dispatch()', function(){ var box = newBox(); var path = 'a.txt'; @@ -291,23 +287,28 @@ describe('Box', function(){ }); }); + // NEED FIX: Timeout on Windows it('watch() - update', function(callback){ var box = newBox('test'); var path = 'a.txt'; var src = pathFn.join(box.base, path); + var cacheId = 'test/' + path; + var Cache = box.Cache; - box.watch().then(function(){ - return fs.writeFile(src, 'a'); + Promise.all([ + fs.writeFile(src, 'a'), + Cache.insert({_id: cacheId, checksum: 'a'}) + ]).then(function(){ + return box.watch(); }).then(function(){ - // Wait for 200ms because chokidar uses polling - return wait(200); + return wait(300); }).then(function(){ box.addProcessor(function(file){ file.source.should.eql(src); file.path.should.eql(path); file.type.should.eql('update'); file.params.should.eql({}); - file.content.toString().should.eql('a'); + file.content.should.eql(new Buffer('ab')); box.unwatch(); fs.rmdir(box.base, callback); @@ -321,13 +322,18 @@ describe('Box', function(){ var box = newBox('test'); var path = 'a.txt'; var src = pathFn.join(box.base, path); + var cacheId = 'test/' + path; + var Cache = box.Cache; - fs.writeFile(src, 'a').then(function(){ + Promise.all([ + fs.writeFile(src, 'a'), + Cache.insert({_id: cacheId, checksum: 'a'}) + ]).then(function(){ return box.watch(); }).then(function(){ - // Wait for 200ms because chokidar uses polling - return wait(200); + return wait(300); }).then(function(){ + box.addProcessor(function(file){ file.source.should.eql(src); file.path.should.eql(path); From 83fe2dd88b4685cc37885d1a881a09d9933db42d Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Sun, 14 Dec 2014 16:26:42 +0800 Subject: [PATCH 13/15] Rewrite processors and add processor tests. --- lib/box/file.js | 25 +- lib/box/index.js | 2 +- lib/box/pattern.js | 8 +- lib/hexo/register_models.js | 1 + lib/models/asset.js | 7 +- lib/models/index.js | 1 + lib/models/post.js | 4 +- lib/models/post_asset.js | 12 + lib/plugins/processor/asset.js | 99 ++++ lib/plugins/processor/common.js | 19 + lib/plugins/processor/index.js | 12 +- lib/plugins/processor/post.js | 259 ++++++++++ test/fixtures/test.yml | 9 - test/index.js | 1 + test/scripts/box/file.js | 124 +++-- test/scripts/hexo/render.js | 108 ++-- test/scripts/models/asset.js | 31 ++ test/scripts/models/assets.js | 4 +- test/scripts/models/index.js | 3 +- test/scripts/models/post_asset.js | 38 ++ test/scripts/processors/asset.js | 310 ++++++++++++ test/scripts/processors/common.js | 27 + test/scripts/processors/index.js | 5 + test/scripts/processors/post.js | 798 ++++++++++++++++++++++++++++++ 24 files changed, 1772 insertions(+), 135 deletions(-) create mode 100644 lib/models/post_asset.js create mode 100644 lib/plugins/processor/asset.js create mode 100644 lib/plugins/processor/common.js create mode 100644 lib/plugins/processor/post.js delete mode 100644 test/fixtures/test.yml create mode 100644 test/scripts/models/asset.js create mode 100644 test/scripts/models/post_asset.js create mode 100644 test/scripts/processors/asset.js create mode 100644 test/scripts/processors/common.js create mode 100644 test/scripts/processors/index.js create mode 100644 test/scripts/processors/post.js diff --git a/lib/box/file.js b/lib/box/file.js index 089e06f310..484f73ae63 100644 --- a/lib/box/file.js +++ b/lib/box/file.js @@ -13,6 +13,7 @@ function wrapReadOptions(options){ options = options || {}; if (typeof options === 'string') options = {encoding: options}; if (!options.hasOwnProperty('encoding')) options.encoding = 'utf8'; + if (!options.hasOwnProperty('cache')) options.cache = true; return options; } @@ -29,25 +30,29 @@ File.prototype.read = function(options, callback){ options = wrapReadOptions(options); return new Promise(function(resolve, reject){ - if (content){ - var encoding = options.encoding; - - if (encoding){ - resolve(content.toString(encoding)); - } else { - resolve(content); - } + if (!options.cache || !content){ + return fs.readFile(self.source, options).then(resolve, reject); + } + + var encoding = options.encoding; + + if (encoding){ + resolve(content.toString(encoding)); } else { - reject(new Error('File "' + self.source + '" was deleted.')); + resolve(content); } }).nodeify(callback); }; File.prototype.readSync = function(options){ var content = this.content; - if (!content) throw new Error('File "' + this.source + '" was deleted.'); options = wrapReadOptions(options); + + if (!options.cache || !content){ + return fs.readFileSync(this.source, options); + } + var encoding = options.encoding; if (encoding){ diff --git a/lib/box/index.js b/lib/box/index.js index d207a04ad3..9cd9cac238 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -281,7 +281,7 @@ Box.prototype._dispatch = function(item){ content: self.bufferStore[path] }); - return processor.process(file).thenReturn(count + 1); + return processor.process.call(ctx, file).thenReturn(count + 1); }, 0).then(function(count){ // Display debug message if there're any processors executed if (count){ diff --git a/lib/box/pattern.js b/lib/box/pattern.js index be057ee307..89a073bccd 100644 --- a/lib/box/pattern.js +++ b/lib/box/pattern.js @@ -16,12 +16,16 @@ var rParam = /([:\*])([\w\?]*)?/g; * @module hexo */ function Pattern(rule){ - if (typeof rule === 'function'){ + if (rule instanceof Pattern){ + return rule; + } else if (typeof rule === 'function'){ this.match = rule; } else if (rule instanceof RegExp){ this.match = regexFilter(rule); - } else { + } else if (typeof rule === 'string'){ this.match = stringFilter(rule); + } else { + throw new TypeError('rule must be a function, a string or a regular expression.'); } } diff --git a/lib/hexo/register_models.js b/lib/hexo/register_models.js index 77af19e284..ac00e88790 100644 --- a/lib/hexo/register_models.js +++ b/lib/hexo/register_models.js @@ -8,6 +8,7 @@ module.exports = function(ctx){ db.model('Category', models.Category(ctx)); db.model('Page', models.Page(ctx)); db.model('Post', models.Post(ctx)); + db.model('PostAsset', models.PostAsset(ctx)); db.model('PostCategory', models.PostCategory(ctx)); db.model('PostTag', models.PostTag(ctx)); db.model('Tag', models.Tag(ctx)); diff --git a/lib/models/asset.js b/lib/models/asset.js index 7bc236f0ca..6ee9aabadc 100644 --- a/lib/models/asset.js +++ b/lib/models/asset.js @@ -1,10 +1,11 @@ var Schema = require('warehouse').Schema; module.exports = function(ctx){ - return new Schema({ + var Asset = new Schema({ _id: {type: String, required: true}, path: {type: String, required: true}, - modified: {type: Boolean, default: true}, - post: {type: Schema.Types.CUID, ref: 'Post'} + modified: {type: Boolean, default: true} }); + + return Asset; }; \ No newline at end of file diff --git a/lib/models/index.js b/lib/models/index.js index 04af414fbc..59ace71671 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -3,6 +3,7 @@ exports.Cache = require('./cache'); exports.Category = require('./category'); exports.Page = require('./page'); exports.Post = require('./post'); +exports.PostAsset = require('./post_asset'); exports.PostCategory = require('./post_category'); exports.PostTag = require('./post_tag'); exports.Tag = require('./tag'); \ No newline at end of file diff --git a/lib/models/post.js b/lib/models/post.js index 8881625e79..5c34b6377e 100644 --- a/lib/models/post.js +++ b/lib/models/post.js @@ -147,8 +147,8 @@ module.exports = function(ctx){ // Remove assets Post.pre('remove', function(data){ - var Asset = ctx.model('Asset'); - return Asset.remove({post: data._id}); + var PostAsset = ctx.model('PostAsset'); + return PostAsset.remove({post: data._id}); }); return Post; diff --git a/lib/models/post_asset.js b/lib/models/post_asset.js new file mode 100644 index 0000000000..1fbf7517f1 --- /dev/null +++ b/lib/models/post_asset.js @@ -0,0 +1,12 @@ +var Schema = require('warehouse').Schema; + +module.exports = function(ctx){ + var PostAsset = new Schema({ + _id: {type: String, required: true}, + modified: {type: Boolean, default: true}, + // TODO: wrong post reference when set post to required + post: {type: Schema.Types.CUID, ref: 'Post'} + }); + + return PostAsset; +}; \ No newline at end of file diff --git a/lib/plugins/processor/asset.js b/lib/plugins/processor/asset.js new file mode 100644 index 0000000000..0252dcabec --- /dev/null +++ b/lib/plugins/processor/asset.js @@ -0,0 +1,99 @@ +var common = require('./common'); +var Promise = require('bluebird'); +var yfm = require('hexo-front-matter'); +var moment = require('moment'); +var pathFn = require('path'); + +exports.process = function(file){ + if (this.render.isRenderable(file.path)){ + return processPage.call(this, file); + } else { + return processAsset.call(this, file); + } +}; + +exports.pattern = common.ignoreTmpAndHiddenFile; + +function processPage(file){ + var Page = this.model('Page'); + var path = file.path; + var doc = Page.findOne({source: path}); + var self = this; + + if (file.type === 'skip' && doc){ + return; + } + + if (file.type === 'delete'){ + if (doc){ + return doc.remove(); + } else { + return; + } + } + + return Promise.all([ + file.stat(), + file.read() + ]).spread(function(stats, content){ + var data = yfm(content); + data.content = data._content; + delete data._content; + + data.source = path; + data.raw = content; + data.date = moment(data.date || stats.ctime); + data.updated = moment(data.updated || stats.mtime); + + if (data.permalink){ + data.path = data.permalink; + delete data.permalink; + + if (data.path[data.path.length - 1] === '/'){ + data.path += 'index'; + } + } else { + var extname = pathFn.extname(path); + data.path = path.substring(0, path.length - extname.length); + } + + if (!pathFn.extname(data.path)){ + var output = self.render.getOutput(path); + data.path += '.' + output; + } + + return self.post.render(file.source, data); + }).then(function(data){ + if (doc){ + return doc.replace(data); + } else { + return Page.insert(data); + } + }); +} + +function processAsset(file){ + var id = file.source.substring(this.base_dir.length); + var Asset = this.model('Asset'); + var doc = Asset.findById(id); + + if (file.type === 'delete'){ + if (doc){ + return doc.remove(); + } else { + return; + } + } + + if (doc){ + doc.path = file.path; + doc.modified = file.type === 'update'; + + return doc.save(); + } else { + return Asset.insert({ + _id: id, + path: file.path + }); + } +} \ No newline at end of file diff --git a/lib/plugins/processor/common.js b/lib/plugins/processor/common.js new file mode 100644 index 0000000000..fa632305b6 --- /dev/null +++ b/lib/plugins/processor/common.js @@ -0,0 +1,19 @@ +var Pattern = require('../../box/pattern'); + +function isTmpFile(path){ + var last = path[path.length - 1]; + return last === '%' || last === '~'; +} + +function isHiddenFile(path){ + if (path[0] === '_') return true; + return /\/_/.test(path); +} + +exports.ignoreTmpAndHiddenFile = new Pattern(function(path){ + if (isTmpFile(path) || isHiddenFile(path)) return false; + return true; +}); + +exports.isTmpFile = isTmpFile; +exports.isHiddenFile = isHiddenFile; \ No newline at end of file diff --git a/lib/plugins/processor/index.js b/lib/plugins/processor/index.js index 98d1fc1cc0..199abc5dcd 100644 --- a/lib/plugins/processor/index.js +++ b/lib/plugins/processor/index.js @@ -1,3 +1,11 @@ -module.exports = function(){ - // +module.exports = function(ctx){ + var processor = ctx.extend.processor; + + function register(name){ + var obj = require('./' + name); + processor.register(obj.pattern, obj.process); + } + + register('asset'); + register('post'); }; \ No newline at end of file diff --git a/lib/plugins/processor/post.js b/lib/plugins/processor/post.js new file mode 100644 index 0000000000..5e147f6e06 --- /dev/null +++ b/lib/plugins/processor/post.js @@ -0,0 +1,259 @@ +var Pattern = require('../../box/pattern'); +var common = require('./common'); +var Promise = require('bluebird'); +var yfm = require('hexo-front-matter'); +var pathFn = require('path'); +var moment = require('moment'); +var fs = require('hexo-fs'); +var util = require('../../util'); +var escape = util.escape; +var Permalink = util.permalink; + +var postDir = '_posts/'; +var draftDir = '_drafts/'; +var permalink; + +var preservedKeys = { + title: true, + year: true, + month: true, + day: true, + i_month: true, + i_day: true +}; + +function startsWith(str, prefix){ + return str.substring(0, prefix.length) === prefix; +} + +exports.process = function(file){ + if (this.render.isRenderable(file.path)){ + return processPost.call(this, file); + } else if (this.config.post_asset_folder){ + return processAsset.call(this, file); + } +}; + +exports.pattern = new Pattern(function(path){ + if (common.isTmpFile(path)) return false; + + var result; + + if (startsWith(path, postDir)){ + result = { + published: true, + path: path.substring(postDir.length) + }; + } else if (startsWith(path, draftDir)){ + result = { + published: false, + path: path.substring(draftDir.length) + }; + } else { + return false; + } + + if (common.isHiddenFile(result.path)) return false; + return result; +}); + +function processPost(file){ + var Post = this.model('Post'); + var path = file.params.path; + var doc = Post.findOne({source: file.path}); + var self = this; + var categories, tags; + + if (file.type === 'skip' && doc){ + return; + } + + if (file.type === 'delete'){ + if (doc){ + return doc.remove(); + } else { + return; + } + } + + return Promise.all([ + file.stat(), + file.read() + ]).spread(function(stats, content){ + var data = yfm(content); + var info = parseFilename(self.config.new_post_name, path); + var keys = Object.keys(info); + var key; + + data.content = data._content; + delete data._content; + + data.source = file.path; + data.raw = content; + data.slug = info.title; + + if (file.params.published){ + if (!data.hasOwnProperty('published')) data.published = true; + } else { + data.published = false; + } + + for (var i = 0, len = keys.length; i < len; i++){ + key = keys[i]; + if (!preservedKeys[key]) data[key] = info[key]; + } + + if (data.date){ + data.date = moment(data.date); + } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)){ + data.date = moment([ + info.year, + parseInt(info.month || info.i_month, 10) - 1, + parseInt(info.day || info.i_day) + ]); + } else { + data.date = moment(stats.ctime); + } + + data.updated = moment(data.updated || stats.mtime); + + if (data.category && !data.categories){ + data.categories = data.category; + delete data.category; + } + + if (data.tag && !data.tags){ + data.tags = data.tag; + delete data.tag; + } + + categories = data.categories || []; + tags = data.tags || []; + + if (!Array.isArray(categories)) categories = [categories]; + if (!Array.isArray(tags)) tags = [tags]; + + if (data.photo && !data.photos){ + data.photos = data.photo; + delete data.photo; + } + + if (data.photos && !Array.isArray(data.photos)){ + data.photos = [data.photos]; + } + + if (data.link && !data.title){ + data.title = data.link.replace(/^https?:\/\/|\/$/g, ''); + } + + return self.post.render(file.source, data); + }).then(function(data){ + if (doc){ + return doc.replace(data); + } else { + return Post.insert(data); + } + }).then(function(doc){ + return Promise.all([ + doc.setCategories(categories), + doc.setTags(tags), + scanAssetDir.call(self, doc) + ]); + }); +} + +function parseFilename(config, path){ + config = config.substring(0, config.length - pathFn.extname(config).length); + path = path.substring(0, path.length - pathFn.extname(path).length); + + if (!permalink || permalink.rule !== config){ + permalink = new Permalink(config, { + segments: { + year: /(\d{4})/, + month: /(\d{2})/, + day: /(\d{2})/, + i_month: /(\d{1,2})/, + i_day: /(\d{1,2})/ + } + }); + } + + var data = permalink.parse(path); + + if (data){ + return data; + } else { + return { + title: escape.path(path) + }; + } +} + +function scanAssetDir(post){ + if (!this.config.post_asset_folder) return; + + var assetDir = post.asset_dir; + var baseDir = this.base_dir; + var baseDirLength = baseDir.length; + var PostAsset = this.model('PostAsset'); + + return fs.exists(assetDir).then(function(exist){ + return exist ? fs.listDir(assetDir) : []; + }).filter(function(item){ + return !common.isTmpFile(item) && !common.isHiddenFile(item); + }).map(function(item){ + var id = pathFn.join(assetDir, item).substring(baseDirLength); + var asset = PostAsset.findById(id); + if (asset) return; + + return PostAsset.insert({ + _id: id, + post: post._id, + modified: true + }); + }); +} + +function processAsset(file){ + var PostAsset = this.model('PostAsset'); + var Post = this.model('Post'); + var id = file.source.substring(this.base_dir.length); + var doc = PostAsset.findById(id); + var post; + + if (file.type === 'skip' && doc){ + return; + } + + if (file.type === 'delete'){ + if (doc){ + return doc.remove(); + } else { + return; + } + } + + if (doc){ + post = Post.findById(doc.post); + + if (post){ + doc.modified = file.type === 'update'; + return doc.save(); + } else { + return doc.remove(); + } + } else { + var posts = Post.toArray(); + + for (var i = 0, len = posts.length; i < len; i++){ + post = posts[i]; + + if (startsWith(file.source, post.asset_dir)){ + return PostAsset.insert({ + _id: id, + post: post._id + }); + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/test.yml b/test/fixtures/test.yml deleted file mode 100644 index 6a54f9f7e3..0000000000 --- a/test/fixtures/test.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: - first: John - last: Doe - -age: 23 - -list: -- Apple -- Banana \ No newline at end of file diff --git a/test/index.js b/test/index.js index 7f05fba1e9..c0276f16e1 100644 --- a/test/index.js +++ b/test/index.js @@ -6,6 +6,7 @@ describe('Hexo', function(){ require('./scripts/helpers'); require('./scripts/hexo'); require('./scripts/models'); + require('./scripts/processors'); require('./scripts/renderers'); require('./scripts/tags'); require('./scripts/util'); diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index 36e37f3315..187b3a3a99 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -8,27 +8,43 @@ describe('File', function(){ var Hexo = require('../../../lib/hexo'); var hexo = new Hexo(__dirname); var Box = require('../../../lib/box'); - var box = new Box(hexo, __dirname); - var target = pathFn.join(__dirname, '../../fixtures/test.yml'); - var body, obj, file; + var box = new Box(hexo, pathFn.join(hexo.base_dir, 'file_test')); + var File = box.File; + + var body = [ + 'name:', + ' first: John', + ' last: Doe', + '', + 'age: 23', + '', + 'list:', + '- Apple', + '- Banana' + ].join('\n'); + + var obj = yaml.load(body); + var path = 'test.yml'; + + var file = new File({ + source: pathFn.join(box.base, path), + path: path, + type: 'create', + params: {foo: 'bar'}, + content: new Buffer(body) + }); before(function(){ - return fs.readFile(target).then(function(result){ - body = result; - obj = yaml.load(result); - - file = new box.File({ - source: target, - path: target, - type: 'create', - params: {foo: 'bar'}, - content: new Buffer(body) - }); - - return hexo.init(); - }); + return Promise.all([ + fs.writeFile(file.source, body), + hexo.init() + ]); }); + after(function(){ + return fs.rmdir(box.base); + }) + it('read()', function(){ return file.read().then(function(content){ content.should.eql(body); @@ -55,11 +71,34 @@ describe('File', function(){ }); }); - it('read() - file was deleted', function(){ - var file = new box.File({source: 'foo'}); + it('read() - cache off', function(){ + var path = 'nocache.txt'; + var file = new File({ + source: pathFn.join(box.base, path), + path: path, + content: new Buffer(body) + }); + + return fs.writeFile(file.source, 'abc').then(function(){ + return file.read({cache: false}); + }).then(function(content){ + content.should.eql('abc'); + return fs.unlink(file.source); + }); + }); + + it('read() - no content', function(){ + var path = 'nocache.txt'; + var file = new File({ + source: pathFn.join(box.base, path), + path: path + }); - file.read().catch(function(err){ - err.should.have.property('message', 'File "foo" was deleted.'); + return fs.writeFile(file.source, 'abc').then(function(){ + return file.read(); + }).then(function(content){ + content.should.eql('abc'); + return fs.unlink(file.source); }); }); @@ -75,19 +114,42 @@ describe('File', function(){ file.readSync('hex').should.eql(new Buffer(body).toString('hex')); }); - it('readSync() - file was deleted', function(){ - var file = new box.File({source: 'foo'}); + it('readSync() - cache off', function(){ + var path = 'nocache.txt'; + var file = new File({ + source: pathFn.join(box.base, path), + path: path, + content: new Buffer(body) + }); + + return fs.writeFile(file.source, 'abc').then(function(){ + var content = file.readSync({cache: false}); + + content.should.eql('abc'); + + return fs.unlink(file.source); + }); + }); - try { - file.readSync(); - } catch (err){ - err.should.have.property('message', 'File "foo" was deleted.'); - } + it('readSync() - no content', function(){ + var path = 'nocache.txt'; + var file = new File({ + source: pathFn.join(box.base, path), + path: path + }); + + return fs.writeFile(file.source, 'abc').then(function(){ + var content = file.readSync(); + + content.should.eql('abc'); + + return fs.unlink(file.source); + }); }); it('stat()', function(){ return Promise.all([ - fs.stat(target), + fs.stat(file.source), file.stat() ]).then(function(stats){ stats[0].should.eql(stats[1]); @@ -98,7 +160,7 @@ describe('File', function(){ file.stat(function(err, stats){ should.not.exist(err); - fs.stat(target, function(err, realStats){ + fs.stat(file.source, function(err, realStats){ stats.should.eql(realStats); callback(); }); @@ -106,7 +168,7 @@ describe('File', function(){ }); it('statSync()', function(){ - return fs.stat(target).then(function(stats){ + return fs.stat(file.source).then(function(stats){ file.statSync().should.eql(stats); }); }); diff --git a/test/scripts/hexo/render.js b/test/scripts/hexo/render.js index b80d6cdf63..74d6e1a0c8 100644 --- a/test/scripts/hexo/render.js +++ b/test/scripts/hexo/render.js @@ -1,13 +1,35 @@ var Hexo = require('../../../lib/hexo'); var fs = require('hexo-fs'); var pathFn = require('path'); -var fixtureDir = pathFn.join(__dirname, '../../fixtures'); +var Promise = require('bluebird'); +var yaml = require('js-yaml'); describe('Render', function(){ - var hexo = new Hexo(__dirname); + var hexo = new Hexo(pathFn.join(__dirname, 'render_test')); + + var body = [ + 'name:', + ' first: John', + ' last: Doe', + '', + 'age: 23', + '', + 'list:', + '- Apple', + '- Banana' + ].join('\n'); + + var obj = yaml.load(body); + var path = pathFn.join(hexo.base_dir, 'test.yml'); before(function(){ - return hexo.init(); + return fs.writeFile(path, body).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); }); it('isRenderable()', function(){ @@ -56,51 +78,20 @@ describe('Render', function(){ }); it('render() - path', function(){ - return hexo.render.render({ - path: pathFn.join(fixtureDir, 'test.yml') - }).then(function(result){ - result.should.eql({ - name: { - first: 'John', - last: 'Doe' - }, - age: 23, - list: ['Apple', 'Banana'] - }); + return hexo.render.render({path: path}).then(function(result){ + result.should.eql(obj); }); }); it('render() - text (without engine)', function(){ - var path = pathFn.join(fixtureDir, 'test.yml'); - var content; - - return fs.readFile(path).then(function(raw){ - content = raw; - return hexo.render.render({ - text: raw - }); - }).then(function(result){ - result.should.eql(content); + return hexo.render.render({text: body}).then(function(result){ + result.should.eql(body); }); }); it('render() - text (with engine)', function(){ - var path = pathFn.join(fixtureDir, 'test.yml'); - - return fs.readFile(path).then(function(raw){ - return hexo.render.render({ - text: raw, - engine: 'yaml' - }); - }).then(function(result){ - result.should.eql({ - name: { - first: 'John', - last: 'Doe' - }, - age: 23, - list: ['Apple', 'Banana'] - }); + return hexo.render.render({text: body, engine: 'yaml'}).then(function(result){ + result.should.eql(obj); }); }); @@ -129,43 +120,18 @@ describe('Render', function(){ }); it('renderSync() - path', function(){ - var result = hexo.render.renderSync({ - path: pathFn.join(fixtureDir, 'test.yml') - }); - - result.should.eql({ - name: { - first: 'John', - last: 'Doe' - }, - age: 23, - list: ['Apple', 'Banana'] - }); + var result = hexo.render.renderSync({path: path}); + result.should.eql(obj); }); it('renderSync() - text (without engine)', function(){ - var path = pathFn.join(fixtureDir, 'test.yml'); - - return fs.readFile(path).then(function(raw){ - hexo.render.renderSync({text: raw}).should.eql(raw); - }); + var result = hexo.render.renderSync({text: body}); + result.should.eql(body); }); it('renderSync() - text (with engine)', function(){ - var path = pathFn.join(fixtureDir, 'test.yml'); - - return fs.readFile(path).then(function(raw){ - var result = hexo.render.renderSync({text: raw, engine: 'yaml'}); - - result.should.eql({ - name: { - first: 'John', - last: 'Doe' - }, - age: 23, - list: ['Apple', 'Banana'] - }); - }); + var result = hexo.render.renderSync({text: body, engine: 'yaml'}); + result.should.eql(obj); }); it('renderSync() - no path and text', function(){ diff --git a/test/scripts/models/asset.js b/test/scripts/models/asset.js new file mode 100644 index 0000000000..40ea469494 --- /dev/null +++ b/test/scripts/models/asset.js @@ -0,0 +1,31 @@ +var should = require('chai').should(); + +describe('Asset', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var Asset = hexo.model('Asset'); + + it('default values', function(){ + return Asset.insert({ + _id: 'foo', + path: 'bar' + }).then(function(data){ + data.modified.should.be.true; + return Asset.removeById(data._id); + }); + }); + + it('_id - required', function(){ + return Asset.insert({}).catch(function(err){ + err.should.have.property('message', 'ID is not defined'); + }); + }); + + it('path - required', function(){ + return Asset.insert({ + _id: 'foo' + }).catch(function(err){ + err.should.have.property('message', '`path` is required!'); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/models/assets.js b/test/scripts/models/assets.js index c1bbde17a9..40ea469494 100644 --- a/test/scripts/models/assets.js +++ b/test/scripts/models/assets.js @@ -1,6 +1,6 @@ var should = require('chai').should(); -describe('Assets', function(){ +describe('Asset', function(){ var Hexo = require('../../../lib/hexo'); var hexo = new Hexo(); var Asset = hexo.model('Asset'); @@ -28,6 +28,4 @@ describe('Assets', function(){ err.should.have.property('message', '`path` is required!'); }); }); - - it.skip('post - reference'); }); \ No newline at end of file diff --git a/test/scripts/models/index.js b/test/scripts/models/index.js index 67511a7b70..4e30aa484f 100644 --- a/test/scripts/models/index.js +++ b/test/scripts/models/index.js @@ -1,9 +1,10 @@ describe('Models', function(){ - require('./assets'); + require('./asset'); require('./cache'); require('./category'); require('./moment'); require('./page'); require('./post'); + require('./post_asset'); require('./tag'); }); \ No newline at end of file diff --git a/test/scripts/models/post_asset.js b/test/scripts/models/post_asset.js new file mode 100644 index 0000000000..e8b66d1f1a --- /dev/null +++ b/test/scripts/models/post_asset.js @@ -0,0 +1,38 @@ +var should = require('chai').should(); + +describe('PostAsset', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(); + var PostAsset = hexo.model('PostAsset'); + var Post = hexo.model('Post'); + var post; + + before(function(){ + return hexo.init().then(function(){ + return Post.insert({ + source: 'foo.md', + slug: 'bar' + }); + }).then(function(post_){ + post = post_; + }); + }); + + it('default values', function(){ + return PostAsset.insert({ + _id: 'foo', + post: post._id + }).then(function(data){ + data.modified.should.be.true; + return PostAsset.removeById(data._id); + }); + }); + + it('_id - required', function(){ + return PostAsset.insert({}).catch(function(err){ + err.should.have.property('message', 'ID is not defined'); + }); + }); + + it.skip('post - required'); +}); \ No newline at end of file diff --git a/test/scripts/processors/asset.js b/test/scripts/processors/asset.js new file mode 100644 index 0000000000..e5c9e27016 --- /dev/null +++ b/test/scripts/processors/asset.js @@ -0,0 +1,310 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +var dateFormat = 'YYYY-MM-DD HH:mm:ss'; + +describe('asset', function(){ + var Hexo = require('../../../lib/hexo'); + var baseDir = pathFn.join(__dirname, 'asset_test'); + var hexo = new Hexo(baseDir); + var asset = require('../../../lib/plugins/processor/asset'); + var process = asset.process.bind(hexo); + var source = hexo.source; + var File = source.File; + var Asset = hexo.model('Asset'); + var Page = hexo.model('Page'); + + function newFile(options){ + options.source = pathFn.join(source.base, options.path); + return new File(options); + } + + before(function(){ + return fs.mkdirs(baseDir).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(baseDir); + }); + + it('asset - type: create', function(){ + var file = newFile({ + path: 'foo.jpg', + type: 'create' + }); + + return process(file).then(function(){ + var id = 'source/' + file.path; + var asset = Asset.findById(id); + + asset._id.should.eql(id); + asset.path.should.eql(file.path); + asset.modified.should.be.true; + + return asset.remove(); + }); + }); + + it('asset - type: update', function(){ + var file = newFile({ + path: 'foo.jpg', + type: 'update' + }); + + var id = 'source/' + file.path; + + return Asset.insert({ + _id: id, + path: file.path, + modified: false + }).then(function(){ + return process(file); + }).then(function(){ + var asset = Asset.findById(id); + + asset._id.should.eql(id); + asset.path.should.eql(file.path); + asset.modified.should.be.true; + + return asset.remove(); + }); + }); + + it('asset - type: skip', function(){ + var file = newFile({ + path: 'foo.jpg', + type: 'skip' + }); + + var id = 'source/' + file.path; + + return Asset.insert({ + _id: id, + path: file.path, + modified: false + }).then(function(){ + return process(file); + }).then(function(){ + var asset = Asset.findById(id); + + asset._id.should.eql(id); + asset.path.should.eql(file.path); + asset.modified.should.be.false; + + return asset.remove(); + }); + }); + + it('asset - type: delete', function(){ + var file = newFile({ + path: 'foo.jpg', + type: 'delete' + }); + + var id = 'source/' + file.path; + + return Asset.insert({ + _id: id, + path: file.path + }).then(function(){ + return process(file); + }).then(function(){ + should.not.exist(Asset.findById(id)); + }); + }); + + it('page - type: create', function(){ + var body = [ + 'title: "Hello world"', + 'date: 2006-01-02 15:04:05', + 'updated: 2014-12-13 01:02:03', + '---', + 'The quick brown fox jumps over the lazy dog' + ].join('\n'); + + var file = newFile({ + path: 'hello.swig', + type: 'create', + content: new Buffer(body) + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var page = Page.findOne({source: file.path}); + + page.title.should.eql('Hello world'); + page.date.format(dateFormat).should.eql('2006-01-02 15:04:05'); + page.updated.format(dateFormat).should.eql('2014-12-13 01:02:03'); + page.content.should.eql('The quick brown fox jumps over the lazy dog'); + page.source.should.eql(file.path); + page.raw.should.eql(body); + page.path.should.eql('hello.html'); + page.layout.should.eql('page'); + should.not.exist(page._content); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('page - type: update', function(){ + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'hello.swig', + type: 'update', + content: new Buffer(body) + }); + + var id; + + return Promise.all([ + Page.insert({source: file.path, path: 'hello.html'}), + fs.writeFile(file.source, body) + ]).spread(function(doc){ + id = doc._id; + return process(file); + }).then(function(){ + var page = Page.findOne({source: file.path}); + + page._id.should.eql(id); + page.title.should.eql('Hello world'); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('page - type: delete', function(){ + var file = newFile({ + path: 'hello.swig', + type: 'delete' + }); + + return Page.insert({ + source: file.path, + path: 'hello.html' + }).then(function(){ + return process(file); + }).then(function(){ + should.not.exist(Page.findOne({source: file.path})); + }); + }); + + it('page - use the status of the source file if date not set', function(){ + var file = newFile({ + path: 'hello.swig', + type: 'create' + }); + + return fs.writeFile(file.source, '').then(function(){ + return Promise.all([ + fs.stat(file.source), + process(file) + ]); + }).spread(function(stats){ + var page = Page.findOne({source: file.path}); + + page.date.toDate().should.eql(stats.ctime); + page.updated.toDate().should.eql(stats.mtime); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('page - permalink', function(){ + var body = [ + 'title: "Hello world"', + 'permalink: foo.html', + '---' + ].join('\n'); + + var file = newFile({ + path: 'hello.swig', + type: 'create', + content: new Buffer(body) + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var page = Page.findOne({source: file.path}); + + page.path.should.eql('foo.html'); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('page - permalink (without extension name)', function(){ + var body = [ + 'title: "Hello world"', + 'permalink: foo', + '---' + ].join('\n'); + + var file = newFile({ + path: 'hello.swig', + type: 'create', + content: new Buffer(body) + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var page = Page.findOne({source: file.path}); + + page.path.should.eql('foo.html'); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('page - permalink (with trailing slash)', function(){ + var body = [ + 'title: "Hello world"', + 'permalink: foo/', + '---' + ].join('\n'); + + var file = newFile({ + path: 'hello.swig', + type: 'create', + content: new Buffer(body) + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var page = Page.findOne({source: file.path}); + + page.path.should.eql('foo/index.html'); + + return Promise.all([ + page.remove(), + fs.unlink(file.source) + ]); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/processors/common.js b/test/scripts/processors/common.js new file mode 100644 index 0000000000..d7efba8b84 --- /dev/null +++ b/test/scripts/processors/common.js @@ -0,0 +1,27 @@ +var should = require('chai').should(); + +describe('common', function(){ + var common = require('../../../lib/plugins/processor/common'); + + it('isTmpFile()', function(){ + common.isTmpFile('foo').should.be.false; + common.isTmpFile('foo%').should.be.true; + common.isTmpFile('foo~').should.be.true; + }); + + it('isHiddenFile()', function(){ + common.isHiddenFile('foo').should.be.false; + common.isHiddenFile('_foo').should.be.true; + common.isHiddenFile('foo/_bar').should.be.true; + }); + + it('ignoreTmpAndHiddenFile()', function(){ + var pattern = common.ignoreTmpAndHiddenFile; + + pattern.match('foo').should.be.true; + pattern.match('foo%').should.be.false; + pattern.match('foo~').should.be.false; + pattern.match('_foo').should.be.false; + pattern.match('foo/_bar').should.be.false; + }); +}); \ No newline at end of file diff --git a/test/scripts/processors/index.js b/test/scripts/processors/index.js new file mode 100644 index 0000000000..5b41cd845e --- /dev/null +++ b/test/scripts/processors/index.js @@ -0,0 +1,5 @@ +describe('Processors', function(){ + require('./asset'); + require('./common'); + require('./post'); +}); \ No newline at end of file diff --git a/test/scripts/processors/post.js b/test/scripts/processors/post.js new file mode 100644 index 0000000000..7bc80e27de --- /dev/null +++ b/test/scripts/processors/post.js @@ -0,0 +1,798 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); +var defaultConfig = require('../../../lib/hexo/default_config'); + +var dateFormat = 'YYYY-MM-DD HH:mm:ss'; +var newPostName = defaultConfig.new_post_name; + +describe('post', function(){ + var Hexo = require('../../../lib/hexo'); + var baseDir = pathFn.join(__dirname, 'post_test'); + var hexo = new Hexo(baseDir); + var post = require('../../../lib/plugins/processor/post'); + var process = Promise.method(post.process.bind(hexo)); + var pattern = post.pattern; + var source = hexo.source; + var File = source.File; + var PostAsset = hexo.model('PostAsset'); + var Post = hexo.model('Post'); + + function newFile(options){ + var path = options.path; + + options.path = (options.published ? '_posts' : '_drafts') + '/' + path; + options.source = pathFn.join(source.base, options.path); + + options.params = { + published: options.published, + path: path + }; + + return new File(options); + } + + before(function(){ + return fs.mkdirs(baseDir).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(baseDir); + }); + + it('pattern', function(){ + pattern.match('_posts/foo.html').should.eql({ + published: true, + path: 'foo.html' + }); + + pattern.match('_drafts/bar.html').should.eql({ + published: false, + path: 'bar.html' + }); + + pattern.match('_posts/foo.html~').should.be.false; + pattern.match('_posts/foo.html%').should.be.false; + pattern.match('_posts/_foo.html').should.be.false; + pattern.match('_posts/foo/_bar.html').should.be.false; + pattern.match('_foo/bar.html').should.be.false; + }); + + it('asset - post_asset_folder disabled', function(){ + hexo.config.post_asset_folder = false; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'create' + }); + + return process(file).then(function(){ + var id = 'source/' + file.path; + should.not.exist(PostAsset.findById(id)); + }); + }); + + it('asset - type: create', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'create' + }); + + var postId; + + return Post.insert({ + source: '_posts/foo.html', + slug: 'foo' + }).then(function(doc){ + postId = doc._id; + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + var id = 'source/' + file.path; + var asset = PostAsset.findById(id); + + asset._id.should.eql(id); + asset.post.should.eql(postId); + asset.modified.should.be.true; + + return Promise.all([ + asset.remove(), + Post.removeById(postId) + ]); + }); + }); + + it('asset - type: update', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'update' + }); + + var id = 'source/' + file.path; + + return Post.insert({ + source: '_posts/foo.html', + slug: 'foo' + }).then(function(post){ + return PostAsset.insert({ + _id: id, + modified: false, + post: post._id + }); + }).then(function(){ + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + var asset = PostAsset.findById(id); + + asset.modified.should.be.true; + + return Promise.all([ + asset.remove(), + Post.removeById(asset.post) + ]); + }); + }); + + it('asset - type: skip', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'skip' + }); + + var id = 'source/' + file.path; + + return Post.insert({ + source: '_posts/foo.html', + slug: 'foo' + }).then(function(post){ + return PostAsset.insert({ + _id: id, + modified: false, + post: post._id + }); + }).then(function(){ + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + var asset = PostAsset.findById(id); + + asset.modified.should.be.false; + + return Promise.all([ + asset.remove(), + Post.removeById(asset.post) + ]); + }); + }); + + it('asset - type: delete', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'delete' + }); + + var id = 'source/' + file.path; + var postId; + + return Post.insert({ + source: '_posts/foo.html', + slug: 'foo' + }).then(function(post){ + postId = post._id; + + return PostAsset.insert({ + _id: id, + modified: false, + post: postId + }); + }).then(function(){ + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + should.not.exist(PostAsset.findById(id)); + return Post.removeById(postId); + }); + }); + + it('asset - skip if can\'t find a matching post', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'create' + }); + + var id = 'source/' + file.path; + + return process(file).then(function(){ + hexo.config.post_asset_folder = false; + should.not.exist(PostAsset.findById(id)); + }); + }); + + it('asset - the related post has been deleted', function(){ + hexo.config.post_asset_folder = true; + + var file = newFile({ + path: 'foo/bar.jpg', + published: true, + type: 'update' + }); + + var id = 'source/' + file.path; + + return PostAsset.insert({_id: id}).then(function(){ + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + should.not.exist(PostAsset.findById(id)); + }); + }); + + it('post - type: create', function(){ + var body = [ + 'title: "Hello world"', + 'date: 2006-01-02 15:04:05', + 'updated: 2014-12-13 01:02:03', + '---', + 'The quick brown fox jumps over the lazy dog' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.title.should.eql('Hello world'); + post.date.format(dateFormat).should.eql('2006-01-02 15:04:05'); + post.updated.format(dateFormat).should.eql('2014-12-13 01:02:03'); + post.content.should.eql('The quick brown fox jumps over the lazy dog'); + post.source.should.eql(file.path); + post.raw.should.eql(body); + post.slug.should.eql('foo'); + post.published.should.be.true; + should.not.exist(post._content); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - type: update', function(){ + var body = [ + 'title: "New world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'update' + }); + + var id; + + return Promise.all([ + Post.insert({source: file.path, slug: 'foo'}), + fs.writeFile(file.source, body) + ]).spread(function(doc){ + id = doc._id; + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post._id.should.eql(id); + post.title.should.eql('New world'); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - type: delete', function(){ + var file = newFile({ + path: 'foo.html', + published: true, + type: 'delete' + }); + + return Post.insert({ + source: file.path, + slug: 'foo' + }).then(function(){ + return process(file); + }).then(function(){ + should.not.exist(Post.findOne({source: file.path})); + }); + }); + + it('post - parse file name', function(){ + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: '2006/01/02/foo.html', + published: true, + type: 'create' + }); + + hexo.config.new_post_name = ':year/:month/:day/:title'; + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + hexo.config.new_post_name = newPostName; + + var post = Post.findOne({source: file.path}); + + post.slug.should.eql('foo'); + post.date.format('YYYY-MM-DD').should.eql('2006-01-02'); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - extra data in file name', function(){ + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'zh/foo.html', + published: true, + type: 'create' + }); + + hexo.config.new_post_name = ':lang/:title'; + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + hexo.config.new_post_name = newPostName; + + var post = Post.findOne({source: file.path}); + + post.lang.should.eql('zh'); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - file name does not match to the config', function(){ + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + hexo.config.new_post_name = ':year/:month/:day/:title'; + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + hexo.config.new_post_name = newPostName; + + var post = Post.findOne({source: file.path}); + + post.slug.should.eql('foo'); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - published', function(){ + var body = [ + 'title: "Hello world"', + 'published: false', + '---' + ].join('\n'); + + var file = newFile({ + path: 'zh/foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.published.should.be.false; + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - always set published: false for drafts', function(){ + var body = [ + 'title: "Hello world"', + 'published: true', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: false, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.published.should.be.false; + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - use the status of the source file if date not set', function(){ + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return Promise.all([ + file.stat(), + process(file) + ]); + }).spread(function(stats){ + var post = Post.findOne({source: file.path}); + + post.date.toDate().should.eql(stats.ctime); + post.updated.toDate().should.eql(stats.mtime); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - photo is an alias for photos', function(){ + var body = [ + 'title: "Hello world"', + 'photo:', + '- http://hexo.io/foo.jpg', + '- http://hexo.io/bar.png', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.photos.should.eql([ + 'http://hexo.io/foo.jpg', + 'http://hexo.io/bar.png' + ]); + + should.not.exist(post.photo); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - photos (not array)', function(){ + var body = [ + 'title: "Hello world"', + 'photos: http://hexo.io/foo.jpg', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.photos.should.eql([ + 'http://hexo.io/foo.jpg' + ]); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - link without title', function(){ + var body = [ + 'link: http://hexo.io/', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.link.should.eql('http://hexo.io/'); + post.title.should.eql('hexo.io'); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - category is an alias for categories', function(){ + var body = [ + 'title: "Hello world"', + 'category:', + '- foo', + '- bar', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + should.not.exist(post.category); + post.categories.map(function(item){ + return item.name; + }).should.eql(['foo', 'bar']); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - categories (not array)', function(){ + var body = [ + 'title: "Hello world"', + 'categories: foo', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.categories.map(function(item){ + return item.name; + }).should.eql(['foo']); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - tag is an alias for tags', function(){ + var body = [ + 'title: "Hello world"', + 'tags:', + '- foo', + '- bar', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + should.not.exist(post.tag); + post.tags.map(function(item){ + return item.name; + }).should.eql(['foo', 'bar']); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - tags (not array)', function(){ + var body = [ + 'title: "Hello world"', + 'tags: foo', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + return fs.writeFile(file.source, body).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + + post.tags.map(function(item){ + return item.name; + }).should.eql(['foo']); + + return Promise.all([ + post.remove(), + fs.unlink(file.source) + ]); + }); + }); + + it('post - post_asset_folder enabled', function(){ + hexo.config.post_asset_folder = true; + + var body = [ + 'title: "Hello world"', + '---' + ].join('\n'); + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + var assetId = 'source/_posts/foo/bar.jpg'; + var assetPath = pathFn.join(hexo.base_dir, assetId); + + return Promise.all([ + fs.writeFile(file.source, body), + fs.writeFile(assetPath, '') + ]).then(function(){ + return process(file); + }).then(function(){ + hexo.config.post_asset_folder = false; + + var post = Post.findOne({source: file.path}); + var asset = PostAsset.findById(assetId); + + asset._id.should.eql(assetId); + asset.post.should.eql(post._id); + asset.modified.should.be.true; + + return Promise.all([ + post.remove(), + asset.remove(), + fs.unlink(file.source), + fs.unlink(assetPath) + ]); + }); + }); + + it('post - post_asset_folder disabled', function(){ + hexo.config.post_asset_folder = false; + + var file = newFile({ + path: 'foo.html', + published: true, + type: 'create' + }); + + var assetId = 'source/_posts/foo/bar.jpg'; + var assetPath = pathFn.join(hexo.base_dir, assetId); + + return Promise.all([ + fs.writeFile(file.source, ''), + fs.writeFile(assetPath, '') + ]).then(function(){ + return process(file); + }).then(function(){ + var post = Post.findOne({source: file.path}); + should.not.exist(PostAsset.findById(assetId)); + + return Promise.all([ + post.remove(), + fs.unlink(file.source), + fs.unlink(assetPath) + ]); + }); + }); +}); \ No newline at end of file From 262fc82ad6e505ccd18d9c9b283b77949ec10524 Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Sun, 14 Dec 2014 17:46:05 +0800 Subject: [PATCH 14/15] Add plugin loader test --- test/scripts/hexo/hexo.js | 2 + test/scripts/hexo/index.js | 1 + test/scripts/hexo/load_plugins.js | 99 +++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 test/scripts/hexo/load_plugins.js diff --git a/test/scripts/hexo/hexo.js b/test/scripts/hexo/hexo.js index 91f0ae512f..ec84fe1972 100644 --- a/test/scripts/hexo/hexo.js +++ b/test/scripts/hexo/hexo.js @@ -1,3 +1,5 @@ +var should = require('chai').should(); + describe('Hexo', function(){ // }); \ No newline at end of file diff --git a/test/scripts/hexo/index.js b/test/scripts/hexo/index.js index 307ed7fefe..82dfa217e3 100644 --- a/test/scripts/hexo/index.js +++ b/test/scripts/hexo/index.js @@ -2,6 +2,7 @@ describe('Core', function(){ require('./hexo'); require('./load_config'); require('./load_database'); + require('./load_plugins'); require('./post'); require('./render'); require('./scaffold'); diff --git a/test/scripts/hexo/load_plugins.js b/test/scripts/hexo/load_plugins.js new file mode 100644 index 0000000000..2850e2e9c9 --- /dev/null +++ b/test/scripts/hexo/load_plugins.js @@ -0,0 +1,99 @@ +var should = require('chai').should(); +var fs = require('hexo-fs'); +var pathFn = require('path'); + +describe('Load plugins', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'plugin_test'), {silent: true}); + var loadPlugins = require('../../../lib/hexo/load_plugins'); + + var script = [ + 'hexo._script_test = {', + ' filename: __filename,', + ' dirname: __dirname,', + ' module: module,', + ' require: require', + '}' + ].join('\n'); + + function validate(path){ + var result = hexo._script_test; + + result.filename = path; + result.dirname = pathFn.dirname(path); + result.module.id = path; + result.module.filename = path; + + delete hexo._script_test; + } + + hexo.env.init = true; + hexo.theme_script_dir = pathFn.join(hexo.base_dir, 'themes', 'test', 'scripts'); + + before(function(){ + return fs.mkdir(hexo.base_dir); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('load plugins', function(){ + var path = pathFn.join(hexo.plugin_dir, 'hexo-plugin-test', 'index.js'); + + return fs.writeFile(path, script).then(function(){ + return loadPlugins(hexo); + }).then(function(){ + validate(path); + return fs.unlink(path); + }); + }); + + it('ignore plugins whose name is not started with "hexo-"', function(){ + var script = 'hexo._script_test = true'; + var path = pathFn.join(hexo.plugin_dir, 'another-plugin', 'index.js'); + + return fs.writeFile(path, script).then(function(){ + return loadPlugins(hexo); + }).then(function(){ + should.not.exist(hexo._script_test); + return fs.unlink(path); + }); + }); + + it('load scripts', function(){ + var path = pathFn.join(hexo.script_dir, 'test.js'); + + return fs.writeFile(path, script).then(function(){ + return loadPlugins(hexo); + }).then(function(){ + validate(path); + return fs.unlink(path); + }); + }); + + it('load theme scripts', function(){ + var path = pathFn.join(hexo.theme_script_dir, 'test.js'); + + return fs.writeFile(path, script).then(function(){ + return loadPlugins(hexo); + }).then(function(){ + validate(path); + return fs.unlink(path); + }); + }); + + it('don\'t load plugins in safe mode', function(){ + var script = 'hexo._script_test = true'; + var path = pathFn.join(hexo.script_dir, 'test.js'); + + return fs.writeFile(path, script).then(function(){ + hexo.env.safe = true; + return loadPlugins(hexo); + }).then(function(){ + hexo.env.safe = false; + should.not.exist(hexo._script_test); + return fs.unlink(path); + }); + }); +}); \ No newline at end of file From 6db75622495dd4113ca045b1baa52aafea83abed Mon Sep 17 00:00:00 2001 From: Tommy Chen <tommy351@gmail.com> Date: Tue, 16 Dec 2014 23:00:29 +0800 Subject: [PATCH 15/15] Rewrite theme and add theme tests. TODO: Writing test for Theme.generate --- lib/box/index.js | 14 +- lib/theme/index.js | 55 +++--- lib/theme/processors/config.js | 16 +- lib/theme/processors/i18n.js | 20 --- lib/theme/processors/source.js | 32 +++- lib/theme/processors/view.js | 14 +- lib/theme/view.js | 17 +- lib/util/i18n.js | 18 +- test/index.js | 2 + test/scripts/box/file.js | 4 + test/scripts/theme/index.js | 4 + test/scripts/theme/theme.js | 64 +++++++ test/scripts/theme/view.js | 228 ++++++++++++++++++++++++ test/scripts/theme_processors/config.js | 87 +++++++++ test/scripts/theme_processors/index.js | 5 + test/scripts/theme_processors/source.js | 130 ++++++++++++++ test/scripts/theme_processors/view.js | 81 +++++++++ 17 files changed, 704 insertions(+), 87 deletions(-) delete mode 100644 lib/theme/processors/i18n.js create mode 100644 test/scripts/theme/index.js create mode 100644 test/scripts/theme/theme.js create mode 100644 test/scripts/theme/view.js create mode 100644 test/scripts/theme_processors/config.js create mode 100644 test/scripts/theme_processors/index.js create mode 100644 test/scripts/theme_processors/source.js create mode 100644 test/scripts/theme_processors/view.js diff --git a/lib/box/index.js b/lib/box/index.js index 9cd9cac238..8e4d6eacec 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -49,11 +49,21 @@ function Box(ctx, base, options){ options = {}; } - return ctx.render.render({path: this.source}, options).nodeify(callback); + var self = this; + + return this.read().then(function(content){ + return ctx.render.render({ + text: content, + path: self.source + }, options) + }).nodeify(callback); }; _File.prototype.renderSync = function(options){ - return ctx.render.renderSync({path: this.source}, options); + return ctx.render.renderSync({ + text: this.readSync(), + path: this.source + }, options); }; } diff --git a/lib/theme/index.js b/lib/theme/index.js index aeb45305a8..abf0efc7db 100644 --- a/lib/theme/index.js +++ b/lib/theme/index.js @@ -5,8 +5,6 @@ var util = require('../util'); var Box = require('../box'); var View = require('./view'); -var i18n = util.i18n; - require('colors'); function Theme(ctx){ @@ -14,27 +12,23 @@ function Theme(ctx){ this.config = {}; - this.i18n = new i18n({ - code: ctx.config.language - }); - this.views = {}; this.processors = [ require('./processors/config'), - require('./processors/i18n'), require('./processors/view'), require('./processors/source') ]; - var _View = this.View = function(path){ - View.call(this, path); + var _View = this.View = function(path, data){ + View.call(this, path, data); }; util.inherits(_View, View); - _View.prototype.theme = this; - _View.prototype.context = ctx; + _View.prototype._theme = this; + _View.prototype._render = ctx.render; + _View.prototype._helper = ctx.extend.helper; } util.inherits(Theme, Box); @@ -47,7 +41,6 @@ Theme.prototype._generate = function(options){ var ctx = this.context; var config = ctx.config; var siteLocals = ctx.locals; - var i18n = this.i18n; var generators = ctx.extend.generator.list(); var generatorKeys = Object.keys(generators); @@ -55,8 +48,7 @@ Theme.prototype._generate = function(options){ function Locals(path, locals){ this.page = _.extend({ - path: path, - lang: ensureLanguage(this) // every page should have a 'lang' value + path: path }, locals); this.path = path; @@ -67,8 +59,6 @@ Theme.prototype._generate = function(options){ Locals.prototype.config = config; Locals.prototype.theme = _.extend({}, config, this.config, config.theme_config); Locals.prototype._ = _; - Locals.prototype.__ = i18n.__(); - Locals.prototype._p = i18n._p(); Locals.prototype.layout = 'layout'; Locals.prototype.cache = !options.watch; Locals.prototype.env = ctx.env; @@ -91,21 +81,6 @@ Theme.prototype._generate = function(options){ }); }; -function ensureLanguage(locals){ - var page = locals.page; - var lang = page.lang; - var config = locals.config; - - if (lang) return lang; - if (!config.language) return 'default'; - if (!Array.isArray(config.language)) return config.language; - - var path = locals.path; - if (path[0] !== '/') path = '/' + path; - - // TODO: Optimize language query -} - Theme.prototype.generate = function(options, callback){ if (!callback && typeof options === 'function'){ callback = options; @@ -137,4 +112,22 @@ Theme.prototype.getView = function(path){ } }; +Theme.prototype.setView = function(path, data){ + var extname = pathFn.extname(path); + var name = path.substring(0, path.length - extname.length); + var views = this.views[name] = this.views[name] || {}; + + views[extname] = new this.View(path, data); +}; + +Theme.prototype.removeView = function(path){ + var extname = pathFn.extname(path); + var name = path.substring(0, path.length - extname.length); + var views = this.views[name]; + + if (!views) return; + + delete views[extname]; +}; + module.exports = Theme; \ No newline at end of file diff --git a/lib/theme/processors/config.js b/lib/theme/processors/config.js index 17f348e798..32db410652 100644 --- a/lib/theme/processors/config.js +++ b/lib/theme/processors/config.js @@ -1,18 +1,18 @@ var Pattern = require('../../box/pattern'); -exports.process = function(data){ - if (data.type === 'delete'){ - this.config = {}; +exports.process = function(file){ + if (file.type === 'delete'){ + file.box.config = {}; return; } - var ctx = this.context; + var self = this; - return data.render().then(function(result){ - data.box.config = result; - ctx.log.debug('Theme config loaded.'); + return file.render().then(function(result){ + file.box.config = result; + self.log.debug('Theme config loaded.'); }, function(err){ - ctx.log.error('Theme config load failed.'); + self.log.error('Theme config load failed.'); throw err; }); }; diff --git a/lib/theme/processors/i18n.js b/lib/theme/processors/i18n.js deleted file mode 100644 index 5c8be782f4..0000000000 --- a/lib/theme/processors/i18n.js +++ /dev/null @@ -1,20 +0,0 @@ -var pathFn = require('path'); -var Pattern = require('../../box/pattern'); - -exports.process = function(data){ - var path = data.params.path; - var extname = pathFn.extname(path); - var name = pathFn.substring(0, path.length - extname.length); - var i18n = this.i18n; - - if (data.type === 'delete'){ - i18n.remove(name); - return; - } - - return data.render().then(function(result){ - i18n.set(name, result); - }); -}; - -exports.pattern = new Pattern('languages/*path'); \ No newline at end of file diff --git a/lib/theme/processors/source.js b/lib/theme/processors/source.js index 8d0a0323e0..6258872536 100644 --- a/lib/theme/processors/source.js +++ b/lib/theme/processors/source.js @@ -1,16 +1,38 @@ var Pattern = require('../../box/pattern'); +var common = require('../../plugins/processor/common'); -var rHiddenFile = /^_|\/_/; +exports.process = function(file){ + var Asset = this.model('Asset'); + var id = file.source.substring(this.base_dir.length); + var path = file.params.path; + var doc = Asset.findById(id); -exports.process = function(data){ - // + if (file.type === 'delete'){ + if (doc){ + return doc.remove(); + } else { + return; + } + } + + if (doc){ + doc.path = path; + doc.modified = file.type === 'update'; + + return doc.save(); + } else { + return Asset.insert({ + _id: id, + path: path + }); + } }; exports.pattern = new Pattern(function(path){ - if (path.substring(0, 6) !== 'source') return; + if (path.substring(0, 7) !== 'source/') return false; path = path.substring(7); - if (rHiddenFile.test(path)) return; + if (common.isHiddenFile(path) || common.isTmpFile(path)) return false; return {path: path}; }); \ No newline at end of file diff --git a/lib/theme/processors/view.js b/lib/theme/processors/view.js index 1b607b97c5..3b70c72a9c 100644 --- a/lib/theme/processors/view.js +++ b/lib/theme/processors/view.js @@ -1,7 +1,17 @@ var Pattern = require('../../box/pattern'); +var pathFn = require('path'); -exports.process = function(data){ - // +exports.process = function(file){ + var path = file.params.path; + + if (file.type === 'delete'){ + file.box.removeView(path); + return; + } + + return file.read().then(function(result){ + file.box.setView(path, result); + }); }; exports.pattern = new Pattern('layout/*path'); \ No newline at end of file diff --git a/lib/theme/view.js b/lib/theme/view.js index 86500a3e4d..e99fc986b6 100644 --- a/lib/theme/view.js +++ b/lib/theme/view.js @@ -3,13 +3,8 @@ var _ = require('lodash'); var yfm = require('hexo-front-matter'); function View(path, data){ - var ctx = this.context; - this.path = path; - this.source = pathFn.join(this.theme.base, path); - this.extname = pathFn.extname(path); - this.render = ctx.render; - this.helper = ctx.extend.helper; + this.source = pathFn.join(this._theme.base, 'layout', path); this.data = typeof data === 'string' ? yfm(data) : data; } @@ -26,7 +21,7 @@ View.prototype.render = function(options, callback){ var locals = this._buildLocals(options); var self = this; - return this.render.render({ + return this._render.render({ path: this.source, text: data._content }, this._bindHelpers(locals)).then(function(result){ @@ -50,7 +45,7 @@ View.prototype.renderSync = function(options){ var layout = data.hasOwnProperty('layout') ? data.layout : options.layout; var locals = this._buildLocals(options); - var result = this.render.renderSync({ + var result = this._render.renderSync({ path: this.source, text: data._content }, this._bindHelpers(locals)); @@ -97,7 +92,7 @@ View.prototype._buildLocals = function(locals){ }; View.prototype._bindHelpers = function(locals){ - var helpers = this.helper.list(); + var helpers = this._helper.list(); var keys = Object.keys(helpers); var result = {}; var key = ''; @@ -115,12 +110,12 @@ View.prototype._bindHelpers = function(locals){ View.prototype._resolveLayout = function(name){ // Relative path var layoutPath = pathFn.join(pathFn.dirname(this.path), name); - var layoutView = this.theme.getView(layoutPath); + var layoutView = this._theme.getView(layoutPath); if (layoutView && layoutView.source !== this.source) return layoutView; // Absolute path - layoutView = this.theme.getView(name); + layoutView = this._theme.getView(name); if (layoutView && layoutView.source !== this.source) return layoutView; }; diff --git a/lib/util/i18n.js b/lib/util/i18n.js index c213fc190c..20d758c83f 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -1,5 +1,5 @@ -var _ = require('lodash'), - vsprintf = require('sprintf-js').vsprintf; +var _ = require('lodash'); +var vsprintf = require('sprintf-js').vsprintf; /** * i18n module. @@ -10,7 +10,7 @@ var _ = require('lodash'), * @constructor * @module hexo */ -var i18n = module.exports = function i18n(options){ +function i18n(options){ /** * Language data. * @@ -29,7 +29,7 @@ var i18n = module.exports = function i18n(options){ code: 'default' }, options); - this.options.code = _getCodeToken(this.options.code); + this.options.code = getCodeToken(this.options.code); }; /** @@ -84,12 +84,12 @@ var _getProperty = function(obj, key){ return cursor; }; -var _getCodeToken = function(code){ +function getCodeToken(code){ var arr = []; if (Array.isArray(code)){ code.forEach(function(item){ - arr = arr.concat(_getCodeToken(item)); + arr = arr.concat(getCodeToken(item)); }); } else if (code){ var split = code.split('-'); @@ -102,7 +102,7 @@ var _getCodeToken = function(code){ } return arr; -}; +} /** * Parses the language code. @@ -112,7 +112,7 @@ var _getCodeToken = function(code){ * @return {Array} */ i18n.prototype._parseCode = function(code){ - var arr = [].concat(_getCodeToken(code), this.options.code); + var arr = [].concat(getCodeToken(code), this.options.code); if (arr.indexOf('default') === -1){ arr.push('default'); @@ -206,3 +206,5 @@ i18n.prototype._p = function(code){ return _p; }; + +module.exports = i18n; \ No newline at end of file diff --git a/test/index.js b/test/index.js index c0276f16e1..2f566964e1 100644 --- a/test/index.js +++ b/test/index.js @@ -9,5 +9,7 @@ describe('Hexo', function(){ require('./scripts/processors'); require('./scripts/renderers'); require('./scripts/tags'); + require('./scripts/theme'); + require('./scripts/theme_processors') require('./scripts/util'); }); \ No newline at end of file diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index 187b3a3a99..f8ba405aa5 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -187,7 +187,11 @@ describe('File', function(){ }); }); + it('render() - use cache'); + it('renderSync()', function(){ file.renderSync().should.eql(obj); }); + + it('renderSync() - use cache'); }); \ No newline at end of file diff --git a/test/scripts/theme/index.js b/test/scripts/theme/index.js new file mode 100644 index 0000000000..287bcbc966 --- /dev/null +++ b/test/scripts/theme/index.js @@ -0,0 +1,4 @@ +describe('Theme', function(){ + require('./theme'); + require('./view'); +}); \ No newline at end of file diff --git a/test/scripts/theme/theme.js b/test/scripts/theme/theme.js new file mode 100644 index 0000000000..39424ea694 --- /dev/null +++ b/test/scripts/theme/theme.js @@ -0,0 +1,64 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +describe('Theme', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'theme_test')); + var themeDir = pathFn.join(hexo.base_dir, 'themes', 'test'); + + before(function(){ + return Promise.all([ + fs.mkdirs(themeDir), + fs.writeFile(hexo.config_path, 'theme: test') + ]).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('generate()'); + + it('getView()', function(){ + hexo.theme.setView('test.swig', ''); + + // With extension name + hexo.theme.getView('test.swig').should.have.property('path', 'test.swig'); + + // Without extension name + hexo.theme.getView('test').should.have.property('path', 'test.swig'); + + // not exist + should.not.exist(hexo.theme.getView('abc.swig')); + + hexo.theme.removeView('test.swig'); + }); + + it('getView() - escape backslashes', function(){ + hexo.theme.setView('foo/bar.swig', ''); + + hexo.theme.getView('foo\\bar.swig').should.have.property('path', 'foo/bar.swig'); + + hexo.theme.removeView('foo/bar.swig'); + }); + + it('setView()', function(){ + hexo.theme.setView('test.swig', ''); + + var view = hexo.theme.getView('test.swig'); + view.path.should.eql('test.swig'); + + hexo.theme.removeView('test.swig'); + }); + + it('removeView()', function(){ + hexo.theme.setView('test.swig', ''); + hexo.theme.removeView('test.swig'); + + should.not.exist(hexo.theme.getView('test.swig')); + }); +}); \ No newline at end of file diff --git a/test/scripts/theme/view.js b/test/scripts/theme/view.js new file mode 100644 index 0000000000..514b59070a --- /dev/null +++ b/test/scripts/theme/view.js @@ -0,0 +1,228 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); +var moment = require('moment'); + +describe('View', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'theme_test')); + var themeDir = pathFn.join(hexo.base_dir, 'themes', 'test'); + + function newView(path, data){ + return new hexo.theme.View(path, data); + } + + before(function(){ + return Promise.all([ + fs.mkdirs(themeDir), + fs.writeFile(hexo.config_path, 'theme: test') + ]).then(function(){ + return hexo.init(); + }).then(function(){ + // Setup layout + hexo.theme.setView('layout.swig', [ + 'pre', + '{{ body }}', + 'post' + ].join('\n')); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('constructor', function(){ + var view = newView('index.swig', {}); + + view.path.should.eql('index.swig'); + view.source.should.eql(pathFn.join(themeDir, 'layout', 'index.swig')); + view.data.should.eql({}); + }); + + it('parse front-matter', function(){ + var body = [ + 'layout: false', + '---', + 'content' + ].join('\n'); + + var view = newView('index.swig', body); + + view.data.should.eql({ + layout: false, + _content: 'content' + }); + }); + + it('render()', function(){ + var body = [ + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + + return view.render({ + test: 'foo' + }).then(function(content){ + content.should.eql('foo'); + }); + }); + + it('render() - front-matter', function(){ + // The priority of front-matter is higher + var body = [ + 'foo: bar', + '---', + '{{ foo }}', + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + + return view.render({ + foo: 'foo', + test: 'test' + }).then(function(content){ + content.should.eql('bar\ntest'); + }); + }); + + it('render() - helper', function(){ + var body = [ + '{{ date() }}' + ].join('\n'); + + var view = newView('index.swig', body); + + return view.render({ + config: hexo.config + }).then(function(content){ + content.should.eql(moment().format(hexo.config.date_format)); + }); + }); + + it('render() - layout', function(){ + var body = 'content'; + var view = newView('index.swig', body); + + return view.render({ + layout: 'layout' + }).then(function(content){ + content.should.eql('pre\n' + body + '\npost'); + }); + }); + + it('render() - layout not found', function(){ + var body = 'content'; + var view = newView('index.swig', body); + + return view.render({ + layout: 'wtf' + }).then(function(content){ + content.should.eql(body); + }); + }); + + it('render() - callback', function(callback){ + var body = [ + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + + view.render({ + test: 'foo' + }, function(err, content){ + should.not.exist(err); + content.should.eql('foo'); + callback(); + }); + }); + + it('render() - callback (without options)', function(callback){ + var body = [ + 'test: foo', + '---', + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + + view.render(function(err, content){ + should.not.exist(err); + content.should.eql('foo'); + callback(); + }); + }); + + it('renderSync()', function(){ + var body = [ + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + view.renderSync({test: 'foo'}).should.eql('foo'); + }); + + it('renderSync() - front-matter', function(){ + // The priority of front-matter is higher + var body = [ + 'foo: bar', + '---', + '{{ foo }}', + '{{ test }}' + ].join('\n'); + + var view = newView('index.swig', body); + + view.renderSync({ + foo: 'foo', + test: 'test' + }).should.eql('bar\ntest'); + }); + + it('renderSync() - helper', function(){ + var body = [ + '{{ date() }}' + ].join('\n'); + + var view = newView('index.swig', body); + + view.renderSync({ + config: hexo.config + }).should.eql(moment().format(hexo.config.date_format)); + }); + + it('renderSync() - layout', function(){ + var body = 'content'; + var view = newView('index.swig', body); + + view.renderSync({ + layout: 'layout' + }).should.eql('pre\n' + body + '\npost'); + }); + + it('renderSync() - layout not found', function(){ + var body = 'content'; + var view = newView('index.swig', body); + + view.renderSync({ + layout: 'wtf' + }).should.eql(body); + }); + + it('_resolveLayout()', function(){ + var view = newView('partials/header.swig', 'header'); + + // Relative path + view._resolveLayout('../layout').should.have.property('path', 'layout.swig'); + + // Absolute path + view._resolveLayout('layout').should.have.property('path', 'layout.swig'); + + // Can't be itself + should.not.exist(view._resolveLayout('header')); + }); +}); \ No newline at end of file diff --git a/test/scripts/theme_processors/config.js b/test/scripts/theme_processors/config.js new file mode 100644 index 0000000000..cc5937f4bd --- /dev/null +++ b/test/scripts/theme_processors/config.js @@ -0,0 +1,87 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +describe('config', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'config_test'), {silent: true}); + var processor = require('../../../lib/theme/processors/config'); + var process = Promise.method(processor.process.bind(hexo)); + var themeDir = pathFn.join(hexo.base_dir, 'themes', 'test'); + + function newFile(options){ + options.source = pathFn.join(themeDir, options.path); + return new hexo.theme.File(options); + } + + before(function(){ + return Promise.all([ + fs.mkdirs(themeDir), + fs.writeFile(hexo.config_path, 'theme: test') + ]).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('pattern', function(){ + var pattern = processor.pattern; + + pattern.match('_config.yml').should.be.ok; + pattern.match('_config.json').should.be.ok; + should.not.exist(pattern.match('_config/foo.yml')); + should.not.exist(pattern.match('foo.yml')); + }); + + it('type: create', function(){ + var body = [ + 'name:', + ' first: John', + ' last: Doe' + ].join('\n'); + + var file = newFile({ + path: '_config.yml', + type: 'create', + content: body + }); + + return process(file).then(function(){ + hexo.theme.config.should.eql({ + name: {first: 'John', last: 'Doe'} + }); + + hexo.theme.config = {}; + }); + }); + + it('type: delete', function(){ + var file = newFile({ + path: '_config.yml', + type: 'delete' + }); + + hexo.theme.config = {foo: 'bar'}; + + return process(file).then(function(){ + hexo.theme.config.should.eql({}); + }); + }); + + it('load failed', function(){ + var body = 'foo:bar'; + + var file = newFile({ + path: '_config.yml', + type: 'create' + }); + + return process(file).catch(function(err){ + err.should.have.property('message', 'Theme config load failed.'); + }).catch(function(){}); // Catch again because it throws error + }); +}); \ No newline at end of file diff --git a/test/scripts/theme_processors/index.js b/test/scripts/theme_processors/index.js new file mode 100644 index 0000000000..7e380ade5a --- /dev/null +++ b/test/scripts/theme_processors/index.js @@ -0,0 +1,5 @@ +describe('Theme processors', function(){ + require('./config'); + require('./source'); + require('./view'); +}); \ No newline at end of file diff --git a/test/scripts/theme_processors/source.js b/test/scripts/theme_processors/source.js new file mode 100644 index 0000000000..74af76da54 --- /dev/null +++ b/test/scripts/theme_processors/source.js @@ -0,0 +1,130 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +describe('source', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'source_test'), {silent: true}); + var processor = require('../../../lib/theme/processors/source'); + var process = Promise.method(processor.process.bind(hexo)); + var themeDir = pathFn.join(hexo.base_dir, 'themes', 'test'); + var Asset = hexo.model('Asset'); + + function newFile(options){ + var path = options.path; + + options.params = {path: path}; + options.path = 'source/' + path; + options.source = pathFn.join(themeDir, options.path); + + return new hexo.theme.File(options); + } + + before(function(){ + return Promise.all([ + fs.mkdirs(themeDir), + fs.writeFile(hexo.config_path, 'theme: test') + ]).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('pattern', function(){ + var pattern = processor.pattern; + + pattern.match('source/foo.jpg').should.eql({path: 'foo.jpg'}); + pattern.match('source/_foo.jpg').should.be.false; + pattern.match('source/foo/_bar.jpg').should.be.false; + pattern.match('source/foo.jpg~').should.be.false; + pattern.match('source/foo.jpg%').should.be.false; + pattern.match('layout/foo.swig').should.be.false; + pattern.match('package.json').should.be.false; + }); + + it('type: create', function(){ + var file = newFile({ + path: 'style.css', + type: 'create' + }); + + return process(file).then(function(){ + var id = 'themes/test/' + file.path; + var asset = Asset.findById(id); + + asset._id.should.eql(id); + asset.path.should.eql(file.params.path); + asset.modified.should.be.true; + + return asset.remove(); + }); + }); + + it('type: update', function(){ + var file = newFile({ + path: 'style.css', + type: 'update' + }); + + var id = 'themes/test/' + file.path; + + return Asset.insert({ + _id: id, + path: file.params.path, + modified: false + }).then(function(){ + return process(file); + }).then(function(){ + var asset = Asset.findById(id); + + asset.modified.should.be.true; + + return asset.remove(); + }); + }); + + it('type: skip', function(){ + var file = newFile({ + path: 'style.css', + type: 'skip' + }); + + var id = 'themes/test/' + file.path; + + return Asset.insert({ + _id: id, + path: file.params.path, + modified: true + }).then(function(){ + return process(file); + }).then(function(){ + var asset = Asset.findById(id); + + asset.modified.should.be.false; + + return asset.remove(); + }); + }); + + it('type: delete', function(){ + var file = newFile({ + path: 'style.css', + type: 'delete' + }); + + var id = 'themes/test/' + file.path; + + return Asset.insert({ + _id: id, + path: file.params.path + }).then(function(){ + return process(file); + }).then(function(){ + should.not.exist(Asset.findById(id)); + }); + }); +}); \ No newline at end of file diff --git a/test/scripts/theme_processors/view.js b/test/scripts/theme_processors/view.js new file mode 100644 index 0000000000..c47f505fd5 --- /dev/null +++ b/test/scripts/theme_processors/view.js @@ -0,0 +1,81 @@ +var should = require('chai').should(); +var pathFn = require('path'); +var fs = require('hexo-fs'); +var Promise = require('bluebird'); + +describe('view', function(){ + var Hexo = require('../../../lib/hexo'); + var hexo = new Hexo(pathFn.join(__dirname, 'view_test'), {silent: true}); + var processor = require('../../../lib/theme/processors/view'); + var process = Promise.method(processor.process.bind(hexo)); + var themeDir = pathFn.join(hexo.base_dir, 'themes', 'test'); + + function newFile(options){ + var path = options.path; + + options.params = {path: path}; + options.path = 'layout/' + path; + options.source = pathFn.join(themeDir, options.path); + + return new hexo.theme.File(options); + } + + before(function(){ + return Promise.all([ + fs.mkdirs(themeDir), + fs.writeFile(hexo.config_path, 'theme: test') + ]).then(function(){ + return hexo.init(); + }); + }); + + after(function(){ + return fs.rmdir(hexo.base_dir); + }); + + it('pattern', function(){ + var pattern = processor.pattern; + + pattern.match('layout/index.swig').path.should.eql('index.swig'); + should.not.exist(pattern.match('index.swig')); + should.not.exist(pattern.match('view/index.swig')); + }); + + it('type: create', function(){ + var body = [ + 'foo: bar', + '---', + 'test' + ].join('\n'); + + var file = newFile({ + path: 'index.swig', + type: 'create', + content: new Buffer(body) + }); + + return process(file).then(function(){ + var view = hexo.theme.getView('index.swig'); + + view.path.should.eql('index.swig'); + view.source.should.eql(pathFn.join(themeDir, 'layout', 'index.swig')); + view.data.should.eql({ + foo: 'bar', + _content: 'test' + }); + + hexo.theme.removeView('index.swig'); + }); + }); + + it('type: delete', function(){ + var file = newFile({ + path: 'index.swig', + type: 'delete' + }); + + return process(file).then(function(){ + should.not.exist(hexo.theme.getView('index.swig')); + }); + }); +}); \ No newline at end of file